@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,350 @@
|
|
|
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 AuthenticationStorage_1;
|
|
14
|
+
import { Inject, Injectable, Logger, Optional } from '@nestjs/common';
|
|
15
|
+
import { InMemoryEmailTokenStore } from '../account/in-memory-email-token.store.js';
|
|
16
|
+
import { InMemoryRefreshTokenStore } from '../jwt/in-memory-refresh-token.store.js';
|
|
17
|
+
import { InMemoryMagicLinkStore } from '../magic-link/in-memory-magic-link.store.js';
|
|
18
|
+
import { InMemoryMfaStore } from '../mfa/in-memory-mfa.store.js';
|
|
19
|
+
import { InMemoryOidcStateStore } from '../oidc/in-memory-oidc-state.store.js';
|
|
20
|
+
import { InMemorySessionStore } from '../session/in-memory-session.store.js';
|
|
21
|
+
import { AUTHENTICATION_STORAGE_REQUIREMENTS } from '../authentication.constants.js';
|
|
22
|
+
/**
|
|
23
|
+
* @internal What each feature reads or writes, audited against the code
|
|
24
|
+
* paths: the production guard's rule.
|
|
25
|
+
* Any feature that signs someone in reads `mfa`, configured or not: a user
|
|
26
|
+
* may have enrolled an authenticator through another instance, and
|
|
27
|
+
* `MfaService.isEnrolled()` is how a sign-in finds out. Any feature that
|
|
28
|
+
* revokes a user's sign-ins reaches both `sessions` and `refreshTokens`.
|
|
29
|
+
*/
|
|
30
|
+
export const CONTRACTS_BY_FEATURE = {
|
|
31
|
+
/**
|
|
32
|
+
* A `SessionCookieProvider` in `providers`: `SessionService` (every
|
|
33
|
+
* request), `SignInService.signIn()` (`isEnrolled()`), `completeMfa()`,
|
|
34
|
+
* and `signOutEverywhere()`, which also revokes the user's refresh-token
|
|
35
|
+
* families.
|
|
36
|
+
*/
|
|
37
|
+
sessionCookie: ['sessions', 'refreshTokens', 'mfa'],
|
|
38
|
+
/** `accessToken`: `TokenService` (refresh-token families), whose `issue()` checks for an authenticator. */
|
|
39
|
+
accessToken: ['refreshTokens', 'mfa'],
|
|
40
|
+
/** `mfa`: enrollment, codes, recovery codes and the lockout. */
|
|
41
|
+
mfa: ['mfa'],
|
|
42
|
+
/** `magicLinkHandler`: pending links, then `SignInService.signIn()`. */
|
|
43
|
+
magicLink: ['sessions', 'mfa', 'magicLinks'],
|
|
44
|
+
/** `oidcAccountResolver`: logins in progress, the session that links an account, then `SignInService.signIn()`. */
|
|
45
|
+
oidc: ['sessions', 'mfa', 'oidcStates'],
|
|
46
|
+
/**
|
|
47
|
+
* `passwordResetHandler`: the links; `reset()` revokes every session and
|
|
48
|
+
* refresh-token family of the user, and with `signIn: true` signs in.
|
|
49
|
+
*/
|
|
50
|
+
passwordReset: ['sessions', 'refreshTokens', 'mfa', 'emailTokens'],
|
|
51
|
+
/** `emailVerificationHandler`: the links. */
|
|
52
|
+
emailVerification: ['emailTokens'],
|
|
53
|
+
};
|
|
54
|
+
/** @internal The contracts the features use, in the order messages list them. */
|
|
55
|
+
export function contractsUsedBy(features) {
|
|
56
|
+
const used = new Set();
|
|
57
|
+
for (const feature of features) {
|
|
58
|
+
for (const contract of CONTRACTS_BY_FEATURE[feature]) {
|
|
59
|
+
used.add(contract);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return CONTRACT_NAMES.filter((contract) => used.has(contract));
|
|
63
|
+
}
|
|
64
|
+
/** @internal Every contract, in the order they are listed in messages. */
|
|
65
|
+
export const STORAGE_CONTRACTS = {
|
|
66
|
+
sessions: {
|
|
67
|
+
interfaceName: 'SessionStore',
|
|
68
|
+
holds: 'sessions',
|
|
69
|
+
methods: ['getSession', 'createSession', 'touchSession', 'deleteSession', 'listUserSessions', 'deleteUserSessions'],
|
|
70
|
+
inMemory: () => new InMemorySessionStore(),
|
|
71
|
+
},
|
|
72
|
+
refreshTokens: {
|
|
73
|
+
interfaceName: 'RefreshTokenStore',
|
|
74
|
+
holds: 'refresh tokens',
|
|
75
|
+
methods: [
|
|
76
|
+
'getRefreshToken',
|
|
77
|
+
'saveRefreshToken',
|
|
78
|
+
'markRefreshTokenUsed',
|
|
79
|
+
'revokeRefreshTokenFamily',
|
|
80
|
+
'isRefreshTokenFamilyRevoked',
|
|
81
|
+
'revokeUserRefreshTokens',
|
|
82
|
+
],
|
|
83
|
+
inMemory: () => new InMemoryRefreshTokenStore(),
|
|
84
|
+
},
|
|
85
|
+
mfa: {
|
|
86
|
+
interfaceName: 'MfaStore',
|
|
87
|
+
holds: 'authenticators and recovery codes',
|
|
88
|
+
methods: [
|
|
89
|
+
'getTotp',
|
|
90
|
+
'saveTotp',
|
|
91
|
+
'claimTotpStep',
|
|
92
|
+
'saveRecoveryCodes',
|
|
93
|
+
'consumeRecoveryCode',
|
|
94
|
+
'countRecoveryCodes',
|
|
95
|
+
'recordMfaFailure',
|
|
96
|
+
'countMfaFailures',
|
|
97
|
+
'clearMfaFailures',
|
|
98
|
+
],
|
|
99
|
+
inMemory: () => new InMemoryMfaStore(),
|
|
100
|
+
},
|
|
101
|
+
magicLinks: {
|
|
102
|
+
interfaceName: 'MagicLinkStore',
|
|
103
|
+
holds: 'pending magic links',
|
|
104
|
+
methods: ['saveMagicLink', 'consumeMagicLink'],
|
|
105
|
+
inMemory: () => new InMemoryMagicLinkStore(),
|
|
106
|
+
},
|
|
107
|
+
oidcStates: {
|
|
108
|
+
interfaceName: 'OidcStateStore',
|
|
109
|
+
holds: 'OIDC logins in progress',
|
|
110
|
+
methods: ['saveOidcState', 'consumeOidcState'],
|
|
111
|
+
inMemory: () => new InMemoryOidcStateStore(),
|
|
112
|
+
},
|
|
113
|
+
emailTokens: {
|
|
114
|
+
interfaceName: 'EmailTokenStore',
|
|
115
|
+
holds: 'password reset and verification links',
|
|
116
|
+
methods: ['saveEmailToken', 'consumeEmailToken', 'deleteUserEmailTokens'],
|
|
117
|
+
inMemory: () => new InMemoryEmailTokenStore(),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const CONTRACT_NAMES = Object.keys(STORAGE_CONTRACTS);
|
|
121
|
+
const KNOWN = CONTRACT_NAMES.map((name) => `${name} (${STORAGE_CONTRACTS[name].interfaceName})`).join(', ');
|
|
122
|
+
const DEFAULT = 'in-memory stores (the default: state is lost on restart and not shared between instances)';
|
|
123
|
+
/** @internal Locks the registry. `AuthenticationModule.onModuleInit()` and the first read call it. */
|
|
124
|
+
export const LOCK_STORAGE = Symbol('AuthenticationStorage.lock');
|
|
125
|
+
/**
|
|
126
|
+
* Where the module keeps its state. The app registers its own stores here,
|
|
127
|
+
* from the constructor of an ordinary provider:
|
|
128
|
+
*
|
|
129
|
+
* ```ts
|
|
130
|
+
* @Injectable()
|
|
131
|
+
* export class DrizzleAuthenticationStore implements SessionStore, RefreshTokenStore, MfaStore {
|
|
132
|
+
* constructor(@Inject(DRIZZLE) private readonly db: Database, storage: AuthenticationStorage) {
|
|
133
|
+
* storage.registerSource({ sessions: this, refreshTokens: this, mfa: this });
|
|
134
|
+
* }
|
|
135
|
+
* // ...
|
|
136
|
+
* }
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* A contract no source registered uses the in-memory store, so an app that
|
|
140
|
+
* imports the module boots with no storage configured. The registry locks
|
|
141
|
+
* in `AuthenticationModule`'s `onModuleInit` (every provider constructor has
|
|
142
|
+
* run by then), or at the first read of a store if that is earlier (another
|
|
143
|
+
* module's `onModuleInit`): it logs the active sources, and in production
|
|
144
|
+
* refuses to start while a contract a configured feature uses has no
|
|
145
|
+
* source, unless `allowInMemoryStorage` is set. In production, a read of
|
|
146
|
+
* any other contract without a source (an app calling a service outside
|
|
147
|
+
* the features it configured) fails the same way at that read, instead of
|
|
148
|
+
* running on the in-memory default.
|
|
149
|
+
*/
|
|
150
|
+
let AuthenticationStorage = AuthenticationStorage_1 = class AuthenticationStorage {
|
|
151
|
+
constructor(requirements) {
|
|
152
|
+
this.requirements = requirements;
|
|
153
|
+
this.registered = new Map();
|
|
154
|
+
this.active = new Map();
|
|
155
|
+
this.locked = false;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Registers stores by contract: `{ sessions: this, refreshTokens: this }`.
|
|
159
|
+
* Call it from the constructor of a singleton provider. Throws, registering
|
|
160
|
+
* nothing, when a store lacks one of its contract's methods, when a
|
|
161
|
+
* contract already has a source (unless `replace` is set: tests,
|
|
162
|
+
* wrappers), and once the registry has locked.
|
|
163
|
+
*/
|
|
164
|
+
registerSource(sources, options = {}) {
|
|
165
|
+
const entries = validate(sources);
|
|
166
|
+
if (this.locked) {
|
|
167
|
+
throw new Error(`AuthenticationStorage.registerSource(): ${describe(entries)} registered after AuthenticationModule initialized ` +
|
|
168
|
+
`(or after its storage was first read), which already uses ${this.summary(CONTRACT_NAMES)}. Register from ` +
|
|
169
|
+
'the constructor of a singleton provider: providers of lazy-loaded modules, request-scoped and transient ' +
|
|
170
|
+
'providers, and lifecycle hooks run too late.');
|
|
171
|
+
}
|
|
172
|
+
if (!options.replace) {
|
|
173
|
+
for (const [contract, source] of entries) {
|
|
174
|
+
const previous = this.registered.get(contract);
|
|
175
|
+
if (previous) {
|
|
176
|
+
throw new Error(`AuthenticationStorage.registerSource(): ${nameOf(source)} can't register \`${contract}\`, ` +
|
|
177
|
+
`${previous === source ? 'it already did (the same instance, twice)' : `${nameOf(previous)} already did`}. ` +
|
|
178
|
+
'Register each contract once, or pass { replace: true } to replace it on purpose (tests, wrappers).');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const [contract, source] of entries) {
|
|
183
|
+
this.registered.set(contract, source);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** The active session store: the registered one, else the in-memory default. Reading it locks the registry. */
|
|
187
|
+
get sessions() {
|
|
188
|
+
return this.source('sessions');
|
|
189
|
+
}
|
|
190
|
+
get refreshTokens() {
|
|
191
|
+
return this.source('refreshTokens');
|
|
192
|
+
}
|
|
193
|
+
get mfa() {
|
|
194
|
+
return this.source('mfa');
|
|
195
|
+
}
|
|
196
|
+
get magicLinks() {
|
|
197
|
+
return this.source('magicLinks');
|
|
198
|
+
}
|
|
199
|
+
get oidcStates() {
|
|
200
|
+
return this.source('oidcStates');
|
|
201
|
+
}
|
|
202
|
+
get emailTokens() {
|
|
203
|
+
return this.source('emailTokens');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* @internal Freezes the sources, logs them, and enforces the production
|
|
207
|
+
* guard (which leaves the registry open): in production, a contract in use
|
|
208
|
+
* with no source fails unless in-memory storage was allowed. `log: false`
|
|
209
|
+
* for the contract suite, which builds a registry per case.
|
|
210
|
+
*/
|
|
211
|
+
[LOCK_STORAGE]({ log = true } = {}) {
|
|
212
|
+
if (this.locked) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const inUse = this.requirements?.contracts ?? [];
|
|
216
|
+
const missing = inUse.filter((contract) => !this.registered.has(contract));
|
|
217
|
+
if (missing.length > 0 && this.refusesInMemory()) {
|
|
218
|
+
throw new Error(productionError(missing));
|
|
219
|
+
}
|
|
220
|
+
this.locked = true;
|
|
221
|
+
for (const [contract, source] of this.registered) {
|
|
222
|
+
this.active.set(contract, source);
|
|
223
|
+
}
|
|
224
|
+
if (log) {
|
|
225
|
+
AuthenticationStorage_1.logger.log(`AuthenticationStorage: ${this.summary(inUse)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
source(contract) {
|
|
229
|
+
this[LOCK_STORAGE]();
|
|
230
|
+
let source = this.active.get(contract);
|
|
231
|
+
if (!source) {
|
|
232
|
+
// A contract no configured feature showed as used: never in memory in production either.
|
|
233
|
+
if (this.refusesInMemory()) {
|
|
234
|
+
throw new Error(productionError([contract]));
|
|
235
|
+
}
|
|
236
|
+
source = STORAGE_CONTRACTS[contract].inMemory();
|
|
237
|
+
this.active.set(contract, source);
|
|
238
|
+
}
|
|
239
|
+
return source;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* In production, unless `allowInMemoryStorage` is set. A registry built
|
|
243
|
+
* with `new`, outside the module, has no options: it refuses too.
|
|
244
|
+
*/
|
|
245
|
+
refusesInMemory() {
|
|
246
|
+
// `Production` and `production ` (a stray space in a .env file) are production too.
|
|
247
|
+
return process.env.NODE_ENV?.trim().toLowerCase() === 'production' && !this.requirements?.allowInMemoryStorage;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The in-memory default, `DrizzleAuthenticationStore` (one source for every
|
|
251
|
+
* contract), or the contracts in use or registered, by source:
|
|
252
|
+
* `RedisSessionStore (sessions); DrizzleAuthenticationStore (mfa); in-memory (oidcStates)`.
|
|
253
|
+
*/
|
|
254
|
+
summary(inUse) {
|
|
255
|
+
const sources = new Set(this.registered.values());
|
|
256
|
+
if (sources.size === 0) {
|
|
257
|
+
return DEFAULT;
|
|
258
|
+
}
|
|
259
|
+
if (sources.size === 1 && this.registered.size === CONTRACT_NAMES.length) {
|
|
260
|
+
return nameOf([...sources][0]);
|
|
261
|
+
}
|
|
262
|
+
const listed = CONTRACT_NAMES.filter((contract) => inUse.includes(contract) || this.registered.has(contract));
|
|
263
|
+
const groups = new Map();
|
|
264
|
+
// The registered sources first, then what runs in memory.
|
|
265
|
+
for (const contract of [...listed.filter((c) => this.registered.has(c)), ...listed.filter((c) => !this.registered.has(c))]) {
|
|
266
|
+
const source = this.registered.get(contract);
|
|
267
|
+
const name = source ? nameOf(source) : 'in-memory';
|
|
268
|
+
groups.set(name, [...(groups.get(name) ?? []), contract]);
|
|
269
|
+
}
|
|
270
|
+
return [...groups].map(([name, contracts]) => `${name} (${contracts.join(', ')})`).join('; ');
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
AuthenticationStorage.logger = new Logger('AuthenticationModule');
|
|
274
|
+
AuthenticationStorage = AuthenticationStorage_1 = __decorate([
|
|
275
|
+
Injectable(),
|
|
276
|
+
__param(0, Optional()),
|
|
277
|
+
__param(0, Inject(AUTHENTICATION_STORAGE_REQUIREMENTS)),
|
|
278
|
+
__metadata("design:paramtypes", [Object])
|
|
279
|
+
], AuthenticationStorage);
|
|
280
|
+
export { AuthenticationStorage };
|
|
281
|
+
/** The `[contract, store]` pairs of a `registerSource()` argument, after checking every one. */
|
|
282
|
+
function validate(sources) {
|
|
283
|
+
if (sources === null || typeof sources !== 'object') {
|
|
284
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): the contracts go by name, and got ${nameOf(sources)}. ${usage()}`);
|
|
285
|
+
}
|
|
286
|
+
// A store passed as itself (`registerSource(this)`): say which call it meant.
|
|
287
|
+
const implemented = CONTRACT_NAMES.filter((contract) => STORAGE_CONTRACTS[contract].methods.some((method) => typeof sources[method] === 'function'));
|
|
288
|
+
if (implemented.length > 0) {
|
|
289
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): the contracts go by name, and ${nameOf(sources)} was passed as itself. ` +
|
|
290
|
+
`Name what it implements: \`registerSource({ ${implemented.map((contract) => `${contract}: this`).join(', ')} })\`.`);
|
|
291
|
+
}
|
|
292
|
+
const keys = Object.keys(sources);
|
|
293
|
+
if (keys.length === 0) {
|
|
294
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): the contracts go by name, and got none. ${usage()}`);
|
|
295
|
+
}
|
|
296
|
+
const unknown = keys.filter((key) => !Object.hasOwn(STORAGE_CONTRACTS, key));
|
|
297
|
+
if (unknown.length > 0) {
|
|
298
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): unknown contract ${unknown.map((key) => `\`${key}\``).join(', ')}. ` +
|
|
299
|
+
`The contracts are ${KNOWN}.`);
|
|
300
|
+
}
|
|
301
|
+
return keys.map((contract) => {
|
|
302
|
+
const source = sources[contract];
|
|
303
|
+
const { interfaceName, methods } = STORAGE_CONTRACTS[contract];
|
|
304
|
+
if (source === null || typeof source !== 'object') {
|
|
305
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): expected an object implementing ${interfaceName} as \`${contract}\`, ` +
|
|
306
|
+
`got ${nameOf(source)}.`);
|
|
307
|
+
}
|
|
308
|
+
const missing = methods.filter((method) => typeof source[method] !== 'function');
|
|
309
|
+
if (missing.length > 0) {
|
|
310
|
+
throw new TypeError(`AuthenticationStorage.registerSource(): ${nameOf(source)} doesn't implement ${interfaceName} for \`${contract}\`: ` +
|
|
311
|
+
`${missing.map((method) => `${method}()`).join(', ')} ${missing.length === 1 ? 'is' : 'are'} missing.`);
|
|
312
|
+
}
|
|
313
|
+
return [contract, source];
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function usage() {
|
|
317
|
+
return `Pass the stores a provider implements: \`registerSource({ sessions: this, refreshTokens: this })\`. The contracts are ${KNOWN}.`;
|
|
318
|
+
}
|
|
319
|
+
function productionError(missing) {
|
|
320
|
+
const specs = missing.map((contract) => STORAGE_CONTRACTS[contract]);
|
|
321
|
+
const example = missing.map((contract) => `${contract}: this`).join(', ');
|
|
322
|
+
return (`AuthenticationStorage: no store is registered for ${and(missing.map((contract, i) => `\`${contract}\` (${specs[i].interfaceName})`))}, ` +
|
|
323
|
+
`and NODE_ENV is "production": in memory, ${and(specs.map((spec) => spec.holds))} would be lost on restart and not ` +
|
|
324
|
+
`shared between instances. Implement ${and(specs.map((spec) => spec.interfaceName))} in a provider that injects ` +
|
|
325
|
+
`AuthenticationStorage and calls \`storage.registerSource({ ${example} })\` in its constructor, or set ` +
|
|
326
|
+
'`allowInMemoryStorage: true` in the AuthenticationModule options to run in memory anyway.');
|
|
327
|
+
}
|
|
328
|
+
/** `a`, `a and b`, `a, b, and c`. */
|
|
329
|
+
function and(items) {
|
|
330
|
+
if (items.length <= 2) {
|
|
331
|
+
return items.join(' and ');
|
|
332
|
+
}
|
|
333
|
+
return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
|
|
334
|
+
}
|
|
335
|
+
/** How a message names a value: its class, or what it is instead of an instance. */
|
|
336
|
+
function nameOf(value) {
|
|
337
|
+
if (typeof value === 'function') {
|
|
338
|
+
return `the class ${value.name || '(anonymous)'} (pass an instance)`;
|
|
339
|
+
}
|
|
340
|
+
if (value === null || typeof value !== 'object') {
|
|
341
|
+
return String(value);
|
|
342
|
+
}
|
|
343
|
+
const name = value.constructor?.name;
|
|
344
|
+
return name && name !== 'Object' ? name : 'an object';
|
|
345
|
+
}
|
|
346
|
+
/** `DrizzleAuthenticationStore (sessions, mfa)`: the sources of a `registerSource()` call. */
|
|
347
|
+
function describe(entries) {
|
|
348
|
+
const names = [...new Set(entries.map(([, source]) => nameOf(source)))].join(', ');
|
|
349
|
+
return `${names} (${entries.map(([contract]) => contract).join(', ')})`;
|
|
350
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './authentication.storage.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './authentication.storage.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type AuthenticationStorageContract, type AuthenticationStorageSources } from '../interfaces/authentication-storage.interface.js';
|
|
2
|
+
export interface AuthenticationStoreContractOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The contracts to check, which `create()` must return. Default: all six.
|
|
5
|
+
* An app that splits storage runs the suite once per provider:
|
|
6
|
+
* `{ contracts: ['sessions'] }` for its Redis store, the rest for its SQL one.
|
|
7
|
+
*/
|
|
8
|
+
contracts?: AuthenticationStorageContract[];
|
|
9
|
+
/**
|
|
10
|
+
* Adds the concurrency cases: parallel calls on one token, one code, one
|
|
11
|
+
* link, which a store that reads and then writes (instead of one
|
|
12
|
+
* conditional write) fails. Set it for every store you run in production,
|
|
13
|
+
* on a pooled connection where you can, so the calls really race.
|
|
14
|
+
*/
|
|
15
|
+
concurrent?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The most pending magic links, OIDC logins and email tokens your store
|
|
18
|
+
* keeps (per kind). When set, a case per kind saves `maxPending + 2` and
|
|
19
|
+
* checks the two that expire first are gone: configure your store with a
|
|
20
|
+
* small cap in the test.
|
|
21
|
+
*/
|
|
22
|
+
maxPending?: number;
|
|
23
|
+
}
|
|
24
|
+
/** One case: `run()` resolves, or rejects with an assertion error. */
|
|
25
|
+
export interface AuthenticationStoreContractCase {
|
|
26
|
+
/** `<contract>: <what it checks>`. */
|
|
27
|
+
name: string;
|
|
28
|
+
contract: AuthenticationStorageContract;
|
|
29
|
+
run(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Stores on fresh state: `create()` is called once per case. */
|
|
32
|
+
export type AuthenticationStoreFactory = () => AuthenticationStorageSources | Promise<AuthenticationStorageSources>;
|
|
33
|
+
/**
|
|
34
|
+
* The cases every implementation of the given contracts must pass. Ids and
|
|
35
|
+
* user ids are unique per case, so a store on a shared database works, but
|
|
36
|
+
* the `maxPending` cases need the pending tables empty: return stores on
|
|
37
|
+
* fresh state (new tables, or truncated ones) from `create()`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function authenticationStoreContract(create: AuthenticationStoreFactory, options?: AuthenticationStoreContractOptions): AuthenticationStoreContractCase[];
|