@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,259 @@
|
|
|
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
|
+
import { BadGatewayException, BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException, Optional, UnauthorizedException, } from '@nestjs/common';
|
|
14
|
+
import { HttpAdapterHost } from '@nestjs/core';
|
|
15
|
+
import { AuthenticationScope } from '../context/authentication-scope.service.js';
|
|
16
|
+
import { AuthenticationRegistry } from '../services/authentication-registry.service.js';
|
|
17
|
+
import { AuthenticationStorage } from '../storage/authentication.storage.js';
|
|
18
|
+
import { AUTHENTICATION_MODULE_OPTIONS } from '../authentication.constants.js';
|
|
19
|
+
import { safeEqual, safeRedirectPath, sha256 } from '../utils/crypto.util.js';
|
|
20
|
+
import { durationOr, hasExpired } from '../utils/duration.util.js';
|
|
21
|
+
import { requireOption } from '../utils/options.util.js';
|
|
22
|
+
import { defaultCookieName, readCookie, serializeCookie } from '../session/cookies.util.js';
|
|
23
|
+
import { SessionService } from '../session/session.service.js';
|
|
24
|
+
import { SignInService } from '../session/sign-in.service.js';
|
|
25
|
+
import { OidcError } from '../errors/oidc.error.js';
|
|
26
|
+
import { OidcClient } from './oidc.client.js';
|
|
27
|
+
const header = (headers, name) => {
|
|
28
|
+
const value = headers[name];
|
|
29
|
+
return Array.isArray(value) ? value[0] : value;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* OpenID Connect sign-in, behind two routes of your own: a login route that
|
|
33
|
+
* redirects to the provider, and the callback route it sends the browser
|
|
34
|
+
* back to. Called from an HTTP handler, each method reads the request and
|
|
35
|
+
* sets its cookies on the response, on Express and Fastify alike:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Public()
|
|
39
|
+
* @Controller('auth/oidc')
|
|
40
|
+
* export class OidcController {
|
|
41
|
+
* constructor(private readonly oidc: OidcService) {}
|
|
42
|
+
*
|
|
43
|
+
* @Get(':provider/login')
|
|
44
|
+
* @Redirect()
|
|
45
|
+
* login(@Param('provider') provider: string, @Query('redirectTo') redirectTo?: string, @Query('link') link?: string) {
|
|
46
|
+
* return this.oidc.start(provider, { redirectTo, link: link === 'true' });
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* @Get(':provider/callback')
|
|
50
|
+
* @Redirect()
|
|
51
|
+
* callback(@Param('provider') provider: string, @Query() query: Record<string, string>) {
|
|
52
|
+
* return this.oidc.finish(provider, query);
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* The transaction (PKCE verifier, nonce, return path) stays server-side,
|
|
58
|
+
* keyed by `state`; the browser gets, in an `HttpOnly` cookie, the secret
|
|
59
|
+
* whose SHA-256 is the `state`. The callback must present both, which binds
|
|
60
|
+
* the response to the browser that started the login: another browser
|
|
61
|
+
* cannot finish it (login CSRF), nor can whoever reads the callback URL in a
|
|
62
|
+
* log or a history. `SameSite=Lax` lets the cookie through the IdP's
|
|
63
|
+
* top-level redirect, and the `__Host-` prefix keeps sibling subdomains from
|
|
64
|
+
* planting one.
|
|
65
|
+
*
|
|
66
|
+
* `start(provider, { link: true })` begins a link flow for the signed-in
|
|
67
|
+
* user instead of a sign-in: the callback re-checks that the same session is
|
|
68
|
+
* still live, hands `{ linkTo }` to the resolver, and keeps the session as
|
|
69
|
+
* it is.
|
|
70
|
+
*/
|
|
71
|
+
let OidcService = class OidcService {
|
|
72
|
+
constructor(storage, registry, signIn, sessions, options, scope = new AuthenticationScope(), adapterHost) {
|
|
73
|
+
this.storage = storage;
|
|
74
|
+
this.registry = registry;
|
|
75
|
+
this.signIn = signIn;
|
|
76
|
+
this.sessions = sessions;
|
|
77
|
+
this.scope = scope;
|
|
78
|
+
this.adapterHost = adapterHost;
|
|
79
|
+
this.clients = new Map();
|
|
80
|
+
this.options = options?.oidc;
|
|
81
|
+
if (this.options) {
|
|
82
|
+
assertCallbackUrl(this.options.callbackUrl);
|
|
83
|
+
assertProviderCredentials(this.options.providers);
|
|
84
|
+
}
|
|
85
|
+
this.transactionTtl = durationOr(this.options?.transactionTtl, '10m');
|
|
86
|
+
this.txCookieName = defaultCookieName('oidc_tx', this.options?.cookie);
|
|
87
|
+
}
|
|
88
|
+
client(name) {
|
|
89
|
+
const { options } = this.feature();
|
|
90
|
+
const config = Object.hasOwn(options.providers, name) ? options.providers[name] : undefined;
|
|
91
|
+
if (!config) {
|
|
92
|
+
throw new NotFoundException();
|
|
93
|
+
}
|
|
94
|
+
let client = this.clients.get(name);
|
|
95
|
+
if (!client) {
|
|
96
|
+
client = new OidcClient(name, config, options);
|
|
97
|
+
this.clients.set(name, client);
|
|
98
|
+
}
|
|
99
|
+
return client;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Starts a login: stores the transaction, sets its cookie, and returns
|
|
103
|
+
* the provider's authorization URL. 404 for a provider that isn't
|
|
104
|
+
* configured; 401 for a link flow without a signed-in session.
|
|
105
|
+
*/
|
|
106
|
+
async start(provider, { redirectTo, link = false, request } = {}) {
|
|
107
|
+
const exchange = this.scope.exchange();
|
|
108
|
+
const req = request ?? exchange?.request ?? { headers: {} };
|
|
109
|
+
let linking;
|
|
110
|
+
if (link) {
|
|
111
|
+
// A link attaches whatever account the provider has signed in on this browser: never at the
|
|
112
|
+
// bidding of another site's page (a top-level navigation carries the Lax session cookie).
|
|
113
|
+
if (header(req.headers, 'sec-fetch-site') === 'cross-site') {
|
|
114
|
+
throw new ForbiddenException('Cross-site link refused');
|
|
115
|
+
}
|
|
116
|
+
const session = await this.currentSession(req);
|
|
117
|
+
if (!session) {
|
|
118
|
+
throw new UnauthorizedException('sign in to link an account');
|
|
119
|
+
}
|
|
120
|
+
linking = { userId: session.userId, sessionId: session.id };
|
|
121
|
+
}
|
|
122
|
+
const { url, transaction, binding } = await this.client(provider)
|
|
123
|
+
.authorizationRequest(this.callbackUrl(provider), safeRedirectPath(redirectTo), this.transactionTtl)
|
|
124
|
+
.catch(toHttp);
|
|
125
|
+
await this.storage.oidcStates.saveOidcState({ ...transaction, ...(linking && { link: linking }) });
|
|
126
|
+
return this.redirect(exchange?.response, url, [this.txCookie(binding, this.transactionTtl / 1000)]);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Completes a login on the callback route, and returns where to send the
|
|
130
|
+
* browser: the login's `redirectTo`, else `redirectAfterLogin`. A sign-in
|
|
131
|
+
* goes through `SignInService`, which sets the session cookie on the
|
|
132
|
+
* response; the transaction cookie is cleared.
|
|
133
|
+
*/
|
|
134
|
+
async finish(provider, query, { request } = {}) {
|
|
135
|
+
const { options, resolver } = this.feature();
|
|
136
|
+
const exchange = this.scope.exchange();
|
|
137
|
+
const req = request ?? exchange?.request ?? { headers: {} };
|
|
138
|
+
// The cookie holds the secret whose SHA-256 is the state. The state travels in URLs, the
|
|
139
|
+
// secret never does: a callback URL read from a log is useless without this browser's cookie.
|
|
140
|
+
const state = query.state;
|
|
141
|
+
const bound = readCookie(header(req.headers, 'cookie'), this.txCookieName);
|
|
142
|
+
if (typeof state !== 'string' || !bound || !safeEqual(sha256(bound), state)) {
|
|
143
|
+
throw new BadRequestException('state mismatch');
|
|
144
|
+
}
|
|
145
|
+
const transaction = await this.storage.oidcStates.consumeOidcState(state);
|
|
146
|
+
if (!transaction || transaction.provider !== provider || hasExpired(transaction.expiresAt, this.now())) {
|
|
147
|
+
throw new BadRequestException('unknown or expired login');
|
|
148
|
+
}
|
|
149
|
+
const result = await this.client(provider).callback(query, transaction, this.callbackUrl(provider)).catch(toHttp);
|
|
150
|
+
const location = transaction.redirectTo ?? options.redirectAfterLogin ?? '/';
|
|
151
|
+
if (transaction.link) {
|
|
152
|
+
// The session that asked to link must still be the one on this browser.
|
|
153
|
+
const session = await this.currentSession(req);
|
|
154
|
+
if (session?.id !== transaction.link.sessionId) {
|
|
155
|
+
throw new UnauthorizedException('sign in to link an account');
|
|
156
|
+
}
|
|
157
|
+
const user = await resolver.resolveUser(result.profile, result.tokens, { linkTo: { id: session.userId } });
|
|
158
|
+
if (!user || user.id !== session.userId) {
|
|
159
|
+
throw new ForbiddenException('account not linked');
|
|
160
|
+
}
|
|
161
|
+
return this.redirect(exchange?.response, location, [this.txCookie('', 0)]);
|
|
162
|
+
}
|
|
163
|
+
const user = await resolver.resolveUser(result.profile, result.tokens, {});
|
|
164
|
+
if (!user) {
|
|
165
|
+
throw new ForbiddenException('account not allowed');
|
|
166
|
+
}
|
|
167
|
+
const issued = await this.signIn.signIn(user.id, { method: `oidc:${provider}` });
|
|
168
|
+
// `signIn()` set the session cookie on the response itself; the caller outside HTTP needs it too.
|
|
169
|
+
const { url } = this.redirect(exchange?.response, location, [this.txCookie('', 0)]);
|
|
170
|
+
return { url, cookies: [issued.cookie, this.txCookie('', 0)] };
|
|
171
|
+
}
|
|
172
|
+
/** Sets the cookies (and `no-store`) on the HTTP response, if there is one. */
|
|
173
|
+
redirect(response, url, cookies) {
|
|
174
|
+
const adapter = this.adapterHost?.httpAdapter;
|
|
175
|
+
if (response && adapter) {
|
|
176
|
+
for (const cookie of cookies) {
|
|
177
|
+
adapter.appendHeader(response, 'Set-Cookie', cookie);
|
|
178
|
+
}
|
|
179
|
+
adapter.setHeader(response, 'Cache-Control', 'no-store');
|
|
180
|
+
}
|
|
181
|
+
return { url, cookies };
|
|
182
|
+
}
|
|
183
|
+
/** The options and the resolver; the module refuses to start with one and not the other. */
|
|
184
|
+
feature() {
|
|
185
|
+
const resolver = this.registry.handler('oidc');
|
|
186
|
+
if (!this.options || !resolver) {
|
|
187
|
+
throw new Error('OidcService: OpenID Connect is not enabled. Configure `oidc` in the AuthenticationModule options, and register ' +
|
|
188
|
+
"an OidcAccountResolver: `registry.registerHandler('oidc', this)`.");
|
|
189
|
+
}
|
|
190
|
+
return { options: this.options, resolver };
|
|
191
|
+
}
|
|
192
|
+
/** A fully signed-in session (second factor done, if any) on this request. */
|
|
193
|
+
async currentSession(request) {
|
|
194
|
+
const session = await this.sessions.validate(this.sessions.tokenFrom(request));
|
|
195
|
+
return session && session.mfa !== 'pending' ? session : null;
|
|
196
|
+
}
|
|
197
|
+
callbackUrl(provider) {
|
|
198
|
+
return this.feature().options.callbackUrl.replaceAll(':provider', encodeURIComponent(provider));
|
|
199
|
+
}
|
|
200
|
+
txCookie(value, maxAge) {
|
|
201
|
+
return serializeCookie(this.txCookieName, value, { ...this.options?.cookie, sameSite: 'lax', maxAge });
|
|
202
|
+
}
|
|
203
|
+
now() {
|
|
204
|
+
return this.options?.now?.() ?? Date.now();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
OidcService = __decorate([
|
|
208
|
+
Injectable(),
|
|
209
|
+
__param(4, Optional()),
|
|
210
|
+
__param(4, Inject(AUTHENTICATION_MODULE_OPTIONS)),
|
|
211
|
+
__param(6, Optional()),
|
|
212
|
+
__metadata("design:paramtypes", [AuthenticationStorage,
|
|
213
|
+
AuthenticationRegistry,
|
|
214
|
+
SignInService,
|
|
215
|
+
SessionService, Object, AuthenticationScope,
|
|
216
|
+
HttpAdapterHost])
|
|
217
|
+
], OidcService);
|
|
218
|
+
export { OidcService };
|
|
219
|
+
/** `oidc.callbackUrl`, checked at startup: an absolute http(s) URL. */
|
|
220
|
+
function assertCallbackUrl(value) {
|
|
221
|
+
let url;
|
|
222
|
+
try {
|
|
223
|
+
url = typeof value === 'string' ? new URL(value.replaceAll(':provider', 'provider')) : undefined;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
url = undefined;
|
|
227
|
+
}
|
|
228
|
+
if (!url || (url.protocol !== 'https:' && url.protocol !== 'http:')) {
|
|
229
|
+
throw new Error('OidcService: `oidc.callbackUrl` must be the absolute URL of your callback route, with `:provider` where the ' +
|
|
230
|
+
`provider's name goes, e.g. 'https://app.example.com/auth/oidc/:provider/callback'. Got ${JSON.stringify(value)}.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* `oidc.providers.<name>.clientId`, and `clientSecret` unless the provider
|
|
235
|
+
* is a public client (`tokenEndpointAuthMethod: 'none'`), checked at
|
|
236
|
+
* startup: an unset environment variable would otherwise turn a
|
|
237
|
+
* confidential client into a public one, and fail at the token exchange.
|
|
238
|
+
*/
|
|
239
|
+
function assertProviderCredentials(providers) {
|
|
240
|
+
requireOption(providers, 'oidc.providers', 'the providers users can sign in with, by name');
|
|
241
|
+
for (const [name, config] of Object.entries(providers)) {
|
|
242
|
+
requireOption(config?.clientId, `oidc.providers.${name}.clientId`, 'the client id the provider issued');
|
|
243
|
+
if (config.tokenEndpointAuthMethod !== 'none') {
|
|
244
|
+
requireOption(config.clientSecret, `oidc.providers.${name}.clientSecret`, "the client secret the provider issued; a public client sets `tokenEndpointAuthMethod: 'none'` instead");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** An `OidcError` as the HTTP answer: 400 for a bad callback, 401 for a failed check, 502 for a provider that is down. */
|
|
249
|
+
function toHttp(error) {
|
|
250
|
+
if (!(error instanceof OidcError)) {
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
const Exception = {
|
|
254
|
+
request: BadRequestException,
|
|
255
|
+
verification: UnauthorizedException,
|
|
256
|
+
unavailable: BadGatewayException,
|
|
257
|
+
}[error.kind];
|
|
258
|
+
throw new Exception(error.message);
|
|
259
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import type { ApiKeyProviderOptions, ApiKeyRecord, ApiKeySession, GeneratedApiKey } from '../interfaces/api-key.interface.js';
|
|
3
|
+
import type { AuthenticationResult } from '../interfaces/authentication-result.interface.js';
|
|
4
|
+
import { AuthenticationProvider } from './authentication.provider.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for API keys: `Authorization: Bearer <prefix>_<id>_<secret>`,
|
|
7
|
+
* for scripts and partner systems. Extend it and look a key up by its id in
|
|
8
|
+
* your own table:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* @Injectable()
|
|
12
|
+
* export class ApiKeyAuth extends ApiKeyProvider<User> {
|
|
13
|
+
* constructor(private readonly apiKeysRepository: ApiKeysRepository, registry: AuthenticationRegistry) {
|
|
14
|
+
* super({ prefix: 'cat' });
|
|
15
|
+
* registry.registerProvider(this, { order: 2 });
|
|
16
|
+
* }
|
|
17
|
+
* findKey(id: string) {
|
|
18
|
+
* return this.apiKeysRepository.findWithUser(id); // { hash, user, expiresAt } or null
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `generate()` makes a key: show the whole key once, and store its `id` and
|
|
24
|
+
* `hash`. Keys carry 256 random bits, so a SHA-256 of the key is all the
|
|
25
|
+
* store needs (a slow hash would only cost CPU on every request), and the
|
|
26
|
+
* hash is compared in constant time, with the same work for an unknown id.
|
|
27
|
+
*
|
|
28
|
+
* A key signs its user in on every route, but never counts as a second
|
|
29
|
+
* factor: `@Authenticate({ mfa: true })` refuses it. Keep keys to the routes
|
|
30
|
+
* meant for them with `@Authenticate({ providers })`. Permissions per key
|
|
31
|
+
* are authorization's job (`@nestjs/authorization`), with the key id from
|
|
32
|
+
* the session. Tokens without this provider's prefix are left to the other
|
|
33
|
+
* providers; a JWT never looks like a key, so `JwtBearerProvider` can run
|
|
34
|
+
* before or after this one.
|
|
35
|
+
*/
|
|
36
|
+
export declare abstract class ApiKeyProvider<TUser> extends AuthenticationProvider<TUser, ApiKeySession> {
|
|
37
|
+
private readonly prefix;
|
|
38
|
+
private readonly realm;
|
|
39
|
+
private readonly now;
|
|
40
|
+
constructor({ prefix, realm, now }: ApiKeyProviderOptions);
|
|
41
|
+
/**
|
|
42
|
+
* The key with this id, as stored, or `null` for none: unknown, revoked,
|
|
43
|
+
* or its user is gone. The provider checks the hash and `expiresAt`.
|
|
44
|
+
*/
|
|
45
|
+
protected abstract findKey(id: string): ApiKeyRecord<TUser> | null | undefined | Promise<ApiKeyRecord<TUser> | null | undefined>;
|
|
46
|
+
/** A new key with this provider's prefix. */
|
|
47
|
+
generate(): GeneratedApiKey;
|
|
48
|
+
authenticate(context: ExecutionContext): Promise<AuthenticationResult<TUser, ApiKeySession> | null>;
|
|
49
|
+
challenge(): string;
|
|
50
|
+
private reject;
|
|
51
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { AuthenticationError } from '../errors/authentication.error.js';
|
|
3
|
+
import { API_KEY_PATTERN, API_KEY_PREFIX_PATTERN } from '../utils/api-key.util.js';
|
|
4
|
+
import { bearerToken } from '../utils/bearer.util.js';
|
|
5
|
+
import { randomToken, safeEqual, sha256 } from '../utils/crypto.util.js';
|
|
6
|
+
import { hasExpired } from '../utils/duration.util.js';
|
|
7
|
+
import { AuthenticationProvider } from './authentication.provider.js';
|
|
8
|
+
/** Compared against when the id is unknown, so every key costs the same hashing. */
|
|
9
|
+
const NO_KEY = sha256('');
|
|
10
|
+
/**
|
|
11
|
+
* Base class for API keys: `Authorization: Bearer <prefix>_<id>_<secret>`,
|
|
12
|
+
* for scripts and partner systems. Extend it and look a key up by its id in
|
|
13
|
+
* your own table:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* @Injectable()
|
|
17
|
+
* export class ApiKeyAuth extends ApiKeyProvider<User> {
|
|
18
|
+
* constructor(private readonly apiKeysRepository: ApiKeysRepository, registry: AuthenticationRegistry) {
|
|
19
|
+
* super({ prefix: 'cat' });
|
|
20
|
+
* registry.registerProvider(this, { order: 2 });
|
|
21
|
+
* }
|
|
22
|
+
* findKey(id: string) {
|
|
23
|
+
* return this.apiKeysRepository.findWithUser(id); // { hash, user, expiresAt } or null
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* `generate()` makes a key: show the whole key once, and store its `id` and
|
|
29
|
+
* `hash`. Keys carry 256 random bits, so a SHA-256 of the key is all the
|
|
30
|
+
* store needs (a slow hash would only cost CPU on every request), and the
|
|
31
|
+
* hash is compared in constant time, with the same work for an unknown id.
|
|
32
|
+
*
|
|
33
|
+
* A key signs its user in on every route, but never counts as a second
|
|
34
|
+
* factor: `@Authenticate({ mfa: true })` refuses it. Keep keys to the routes
|
|
35
|
+
* meant for them with `@Authenticate({ providers })`. Permissions per key
|
|
36
|
+
* are authorization's job (`@nestjs/authorization`), with the key id from
|
|
37
|
+
* the session. Tokens without this provider's prefix are left to the other
|
|
38
|
+
* providers; a JWT never looks like a key, so `JwtBearerProvider` can run
|
|
39
|
+
* before or after this one.
|
|
40
|
+
*/
|
|
41
|
+
export class ApiKeyProvider extends AuthenticationProvider {
|
|
42
|
+
constructor({ prefix, realm = 'api', now = Date.now }) {
|
|
43
|
+
super();
|
|
44
|
+
if (typeof prefix !== 'string' || !API_KEY_PREFIX_PATTERN.test(prefix)) {
|
|
45
|
+
throw new TypeError(`${new.target.name}: \`prefix\` must be 1-16 lowercase letters and digits, starting with a letter ` +
|
|
46
|
+
`(e.g. 'cat' for cat_<id>_<secret>), got ${JSON.stringify(prefix)}.`);
|
|
47
|
+
}
|
|
48
|
+
this.prefix = prefix;
|
|
49
|
+
this.realm = realm;
|
|
50
|
+
this.now = now;
|
|
51
|
+
}
|
|
52
|
+
/** A new key with this provider's prefix. */
|
|
53
|
+
generate() {
|
|
54
|
+
const id = randomBytes(8).toString('hex');
|
|
55
|
+
const key = `${this.prefix}_${id}_${randomToken()}`;
|
|
56
|
+
return { key, id, hash: sha256(key) };
|
|
57
|
+
}
|
|
58
|
+
async authenticate(context) {
|
|
59
|
+
const token = bearerToken(context, this.header(context, 'authorization'));
|
|
60
|
+
if (token === null) {
|
|
61
|
+
this.reject('malformed authorization header');
|
|
62
|
+
}
|
|
63
|
+
if (token === undefined || !token.startsWith(`${this.prefix}_`)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const match = API_KEY_PATTERN.exec(token);
|
|
67
|
+
if (!match) {
|
|
68
|
+
this.reject('malformed api key');
|
|
69
|
+
}
|
|
70
|
+
const id = match[2];
|
|
71
|
+
const record = await this.findKey(id);
|
|
72
|
+
// Hash and compare whether or not the id exists.
|
|
73
|
+
const matches = safeEqual(sha256(token), typeof record?.hash === 'string' ? record.hash : NO_KEY);
|
|
74
|
+
if (!record || !matches || record.user === null || record.user === undefined) {
|
|
75
|
+
this.reject('invalid api key');
|
|
76
|
+
}
|
|
77
|
+
const expiresAt = record.expiresAt ?? undefined;
|
|
78
|
+
if (expiresAt && hasExpired(expiresAt, this.now())) {
|
|
79
|
+
this.reject('api key expired');
|
|
80
|
+
}
|
|
81
|
+
const session = { method: 'api-key', keyId: id, ...(expiresAt && { expiresAt }) };
|
|
82
|
+
return { user: record.user, session };
|
|
83
|
+
}
|
|
84
|
+
challenge() {
|
|
85
|
+
return `Bearer realm="${this.realm}"`;
|
|
86
|
+
}
|
|
87
|
+
reject(description) {
|
|
88
|
+
// RFC 6750 §3.1, as JwtBearerProvider answers
|
|
89
|
+
throw new AuthenticationError(description, {
|
|
90
|
+
challenge: `Bearer realm="${this.realm}", error="invalid_token", error_description="${description}"`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import type { AuthenticatedSession, AuthenticatedUser, AuthenticationResult } from '../interfaces/authentication-result.interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* A credential source: the contract every provider meets, and the base
|
|
5
|
+
* class of `SessionCookieProvider` and `JwtBearerProvider`.
|
|
6
|
+
*
|
|
7
|
+
* - Resolve to `null` when the request carries no credentials this provider
|
|
8
|
+
* understands; the guard tries the next provider.
|
|
9
|
+
* - Throw `AuthenticationError` when credentials are present but invalid;
|
|
10
|
+
* the guard answers 401, even on `@Authenticate({ optional: true })`
|
|
11
|
+
* routes.
|
|
12
|
+
*
|
|
13
|
+
* Subclasses are ordinary `@Injectable()` classes: they can live in any
|
|
14
|
+
* module and inject repositories, config, etc. The same class serves HTTP,
|
|
15
|
+
* GraphQL, WebSockets and RPC, because it receives the `ExecutionContext`.
|
|
16
|
+
*/
|
|
17
|
+
export declare abstract class AuthenticationProvider<TUser = AuthenticatedUser, TSession = AuthenticatedSession> {
|
|
18
|
+
abstract authenticate(context: ExecutionContext): Promise<AuthenticationResult<TUser, TSession> | null> | AuthenticationResult<TUser, TSession> | null;
|
|
19
|
+
/**
|
|
20
|
+
* A request header, whatever the transport: the HTTP request, GraphQL's
|
|
21
|
+
* `context.req`, or the WebSocket handshake. `undefined` over RPC, whose
|
|
22
|
+
* metadata is transport specific. A repeated header gives its first value.
|
|
23
|
+
*/
|
|
24
|
+
protected header(context: ExecutionContext, name: string): string | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface AuthenticationProvider<TUser = AuthenticatedUser, TSession = AuthenticatedSession> {
|
|
27
|
+
/** `WWW-Authenticate` challenge sent with a 401 when no provider found credentials. */
|
|
28
|
+
challenge?(context: ExecutionContext): string | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** A provider class, possibly abstract (`SessionCookieProvider`), for `@Authenticate({ providers })`. */
|
|
31
|
+
export type ProviderClass = abstract new (...args: any[]) => AuthenticationProvider<any, any>;
|
|
32
|
+
/**
|
|
33
|
+
* @internal Called by the module once the provider is resolved, before the
|
|
34
|
+
* first request, so a misconfigured provider fails at startup. `resolve`
|
|
35
|
+
* looks up the module's providers, for instances created with `new` (which
|
|
36
|
+
* Nest did not property-inject).
|
|
37
|
+
*/
|
|
38
|
+
export declare const PROVIDER_INIT: unique symbol;
|
|
39
|
+
/** @internal */
|
|
40
|
+
export type ProviderInit = (resolve: <T>(token: string | symbol | (abstract new (...args: any[]) => T)) => T) => void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { firstHeader, getRequestHeaders } from '../utils/auth-state.util.js';
|
|
2
|
+
/**
|
|
3
|
+
* A credential source: the contract every provider meets, and the base
|
|
4
|
+
* class of `SessionCookieProvider` and `JwtBearerProvider`.
|
|
5
|
+
*
|
|
6
|
+
* - Resolve to `null` when the request carries no credentials this provider
|
|
7
|
+
* understands; the guard tries the next provider.
|
|
8
|
+
* - Throw `AuthenticationError` when credentials are present but invalid;
|
|
9
|
+
* the guard answers 401, even on `@Authenticate({ optional: true })`
|
|
10
|
+
* routes.
|
|
11
|
+
*
|
|
12
|
+
* Subclasses are ordinary `@Injectable()` classes: they can live in any
|
|
13
|
+
* module and inject repositories, config, etc. The same class serves HTTP,
|
|
14
|
+
* GraphQL, WebSockets and RPC, because it receives the `ExecutionContext`.
|
|
15
|
+
*/
|
|
16
|
+
// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- see the interface below
|
|
17
|
+
export class AuthenticationProvider {
|
|
18
|
+
/**
|
|
19
|
+
* A request header, whatever the transport: the HTTP request, GraphQL's
|
|
20
|
+
* `context.req`, or the WebSocket handshake. `undefined` over RPC, whose
|
|
21
|
+
* metadata is transport specific. A repeated header gives its first value.
|
|
22
|
+
*/
|
|
23
|
+
header(context, name) {
|
|
24
|
+
return firstHeader(getRequestHeaders(context), name.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @internal Called by the module once the provider is resolved, before the
|
|
29
|
+
* first request, so a misconfigured provider fails at startup. `resolve`
|
|
30
|
+
* looks up the module's providers, for instances created with `new` (which
|
|
31
|
+
* Nest did not property-inject).
|
|
32
|
+
*/
|
|
33
|
+
export const PROVIDER_INIT = Symbol('nestjs.authentication.provider-init');
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ModuleRef } from '@nestjs/core';
|
|
2
|
+
import type { AuthenticationHandlers, AuthenticationHandlerName, RegisterProviderOptions, RegisterHandlerOptions, CredentialProvider } from '../interfaces/authentication-registry.interface.js';
|
|
3
|
+
/** @internal Locks the registry. `AuthenticationModule.onModuleInit()` and the first read call it. */
|
|
4
|
+
export declare const LOCK_REGISTRY: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* Where the app's credential providers and feature handlers register, from
|
|
7
|
+
* their own constructors, as stores do with `AuthenticationStorage`. They
|
|
8
|
+
* are ordinary providers of the app's modules, so they inject whatever
|
|
9
|
+
* those modules provide:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* @Injectable()
|
|
13
|
+
* export class SessionAuth extends SessionCookieProvider<User> {
|
|
14
|
+
* constructor(private readonly users: UsersRepository, registry: AuthenticationRegistry) {
|
|
15
|
+
* super();
|
|
16
|
+
* registry.registerProvider(this);
|
|
17
|
+
* }
|
|
18
|
+
* // ...
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @Injectable()
|
|
22
|
+
* export class PasswordResetMailer extends PasswordResetHandler {
|
|
23
|
+
* constructor(private readonly users: UsersRepository, registry: AuthenticationRegistry) {
|
|
24
|
+
* super();
|
|
25
|
+
* registry.registerHandler('passwordReset', this);
|
|
26
|
+
* }
|
|
27
|
+
* // ...
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* The registry locks in `AuthenticationModule`'s `onModuleInit` (every
|
|
32
|
+
* provider constructor has run by then), or at the first read if that is
|
|
33
|
+
* earlier. At the lock it fails when a feature has its option without its
|
|
34
|
+
* handler, or the reverse, and it logs the chain and the handlers.
|
|
35
|
+
*/
|
|
36
|
+
export declare class AuthenticationRegistry {
|
|
37
|
+
private readonly options?;
|
|
38
|
+
private readonly moduleRef?;
|
|
39
|
+
private static readonly logger;
|
|
40
|
+
private readonly registered;
|
|
41
|
+
private readonly handlers;
|
|
42
|
+
private chain;
|
|
43
|
+
private locked;
|
|
44
|
+
constructor(options?: Record<string, unknown> | undefined, moduleRef?: ModuleRef | undefined);
|
|
45
|
+
/**
|
|
46
|
+
* Adds a credential provider to the chain the guard runs, in ascending
|
|
47
|
+
* `order` (default `0`). Call it from the constructor of a singleton
|
|
48
|
+
* provider. Throws, registering nothing, for an object without
|
|
49
|
+
* `authenticate()`, an order another provider has (unless `replace` is
|
|
50
|
+
* set), a provider registered twice, and once the registry has locked.
|
|
51
|
+
*/
|
|
52
|
+
registerProvider(provider: CredentialProvider, { order, replace }?: RegisterProviderOptions): void;
|
|
53
|
+
/**
|
|
54
|
+
* Registers the handler of a feature: `registerHandler('passwordReset', this)`.
|
|
55
|
+
* The feature also needs its module option. Throws, registering nothing,
|
|
56
|
+
* for an unknown feature, a handler missing one of its methods, a feature
|
|
57
|
+
* that already has one (unless `replace` is set), and once the registry
|
|
58
|
+
* has locked.
|
|
59
|
+
*/
|
|
60
|
+
registerHandler<K extends AuthenticationHandlerName>(feature: K, handler: AuthenticationHandlers[K], { replace }?: RegisterHandlerOptions): void;
|
|
61
|
+
/** The credential providers, in the order the guard runs them. Reading them locks the registry. */
|
|
62
|
+
get providers(): readonly CredentialProvider<unknown, unknown>[];
|
|
63
|
+
/** The handler registered for a feature, or `undefined`. Reading it locks the registry. */
|
|
64
|
+
handler<K extends AuthenticationHandlerName>(feature: K): AuthenticationHandlers[K] | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* @internal Checks every feature has both its option and its handler,
|
|
67
|
+
* orders the chain, lets the providers check their configuration, and
|
|
68
|
+
* logs what registered. `log: false` for registries built outside a
|
|
69
|
+
* module (tests, the contract suite).
|
|
70
|
+
*/
|
|
71
|
+
[LOCK_REGISTRY]({ log }?: {
|
|
72
|
+
log?: boolean;
|
|
73
|
+
}): void;
|
|
74
|
+
private assertOpen;
|
|
75
|
+
}
|