@nestjs/authentication 0.0.1
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/LICENSE +23 -0
- package/README.md +50 -0
- package/dist/account/email-verification.handler.d.ts +23 -0
- package/dist/account/email-verification.handler.js +17 -0
- package/dist/account/email-verification.service.d.ts +46 -0
- package/dist/account/email-verification.service.js +111 -0
- package/dist/account/email.util.d.ts +14 -0
- package/dist/account/email.util.js +28 -0
- package/dist/account/in-memory-email-token.store.d.ts +12 -0
- package/dist/account/in-memory-email-token.store.js +60 -0
- package/dist/account/password-reset.handler.d.ts +19 -0
- package/dist/account/password-reset.handler.js +9 -0
- package/dist/account/password-reset.service.d.ts +69 -0
- package/dist/account/password-reset.service.js +204 -0
- package/dist/authentication.constants.d.ts +13 -0
- package/dist/authentication.constants.js +13 -0
- package/dist/authentication.module-definition.d.ts +21 -0
- package/dist/authentication.module-definition.js +147 -0
- package/dist/authentication.module.d.ts +34 -0
- package/dist/authentication.module.js +139 -0
- package/dist/context/authentication-scope.service.d.ts +39 -0
- package/dist/context/authentication-scope.service.js +64 -0
- package/dist/context/authentication.context.d.ts +28 -0
- package/dist/context/authentication.context.js +58 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/decorators/authenticate.decorator.d.ts +10 -0
- package/dist/decorators/authenticate.decorator.js +18 -0
- package/dist/decorators/current-session.decorator.d.ts +2 -0
- package/dist/decorators/current-session.decorator.js +6 -0
- package/dist/decorators/current-user.decorator.d.ts +6 -0
- package/dist/decorators/current-user.decorator.js +11 -0
- package/dist/decorators/index.d.ts +4 -0
- package/dist/decorators/index.js +4 -0
- package/dist/decorators/public.decorator.d.ts +5 -0
- package/dist/decorators/public.decorator.js +6 -0
- package/dist/errors/authentication.error.d.ts +27 -0
- package/dist/errors/authentication.error.js +33 -0
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/index.js +7 -0
- package/dist/errors/jwt.error.d.ts +9 -0
- package/dist/errors/jwt.error.js +12 -0
- package/dist/errors/magic-link.error.d.ts +18 -0
- package/dist/errors/magic-link.error.js +21 -0
- package/dist/errors/mfa-already-enrolled.error.d.ts +14 -0
- package/dist/errors/mfa-already-enrolled.error.js +17 -0
- package/dist/errors/oidc.error.d.ts +15 -0
- package/dist/errors/oidc.error.js +13 -0
- package/dist/errors/refresh-token.error.d.ts +10 -0
- package/dist/errors/refresh-token.error.js +12 -0
- package/dist/errors/secret-decryption.error.d.ts +3 -0
- package/dist/errors/secret-decryption.error.js +6 -0
- package/dist/events/authentication-events.interface.d.ts +121 -0
- package/dist/events/authentication-events.interface.js +1 -0
- package/dist/events/authentication-events.service.d.ts +19 -0
- package/dist/events/authentication-events.service.js +39 -0
- package/dist/events/authentication.channels.d.ts +4 -0
- package/dist/events/authentication.channels.js +11 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.js +3 -0
- package/dist/guards/authentication.guard.d.ts +57 -0
- package/dist/guards/authentication.guard.js +171 -0
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.js +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +52 -0
- package/dist/interceptors/authentication-scope.interceptor.d.ts +20 -0
- package/dist/interceptors/authentication-scope.interceptor.js +48 -0
- package/dist/interceptors/index.d.ts +1 -0
- package/dist/interceptors/index.js +1 -0
- package/dist/interfaces/api-key.interface.d.ts +41 -0
- package/dist/interfaces/api-key.interface.js +1 -0
- package/dist/interfaces/authenticate-options.interface.d.ts +31 -0
- package/dist/interfaces/authenticate-options.interface.js +1 -0
- package/dist/interfaces/authentication-error-options.interface.d.ts +8 -0
- package/dist/interfaces/authentication-error-options.interface.js +1 -0
- package/dist/interfaces/authentication-module-options.interface.d.ts +58 -0
- package/dist/interfaces/authentication-module-options.interface.js +1 -0
- package/dist/interfaces/authentication-registry.interface.d.ts +41 -0
- package/dist/interfaces/authentication-registry.interface.js +1 -0
- package/dist/interfaces/authentication-result.interface.d.ts +25 -0
- package/dist/interfaces/authentication-result.interface.js +1 -0
- package/dist/interfaces/authentication-storage.interface.d.ts +29 -0
- package/dist/interfaces/authentication-storage.interface.js +1 -0
- package/dist/interfaces/duration.interface.d.ts +2 -0
- package/dist/interfaces/duration.interface.js +1 -0
- package/dist/interfaces/email-token-store.interface.d.ts +52 -0
- package/dist/interfaces/email-token-store.interface.js +1 -0
- package/dist/interfaces/email-verification.interface.d.ts +18 -0
- package/dist/interfaces/email-verification.interface.js +1 -0
- package/dist/interfaces/index.d.ts +27 -0
- package/dist/interfaces/index.js +27 -0
- package/dist/interfaces/jwt-options.interface.d.ts +68 -0
- package/dist/interfaces/jwt-options.interface.js +1 -0
- package/dist/interfaces/jwt.interface.d.ts +36 -0
- package/dist/interfaces/jwt.interface.js +1 -0
- package/dist/interfaces/magic-link-store.interface.d.ts +36 -0
- package/dist/interfaces/magic-link-store.interface.js +1 -0
- package/dist/interfaces/magic-link.interface.d.ts +55 -0
- package/dist/interfaces/magic-link.interface.js +1 -0
- package/dist/interfaces/mfa-options.interface.d.ts +44 -0
- package/dist/interfaces/mfa-options.interface.js +1 -0
- package/dist/interfaces/mfa-store.interface.d.ts +70 -0
- package/dist/interfaces/mfa-store.interface.js +1 -0
- package/dist/interfaces/oidc-provider.interface.d.ts +56 -0
- package/dist/interfaces/oidc-provider.interface.js +1 -0
- package/dist/interfaces/oidc-state-store.interface.d.ts +25 -0
- package/dist/interfaces/oidc-state-store.interface.js +1 -0
- package/dist/interfaces/oidc.interface.d.ts +84 -0
- package/dist/interfaces/oidc.interface.js +1 -0
- package/dist/interfaces/password-hasher-options.interface.d.ts +17 -0
- package/dist/interfaces/password-hasher-options.interface.js +1 -0
- package/dist/interfaces/password-reset.interface.d.ts +49 -0
- package/dist/interfaces/password-reset.interface.js +1 -0
- package/dist/interfaces/refresh-token-options.interface.d.ts +10 -0
- package/dist/interfaces/refresh-token-options.interface.js +1 -0
- package/dist/interfaces/refresh-token-store.interface.d.ts +61 -0
- package/dist/interfaces/refresh-token-store.interface.js +1 -0
- package/dist/interfaces/session-store.interface.d.ts +53 -0
- package/dist/interfaces/session-store.interface.js +1 -0
- package/dist/interfaces/session.interface.d.ts +50 -0
- package/dist/interfaces/session.interface.js +1 -0
- package/dist/interfaces/sign-in-options.interface.d.ts +9 -0
- package/dist/interfaces/sign-in-options.interface.js +1 -0
- package/dist/interfaces/token.interface.d.ts +30 -0
- package/dist/interfaces/token.interface.js +1 -0
- package/dist/jwt/amr.util.d.ts +9 -0
- package/dist/jwt/amr.util.js +11 -0
- package/dist/jwt/in-memory-refresh-token.store.d.ts +29 -0
- package/dist/jwt/in-memory-refresh-token.store.js +67 -0
- package/dist/jwt/jwks.client.d.ts +34 -0
- package/dist/jwt/jwks.client.js +154 -0
- package/dist/jwt/jws.util.d.ts +45 -0
- package/dist/jwt/jws.util.js +282 -0
- package/dist/jwt/jwt-bearer.provider.d.ts +48 -0
- package/dist/jwt/jwt-bearer.provider.js +126 -0
- package/dist/jwt/jwt-signer.service.d.ts +19 -0
- package/dist/jwt/jwt-signer.service.js +36 -0
- package/dist/jwt/jwt-verifier.service.d.ts +22 -0
- package/dist/jwt/jwt-verifier.service.js +109 -0
- package/dist/jwt/token.service.d.ts +81 -0
- package/dist/jwt/token.service.js +230 -0
- package/dist/magic-link/in-memory-magic-link.store.d.ts +12 -0
- package/dist/magic-link/in-memory-magic-link.store.js +45 -0
- package/dist/magic-link/magic-link.handler.d.ts +25 -0
- package/dist/magic-link/magic-link.handler.js +14 -0
- package/dist/magic-link/magic-link.service.d.ts +79 -0
- package/dist/magic-link/magic-link.service.js +215 -0
- package/dist/mfa/in-memory-mfa.store.d.ts +25 -0
- package/dist/mfa/in-memory-mfa.store.js +73 -0
- package/dist/mfa/mfa.service.d.ts +104 -0
- package/dist/mfa/mfa.service.js +346 -0
- package/dist/mfa/otp.util.d.ts +7 -0
- package/dist/mfa/otp.util.js +52 -0
- package/dist/mfa/secret-cipher.service.d.ts +28 -0
- package/dist/mfa/secret-cipher.service.js +106 -0
- package/dist/oidc/in-memory-oidc-state.store.d.ts +13 -0
- package/dist/oidc/in-memory-oidc-state.store.js +45 -0
- package/dist/oidc/oidc-account.resolver.d.ts +23 -0
- package/dist/oidc/oidc-account.resolver.js +16 -0
- package/dist/oidc/oidc.client.d.ts +44 -0
- package/dist/oidc/oidc.client.js +286 -0
- package/dist/oidc/oidc.presets.d.ts +24 -0
- package/dist/oidc/oidc.presets.js +53 -0
- package/dist/oidc/oidc.service.d.ts +88 -0
- package/dist/oidc/oidc.service.js +259 -0
- package/dist/providers/api-key.provider.d.ts +51 -0
- package/dist/providers/api-key.provider.js +93 -0
- package/dist/providers/authentication.provider.d.ts +40 -0
- package/dist/providers/authentication.provider.js +33 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/services/authentication-registry.service.d.ts +75 -0
- package/dist/services/authentication-registry.service.js +235 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.js +3 -0
- package/dist/services/password-hasher.service.d.ts +47 -0
- package/dist/services/password-hasher.service.js +170 -0
- package/dist/services/ws-authenticator.service.d.ts +38 -0
- package/dist/services/ws-authenticator.service.js +92 -0
- package/dist/session/cookies.util.d.ts +34 -0
- package/dist/session/cookies.util.js +82 -0
- package/dist/session/in-memory-session.store.d.ts +33 -0
- package/dist/session/in-memory-session.store.js +60 -0
- package/dist/session/session-cookie.provider.d.ts +36 -0
- package/dist/session/session-cookie.provider.js +58 -0
- package/dist/session/session.service.d.ts +97 -0
- package/dist/session/session.service.js +208 -0
- package/dist/session/sign-in.service.d.ts +87 -0
- package/dist/session/sign-in.service.js +206 -0
- package/dist/storage/authentication.storage.d.ts +130 -0
- package/dist/storage/authentication.storage.js +350 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/testing/index.d.ts +39 -0
- package/dist/testing/index.js +531 -0
- package/dist/utils/api-key.util.d.ts +10 -0
- package/dist/utils/api-key.util.js +10 -0
- package/dist/utils/auth-state.util.d.ts +54 -0
- package/dist/utils/auth-state.util.js +209 -0
- package/dist/utils/bearer.util.d.ts +9 -0
- package/dist/utils/bearer.util.js +21 -0
- package/dist/utils/cross-origin.util.d.ts +29 -0
- package/dist/utils/cross-origin.util.js +90 -0
- package/dist/utils/crypto.util.d.ts +19 -0
- package/dist/utils/crypto.util.js +38 -0
- package/dist/utils/duration.util.d.ts +9 -0
- package/dist/utils/duration.util.js +44 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/options.util.d.ts +20 -0
- package/dist/utils/options.util.js +44 -0
- package/dist/utils/transport-errors.util.d.ts +33 -0
- package/dist/utils/transport-errors.util.js +70 -0
- package/package.json +108 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var PasswordResetService_1;
|
|
14
|
+
import { Inject, Injectable, Logger, Optional } from '@nestjs/common';
|
|
15
|
+
import { AuthenticationRegistry } from '../services/authentication-registry.service.js';
|
|
16
|
+
import { AuthenticationStorage } from '../storage/authentication.storage.js';
|
|
17
|
+
import { AUTHENTICATION_MODULE_OPTIONS } from '../authentication.constants.js';
|
|
18
|
+
import { TOKEN_PATTERN, randomToken, safeEqual, sha256 } from '../utils/crypto.util.js';
|
|
19
|
+
import { durationOr, hasExpired } from '../utils/duration.util.js';
|
|
20
|
+
import { AuthenticationEvents } from '../events/authentication-events.service.js';
|
|
21
|
+
import { requireUrlOption } from '../utils/options.util.js';
|
|
22
|
+
import { TokenService } from '../jwt/token.service.js';
|
|
23
|
+
import { PasswordHasher, isHashablePassword } from '../services/password-hasher.service.js';
|
|
24
|
+
import { SessionService } from '../session/session.service.js';
|
|
25
|
+
import { SignInService } from '../session/sign-in.service.js';
|
|
26
|
+
import { isMailableEmail, normalizeEmail } from './email.util.js';
|
|
27
|
+
/**
|
|
28
|
+
* "Forgot your password?": reset links that are 256-bit single-use tokens,
|
|
29
|
+
* stored hashed, short-lived, and bound to the address they were sent to and
|
|
30
|
+
* to the password they replace.
|
|
31
|
+
*
|
|
32
|
+
* - `request(email)` answers the same way, at once, whether or not the
|
|
33
|
+
* address has an account: finding the account, storing the token and
|
|
34
|
+
* sending the link happen after it returned, so neither the answer nor
|
|
35
|
+
* its timing reveals which addresses are registered. The link goes to the
|
|
36
|
+
* address the account has stored, never to the one typed in the form.
|
|
37
|
+
* - `reset(token, password)` burns the token, stores the new password's
|
|
38
|
+
* hash, invalidates the user's other reset links, and ends every session
|
|
39
|
+
* and refresh-token family of the user (whoever knew the old password is
|
|
40
|
+
* signed out). It also marks the address verified when email
|
|
41
|
+
* verification is configured: the link proved it. API keys live in your
|
|
42
|
+
* own table: revoke those yourself, on the `password-reset` event.
|
|
43
|
+
*
|
|
44
|
+
* Point the link at a page that asks for the new password and POSTs it with
|
|
45
|
+
* the token: mail scanners and link previews follow GET links.
|
|
46
|
+
*/
|
|
47
|
+
let PasswordResetService = PasswordResetService_1 = class PasswordResetService {
|
|
48
|
+
constructor(storage, registry, hasher, sessions, tokens, signInService, options, events = new AuthenticationEvents()) {
|
|
49
|
+
this.storage = storage;
|
|
50
|
+
this.registry = registry;
|
|
51
|
+
this.hasher = hasher;
|
|
52
|
+
this.sessions = sessions;
|
|
53
|
+
this.tokens = tokens;
|
|
54
|
+
this.signInService = signInService;
|
|
55
|
+
this.events = events;
|
|
56
|
+
this.inFlight = new Set();
|
|
57
|
+
this.options = options?.passwordReset;
|
|
58
|
+
if (this.options) {
|
|
59
|
+
requireUrlOption(this.options.url, 'passwordReset.url', 'the page that receives the link');
|
|
60
|
+
}
|
|
61
|
+
this.ttl = durationOr(this.options?.ttl, '1h');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Starts a reset for `email`, if it belongs to an account: the link goes
|
|
65
|
+
* to `PasswordResetHandler.send()`. Returns at once, before the account is
|
|
66
|
+
* looked up, so the caller answers every address the same way and in the
|
|
67
|
+
* same time. Failures are logged, not thrown. On shutdown, the module waits
|
|
68
|
+
* for the requests in flight.
|
|
69
|
+
*/
|
|
70
|
+
request(email) {
|
|
71
|
+
this.feature();
|
|
72
|
+
const requested = typeof email === 'string' ? normalizeEmail(email) : '';
|
|
73
|
+
if (!isMailableEmail(requested)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// The lookup runs once the caller has answered: even a handler that works synchronously
|
|
77
|
+
// cannot make the answer, or its timing, depend on the account.
|
|
78
|
+
const work = new Promise((resolve) => setImmediate(resolve))
|
|
79
|
+
.then(() => this.issue(requested))
|
|
80
|
+
.catch((error) => PasswordResetService_1.logger.error('A password reset request failed', error instanceof Error ? error.stack : error));
|
|
81
|
+
this.inFlight.add(work);
|
|
82
|
+
void work.finally(() => this.inFlight.delete(work));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Sets a new password with a reset token. `null` (and nothing changed)
|
|
86
|
+
* for an unknown, used or expired token, and for one whose account
|
|
87
|
+
* changed its address or password since the link was sent. `null` too,
|
|
88
|
+
* before the token is looked up (so the link still works), for a password
|
|
89
|
+
* `PasswordHasher` refuses: not a string, empty, or over 4 KiB. Check your
|
|
90
|
+
* own password rules before calling it.
|
|
91
|
+
*/
|
|
92
|
+
async reset(token, password, { signIn = false } = {}) {
|
|
93
|
+
const { handler } = this.feature();
|
|
94
|
+
if (typeof token !== 'string' || !TOKEN_PATTERN.test(token) || !isHashablePassword(password)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const record = await this.storage.emailTokens.consumeEmailToken(sha256(token), 'password-reset');
|
|
98
|
+
if (!record || hasExpired(record.expiresAt, this.now())) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const found = await handler.findUser(normalizeEmail(record.email));
|
|
102
|
+
const account = found && checked(found);
|
|
103
|
+
if (!account ||
|
|
104
|
+
account.id !== record.userId ||
|
|
105
|
+
account.email !== record.email ||
|
|
106
|
+
!safeEqual(fingerprint(account.passwordHash), record.fingerprint ?? '')) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const passwordHash = await this.hasher.hash(password);
|
|
110
|
+
// Sign-ins end before the new password is stored, so a store that fails leaves the old password
|
|
111
|
+
// without its sessions rather than the new one next to them; and again after, for any sign-in
|
|
112
|
+
// that started in between.
|
|
113
|
+
await this.revokeSignIns(account.id);
|
|
114
|
+
await handler.updatePassword(account.id, passwordHash);
|
|
115
|
+
await this.storage.emailTokens.deleteUserEmailTokens(account.id, 'password-reset');
|
|
116
|
+
await this.revokeSignIns(account.id);
|
|
117
|
+
// The link reached the address, and the password is the owner's now.
|
|
118
|
+
await this.registry.handler('emailVerification')?.markVerified(account.id, account.email);
|
|
119
|
+
this.events.emit({ type: 'password-reset', userId: account.id });
|
|
120
|
+
const signedIn = signIn ? await this.signInService.signIn(account.id, { method: 'password-reset' }) : undefined;
|
|
121
|
+
return { userId: account.id, ...(signedIn && { signedIn }) };
|
|
122
|
+
}
|
|
123
|
+
async onModuleDestroy() {
|
|
124
|
+
await Promise.all(this.inFlight);
|
|
125
|
+
}
|
|
126
|
+
async issue(requested) {
|
|
127
|
+
const { options, handler } = this.feature();
|
|
128
|
+
const found = await handler.findUser(requested);
|
|
129
|
+
if (!found) {
|
|
130
|
+
this.events.emit({ type: 'password-reset-requested', email: requested });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const account = checked(found);
|
|
134
|
+
const token = randomToken();
|
|
135
|
+
const now = this.now();
|
|
136
|
+
const expiresAt = new Date(now + this.ttl);
|
|
137
|
+
await this.storage.emailTokens.saveEmailToken({
|
|
138
|
+
id: sha256(token),
|
|
139
|
+
purpose: 'password-reset',
|
|
140
|
+
userId: account.id,
|
|
141
|
+
email: account.email,
|
|
142
|
+
fingerprint: fingerprint(account.passwordHash),
|
|
143
|
+
createdAt: new Date(now),
|
|
144
|
+
expiresAt,
|
|
145
|
+
});
|
|
146
|
+
this.events.emit({ type: 'password-reset-requested', email: requested, userId: account.id });
|
|
147
|
+
// To the address the account has stored, never to the one typed: a lookup that ignores accents
|
|
148
|
+
// or dots would otherwise mail `victim@example.com`'s link to whoever typed `victim@exämple.com`.
|
|
149
|
+
const url = new URL(options.url);
|
|
150
|
+
url.searchParams.set('token', token);
|
|
151
|
+
await handler.send({ userId: account.id, email: account.email, url: url.toString(), expiresAt });
|
|
152
|
+
}
|
|
153
|
+
/** Ends every session and refresh-token family of the user. */
|
|
154
|
+
async revokeSignIns(userId) {
|
|
155
|
+
await this.sessions.revokeAll(userId);
|
|
156
|
+
await this.tokens.revokeAll(userId);
|
|
157
|
+
}
|
|
158
|
+
/** The options and the handler; the module refuses to start with one and not the other. */
|
|
159
|
+
feature() {
|
|
160
|
+
const handler = this.registry.handler('passwordReset');
|
|
161
|
+
if (!this.options || !handler) {
|
|
162
|
+
throw new Error('PasswordResetService: password reset is not enabled. Configure `passwordReset` in the AuthenticationModule ' +
|
|
163
|
+
"options, and register a PasswordResetHandler: `registry.registerHandler('passwordReset', this)`.");
|
|
164
|
+
}
|
|
165
|
+
return { options: this.options, handler };
|
|
166
|
+
}
|
|
167
|
+
now() {
|
|
168
|
+
return this.options?.now?.() ?? Date.now();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
PasswordResetService.logger = new Logger('PasswordResetService');
|
|
172
|
+
PasswordResetService = PasswordResetService_1 = __decorate([
|
|
173
|
+
Injectable(),
|
|
174
|
+
__param(6, Optional()),
|
|
175
|
+
__param(6, Inject(AUTHENTICATION_MODULE_OPTIONS)),
|
|
176
|
+
__metadata("design:paramtypes", [AuthenticationStorage,
|
|
177
|
+
AuthenticationRegistry,
|
|
178
|
+
PasswordHasher,
|
|
179
|
+
SessionService,
|
|
180
|
+
TokenService,
|
|
181
|
+
SignInService, Object, AuthenticationEvents])
|
|
182
|
+
], PasswordResetService);
|
|
183
|
+
export { PasswordResetService };
|
|
184
|
+
/**
|
|
185
|
+
* The account `findUser()` returned, with what a link is bound to: the
|
|
186
|
+
* address it goes to, and the password hash it replaces. A query that leaves
|
|
187
|
+
* either out would send links to the typed address, or keep them working
|
|
188
|
+
* after a password change.
|
|
189
|
+
*/
|
|
190
|
+
function checked(account) {
|
|
191
|
+
if (typeof account.email !== 'string' || account.email.trim() === '') {
|
|
192
|
+
throw new TypeError('PasswordResetHandler.findUser() must return the account’s stored `email`: the link goes there, never to the ' +
|
|
193
|
+
'address typed in the form.');
|
|
194
|
+
}
|
|
195
|
+
if (account.passwordHash === undefined) {
|
|
196
|
+
throw new TypeError('PasswordResetHandler.findUser() must return `passwordHash` (`null` for an account without a password): a link ' +
|
|
197
|
+
'works only while it is unchanged. Does the query leave the column out (TypeORM `select: false`)?');
|
|
198
|
+
}
|
|
199
|
+
return account;
|
|
200
|
+
}
|
|
201
|
+
/** What a reset link remembers of the password it replaces. */
|
|
202
|
+
function fingerprint(passwordHash) {
|
|
203
|
+
return sha256(`nestjs-authentication password-reset v1 ${passwordHash ?? ''}`);
|
|
204
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The resolved module options: what `forRoot()` received, or what `forRootAsync()`'s factory returned. */
|
|
2
|
+
export declare const AUTHENTICATION_MODULE_OPTIONS = "AUTHENTICATION_MODULE_OPTIONS";
|
|
3
|
+
/** @internal The storage contracts the configured features use, for `AuthenticationStorage`'s production guard. */
|
|
4
|
+
export declare const AUTHENTICATION_STORAGE_REQUIREMENTS: unique symbol;
|
|
5
|
+
/** @internal Route metadata written by `@Public()` and `@Authenticate()`. */
|
|
6
|
+
export declare const AUTHENTICATION_METADATA = "authentication:route";
|
|
7
|
+
/**
|
|
8
|
+
* @internal The static brand on `AuthenticationGuard`, which subclasses
|
|
9
|
+
* inherit. `@nestjs/authorization` recognizes the guard by it, without
|
|
10
|
+
* importing this package or relying on the class name. A registry symbol,
|
|
11
|
+
* so every copy of either package agrees on it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const AUTHENTICATION_GUARD_BRAND: unique symbol;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The resolved module options: what `forRoot()` received, or what `forRootAsync()`'s factory returned. */
|
|
2
|
+
export const AUTHENTICATION_MODULE_OPTIONS = 'AUTHENTICATION_MODULE_OPTIONS';
|
|
3
|
+
/** @internal The storage contracts the configured features use, for `AuthenticationStorage`'s production guard. */
|
|
4
|
+
export const AUTHENTICATION_STORAGE_REQUIREMENTS = Symbol('AUTHENTICATION_STORAGE_REQUIREMENTS');
|
|
5
|
+
/** @internal Route metadata written by `@Public()` and `@Authenticate()`. */
|
|
6
|
+
export const AUTHENTICATION_METADATA = 'authentication:route';
|
|
7
|
+
/**
|
|
8
|
+
* @internal The static brand on `AuthenticationGuard`, which subclasses
|
|
9
|
+
* inherit. `@nestjs/authorization` recognizes the guard by it, without
|
|
10
|
+
* importing this package or relying on the class name. A registry symbol,
|
|
11
|
+
* so every copy of either package agrees on it.
|
|
12
|
+
*/
|
|
13
|
+
export const AUTHENTICATION_GUARD_BRAND = Symbol.for('@nestjs/authentication:guard');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Provider } from '@nestjs/common';
|
|
2
|
+
import type { AuthenticationModuleOptions, AuthenticationModuleExtras } from './interfaces/authentication-module-options.interface.js';
|
|
3
|
+
export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<AuthenticationModuleOptions, "forRoot", "createAuthenticationOptions", AuthenticationModuleExtras>, OPTIONS_TYPE: AuthenticationModuleOptions & Partial<AuthenticationModuleExtras>, ASYNC_OPTIONS_TYPE: import("@nestjs/common").ConfigurableModuleAsyncOptions<AuthenticationModuleOptions, "createAuthenticationOptions"> & Partial<AuthenticationModuleExtras>;
|
|
4
|
+
/** What `forRootAsync()` takes: `useFactory`, `useClass` or `useExisting`, with `imports` and `inject` for them. */
|
|
5
|
+
export type AuthenticationModuleAsyncOptions = typeof ASYNC_OPTIONS_TYPE;
|
|
6
|
+
/** @internal The module options, once `startupChecks()` accepted them. */
|
|
7
|
+
export declare const CHECKED_OPTIONS: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* @internal Runs once at startup: fails on options that don't exist (the
|
|
10
|
+
* classes that used to be options among them), then warns about lifetimes
|
|
11
|
+
* that look like seconds.
|
|
12
|
+
*/
|
|
13
|
+
export declare const startupChecks: Provider;
|
|
14
|
+
/**
|
|
15
|
+
* @internal The storage contracts the configured features use, for the
|
|
16
|
+
* production guard: the features the options and the registered providers
|
|
17
|
+
* enable, and what each one reads or writes (`CONTRACTS_BY_FEATURE`, the one
|
|
18
|
+
* place that says so). Computed when storage locks, which locks the
|
|
19
|
+
* registry first: the credential providers decide whether sessions are used.
|
|
20
|
+
*/
|
|
21
|
+
export declare const storageRequirements: Provider;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder, Logger } from '@nestjs/common';
|
|
2
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
3
|
+
import { AuthenticationGuard } from './guards/authentication.guard.js';
|
|
4
|
+
import { AuthenticationRegistry, LOCK_REGISTRY } from './services/authentication-registry.service.js';
|
|
5
|
+
import { contractsUsedBy, } from './storage/authentication.storage.js';
|
|
6
|
+
import { AUTHENTICATION_MODULE_OPTIONS, AUTHENTICATION_STORAGE_REQUIREMENTS } from './authentication.constants.js';
|
|
7
|
+
import { SessionCookieProvider } from './session/session-cookie.provider.js';
|
|
8
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder({ optionsInjectionToken: AUTHENTICATION_MODULE_OPTIONS })
|
|
9
|
+
.setClassMethodName('forRoot')
|
|
10
|
+
.setFactoryMethodName('createAuthenticationOptions')
|
|
11
|
+
.setExtras({ isGlobal: true, globalGuard: true }, (definition, extras) => ({
|
|
12
|
+
...definition,
|
|
13
|
+
global: extras.isGlobal ?? true,
|
|
14
|
+
providers: [
|
|
15
|
+
...(definition.providers ?? []),
|
|
16
|
+
...(extras.globalGuard === false ? [] : [{ provide: APP_GUARD, useExisting: AuthenticationGuard }]),
|
|
17
|
+
],
|
|
18
|
+
}))
|
|
19
|
+
.build();
|
|
20
|
+
/** @internal The module options, once `startupChecks()` accepted them. */
|
|
21
|
+
export const CHECKED_OPTIONS = Symbol('AUTHENTICATION_CHECKED_OPTIONS');
|
|
22
|
+
/**
|
|
23
|
+
* @internal Runs once at startup: fails on options that don't exist (the
|
|
24
|
+
* classes that used to be options among them), then warns about lifetimes
|
|
25
|
+
* that look like seconds.
|
|
26
|
+
*/
|
|
27
|
+
export const startupChecks = {
|
|
28
|
+
provide: CHECKED_OPTIONS,
|
|
29
|
+
inject: [AUTHENTICATION_MODULE_OPTIONS],
|
|
30
|
+
useFactory: (resolved) => {
|
|
31
|
+
const options = resolved ?? {};
|
|
32
|
+
checkKeys(options);
|
|
33
|
+
warnShortLifetimes(options, new Logger('AuthenticationModule'));
|
|
34
|
+
return options;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* @internal The storage contracts the configured features use, for the
|
|
39
|
+
* production guard: the features the options and the registered providers
|
|
40
|
+
* enable, and what each one reads or writes (`CONTRACTS_BY_FEATURE`, the one
|
|
41
|
+
* place that says so). Computed when storage locks, which locks the
|
|
42
|
+
* registry first: the credential providers decide whether sessions are used.
|
|
43
|
+
*/
|
|
44
|
+
export const storageRequirements = {
|
|
45
|
+
provide: AUTHENTICATION_STORAGE_REQUIREMENTS,
|
|
46
|
+
inject: [CHECKED_OPTIONS, AuthenticationRegistry],
|
|
47
|
+
useFactory: (options, registry) => ({
|
|
48
|
+
get contracts() {
|
|
49
|
+
registry[LOCK_REGISTRY]();
|
|
50
|
+
const enabled = {
|
|
51
|
+
sessionCookie: registry.providers.some((provider) => provider instanceof SessionCookieProvider),
|
|
52
|
+
accessToken: options.accessToken !== undefined,
|
|
53
|
+
mfa: options.mfa !== undefined,
|
|
54
|
+
// With their handlers: the registry refuses one without the other.
|
|
55
|
+
magicLink: options.magicLink !== undefined,
|
|
56
|
+
oidc: options.oidc !== undefined,
|
|
57
|
+
passwordReset: options.passwordReset !== undefined,
|
|
58
|
+
emailVerification: options.emailVerification !== undefined,
|
|
59
|
+
};
|
|
60
|
+
return contractsUsedBy(Object.keys(enabled).filter((feature) => enabled[feature]));
|
|
61
|
+
},
|
|
62
|
+
allowInMemoryStorage: options.allowInMemoryStorage === true,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
const KNOWN_OPTIONS = new Set([
|
|
66
|
+
'session',
|
|
67
|
+
'password',
|
|
68
|
+
'mfa',
|
|
69
|
+
'magicLink',
|
|
70
|
+
'accessToken',
|
|
71
|
+
'refreshToken',
|
|
72
|
+
'oidc',
|
|
73
|
+
'passwordReset',
|
|
74
|
+
'emailVerification',
|
|
75
|
+
'allowInMemoryStorage',
|
|
76
|
+
]);
|
|
77
|
+
/** Where each class that used to be an option registers now. */
|
|
78
|
+
const REGISTERED = {
|
|
79
|
+
providers: 'credential providers register themselves: provide the class in one of your modules, and call ' +
|
|
80
|
+
'`registry.registerProvider(this, { order })` from its constructor, with `registry: AuthenticationRegistry` injected',
|
|
81
|
+
magicLinkHandler: "handlers register themselves: `registry.registerHandler('magicLink', this)` from the handler's constructor",
|
|
82
|
+
oidcAccountResolver: "handlers register themselves: `registry.registerHandler('oidc', this)` from the resolver's constructor",
|
|
83
|
+
passwordResetHandler: "handlers register themselves: `registry.registerHandler('passwordReset', this)` from the handler's constructor",
|
|
84
|
+
emailVerificationHandler: "handlers register themselves: `registry.registerHandler('emailVerification', this)` from the handler's constructor",
|
|
85
|
+
oidcPath: 'the OIDC routes are your own controller now, calling OidcService; set `oidc.callbackUrl` to its callback URL',
|
|
86
|
+
imports: 'forRoot() takes values only; forRootAsync() takes `imports` for its factory',
|
|
87
|
+
stores: 'storage is registered by a provider: implement the store interfaces in an @Injectable() class and call ' +
|
|
88
|
+
'`storage.registerSource({ sessions: this, … })` from its constructor, with `storage: AuthenticationStorage` injected',
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* An option that doesn't exist fails at startup instead of being ignored: a
|
|
92
|
+
* class where registration belongs, a switch returned by a `forRootAsync()`
|
|
93
|
+
* factory (it goes at the top level, next to `useFactory`), or a typo.
|
|
94
|
+
*/
|
|
95
|
+
function checkKeys(options) {
|
|
96
|
+
for (const key of Object.keys(options)) {
|
|
97
|
+
if (KNOWN_OPTIONS.has(key)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (key === 'isGlobal' || key === 'globalGuard') {
|
|
101
|
+
throw new Error(`AuthenticationModule: \`${key}\` is in the options the factory returned. It goes at the top level of ` +
|
|
102
|
+
'forRootAsync(), next to useFactory, because it decides what the module registers.');
|
|
103
|
+
}
|
|
104
|
+
const hint = REGISTERED[key];
|
|
105
|
+
throw new Error(`AuthenticationModule: \`${key}\` is not an option` +
|
|
106
|
+
(hint ? `: ${hint}.` : `. The options are ${[...KNOWN_OPTIONS].join(', ')}.`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** The lifetimes: what `@nestjs/jwt` and jsonwebtoken users are used to giving in seconds. */
|
|
110
|
+
const LIFETIMES = [
|
|
111
|
+
['accessToken.ttl', (options) => options.accessToken?.ttl],
|
|
112
|
+
['refreshToken.ttl', (options) => options.refreshToken?.ttl],
|
|
113
|
+
['refreshToken.absoluteTtl', (options) => options.refreshToken?.absoluteTtl],
|
|
114
|
+
['session.absoluteTtl', (options) => options.session?.absoluteTtl],
|
|
115
|
+
['session.idleTtl', (options) => options.session?.idleTtl],
|
|
116
|
+
['mfa.pendingTtl', (options) => options.mfa?.pendingTtl],
|
|
117
|
+
['magicLink.ttl', (options) => options.magicLink?.ttl],
|
|
118
|
+
['oidc.transactionTtl', (options) => options.oidc?.transactionTtl],
|
|
119
|
+
['passwordReset.ttl', (options) => options.passwordReset?.ttl],
|
|
120
|
+
['emailVerification.ttl', (options) => options.emailVerification?.ttl],
|
|
121
|
+
];
|
|
122
|
+
/**
|
|
123
|
+
* A numeric lifetime is milliseconds, so `ttl: 3600` means 3.6 seconds,
|
|
124
|
+
* not the hour a `@nestjs/jwt` user meant. One warning per lifetime under a
|
|
125
|
+
* minute (`0`, which turns `session.idleTtl` off, excepted), suggesting the
|
|
126
|
+
* string that the number would mean in seconds.
|
|
127
|
+
*/
|
|
128
|
+
function warnShortLifetimes(options, logger) {
|
|
129
|
+
for (const [path, read] of LIFETIMES) {
|
|
130
|
+
const value = read(options);
|
|
131
|
+
if (typeof value !== 'number' || !(value > 0 && value < 60_000)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
logger.warn(`\`${path}\` is ${value} milliseconds. Numeric lifetimes are milliseconds, not seconds as in ` +
|
|
135
|
+
`@nestjs/jwt: did you mean '${asSeconds(value)}'?`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** `value` read as seconds, in the largest whole unit: 900 is `15m`, 3600 is `1h`. */
|
|
139
|
+
function asSeconds(value) {
|
|
140
|
+
if (value % 3_600 === 0) {
|
|
141
|
+
return `${value / 3_600}h`;
|
|
142
|
+
}
|
|
143
|
+
if (value % 60 === 0) {
|
|
144
|
+
return `${value / 60}m`;
|
|
145
|
+
}
|
|
146
|
+
return `${value}s`;
|
|
147
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type DynamicModule, type OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
import { ConfigurableModuleClass } from './authentication.module-definition.js';
|
|
4
|
+
import type { AuthenticationModuleForRootOptions } from './interfaces/authentication-module-options.interface.js';
|
|
5
|
+
import { AuthenticationRegistry } from './services/authentication-registry.service.js';
|
|
6
|
+
import { AuthenticationStorage } from './storage/authentication.storage.js';
|
|
7
|
+
/**
|
|
8
|
+
* `AuthenticationModule.forRoot({ session, mfa, … })`, or
|
|
9
|
+
* `forRootAsync({ inject, useFactory })` for values read from config. Global
|
|
10
|
+
* by default. Registers the authentication guard, the interceptor that opens
|
|
11
|
+
* `AuthenticationContext` around each handler, `AuthenticationRegistry`,
|
|
12
|
+
* where the app's credential providers and feature handlers register, and
|
|
13
|
+
* `AuthenticationStorage`, where its stores do.
|
|
14
|
+
*
|
|
15
|
+
* Global enhancers do not reach everywhere by default: a hybrid app's
|
|
16
|
+
* message handlers need `app.connectMicroservice(options, { inheritAppConfig:
|
|
17
|
+
* true })`, and GraphQL field resolvers `fieldResolverEnhancers: ['guards',
|
|
18
|
+
* 'interceptors']`, or the guard does not run there.
|
|
19
|
+
*/
|
|
20
|
+
export declare class AuthenticationModule extends ConfigurableModuleClass implements OnModuleInit {
|
|
21
|
+
private readonly registry;
|
|
22
|
+
private readonly storage;
|
|
23
|
+
private readonly moduleRef;
|
|
24
|
+
constructor(registry: AuthenticationRegistry, storage: AuthenticationStorage, moduleRef: ModuleRef);
|
|
25
|
+
static forRoot(options?: AuthenticationModuleForRootOptions): DynamicModule;
|
|
26
|
+
/**
|
|
27
|
+
* Locks both registries: every provider constructor (where providers,
|
|
28
|
+
* handlers and stores register) has run, and nothing has served a request
|
|
29
|
+
* yet. Fails on a feature missing its handler or its option, logs what
|
|
30
|
+
* registered, and fails in production on in-memory stores a configured
|
|
31
|
+
* feature relies on (see `allowInMemoryStorage`).
|
|
32
|
+
*/
|
|
33
|
+
onModuleInit(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Module } from '@nestjs/common';
|
|
11
|
+
import { APP_INTERCEPTOR, ModuleRef } from '@nestjs/core';
|
|
12
|
+
import { EmailVerificationService } from './account/email-verification.service.js';
|
|
13
|
+
import { PasswordResetService } from './account/password-reset.service.js';
|
|
14
|
+
import { ConfigurableModuleClass, startupChecks, storageRequirements, } from './authentication.module-definition.js';
|
|
15
|
+
import { AuthenticationContext } from './context/authentication.context.js';
|
|
16
|
+
import { AuthenticationScopeInterceptor } from './interceptors/authentication-scope.interceptor.js';
|
|
17
|
+
import { AuthenticationScope } from './context/authentication-scope.service.js';
|
|
18
|
+
import { AuthenticationRegistry, LOCK_REGISTRY } from './services/authentication-registry.service.js';
|
|
19
|
+
import { AuthenticationStorage, LOCK_STORAGE } from './storage/authentication.storage.js';
|
|
20
|
+
import { AuthenticationGuard } from './guards/authentication.guard.js';
|
|
21
|
+
import { AUTHENTICATION_MODULE_OPTIONS } from './authentication.constants.js';
|
|
22
|
+
import { AuthenticationEvents } from './events/authentication-events.service.js';
|
|
23
|
+
import { WsAuthenticator } from './services/ws-authenticator.service.js';
|
|
24
|
+
import { TokenService } from './jwt/token.service.js';
|
|
25
|
+
import { MagicLinkService } from './magic-link/magic-link.service.js';
|
|
26
|
+
import { MfaService } from './mfa/mfa.service.js';
|
|
27
|
+
import { OidcService } from './oidc/oidc.service.js';
|
|
28
|
+
import { PasswordHasher } from './services/password-hasher.service.js';
|
|
29
|
+
import { SessionService } from './session/session.service.js';
|
|
30
|
+
import { SignInService } from './session/sign-in.service.js';
|
|
31
|
+
/**
|
|
32
|
+
* `AuthenticationModule.forRoot({ session, mfa, … })`, or
|
|
33
|
+
* `forRootAsync({ inject, useFactory })` for values read from config. Global
|
|
34
|
+
* by default. Registers the authentication guard, the interceptor that opens
|
|
35
|
+
* `AuthenticationContext` around each handler, `AuthenticationRegistry`,
|
|
36
|
+
* where the app's credential providers and feature handlers register, and
|
|
37
|
+
* `AuthenticationStorage`, where its stores do.
|
|
38
|
+
*
|
|
39
|
+
* Global enhancers do not reach everywhere by default: a hybrid app's
|
|
40
|
+
* message handlers need `app.connectMicroservice(options, { inheritAppConfig:
|
|
41
|
+
* true })`, and GraphQL field resolvers `fieldResolverEnhancers: ['guards',
|
|
42
|
+
* 'interceptors']`, or the guard does not run there.
|
|
43
|
+
*/
|
|
44
|
+
let AuthenticationModule = class AuthenticationModule extends ConfigurableModuleClass {
|
|
45
|
+
constructor(registry, storage, moduleRef) {
|
|
46
|
+
super();
|
|
47
|
+
this.registry = registry;
|
|
48
|
+
this.storage = storage;
|
|
49
|
+
this.moduleRef = moduleRef;
|
|
50
|
+
}
|
|
51
|
+
static forRoot(options = {}) {
|
|
52
|
+
return super.forRoot(options);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Locks both registries: every provider constructor (where providers,
|
|
56
|
+
* handlers and stores register) has run, and nothing has served a request
|
|
57
|
+
* yet. Fails on a feature missing its handler or its option, logs what
|
|
58
|
+
* registered, and fails in production on in-memory stores a configured
|
|
59
|
+
* feature relies on (see `allowInMemoryStorage`).
|
|
60
|
+
*/
|
|
61
|
+
onModuleInit() {
|
|
62
|
+
this.registry[LOCK_REGISTRY]();
|
|
63
|
+
this.storage[LOCK_STORAGE]();
|
|
64
|
+
assertGraphqlContextPerRequest(this.moduleRef);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
AuthenticationModule = __decorate([
|
|
68
|
+
Module({
|
|
69
|
+
providers: [
|
|
70
|
+
startupChecks,
|
|
71
|
+
storageRequirements,
|
|
72
|
+
AuthenticationRegistry,
|
|
73
|
+
AuthenticationStorage,
|
|
74
|
+
AuthenticationScope,
|
|
75
|
+
AuthenticationContext,
|
|
76
|
+
AuthenticationScopeInterceptor,
|
|
77
|
+
{ provide: APP_INTERCEPTOR, useExisting: AuthenticationScopeInterceptor },
|
|
78
|
+
AuthenticationGuard,
|
|
79
|
+
AuthenticationEvents,
|
|
80
|
+
WsAuthenticator,
|
|
81
|
+
SessionService,
|
|
82
|
+
SignInService,
|
|
83
|
+
{
|
|
84
|
+
provide: PasswordHasher,
|
|
85
|
+
inject: [AUTHENTICATION_MODULE_OPTIONS],
|
|
86
|
+
useFactory: (options) => new PasswordHasher(options.password),
|
|
87
|
+
},
|
|
88
|
+
MfaService,
|
|
89
|
+
TokenService,
|
|
90
|
+
EmailVerificationService,
|
|
91
|
+
PasswordResetService,
|
|
92
|
+
MagicLinkService,
|
|
93
|
+
OidcService,
|
|
94
|
+
],
|
|
95
|
+
exports: [
|
|
96
|
+
AUTHENTICATION_MODULE_OPTIONS,
|
|
97
|
+
AuthenticationRegistry,
|
|
98
|
+
AuthenticationStorage,
|
|
99
|
+
AuthenticationContext,
|
|
100
|
+
AuthenticationGuard,
|
|
101
|
+
AuthenticationEvents,
|
|
102
|
+
WsAuthenticator,
|
|
103
|
+
SessionService,
|
|
104
|
+
SignInService,
|
|
105
|
+
PasswordHasher,
|
|
106
|
+
MfaService,
|
|
107
|
+
TokenService,
|
|
108
|
+
EmailVerificationService,
|
|
109
|
+
PasswordResetService,
|
|
110
|
+
MagicLinkService,
|
|
111
|
+
OidcService,
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
__metadata("design:paramtypes", [AuthenticationRegistry,
|
|
115
|
+
AuthenticationStorage,
|
|
116
|
+
ModuleRef])
|
|
117
|
+
], AuthenticationModule);
|
|
118
|
+
export { AuthenticationModule };
|
|
119
|
+
/**
|
|
120
|
+
* The guard authenticates a GraphQL operation from its context's `req`. A
|
|
121
|
+
* `context` given as an object is one object for every request, and
|
|
122
|
+
* `@nestjs/apollo` sets the first request as its `req` and keeps it: every
|
|
123
|
+
* later operation would be authenticated with the first caller's
|
|
124
|
+
* credentials. So such a configuration fails at startup.
|
|
125
|
+
*/
|
|
126
|
+
function assertGraphqlContextPerRequest(moduleRef) {
|
|
127
|
+
let options;
|
|
128
|
+
try {
|
|
129
|
+
options = moduleRef.get('GqlModuleOptions', { strict: false });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return; // no GraphQL module
|
|
133
|
+
}
|
|
134
|
+
if (typeof options?.context === 'object' && options.context !== null) {
|
|
135
|
+
throw new Error('AuthenticationModule: the GraphQL module’s `context` is an object, shared by every request: the first ' +
|
|
136
|
+
'request is kept as its `req`, and every later operation would be authenticated as that caller. Make it a ' +
|
|
137
|
+
'function that builds one per request: `context: ({ req, res }) => ({ req, res, ...yourValues })`.');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from 'node:http';
|
|
2
|
+
import { type ExecutionContext } from '@nestjs/common';
|
|
3
|
+
import type { AuthenticationResult } from '../interfaces/authentication-result.interface.js';
|
|
4
|
+
export interface Scope {
|
|
5
|
+
result: AuthenticationResult<any, any> | null;
|
|
6
|
+
/** The call being handled; absent inside `AuthenticationContext.run()`. */
|
|
7
|
+
context?: ExecutionContext;
|
|
8
|
+
}
|
|
9
|
+
/** The request and response of an HTTP call, as the platform (Express, Fastify) provides them. */
|
|
10
|
+
export interface HttpExchange {
|
|
11
|
+
request: {
|
|
12
|
+
headers: IncomingHttpHeaders;
|
|
13
|
+
method?: string;
|
|
14
|
+
ip?: string;
|
|
15
|
+
};
|
|
16
|
+
response?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** @internal Runs `fn` as the handling of this call (the interceptor, around `next.handle()`). */
|
|
19
|
+
export declare function runCall<R>(scope: Scope, fn: () => R): R;
|
|
20
|
+
/**
|
|
21
|
+
* @internal What the handler of this call sees: the result the guard recorded for it, when the
|
|
22
|
+
* interceptor is handling this handler (param decorators are resolved inside it).
|
|
23
|
+
*/
|
|
24
|
+
export declare function scopedResult(context: ExecutionContext): Scope['result'] | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* @internal The per-call scope behind `AuthenticationContext` and
|
|
27
|
+
* `SignInService`: its own `AsyncLocalStorage`, opened around each handler
|
|
28
|
+
* by `AuthenticationScopeInterceptor`.
|
|
29
|
+
*/
|
|
30
|
+
export declare class AuthenticationScope {
|
|
31
|
+
private readonly storage;
|
|
32
|
+
get current(): Scope | undefined;
|
|
33
|
+
run<R>(scope: Scope, fn: () => R): R;
|
|
34
|
+
/**
|
|
35
|
+
* The HTTP request and response of the current call: an HTTP handler, or
|
|
36
|
+
* a GraphQL resolver whose context carries `req` (and `res`).
|
|
37
|
+
*/
|
|
38
|
+
exchange(): HttpExchange | undefined;
|
|
39
|
+
}
|