@tsed/passport 8.44.0 → 8.44.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.
- package/lib/esm/index.js +1 -0
- package/lib/esm/middlewares/PassportMiddleware.js +36 -7
- package/lib/esm/services/ProtocolsService.js +69 -5
- package/lib/esm/utils/sessionProtocol.js +31 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/interfaces/ProtocolOptions.d.ts +10 -0
- package/lib/types/middlewares/PassportMiddleware.d.ts +14 -1
- package/lib/types/services/ProtocolsService.d.ts +15 -0
- package/lib/types/utils/sessionProtocol.d.ts +17 -0
- package/package.json +7 -7
package/lib/esm/index.js
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
1
|
var PassportMiddleware_1;
|
|
2
2
|
import { __decorate, __metadata, __param } from "tslib";
|
|
3
|
+
import { isArray } from "@tsed/core";
|
|
3
4
|
import { Context } from "@tsed/platform-params";
|
|
4
5
|
import { Inject } from "@tsed/di";
|
|
5
6
|
import { Middleware } from "@tsed/platform-middlewares";
|
|
6
7
|
import { ProtocolsService } from "../services/ProtocolsService.js";
|
|
7
8
|
import { getProtocolsFromRequest } from "../utils/getProtocolsFromRequest.js";
|
|
9
|
+
import { getSessionProtocol, hasLoginSession } from "../utils/sessionProtocol.js";
|
|
10
|
+
/**
|
|
11
|
+
* Reserved protocol name satisfied by an existing login session. It never runs a Passport strategy.
|
|
12
|
+
*/
|
|
13
|
+
export const SESSION_PROTOCOL = "session";
|
|
8
14
|
let PassportMiddleware = PassportMiddleware_1 = class PassportMiddleware {
|
|
9
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Return true when an existing login session satisfies the protocols declared on the endpoint:
|
|
17
|
+
*
|
|
18
|
+
* - the endpoint accepts any protocol (`"*"`),
|
|
19
|
+
* - the endpoint accepts the reserved `"session"` protocol and the request carries a persisted login session,
|
|
20
|
+
* - or the persisted login session was established by one of the endpoint protocols.
|
|
21
|
+
*
|
|
22
|
+
* A session established by another protocol never satisfies the endpoint: its strategies run instead.
|
|
23
|
+
*/
|
|
24
|
+
shouldSkip(protocol, protocols, ctx) {
|
|
10
25
|
const request = ctx.getRequest();
|
|
11
|
-
|
|
26
|
+
if (!(request.user && request.isAuthenticated?.())) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const acceptsAny = isArray(protocol) ? protocol.includes("*") : protocol === "*";
|
|
30
|
+
if (acceptsAny) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
// The session rules require a persisted login session, not only a user set by a stateless strategy.
|
|
34
|
+
if (!hasLoginSession(request)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const sessionProtocol = getSessionProtocol(request);
|
|
38
|
+
return protocols.includes(SESSION_PROTOCOL) || (!!sessionProtocol && protocols.includes(sessionProtocol));
|
|
12
39
|
}
|
|
13
40
|
async use(ctx) {
|
|
14
41
|
const endpoint = ctx.endpoint;
|
|
15
42
|
const request = ctx.getRequest();
|
|
16
|
-
if (this.shouldSkip(ctx)) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
43
|
const { options, protocol, method, originalUrl = true } = endpoint.store.get(PassportMiddleware_1);
|
|
20
44
|
const protocols = getProtocolsFromRequest(request, protocol, this.protocolsService.getProtocolsNames());
|
|
45
|
+
if (this.shouldSkip(protocol, protocols, ctx)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// The session protocol has been checked above: it never runs a Passport strategy.
|
|
49
|
+
const strategies = protocols.filter((name) => name !== SESSION_PROTOCOL);
|
|
21
50
|
if (originalUrl) {
|
|
22
51
|
request.url = request.originalUrl;
|
|
23
52
|
}
|
|
24
53
|
await (method === "authenticate"
|
|
25
|
-
? this.protocolsService.authenticate(
|
|
26
|
-
: this.protocolsService.authorize(
|
|
54
|
+
? this.protocolsService.authenticate(strategies, options, ctx)
|
|
55
|
+
: this.protocolsService.authorize(strategies, options, ctx));
|
|
27
56
|
}
|
|
28
57
|
};
|
|
29
58
|
__decorate([
|
|
@@ -7,12 +7,19 @@ import { PassportMessage } from "../errors/PassportMessage.js";
|
|
|
7
7
|
import { Unauthorized } from "@tsed/exceptions";
|
|
8
8
|
import { ancestorsOf } from "@tsed/core";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
|
+
import { setSessionProtocol } from "../utils/sessionProtocol.js";
|
|
11
|
+
/**
|
|
12
|
+
* Context key holding the name of the protocol whose verify callback accepted the request.
|
|
13
|
+
* @ignore
|
|
14
|
+
*/
|
|
15
|
+
const VERIFIED_PROTOCOL = "PASSPORT_VERIFIED_PROTOCOL";
|
|
10
16
|
/**
|
|
11
17
|
* @ignore
|
|
12
18
|
*/
|
|
13
19
|
export class ProtocolsService {
|
|
14
20
|
constructor() {
|
|
15
21
|
this.strategies = new Map();
|
|
22
|
+
this.loginOnlyProtocols = new Set();
|
|
16
23
|
this.platformHandler = inject(PlatformHandler);
|
|
17
24
|
}
|
|
18
25
|
getProtocols() {
|
|
@@ -21,18 +28,31 @@ export class ProtocolsService {
|
|
|
21
28
|
getProtocolsNames() {
|
|
22
29
|
return Array.from(this.strategies.keys());
|
|
23
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Return true when the protocol must only run on login endpoints (`@Authenticate()`).
|
|
33
|
+
* @param name
|
|
34
|
+
*/
|
|
35
|
+
isLoginOnly(name) {
|
|
36
|
+
return this.loginOnlyProtocols.has(name);
|
|
37
|
+
}
|
|
24
38
|
/**
|
|
25
39
|
* Invoke provider and bind it to passport.
|
|
26
40
|
* @param provider
|
|
27
41
|
*/
|
|
28
42
|
async invoke(provider) {
|
|
29
|
-
let { name, useStrategy: strategy, settings } = this.getOptions(provider);
|
|
43
|
+
let { name, useStrategy: strategy, settings, loginOnly } = this.getOptions(provider);
|
|
30
44
|
const protocol = inject(provider.token);
|
|
31
45
|
if (protocol.$beforeInstall) {
|
|
32
46
|
settings = (await protocol.$beforeInstall(settings)) || settings;
|
|
33
47
|
}
|
|
34
48
|
const instance = new strategy(settings, this.createHandler(provider));
|
|
35
49
|
this.strategies.set(name, instance);
|
|
50
|
+
if (loginOnly) {
|
|
51
|
+
this.loginOnlyProtocols.add(name);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.loginOnlyProtocols.delete(name);
|
|
55
|
+
}
|
|
36
56
|
Passport.use(name, instance);
|
|
37
57
|
if (protocol.$onInstall) {
|
|
38
58
|
await protocol.$onInstall(instance);
|
|
@@ -56,7 +76,9 @@ export class ProtocolsService {
|
|
|
56
76
|
* @param ctx
|
|
57
77
|
*/
|
|
58
78
|
authorize(protocols, options, ctx) {
|
|
59
|
-
|
|
79
|
+
// Login-only protocols never run on a guarded route: they are accepted through their session only.
|
|
80
|
+
const strategies = [].concat(protocols).filter((name) => !this.isLoginOnly(name));
|
|
81
|
+
return this.call("authorize", strategies, options, ctx);
|
|
60
82
|
}
|
|
61
83
|
/**
|
|
62
84
|
* Call passport authenticate or authorize depending on the chosen method.
|
|
@@ -73,6 +95,7 @@ export class ProtocolsService {
|
|
|
73
95
|
if (protocols.length === 0) {
|
|
74
96
|
throw new Unauthorized("Not authorized");
|
|
75
97
|
}
|
|
98
|
+
const restoreLogIn = method === "authenticate" ? this.recordSessionProtocol(protocols, ctx) : undefined;
|
|
76
99
|
try {
|
|
77
100
|
options.failWithError = true;
|
|
78
101
|
// @ts-ignore
|
|
@@ -85,6 +108,41 @@ export class ProtocolsService {
|
|
|
85
108
|
}
|
|
86
109
|
throw er;
|
|
87
110
|
}
|
|
111
|
+
finally {
|
|
112
|
+
restoreLogIn?.();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Wrap `req.logIn()` for the running `@Authenticate()` call to record the protocol that establishes the
|
|
117
|
+
* login session. The record is written in the `req.logIn()` callback, once Passport.js has finished updating
|
|
118
|
+
* the session (`keepSessionInfo` merge, Passport.js 0.4 session object replacement) and before it ends the
|
|
119
|
+
* request itself (`successRedirect`).
|
|
120
|
+
*
|
|
121
|
+
* Return a function restoring the original `req.logIn()`.
|
|
122
|
+
*/
|
|
123
|
+
recordSessionProtocol(protocols, ctx) {
|
|
124
|
+
const request = ctx.getRequest();
|
|
125
|
+
const logIn = request.logIn;
|
|
126
|
+
if (typeof logIn !== "function") {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
request.login = request.logIn = function (user, options, done) {
|
|
130
|
+
if (typeof options === "function") {
|
|
131
|
+
done = options;
|
|
132
|
+
options = {};
|
|
133
|
+
}
|
|
134
|
+
return logIn.call(this, user, options, (err) => {
|
|
135
|
+
const verified = ctx.get(VERIFIED_PROTOCOL);
|
|
136
|
+
const protocol = protocols.includes(verified) ? verified : protocols.length === 1 ? protocols[0] : undefined;
|
|
137
|
+
if (!err && options?.session !== false && protocol) {
|
|
138
|
+
setSessionProtocol(this, protocol);
|
|
139
|
+
}
|
|
140
|
+
done?.(err);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
return () => {
|
|
144
|
+
request.login = request.logIn = logIn;
|
|
145
|
+
};
|
|
88
146
|
}
|
|
89
147
|
/**
|
|
90
148
|
* Create strategy options based on decorator metadata and global configuration
|
|
@@ -92,11 +150,12 @@ export class ProtocolsService {
|
|
|
92
150
|
* @private
|
|
93
151
|
*/
|
|
94
152
|
getOptions(provider) {
|
|
95
|
-
const { name } = provider.store.get("protocol");
|
|
96
|
-
const { useStrategy = Strategy, settings = {} } = constant(`passport.protocols.${name}`)
|
|
153
|
+
const { name, ...metadata } = provider.store.get("protocol");
|
|
154
|
+
const { useStrategy = Strategy, settings = {}, loginOnly = metadata.loginOnly ?? false } = constant(`passport.protocols.${name}`) ?? {};
|
|
97
155
|
return {
|
|
98
156
|
name,
|
|
99
157
|
useStrategy,
|
|
158
|
+
loginOnly,
|
|
100
159
|
settings: {
|
|
101
160
|
...settings,
|
|
102
161
|
passReqToCallback: true
|
|
@@ -110,13 +169,18 @@ export class ProtocolsService {
|
|
|
110
169
|
*/
|
|
111
170
|
createHandler(provider) {
|
|
112
171
|
const middleware = this.platformHandler.createCustomHandler(provider, "$onVerify");
|
|
172
|
+
const { name } = provider.store.get("protocol");
|
|
113
173
|
return async (req, ...args) => {
|
|
114
174
|
const done = args[args.length - 1];
|
|
115
175
|
if (req.$ctx) {
|
|
116
176
|
req.$ctx.set("PROTOCOL_ARGS", args.slice(0, -1));
|
|
117
177
|
try {
|
|
118
178
|
await middleware(req.$ctx);
|
|
119
|
-
|
|
179
|
+
const results = [].concat(req.$ctx.data);
|
|
180
|
+
if (results[0]) {
|
|
181
|
+
req.$ctx.set(VERIFIED_PROTOCOL, name);
|
|
182
|
+
}
|
|
183
|
+
done(null, ...results);
|
|
120
184
|
}
|
|
121
185
|
catch (err) {
|
|
122
186
|
const error = err;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session key used by Passport.js to store the login state (`req.session.passport.user`).
|
|
3
|
+
* @ignore
|
|
4
|
+
*/
|
|
5
|
+
const PASSPORT_SESSION_KEY = "passport";
|
|
6
|
+
/**
|
|
7
|
+
* Return true when the request carries a login session persisted by Passport.js. `req.isAuthenticated()` is not
|
|
8
|
+
* enough: a stateless strategy (`session: false`) sets `req.user` without any session.
|
|
9
|
+
* @ignore
|
|
10
|
+
*/
|
|
11
|
+
export function hasLoginSession(req) {
|
|
12
|
+
return req.session?.[PASSPORT_SESSION_KEY]?.user !== undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Return the name of the protocol that established the current login session.
|
|
16
|
+
* @ignore
|
|
17
|
+
*/
|
|
18
|
+
export function getSessionProtocol(req) {
|
|
19
|
+
return req.session?.[PASSPORT_SESSION_KEY]?.protocol;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Record the protocol that established the current login session, next to the user stored by Passport.js.
|
|
23
|
+
* The record is dropped with the login state on `req.logout()`.
|
|
24
|
+
* @ignore
|
|
25
|
+
*/
|
|
26
|
+
export function setSessionProtocol(req, protocol) {
|
|
27
|
+
const state = req.session?.[PASSPORT_SESSION_KEY];
|
|
28
|
+
if (state && state.user !== undefined) {
|
|
29
|
+
state.protocol = protocol;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,4 +4,14 @@ export interface ProtocolOptions<Settings = any> {
|
|
|
4
4
|
name: string;
|
|
5
5
|
useStrategy: Type<Strategy>;
|
|
6
6
|
settings: Settings;
|
|
7
|
+
/**
|
|
8
|
+
* Run the strategy only on login endpoints decorated with `@Authenticate()`.
|
|
9
|
+
*
|
|
10
|
+
* A route decorated with `@Authorize()` then accepts this protocol only through the session it established,
|
|
11
|
+
* and never runs the strategy itself. Use it for interactive login strategies (`passport-local`, OAuth, ...)
|
|
12
|
+
* so that credentials sent to a business route do not open a new login flow.
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
loginOnly?: boolean;
|
|
7
17
|
}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { Context } from "@tsed/platform-params";
|
|
2
2
|
import { ProtocolsService } from "../services/ProtocolsService.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reserved protocol name satisfied by an existing login session. It never runs a Passport strategy.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SESSION_PROTOCOL = "session";
|
|
3
7
|
export declare class PassportMiddleware {
|
|
4
8
|
protocolsService: ProtocolsService;
|
|
5
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Return true when an existing login session satisfies the protocols declared on the endpoint:
|
|
11
|
+
*
|
|
12
|
+
* - the endpoint accepts any protocol (`"*"`),
|
|
13
|
+
* - the endpoint accepts the reserved `"session"` protocol and the request carries a persisted login session,
|
|
14
|
+
* - or the persisted login session was established by one of the endpoint protocols.
|
|
15
|
+
*
|
|
16
|
+
* A session established by another protocol never satisfies the endpoint: its strategies run instead.
|
|
17
|
+
*/
|
|
18
|
+
shouldSkip(protocol: string | string[], protocols: string[], ctx: Context): boolean;
|
|
6
19
|
use(ctx: Context): Promise<void>;
|
|
7
20
|
}
|
|
@@ -7,9 +7,15 @@ import type { ProtocolMethods } from "../interfaces/ProtocolMethods.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class ProtocolsService {
|
|
9
9
|
readonly strategies: Map<string, Strategy>;
|
|
10
|
+
readonly loginOnlyProtocols: Set<string>;
|
|
10
11
|
protected platformHandler: PlatformHandler;
|
|
11
12
|
getProtocols(): Provider[];
|
|
12
13
|
getProtocolsNames(): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Return true when the protocol must only run on login endpoints (`@Authenticate()`).
|
|
16
|
+
* @param name
|
|
17
|
+
*/
|
|
18
|
+
isLoginOnly(name: string): boolean;
|
|
13
19
|
/**
|
|
14
20
|
* Invoke provider and bind it to passport.
|
|
15
21
|
* @param provider
|
|
@@ -38,6 +44,15 @@ export declare class ProtocolsService {
|
|
|
38
44
|
* @private
|
|
39
45
|
*/
|
|
40
46
|
private call;
|
|
47
|
+
/**
|
|
48
|
+
* Wrap `req.logIn()` for the running `@Authenticate()` call to record the protocol that establishes the
|
|
49
|
+
* login session. The record is written in the `req.logIn()` callback, once Passport.js has finished updating
|
|
50
|
+
* the session (`keepSessionInfo` merge, Passport.js 0.4 session object replacement) and before it ends the
|
|
51
|
+
* request itself (`successRedirect`).
|
|
52
|
+
*
|
|
53
|
+
* Return a function restoring the original `req.logIn()`.
|
|
54
|
+
*/
|
|
55
|
+
private recordSessionProtocol;
|
|
41
56
|
/**
|
|
42
57
|
* Create strategy options based on decorator metadata and global configuration
|
|
43
58
|
* @param provider
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return true when the request carries a login session persisted by Passport.js. `req.isAuthenticated()` is not
|
|
3
|
+
* enough: a stateless strategy (`session: false`) sets `req.user` without any session.
|
|
4
|
+
* @ignore
|
|
5
|
+
*/
|
|
6
|
+
export declare function hasLoginSession(req: any): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Return the name of the protocol that established the current login session.
|
|
9
|
+
* @ignore
|
|
10
|
+
*/
|
|
11
|
+
export declare function getSessionProtocol(req: any): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Record the protocol that established the current login session, next to the user stored by Passport.js.
|
|
14
|
+
* The record is dropped with the login state on `req.logout()`.
|
|
15
|
+
* @ignore
|
|
16
|
+
*/
|
|
17
|
+
export declare function setSessionProtocol(req: any, protocol: string): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsed/passport",
|
|
3
|
-
"version": "8.44.
|
|
3
|
+
"version": "8.44.2",
|
|
4
4
|
"description": "Passport package for Ts.ED framework",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"tslib": ">=2.7.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@tsed/barrels": "8.44.
|
|
32
|
-
"@tsed/core": "8.44.
|
|
33
|
-
"@tsed/di": "8.44.
|
|
34
|
-
"@tsed/platform-http": "8.44.
|
|
35
|
-
"@tsed/platform-router": "8.44.
|
|
36
|
-
"@tsed/typescript": "8.44.
|
|
31
|
+
"@tsed/barrels": "8.44.2",
|
|
32
|
+
"@tsed/core": "8.44.2",
|
|
33
|
+
"@tsed/di": "8.44.2",
|
|
34
|
+
"@tsed/platform-http": "8.44.2",
|
|
35
|
+
"@tsed/platform-router": "8.44.2",
|
|
36
|
+
"@tsed/typescript": "8.44.2",
|
|
37
37
|
"@types/passport": "1.0.16",
|
|
38
38
|
"@types/passport-http": "0.3.11",
|
|
39
39
|
"@types/passport-local": "1.0.38",
|