@tsed/passport 8.0.1 → 8.0.3

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,301 @@
1
+ import {Unauthorized} from "@tsed/exceptions";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+ import Passport from "passport";
4
+
5
+ import {PassportException} from "../errors/PassportException.js";
6
+ import {PassportMiddleware} from "./PassportMiddleware.js";
7
+
8
+ function createContextFixture(options = {}) {
9
+ return PlatformTest.createRequestContext({
10
+ event: {
11
+ request: PlatformTest.createRequest(options)
12
+ }
13
+ });
14
+ }
15
+
16
+ describe("PassportMiddleware", () => {
17
+ beforeEach(() => {
18
+ PlatformTest.create();
19
+ vi.spyOn(Passport, "authenticate").mockImplementation(() => (req: any, res: any, next: any) => {
20
+ return next();
21
+ });
22
+ vi.spyOn(Passport, "authorize").mockImplementation(() => (req: any, res: any, next: any) => {
23
+ return next();
24
+ });
25
+ });
26
+ afterEach(() => {
27
+ PlatformTest.reset();
28
+ });
29
+ it("should call passport with local", async () => {
30
+ // GIVEN
31
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
32
+ const context = createContextFixture();
33
+
34
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local"]);
35
+
36
+ context.endpoint = {
37
+ store: {
38
+ get: vi.fn().mockReturnValue({
39
+ options: {},
40
+ protocol: "local",
41
+ method: "authenticate"
42
+ })
43
+ }
44
+ } as any;
45
+
46
+ // WHEN
47
+ await middleware.use(context);
48
+
49
+ // THEN
50
+ expect(Passport.authenticate).toHaveBeenCalledWith("local", {failWithError: true});
51
+ });
52
+ it("should skip auth when user is authenticated", async () => {
53
+ // GIVEN
54
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
55
+ const context = createContextFixture();
56
+
57
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local"]);
58
+
59
+ context.getRequest().user = {};
60
+ context.getRequest().isAuthenticated = () => {
61
+ return true;
62
+ };
63
+
64
+ context.endpoint = {
65
+ store: {
66
+ get: vi.fn().mockReturnValue({
67
+ options: {},
68
+ protocol: "local",
69
+ method: "authenticate"
70
+ })
71
+ }
72
+ } as any;
73
+
74
+ // WHEN
75
+ await middleware.use(context);
76
+
77
+ // THEN
78
+ return expect(Passport.authenticate).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it("should call passport with defaults protocols", async () => {
82
+ // GIVEN
83
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
84
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
85
+
86
+ const context = createContextFixture();
87
+
88
+ context.endpoint = {
89
+ store: {
90
+ get: vi.fn().mockReturnValue({
91
+ options: {},
92
+ protocol: "*",
93
+ method: "authenticate"
94
+ })
95
+ }
96
+ } as any;
97
+
98
+ // WHEN
99
+ await middleware.use(context);
100
+
101
+ // THEN
102
+ expect(Passport.authenticate).toHaveBeenCalledWith(["local", "basic"], {failWithError: true});
103
+ });
104
+ it("should call passport with :protocol", async () => {
105
+ // GIVEN
106
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
107
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
108
+
109
+ const context = createContextFixture({
110
+ url: "/",
111
+ originalUrl: "/rest",
112
+ query: {
113
+ protocol: "basic"
114
+ }
115
+ });
116
+
117
+ context.endpoint = {
118
+ store: {
119
+ get: vi.fn().mockReturnValue({
120
+ options: {},
121
+ protocol: ":protocol",
122
+ method: "authenticate"
123
+ })
124
+ }
125
+ } as any;
126
+
127
+ // WHEN
128
+ await middleware.use(context);
129
+
130
+ // THEN
131
+ expect(context.getRequest().url).toBe(context.getRequest().originalUrl);
132
+ expect(Passport.authenticate).toHaveBeenCalledWith("basic", {failWithError: true});
133
+ });
134
+ it("should call passport with authorize", async () => {
135
+ // GIVEN
136
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
137
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
138
+
139
+ const context = createContextFixture({
140
+ url: "/",
141
+ originalUrl: "/rest",
142
+ query: {
143
+ protocol: "basic"
144
+ }
145
+ });
146
+
147
+ context.endpoint = {
148
+ store: {
149
+ get: vi.fn().mockReturnValue({
150
+ options: {},
151
+ protocol: ":protocol",
152
+ method: "authorize"
153
+ })
154
+ }
155
+ } as any;
156
+
157
+ // WHEN
158
+ await middleware.use(context);
159
+
160
+ // THEN
161
+ expect(context.getRequest().url).toBe(context.getRequest().originalUrl);
162
+ expect(Passport.authorize).toHaveBeenCalledWith("basic", {failWithError: true});
163
+ });
164
+ it("should throw errors", async () => {
165
+ // GIVEN
166
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
167
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
168
+
169
+ const context = createContextFixture();
170
+ context.endpoint = {
171
+ store: {
172
+ get: vi.fn().mockReturnValue({
173
+ options: {},
174
+ protocol: ":protocol",
175
+ method: "authenticate"
176
+ })
177
+ }
178
+ } as any;
179
+
180
+ // WHEN
181
+ let actualError;
182
+ try {
183
+ await middleware.use(context);
184
+ } catch (er) {
185
+ actualError = er;
186
+ }
187
+
188
+ // THEN
189
+ expect(actualError).toBeInstanceOf(Unauthorized);
190
+ });
191
+ it("should throw errors from passport (AuthenticationError)", async () => {
192
+ // GIVEN
193
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
194
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
195
+
196
+ (Passport.authenticate as any).mockImplementation(() => (req: any, res: any, next: any) => {
197
+ const error: any = {message: "message"};
198
+ error.name = "AuthenticationError";
199
+ error.status = 400;
200
+
201
+ return next(error);
202
+ });
203
+
204
+ const context = createContextFixture();
205
+
206
+ context.endpoint = {
207
+ store: {
208
+ get: vi.fn().mockReturnValue({
209
+ options: {},
210
+ protocol: "*",
211
+ method: "authenticate"
212
+ })
213
+ }
214
+ } as any;
215
+
216
+ // WHEN
217
+ let actualError;
218
+ try {
219
+ await middleware.use(context);
220
+ } catch (er) {
221
+ actualError = er;
222
+ }
223
+
224
+ // THEN
225
+ expect(actualError).toBeInstanceOf(PassportException);
226
+ expect(actualError.message).toBe("message");
227
+ expect(actualError.status).toBe(400);
228
+ });
229
+ it("should throw errors from passport (AnyError)", async () => {
230
+ // GIVEN
231
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
232
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
233
+
234
+ (Passport.authenticate as any).mockImplementation(() => (req: any, res: any, next: any) => {
235
+ const error: any = {message: "message"};
236
+ error.name = "Any";
237
+
238
+ return next(error);
239
+ });
240
+
241
+ const context = createContextFixture();
242
+
243
+ context.endpoint = {
244
+ store: {
245
+ get: vi.fn().mockReturnValue({
246
+ options: {},
247
+ protocol: "*",
248
+ method: "authenticate"
249
+ })
250
+ }
251
+ } as any;
252
+
253
+ // WHEN
254
+ let actualError;
255
+ try {
256
+ await middleware.use(context);
257
+ } catch (er) {
258
+ actualError = er;
259
+ }
260
+
261
+ // THEN
262
+ expect(actualError).toBeInstanceOf(PassportException);
263
+ expect(actualError.message).toBe("message");
264
+ expect(actualError.status).toBe(500);
265
+ });
266
+ it("should throw errors from passport (Error)", async () => {
267
+ // GIVEN
268
+ const middleware = await PlatformTest.invoke<PassportMiddleware>(PassportMiddleware);
269
+ vi.spyOn(middleware.protocolsService, "getProtocolsNames").mockReturnValue(["local", "basic"]);
270
+
271
+ (Passport.authenticate as any).mockImplementation(() => (req: any, res: any, next: any) => {
272
+ const error: any = new Error("message");
273
+
274
+ return next(error);
275
+ });
276
+
277
+ const context = createContextFixture();
278
+
279
+ context.endpoint = {
280
+ store: {
281
+ get: vi.fn().mockReturnValue({
282
+ options: {},
283
+ protocol: "*",
284
+ method: "authenticate"
285
+ })
286
+ }
287
+ } as any;
288
+
289
+ // WHEN
290
+ let actualError;
291
+ try {
292
+ await middleware.use(context);
293
+ } catch (er) {
294
+ actualError = er;
295
+ }
296
+
297
+ // THEN
298
+ expect(actualError).toBeInstanceOf(Error);
299
+ expect(actualError.message).toBe("message");
300
+ });
301
+ });
@@ -0,0 +1,36 @@
1
+ import {Inject} from "@tsed/di";
2
+ import {Middleware} from "@tsed/platform-middlewares";
3
+ import {Context} from "@tsed/platform-params";
4
+
5
+ import {ProtocolsService} from "../services/ProtocolsService.js";
6
+ import {getProtocolsFromRequest} from "../utils/getProtocolsFromRequest.js";
7
+
8
+ @Middleware()
9
+ export class PassportMiddleware {
10
+ @Inject(ProtocolsService)
11
+ protocolsService: ProtocolsService;
12
+
13
+ shouldSkip(ctx: Context) {
14
+ const request = ctx.getRequest();
15
+ return request.user && request.isAuthenticated();
16
+ }
17
+
18
+ async use(@Context() ctx: Context) {
19
+ const endpoint = ctx.endpoint;
20
+ const request = ctx.getRequest();
21
+
22
+ if (this.shouldSkip(ctx)) {
23
+ return;
24
+ }
25
+ const {options, protocol, method, originalUrl = true} = endpoint.store.get(PassportMiddleware);
26
+ const protocols = getProtocolsFromRequest(request, protocol, this.protocolsService.getProtocolsNames());
27
+
28
+ if (originalUrl) {
29
+ request.url = request.originalUrl;
30
+ }
31
+
32
+ await (method === "authenticate"
33
+ ? this.protocolsService.authenticate(protocols, options, ctx)
34
+ : this.protocolsService.authorize(protocols, options, ctx));
35
+ }
36
+ }
@@ -0,0 +1,51 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+
3
+ import {PassportSerializerService, UserInfo} from "../index.js";
4
+
5
+ describe("PassportSerializerService", () => {
6
+ beforeEach(() => PlatformTest.create());
7
+ afterEach(PlatformTest.reset);
8
+
9
+ it(
10
+ "should serialize model",
11
+ PlatformTest.inject([PassportSerializerService], async (service: PassportSerializerService) => {
12
+ const userInfo = new UserInfo();
13
+
14
+ userInfo.id = "id";
15
+ userInfo.email = "email@email.fr";
16
+ userInfo.password = "password";
17
+
18
+ const result = await new Promise((resolve) => service.serialize(userInfo, (...args: any[]) => resolve(args)));
19
+
20
+ expect(result).toEqual([null, '{"id":"id","email":"email@email.fr"}']);
21
+ })
22
+ );
23
+
24
+ it(
25
+ "should deserialize model",
26
+ PlatformTest.inject([PassportSerializerService], async (service: PassportSerializerService) => {
27
+ const result = await new Promise((resolve) =>
28
+ service.deserialize('{"id":"id","email":"email@email.fr"}', (...args: any[]) => resolve(args))
29
+ );
30
+
31
+ expect(result).toEqual([
32
+ null,
33
+ {
34
+ email: "email@email.fr",
35
+ id: "id"
36
+ }
37
+ ]);
38
+ })
39
+ );
40
+
41
+ it(
42
+ "should catch error when deserializing model",
43
+ PlatformTest.inject([PassportSerializerService], async (service: PassportSerializerService) => {
44
+ const result: any = await new Promise((resolve) =>
45
+ service.deserialize('{"id":"id","email":"email@email.fr}', (...args: any[]) => resolve(args))
46
+ );
47
+
48
+ expect(result[0].message).toEqual("Unterminated string in JSON at position 35");
49
+ })
50
+ );
51
+ });
@@ -0,0 +1,38 @@
1
+ import {Type} from "@tsed/core";
2
+ import {Constant, Service} from "@tsed/di";
3
+ import {deserialize, serialize} from "@tsed/json-mapper";
4
+
5
+ import {UserInfo} from "../domain/UserInfo.js";
6
+
7
+ /**
8
+ * @ignore
9
+ */
10
+ @Service()
11
+ export class PassportSerializerService {
12
+ @Constant("passport.userInfoModel", UserInfo)
13
+ private model: Type<UserInfo> | boolean;
14
+
15
+ serialize(user: UserInfo, done: any) {
16
+ try {
17
+ const obj = serialize(user, {type: this.model});
18
+
19
+ // remove password from serialized object
20
+ obj.password = undefined;
21
+
22
+ done(null, JSON.stringify(obj));
23
+ } catch (er) {
24
+ // istanbul ignore next
25
+ done(er);
26
+ }
27
+ }
28
+
29
+ deserialize(obj: any, done: any) {
30
+ try {
31
+ const user = deserialize(JSON.parse(obj), {type: this.model});
32
+
33
+ done(null, user);
34
+ } catch (er) {
35
+ done(er);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,166 @@
1
+ import {runInContext} from "@tsed/di";
2
+ import {Req} from "@tsed/platform-http";
3
+ import {PlatformTest} from "@tsed/platform-http/testing";
4
+ import Passport from "passport";
5
+
6
+ import {PassportMessage} from "../errors/PassportMessage.js";
7
+ import {Protocol, ProtocolsService} from "../index.js";
8
+
9
+ // tslint:disable-next-line:variable-name
10
+ const Strategy = vi.fn();
11
+
12
+ describe("ProtocolsService", () => {
13
+ @Protocol({
14
+ name: "local",
15
+ useStrategy: Strategy as any,
16
+ settings: {
17
+ prop1: "prop1",
18
+ prop2: "prop2"
19
+ }
20
+ })
21
+ class LocalProtocol {
22
+ $beforeInstall(settings: any) {
23
+ return Promise.resolve(settings);
24
+ }
25
+
26
+ $onInstall() {}
27
+
28
+ $onVerify(@Req() req: Req) {
29
+ return {id: 0};
30
+ }
31
+ }
32
+
33
+ beforeEach(() =>
34
+ PlatformTest.create({
35
+ passport: {
36
+ protocols: {
37
+ local: {
38
+ settings: {
39
+ prop2: "prop2-server",
40
+ prop3: "prop3"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ })
46
+ );
47
+ afterEach(PlatformTest.reset);
48
+
49
+ beforeEach(() => {
50
+ vi.spyOn(LocalProtocol.prototype, "$onInstall");
51
+ vi.spyOn(LocalProtocol.prototype, "$onVerify");
52
+ vi.spyOn(LocalProtocol.prototype, "$beforeInstall");
53
+ vi.spyOn(Passport, "use").mockReturnValue(undefined as any);
54
+ Strategy.mockReset();
55
+ });
56
+
57
+ it("should create a protocol", async () => {
58
+ const protocolService = PlatformTest.get<ProtocolsService>(ProtocolsService);
59
+ // GIVEN
60
+ const provider = PlatformTest.injector.getProvider(LocalProtocol)!;
61
+
62
+ // WHEN
63
+ const result = await protocolService.invoke(provider);
64
+
65
+ // THEN
66
+ expect(result).toBeInstanceOf(LocalProtocol);
67
+ expect(result.$onInstall).toHaveBeenCalledWith(protocolService.strategies.get("local"));
68
+ expect(result.$beforeInstall).toHaveBeenCalledWith({
69
+ passReqToCallback: true,
70
+ prop1: "prop1",
71
+ prop2: "prop2-server",
72
+ prop3: "prop3"
73
+ });
74
+ expect(Passport.use).toHaveBeenCalledWith("local", protocolService.strategies.get("local"));
75
+ expect(Strategy).toHaveBeenCalledWith(
76
+ {
77
+ passReqToCallback: true,
78
+ prop1: "prop1",
79
+ prop2: "prop2-server",
80
+ prop3: "prop3"
81
+ },
82
+ expect.any(Function)
83
+ );
84
+ expect(protocolService.getProtocolsNames().includes("local")).toEqual(true);
85
+ });
86
+
87
+ it("should call metadata", async () => {
88
+ const protocolService = PlatformTest.get<ProtocolsService>(ProtocolsService);
89
+ // GIVEN
90
+ (LocalProtocol.prototype.$onVerify as any).mockReturnValue({id: 0});
91
+ const provider = PlatformTest.injector.getProvider(LocalProtocol)!;
92
+ const $ctx = PlatformTest.createRequestContext();
93
+
94
+ // WHEN
95
+ const protocol = await protocolService.invoke(provider);
96
+
97
+ const resultDone: any = await new Promise((resolve) => {
98
+ const verify = Strategy.mock.calls[0][1];
99
+
100
+ return runInContext($ctx, () => verify($ctx.getRequest(), "test", (...args: any[]) => resolve(args)));
101
+ });
102
+
103
+ // THEN
104
+ expect(resultDone).toEqual([null, {id: 0}]);
105
+ expect(protocol.$onVerify).toHaveBeenCalledWith($ctx.getRequest(), $ctx);
106
+ });
107
+
108
+ it("should call metadata and catch error", async () => {
109
+ const protocolService = PlatformTest.get<ProtocolsService>(ProtocolsService);
110
+ // GIVEN
111
+ const error = new Error("message");
112
+ (LocalProtocol.prototype.$onVerify as any).mockRejectedValue(error);
113
+
114
+ const provider = PlatformTest.injector.getProvider(LocalProtocol)!;
115
+ const $ctx = PlatformTest.createRequestContext();
116
+
117
+ // WHEN
118
+ const result = await protocolService.invoke(provider);
119
+ const resultDone: any = await new Promise((resolve) => {
120
+ const verify = Strategy.mock.calls[0][1];
121
+
122
+ return runInContext($ctx, () => verify($ctx.getRequest(), "test", (...args: any[]) => resolve(args)));
123
+ });
124
+
125
+ // THEN
126
+ expect(result.$onVerify).toHaveBeenCalledWith($ctx.getRequest(), $ctx);
127
+ expect(resultDone).toEqual([error, false, {message: "message"}]);
128
+ });
129
+
130
+ it("should call metadata and catch PassportMessage", async () => {
131
+ const protocolService = PlatformTest.get<ProtocolsService>(ProtocolsService);
132
+ // GIVEN
133
+ const error = new PassportMessage("message", {type: "allow"});
134
+ (LocalProtocol.prototype.$onVerify as any).mockRejectedValue(error);
135
+
136
+ const provider = PlatformTest.injector.getProvider(LocalProtocol)!;
137
+ const $ctx = PlatformTest.createRequestContext();
138
+
139
+ // WHEN
140
+ const result = await protocolService.invoke(provider);
141
+ const resultDone: any = await new Promise((resolve) => {
142
+ const verify = Strategy.mock.calls[0][1];
143
+
144
+ return runInContext($ctx, () => verify($ctx.getRequest(), "test", (...args: any[]) => resolve(args)));
145
+ });
146
+
147
+ // THEN
148
+ expect(result.$onVerify).toHaveBeenCalledWith($ctx.getRequest(), $ctx);
149
+ expect(resultDone).toEqual([null, false, {message: "message", type: "allow"}]);
150
+ });
151
+
152
+ it("should call metadata and catch missing $ctx", async () => {
153
+ const protocolService = PlatformTest.get<ProtocolsService>(ProtocolsService);
154
+ // GIVEN
155
+ const provider = PlatformTest.injector.getProvider(LocalProtocol)!;
156
+
157
+ // WHEN
158
+ await protocolService.invoke(provider);
159
+ const resultDone: any = await new Promise((resolve) => {
160
+ Strategy.mock.calls[0][1]({}, "test", (...args: any[]) => resolve(args));
161
+ });
162
+
163
+ // THEN
164
+ expect(resultDone).toEqual([new Error("Headers already sent"), false, {message: "Headers already sent"}]);
165
+ });
166
+ });