From fa16810c3d178c081c300c5a67797801433bd782 Mon Sep 17 00:00:00 2001 From: "romain.lenzotti" Date: Wed, 17 Apr 2019 11:29:56 +0200 Subject: [PATCH] feat(mvc): Authenticated can be used on class controller Closes: #467 --- .../mvc/decorators/method/authenticated.ts | 33 ++++++- .../decorators/method/authenticated.spec.ts | 92 +++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/packages/common/src/mvc/decorators/method/authenticated.ts b/packages/common/src/mvc/decorators/method/authenticated.ts index 83208c0ea1d..3284b4e27eb 100644 --- a/packages/common/src/mvc/decorators/method/authenticated.ts +++ b/packages/common/src/mvc/decorators/method/authenticated.ts @@ -1,7 +1,15 @@ -import {Store} from "@tsed/core"; +import {classOf, DecoratorParameters, descriptorOf, getDecoratorType, methodsOf, prototypeOf, Store} from "@tsed/core"; import {AuthenticatedMiddleware} from "../../components/AuthenticatedMiddleware"; import {UseBefore} from "./useBefore"; +function decorate(options: any) { + return Store.decorate((store: Store) => { + store.set(AuthenticatedMiddleware, options).merge("responses", {"403": {description: "Forbidden"}}); + + return UseBefore(AuthenticatedMiddleware); + }); +} + /** * Set authentication strategy on your endpoint. * @@ -20,9 +28,24 @@ import {UseBefore} from "./useBefore"; * @decorator */ export function Authenticated(options?: any) { - return Store.decorate((store: Store) => { - store.set(AuthenticatedMiddleware, options).merge("responses", {"403": {description: "Forbidden"}}); + return (...parameters: DecoratorParameters): TypedPropertyDescriptor | void => { + switch (getDecoratorType(parameters, true)) { + case "method": + return decorate(options)(...parameters); - return UseBefore(AuthenticatedMiddleware); - }); + case "class": + const [klass] = parameters; + + methodsOf(klass).forEach(({target, propertyKey}) => { + if (target !== classOf(klass)) { + prototypeOf(klass)[propertyKey] = function anonymous(...args: any) { + return prototypeOf(target)[propertyKey].apply(this, args); + }; + } + + decorate(options)(prototypeOf(klass), propertyKey, descriptorOf(klass, propertyKey)); + }); + break; + } + }; } diff --git a/packages/common/test/mvc/decorators/method/authenticated.spec.ts b/packages/common/test/mvc/decorators/method/authenticated.spec.ts index 99d437c2223..5d18534c064 100644 --- a/packages/common/test/mvc/decorators/method/authenticated.spec.ts +++ b/packages/common/test/mvc/decorators/method/authenticated.spec.ts @@ -11,34 +11,86 @@ const {Authenticated} = Proxyquire.load("../../../../src/mvc/decorators/method/a "./useBefore": {UseBefore: useBeforeStub} }); -class Test { - test() { - } -} describe("Authenticated", () => { - before(() => { - this.descriptor = {}; - this.options = {options: "options"}; + describe("when the decorator is used on a method", () => { + it("should set metadata", () => { + // GIVEN - Authenticated(this.options)(Test, "test", descriptorOf(Test, "test")); - this.store = Store.fromMethod(Test, "test"); - }); + // WHEN + class Test { + @Authenticated({options: "options"}) + test() { + } + } - after(() => { - this.store.clear(); - }); + const store = Store.fromMethod(Test, "test"); - it("should set metadata", () => { - expect(this.store.get(AuthenticatedMiddleware)).to.deep.eq(this.options); + // THEN + expect(store.get(AuthenticatedMiddleware)).to.deep.eq({options: "options"}); + expect(store.get("responses")).to.deep.eq({"403": {description: "Forbidden"}}); + useBeforeStub.should.be.calledWithExactly(AuthenticatedMiddleware); + middleware.should.be.calledWithExactly(Test.prototype, "test", descriptorOf(Test, "test")); + store.clear(); + }); }); - it("should set responses metadata", () => { - expect(this.store.get("responses")).to.deep.eq({"403": {description: "Forbidden"}}); + describe("when the decorator is used on a controller", () => { + it("should set metadata", () => { + // GIVEN + + // WHEN + @Authenticated({options: "options"}) + class Test { + test() { + } + } + + const store = Store.fromMethod(Test, "test"); + + // THEN + expect(store.get(AuthenticatedMiddleware)).to.deep.eq({options: "options"}); + expect(store.get("responses")).to.deep.eq({"403": {description: "Forbidden"}}); + useBeforeStub.should.be.calledWithExactly(AuthenticatedMiddleware); + middleware.should.be.calledWithExactly(Test.prototype, "test", descriptorOf(Test, "test")); + store.clear(); + }); }); - it("should create middleware", () => { - useBeforeStub.should.be.calledWithExactly(AuthenticatedMiddleware); - middleware.should.be.calledWithExactly(Test, "test", descriptorOf(Test, "test")); + describe("when the decorator is used on a controller with class inheritance", () => { + it("should set metadata", () => { + // GIVEN + class Base { + value = "1"; + + test2(option: string) { + return "test2" + option + this.value; + } + } + + // WHEN + @Authenticated({options: "options"}) + class Test extends Base { + test() { + } + } + + const store1 = Store.fromMethod(Test, "test"); + const store2 = Store.fromMethod(Test, "test2"); + + // THEN + expect(store1.get(AuthenticatedMiddleware)).to.deep.eq({options: "options"}); + expect(store1.get("responses")).to.deep.eq({"403": {description: "Forbidden"}}); + + expect(store2.get(AuthenticatedMiddleware)).to.deep.eq({options: "options"}); + expect(store2.get("responses")).to.deep.eq({"403": {description: "Forbidden"}}); + + expect(new Test().test2("test")).to.eq("test2test1"); + + useBeforeStub.should.be.calledWithExactly(AuthenticatedMiddleware); + middleware.should.be.calledWithExactly(Test.prototype, "test", descriptorOf(Test, "test")); + store1.clear(); + store2.clear(); + }); }); });