@tsed/passport 8.0.0 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ import {ancestorsOf} from "@tsed/core";
2
+ import {Inject, Injectable, InjectorService, Provider} from "@tsed/di";
3
+ import {Unauthorized} from "@tsed/exceptions";
4
+ import {PlatformContext, PlatformHandler} from "@tsed/platform-http";
5
+ import Passport, {Strategy} from "passport";
6
+ import {promisify} from "util";
7
+
8
+ import {PROVIDER_TYPE_PROTOCOL} from "../contants/constants.js";
9
+ import {PassportException} from "../errors/PassportException.js";
10
+ import {PassportMessage} from "../errors/PassportMessage.js";
11
+ import type {ProtocolMethods} from "../interfaces/ProtocolMethods.js";
12
+ import type {ProtocolOptions} from "../interfaces/ProtocolOptions.js";
13
+
14
+ /**
15
+ * @ignore
16
+ */
17
+ @Injectable()
18
+ export class ProtocolsService {
19
+ readonly strategies: Map<string, Strategy> = new Map();
20
+
21
+ @Inject()
22
+ protected platformHandler: PlatformHandler;
23
+
24
+ @Inject()
25
+ private injector: InjectorService;
26
+
27
+ public getProtocols(): Provider[] {
28
+ return this.injector.getProviders(PROVIDER_TYPE_PROTOCOL);
29
+ }
30
+
31
+ public getProtocolsNames(): string[] {
32
+ return Array.from(this.strategies.keys());
33
+ }
34
+
35
+ /**
36
+ * Invoke provider and bind it to passport.
37
+ * @param provider
38
+ */
39
+ async invoke(provider: Provider) {
40
+ let {name, useStrategy: strategy, settings} = this.getOptions(provider);
41
+ const protocol = this.injector.get<ProtocolMethods & Record<string, any>>(provider.provide)!;
42
+
43
+ if (protocol.$beforeInstall) {
44
+ settings = (await protocol.$beforeInstall(settings)) || settings;
45
+ }
46
+
47
+ const instance = new strategy(settings, this.createHandler(provider));
48
+
49
+ this.strategies.set(name, instance);
50
+
51
+ Passport.use(name, instance);
52
+
53
+ if (protocol.$onInstall) {
54
+ await protocol.$onInstall(instance);
55
+ }
56
+
57
+ protocol.$strategy = instance;
58
+
59
+ return protocol;
60
+ }
61
+
62
+ /**
63
+ * Call authenticate passport method.
64
+ * @param protocols
65
+ * @param options
66
+ * @param ctx
67
+ */
68
+ public authenticate(protocols: string | string[], options: Record<string, any>, ctx: PlatformContext) {
69
+ return this.call("authenticate", protocols, options, ctx);
70
+ }
71
+
72
+ /**
73
+ * Call authorize passport method.
74
+ * @param protocols
75
+ * @param options
76
+ * @param ctx
77
+ */
78
+ public authorize(protocols: string | string[], options: Record<string, any>, ctx: PlatformContext) {
79
+ return this.call("authorize", protocols, options, ctx);
80
+ }
81
+
82
+ /**
83
+ * Call passport authenticate or authorize depending on the chosen method.
84
+ * @param method
85
+ * @param protocols
86
+ * @param options
87
+ * @param ctx
88
+ * @private
89
+ */
90
+ private async call(
91
+ method: "authenticate" | "authorize",
92
+ protocols: string | string[],
93
+ options: Record<string, any>,
94
+ ctx: PlatformContext
95
+ ) {
96
+ const request = ctx.getRequest();
97
+ const response = ctx.getResponse();
98
+ protocols = ([] as string[]).concat(protocols);
99
+
100
+ if (protocols.length === 0) {
101
+ throw new Unauthorized("Not authorized");
102
+ }
103
+
104
+ try {
105
+ options.failWithError = true;
106
+ // @ts-ignore
107
+ const fn = promisify(Passport[method](protocols.length === 1 ? protocols[0] : protocols, options));
108
+
109
+ await fn(request, response);
110
+ } catch (er) {
111
+ if (!ancestorsOf(er).includes(Error)) {
112
+ throw new PassportException(er);
113
+ }
114
+
115
+ throw er;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Create strategy options based on decorator metadata and global configuration
121
+ * @param provider
122
+ * @private
123
+ */
124
+ private getOptions(provider: Provider<any>): ProtocolOptions {
125
+ const {name} = provider.store.get("protocol");
126
+ const {useStrategy = Strategy, settings = {}}: ProtocolOptions = this.injector.settings.get(`passport.protocols.${name}`) || {};
127
+
128
+ return {
129
+ name,
130
+ useStrategy,
131
+ settings: {
132
+ ...settings,
133
+ passReqToCallback: true
134
+ }
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Create the verifier handler for passport
140
+ * @param provider
141
+ * @private
142
+ */
143
+ private createHandler(provider: Provider) {
144
+ const middleware = this.platformHandler.createCustomHandler(provider, "$onVerify");
145
+
146
+ return async (req: any, ...args: any[]) => {
147
+ const done = args[args.length - 1];
148
+
149
+ if (req.$ctx) {
150
+ req.$ctx.set("PROTOCOL_ARGS", args.slice(0, -1));
151
+
152
+ try {
153
+ await middleware(req.$ctx);
154
+
155
+ done(null, ...[].concat(req.$ctx.data));
156
+ } catch (err) {
157
+ if (err instanceof PassportMessage) {
158
+ done(null, false, {...err.opts, message: err.message});
159
+ } else {
160
+ done(err, false, {message: err.message});
161
+ }
162
+ }
163
+ } else {
164
+ done(new Error("Headers already sent"), false, {message: "Headers already sent"});
165
+ }
166
+ };
167
+ }
168
+ }
@@ -0,0 +1,97 @@
1
+ import {getProtocolsFromRequest} from "./getProtocolsFromRequest.js";
2
+
3
+ describe("getProtocolsFromRequest", () => {
4
+ it("should allow all protocol (from default protocols)", () => {
5
+ const defaultProtocols = ["default"];
6
+ const req = {};
7
+ const result = getProtocolsFromRequest(req, "*", defaultProtocols);
8
+
9
+ expect(result).toEqual(["default"]);
10
+ });
11
+
12
+ it("should allow all protocol (from default protocols - array)", () => {
13
+ const defaultProtocols = ["default"];
14
+ const req = {};
15
+ const result = getProtocolsFromRequest(req, ["*"], defaultProtocols);
16
+
17
+ expect(result).toEqual(["default"]);
18
+ });
19
+
20
+ it("should return providers", () => {
21
+ const defaultProtocols = ["default"];
22
+ const req = {};
23
+ const result = getProtocolsFromRequest(req, ["basic", "local"], defaultProtocols);
24
+
25
+ expect(result).toEqual(["basic", "local"]);
26
+ });
27
+
28
+ it("should get protocol from request (params)", () => {
29
+ const defaultProtocols = ["default"];
30
+ const req = {
31
+ params: {
32
+ protocol: "basic"
33
+ }
34
+ };
35
+ const result = getProtocolsFromRequest(req, ":protocol", defaultProtocols);
36
+
37
+ expect(result).toEqual(["basic"]);
38
+ });
39
+
40
+ it("should not get protocol from request", () => {
41
+ const defaultProtocols = ["default"];
42
+ const req = {
43
+ params: {}
44
+ };
45
+ const result = getProtocolsFromRequest(req, ":protocol", defaultProtocols);
46
+
47
+ expect(result).toEqual([]);
48
+ });
49
+
50
+ it("should get protocol from request (query)", () => {
51
+ const defaultProtocols = ["default"];
52
+ const req = {
53
+ query: {
54
+ protocol: "basic"
55
+ }
56
+ };
57
+ const result = getProtocolsFromRequest(req, ":protocol", defaultProtocols);
58
+
59
+ expect(result).toEqual(["basic"]);
60
+ });
61
+
62
+ it("should get protocol from request (body)", () => {
63
+ const defaultProtocols = ["default"];
64
+ const req = {
65
+ body: {
66
+ protocol: "basic"
67
+ }
68
+ };
69
+ const result = getProtocolsFromRequest(req, ":protocol", defaultProtocols);
70
+
71
+ expect(result).toEqual(["basic"]);
72
+ });
73
+
74
+ it("should return basic protocol", () => {
75
+ const defaultProtocols = ["default"];
76
+ const req = {
77
+ params: {
78
+ protocol: "basic"
79
+ }
80
+ };
81
+ const result = getProtocolsFromRequest(req, "basic", defaultProtocols);
82
+
83
+ expect(result).toEqual(["basic"]);
84
+ });
85
+
86
+ it("should not return protocol when protocol doesn't match", () => {
87
+ const defaultProtocols = ["default"];
88
+ const req = {
89
+ params: {
90
+ protocol: "basic"
91
+ }
92
+ };
93
+ const result = getProtocolsFromRequest(req, "other", defaultProtocols);
94
+
95
+ expect(result).toEqual([]);
96
+ });
97
+ });
@@ -0,0 +1,41 @@
1
+ import {isArray} from "@tsed/core";
2
+
3
+ /**
4
+ * @ignore
5
+ */
6
+ function getProtocolName(req: any) {
7
+ const {query = {}, params = {}, body = {}} = req;
8
+
9
+ return params.protocol || query.protocol || body.protocol;
10
+ }
11
+
12
+ /**
13
+ * @ignore
14
+ */
15
+ export function getProtocolsFromRequest(req: any, protocol: string | string[], defaultProtocols: string[]): string[] {
16
+ if (!isArray(protocol)) {
17
+ if (protocol === "*") {
18
+ return defaultProtocols;
19
+ }
20
+
21
+ const protocolReq = getProtocolName(req);
22
+
23
+ if (protocol === ":protocol") {
24
+ return [protocolReq].filter(Boolean);
25
+ }
26
+
27
+ if (protocolReq && protocolReq !== protocol) {
28
+ return [];
29
+ }
30
+
31
+ return [protocol];
32
+ }
33
+
34
+ const protocols: string[] = protocol;
35
+
36
+ if (protocols.includes("*")) {
37
+ return defaultProtocols;
38
+ }
39
+
40
+ return protocols;
41
+ }