@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.
Files changed (215) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +50 -0
  3. package/dist/account/email-verification.handler.d.ts +23 -0
  4. package/dist/account/email-verification.handler.js +17 -0
  5. package/dist/account/email-verification.service.d.ts +46 -0
  6. package/dist/account/email-verification.service.js +111 -0
  7. package/dist/account/email.util.d.ts +14 -0
  8. package/dist/account/email.util.js +28 -0
  9. package/dist/account/in-memory-email-token.store.d.ts +12 -0
  10. package/dist/account/in-memory-email-token.store.js +60 -0
  11. package/dist/account/password-reset.handler.d.ts +19 -0
  12. package/dist/account/password-reset.handler.js +9 -0
  13. package/dist/account/password-reset.service.d.ts +69 -0
  14. package/dist/account/password-reset.service.js +204 -0
  15. package/dist/authentication.constants.d.ts +13 -0
  16. package/dist/authentication.constants.js +13 -0
  17. package/dist/authentication.module-definition.d.ts +21 -0
  18. package/dist/authentication.module-definition.js +147 -0
  19. package/dist/authentication.module.d.ts +34 -0
  20. package/dist/authentication.module.js +139 -0
  21. package/dist/context/authentication-scope.service.d.ts +39 -0
  22. package/dist/context/authentication-scope.service.js +64 -0
  23. package/dist/context/authentication.context.d.ts +28 -0
  24. package/dist/context/authentication.context.js +58 -0
  25. package/dist/context/index.d.ts +2 -0
  26. package/dist/context/index.js +2 -0
  27. package/dist/decorators/authenticate.decorator.d.ts +10 -0
  28. package/dist/decorators/authenticate.decorator.js +18 -0
  29. package/dist/decorators/current-session.decorator.d.ts +2 -0
  30. package/dist/decorators/current-session.decorator.js +6 -0
  31. package/dist/decorators/current-user.decorator.d.ts +6 -0
  32. package/dist/decorators/current-user.decorator.js +11 -0
  33. package/dist/decorators/index.d.ts +4 -0
  34. package/dist/decorators/index.js +4 -0
  35. package/dist/decorators/public.decorator.d.ts +5 -0
  36. package/dist/decorators/public.decorator.js +6 -0
  37. package/dist/errors/authentication.error.d.ts +27 -0
  38. package/dist/errors/authentication.error.js +33 -0
  39. package/dist/errors/index.d.ts +7 -0
  40. package/dist/errors/index.js +7 -0
  41. package/dist/errors/jwt.error.d.ts +9 -0
  42. package/dist/errors/jwt.error.js +12 -0
  43. package/dist/errors/magic-link.error.d.ts +18 -0
  44. package/dist/errors/magic-link.error.js +21 -0
  45. package/dist/errors/mfa-already-enrolled.error.d.ts +14 -0
  46. package/dist/errors/mfa-already-enrolled.error.js +17 -0
  47. package/dist/errors/oidc.error.d.ts +15 -0
  48. package/dist/errors/oidc.error.js +13 -0
  49. package/dist/errors/refresh-token.error.d.ts +10 -0
  50. package/dist/errors/refresh-token.error.js +12 -0
  51. package/dist/errors/secret-decryption.error.d.ts +3 -0
  52. package/dist/errors/secret-decryption.error.js +6 -0
  53. package/dist/events/authentication-events.interface.d.ts +121 -0
  54. package/dist/events/authentication-events.interface.js +1 -0
  55. package/dist/events/authentication-events.service.d.ts +19 -0
  56. package/dist/events/authentication-events.service.js +39 -0
  57. package/dist/events/authentication.channels.d.ts +4 -0
  58. package/dist/events/authentication.channels.js +11 -0
  59. package/dist/events/index.d.ts +3 -0
  60. package/dist/events/index.js +3 -0
  61. package/dist/guards/authentication.guard.d.ts +57 -0
  62. package/dist/guards/authentication.guard.js +171 -0
  63. package/dist/guards/index.d.ts +1 -0
  64. package/dist/guards/index.js +1 -0
  65. package/dist/index.d.ts +52 -0
  66. package/dist/index.js +52 -0
  67. package/dist/interceptors/authentication-scope.interceptor.d.ts +20 -0
  68. package/dist/interceptors/authentication-scope.interceptor.js +48 -0
  69. package/dist/interceptors/index.d.ts +1 -0
  70. package/dist/interceptors/index.js +1 -0
  71. package/dist/interfaces/api-key.interface.d.ts +41 -0
  72. package/dist/interfaces/api-key.interface.js +1 -0
  73. package/dist/interfaces/authenticate-options.interface.d.ts +31 -0
  74. package/dist/interfaces/authenticate-options.interface.js +1 -0
  75. package/dist/interfaces/authentication-error-options.interface.d.ts +8 -0
  76. package/dist/interfaces/authentication-error-options.interface.js +1 -0
  77. package/dist/interfaces/authentication-module-options.interface.d.ts +58 -0
  78. package/dist/interfaces/authentication-module-options.interface.js +1 -0
  79. package/dist/interfaces/authentication-registry.interface.d.ts +41 -0
  80. package/dist/interfaces/authentication-registry.interface.js +1 -0
  81. package/dist/interfaces/authentication-result.interface.d.ts +25 -0
  82. package/dist/interfaces/authentication-result.interface.js +1 -0
  83. package/dist/interfaces/authentication-storage.interface.d.ts +29 -0
  84. package/dist/interfaces/authentication-storage.interface.js +1 -0
  85. package/dist/interfaces/duration.interface.d.ts +2 -0
  86. package/dist/interfaces/duration.interface.js +1 -0
  87. package/dist/interfaces/email-token-store.interface.d.ts +52 -0
  88. package/dist/interfaces/email-token-store.interface.js +1 -0
  89. package/dist/interfaces/email-verification.interface.d.ts +18 -0
  90. package/dist/interfaces/email-verification.interface.js +1 -0
  91. package/dist/interfaces/index.d.ts +27 -0
  92. package/dist/interfaces/index.js +27 -0
  93. package/dist/interfaces/jwt-options.interface.d.ts +68 -0
  94. package/dist/interfaces/jwt-options.interface.js +1 -0
  95. package/dist/interfaces/jwt.interface.d.ts +36 -0
  96. package/dist/interfaces/jwt.interface.js +1 -0
  97. package/dist/interfaces/magic-link-store.interface.d.ts +36 -0
  98. package/dist/interfaces/magic-link-store.interface.js +1 -0
  99. package/dist/interfaces/magic-link.interface.d.ts +55 -0
  100. package/dist/interfaces/magic-link.interface.js +1 -0
  101. package/dist/interfaces/mfa-options.interface.d.ts +44 -0
  102. package/dist/interfaces/mfa-options.interface.js +1 -0
  103. package/dist/interfaces/mfa-store.interface.d.ts +70 -0
  104. package/dist/interfaces/mfa-store.interface.js +1 -0
  105. package/dist/interfaces/oidc-provider.interface.d.ts +56 -0
  106. package/dist/interfaces/oidc-provider.interface.js +1 -0
  107. package/dist/interfaces/oidc-state-store.interface.d.ts +25 -0
  108. package/dist/interfaces/oidc-state-store.interface.js +1 -0
  109. package/dist/interfaces/oidc.interface.d.ts +84 -0
  110. package/dist/interfaces/oidc.interface.js +1 -0
  111. package/dist/interfaces/password-hasher-options.interface.d.ts +17 -0
  112. package/dist/interfaces/password-hasher-options.interface.js +1 -0
  113. package/dist/interfaces/password-reset.interface.d.ts +49 -0
  114. package/dist/interfaces/password-reset.interface.js +1 -0
  115. package/dist/interfaces/refresh-token-options.interface.d.ts +10 -0
  116. package/dist/interfaces/refresh-token-options.interface.js +1 -0
  117. package/dist/interfaces/refresh-token-store.interface.d.ts +61 -0
  118. package/dist/interfaces/refresh-token-store.interface.js +1 -0
  119. package/dist/interfaces/session-store.interface.d.ts +53 -0
  120. package/dist/interfaces/session-store.interface.js +1 -0
  121. package/dist/interfaces/session.interface.d.ts +50 -0
  122. package/dist/interfaces/session.interface.js +1 -0
  123. package/dist/interfaces/sign-in-options.interface.d.ts +9 -0
  124. package/dist/interfaces/sign-in-options.interface.js +1 -0
  125. package/dist/interfaces/token.interface.d.ts +30 -0
  126. package/dist/interfaces/token.interface.js +1 -0
  127. package/dist/jwt/amr.util.d.ts +9 -0
  128. package/dist/jwt/amr.util.js +11 -0
  129. package/dist/jwt/in-memory-refresh-token.store.d.ts +29 -0
  130. package/dist/jwt/in-memory-refresh-token.store.js +67 -0
  131. package/dist/jwt/jwks.client.d.ts +34 -0
  132. package/dist/jwt/jwks.client.js +154 -0
  133. package/dist/jwt/jws.util.d.ts +45 -0
  134. package/dist/jwt/jws.util.js +282 -0
  135. package/dist/jwt/jwt-bearer.provider.d.ts +48 -0
  136. package/dist/jwt/jwt-bearer.provider.js +126 -0
  137. package/dist/jwt/jwt-signer.service.d.ts +19 -0
  138. package/dist/jwt/jwt-signer.service.js +36 -0
  139. package/dist/jwt/jwt-verifier.service.d.ts +22 -0
  140. package/dist/jwt/jwt-verifier.service.js +109 -0
  141. package/dist/jwt/token.service.d.ts +81 -0
  142. package/dist/jwt/token.service.js +230 -0
  143. package/dist/magic-link/in-memory-magic-link.store.d.ts +12 -0
  144. package/dist/magic-link/in-memory-magic-link.store.js +45 -0
  145. package/dist/magic-link/magic-link.handler.d.ts +25 -0
  146. package/dist/magic-link/magic-link.handler.js +14 -0
  147. package/dist/magic-link/magic-link.service.d.ts +79 -0
  148. package/dist/magic-link/magic-link.service.js +215 -0
  149. package/dist/mfa/in-memory-mfa.store.d.ts +25 -0
  150. package/dist/mfa/in-memory-mfa.store.js +73 -0
  151. package/dist/mfa/mfa.service.d.ts +104 -0
  152. package/dist/mfa/mfa.service.js +346 -0
  153. package/dist/mfa/otp.util.d.ts +7 -0
  154. package/dist/mfa/otp.util.js +52 -0
  155. package/dist/mfa/secret-cipher.service.d.ts +28 -0
  156. package/dist/mfa/secret-cipher.service.js +106 -0
  157. package/dist/oidc/in-memory-oidc-state.store.d.ts +13 -0
  158. package/dist/oidc/in-memory-oidc-state.store.js +45 -0
  159. package/dist/oidc/oidc-account.resolver.d.ts +23 -0
  160. package/dist/oidc/oidc-account.resolver.js +16 -0
  161. package/dist/oidc/oidc.client.d.ts +44 -0
  162. package/dist/oidc/oidc.client.js +286 -0
  163. package/dist/oidc/oidc.presets.d.ts +24 -0
  164. package/dist/oidc/oidc.presets.js +53 -0
  165. package/dist/oidc/oidc.service.d.ts +88 -0
  166. package/dist/oidc/oidc.service.js +259 -0
  167. package/dist/providers/api-key.provider.d.ts +51 -0
  168. package/dist/providers/api-key.provider.js +93 -0
  169. package/dist/providers/authentication.provider.d.ts +40 -0
  170. package/dist/providers/authentication.provider.js +33 -0
  171. package/dist/providers/index.d.ts +2 -0
  172. package/dist/providers/index.js +2 -0
  173. package/dist/services/authentication-registry.service.d.ts +75 -0
  174. package/dist/services/authentication-registry.service.js +235 -0
  175. package/dist/services/index.d.ts +3 -0
  176. package/dist/services/index.js +3 -0
  177. package/dist/services/password-hasher.service.d.ts +47 -0
  178. package/dist/services/password-hasher.service.js +170 -0
  179. package/dist/services/ws-authenticator.service.d.ts +38 -0
  180. package/dist/services/ws-authenticator.service.js +92 -0
  181. package/dist/session/cookies.util.d.ts +34 -0
  182. package/dist/session/cookies.util.js +82 -0
  183. package/dist/session/in-memory-session.store.d.ts +33 -0
  184. package/dist/session/in-memory-session.store.js +60 -0
  185. package/dist/session/session-cookie.provider.d.ts +36 -0
  186. package/dist/session/session-cookie.provider.js +58 -0
  187. package/dist/session/session.service.d.ts +97 -0
  188. package/dist/session/session.service.js +208 -0
  189. package/dist/session/sign-in.service.d.ts +87 -0
  190. package/dist/session/sign-in.service.js +206 -0
  191. package/dist/storage/authentication.storage.d.ts +130 -0
  192. package/dist/storage/authentication.storage.js +350 -0
  193. package/dist/storage/index.d.ts +1 -0
  194. package/dist/storage/index.js +1 -0
  195. package/dist/testing/index.d.ts +39 -0
  196. package/dist/testing/index.js +531 -0
  197. package/dist/utils/api-key.util.d.ts +10 -0
  198. package/dist/utils/api-key.util.js +10 -0
  199. package/dist/utils/auth-state.util.d.ts +54 -0
  200. package/dist/utils/auth-state.util.js +209 -0
  201. package/dist/utils/bearer.util.d.ts +9 -0
  202. package/dist/utils/bearer.util.js +21 -0
  203. package/dist/utils/cross-origin.util.d.ts +29 -0
  204. package/dist/utils/cross-origin.util.js +90 -0
  205. package/dist/utils/crypto.util.d.ts +19 -0
  206. package/dist/utils/crypto.util.js +38 -0
  207. package/dist/utils/duration.util.d.ts +9 -0
  208. package/dist/utils/duration.util.js +44 -0
  209. package/dist/utils/index.d.ts +8 -0
  210. package/dist/utils/index.js +8 -0
  211. package/dist/utils/options.util.d.ts +20 -0
  212. package/dist/utils/options.util.js +44 -0
  213. package/dist/utils/transport-errors.util.d.ts +33 -0
  214. package/dist/utils/transport-errors.util.js +70 -0
  215. 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
+ }