@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,25 @@
|
|
|
1
|
+
import type { OidcTransaction } from './oidc.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Storage for logins in progress (PKCE verifier, nonce, return path),
|
|
4
|
+
* keyed by `state`. Implement it on your database or Redis, and register
|
|
5
|
+
* the provider with `AuthenticationStorage.registerSource({ oidcStates: this })`.
|
|
6
|
+
*
|
|
7
|
+
* `consumeOidcState()` must be one atomic get-and-delete
|
|
8
|
+
* (`DELETE … RETURNING`, `GETDEL`), so a callback is accepted once. Anyone
|
|
9
|
+
* can start a login, so the store must stay bounded: drop expired logins
|
|
10
|
+
* as new ones are saved, and cap the rest.
|
|
11
|
+
*/
|
|
12
|
+
export interface OidcStateStore {
|
|
13
|
+
/**
|
|
14
|
+
* Saves a new login (a fresh random `state`: a plain insert), then keeps
|
|
15
|
+
* the store bounded: deletes logins whose `expiresAt` is at or before this
|
|
16
|
+
* one's `createdAt`, and past a cap, those that expire first.
|
|
17
|
+
*/
|
|
18
|
+
saveOidcState(transaction: OidcTransaction): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Removes and returns the login, in one atomic step; `undefined` if there
|
|
21
|
+
* is none. Of several concurrent calls, exactly one gets the record.
|
|
22
|
+
* Expired logins may be returned: `OidcService` checks expiry.
|
|
23
|
+
*/
|
|
24
|
+
consumeOidcState(state: string): Promise<OidcTransaction | undefined>;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CookieAttributes } from '../session/cookies.util.js';
|
|
2
|
+
import type { Duration } from './duration.interface.js';
|
|
3
|
+
import type { OidcProviderConfig } from './oidc-provider.interface.js';
|
|
4
|
+
export interface OidcTransaction {
|
|
5
|
+
state: string;
|
|
6
|
+
provider: string;
|
|
7
|
+
nonce?: string;
|
|
8
|
+
codeVerifier: string;
|
|
9
|
+
redirectTo?: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
/** Set for a link flow: the signed-in user (and their session id) who started it. */
|
|
13
|
+
link?: {
|
|
14
|
+
userId: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface OidcClientOptions {
|
|
19
|
+
fetch?: typeof fetch;
|
|
20
|
+
/** Clock in epoch milliseconds, for tests. */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
/** How long a discovery document is trusted. Default `'1h'`. */
|
|
23
|
+
discoveryTtl?: Duration;
|
|
24
|
+
/** Leeway for ID token time claims. Default `'30s'`. */
|
|
25
|
+
clockTolerance?: Duration;
|
|
26
|
+
/** The providers' key sets: see `JwksClient`. */
|
|
27
|
+
jwks?: {
|
|
28
|
+
cacheTtl?: Duration;
|
|
29
|
+
cooldown?: Duration;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface OidcResolveContext {
|
|
33
|
+
/**
|
|
34
|
+
* Set when a signed-in user started the flow with `?link=true` ("connect
|
|
35
|
+
* your Google account"): attach the identity to this user, and refuse
|
|
36
|
+
* (throw, or return `null`) when it already belongs to someone else.
|
|
37
|
+
* The user's session was re-checked on the callback.
|
|
38
|
+
*/
|
|
39
|
+
linkTo?: {
|
|
40
|
+
id: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface OidcOptions extends OidcClientOptions {
|
|
44
|
+
/**
|
|
45
|
+
* The public URL of your callback route, the redirect URI you register
|
|
46
|
+
* with each provider. `:provider` is replaced by the provider's name:
|
|
47
|
+
* `https://app.example.com/auth/oidc/:provider/callback`.
|
|
48
|
+
*/
|
|
49
|
+
callbackUrl: string;
|
|
50
|
+
providers: Record<string, OidcProviderConfig>;
|
|
51
|
+
/** Where to land after sign-in when the login had no `redirectTo`. Default `/`. */
|
|
52
|
+
redirectAfterLogin?: string;
|
|
53
|
+
/** Lifetime of a login between the redirect to the provider and the callback. Default `'10m'`. */
|
|
54
|
+
transactionTtl?: Duration;
|
|
55
|
+
/**
|
|
56
|
+
* Attributes of the transaction cookie, `__Host-oidc_tx` (or `oidc_tx`
|
|
57
|
+
* when they rule the prefix out). `secure` defaults to `true`;
|
|
58
|
+
* `SameSite` is always `Lax`, which lets the cookie through the
|
|
59
|
+
* provider's redirect back.
|
|
60
|
+
*/
|
|
61
|
+
cookie?: CookieAttributes;
|
|
62
|
+
}
|
|
63
|
+
/** The HTTP request of a login or callback, as Express and Fastify provide it. */
|
|
64
|
+
export type OidcRequest = {
|
|
65
|
+
headers: Record<string, string | string[] | undefined>;
|
|
66
|
+
method?: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Where to send the browser: return it from a handler decorated with
|
|
70
|
+
* `@Redirect()`. `cookies` are the `Set-Cookie` values the service already
|
|
71
|
+
* set on the HTTP response; send them yourself outside an HTTP handler.
|
|
72
|
+
*/
|
|
73
|
+
export interface OidcRedirect {
|
|
74
|
+
url: string;
|
|
75
|
+
cookies: string[];
|
|
76
|
+
}
|
|
77
|
+
export interface OidcStartOptions {
|
|
78
|
+
/** A relative path to land on after signing in; anything else is ignored. */
|
|
79
|
+
redirectTo?: unknown;
|
|
80
|
+
/** Link the provider to the signed-in user ("connect your Google account") instead of signing in. */
|
|
81
|
+
link?: boolean;
|
|
82
|
+
/** The request, outside an HTTP handler (tests). Inside one, the service reads it. */
|
|
83
|
+
request?: OidcRequest;
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scrypt parameters. The ones a stored hash names are bounded the same way,
|
|
3
|
+
* since a tampered row could otherwise demand any amount of memory: at most
|
|
4
|
+
* 1 GiB (128·N·r) and 16 times the default work (N·r·p).
|
|
5
|
+
*/
|
|
6
|
+
export interface PasswordHasherOptions {
|
|
7
|
+
/** log2 of the scrypt cost N, an integer from 10 to 22. Default 17 (OWASP minimum: N=2^17, r=8, p=1; ~128 MiB). */
|
|
8
|
+
logN?: number;
|
|
9
|
+
/** Block size, 1 to 32. Default 8. */
|
|
10
|
+
r?: number;
|
|
11
|
+
/** Parallelism, 1 to 16. Default 1. */
|
|
12
|
+
p?: number;
|
|
13
|
+
/** Derived key length, 16 to 64 bytes. Default 32. */
|
|
14
|
+
keyLength?: number;
|
|
15
|
+
/** Salt length, 16 to 64 bytes. Default 16. */
|
|
16
|
+
saltLength?: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Duration } from './duration.interface.js';
|
|
2
|
+
import type { IssuedSession } from './session.interface.js';
|
|
3
|
+
/** The account behind an address, as `PasswordResetHandler.findUser()` returns it. */
|
|
4
|
+
export interface PasswordResetAccount {
|
|
5
|
+
id: string;
|
|
6
|
+
/**
|
|
7
|
+
* The address the account has stored. The link goes there, never to the
|
|
8
|
+
* address typed in the form: a lookup that ignores accents or dots (MySQL's
|
|
9
|
+
* default collation does) would otherwise mail `victim@example.com`'s link
|
|
10
|
+
* to whoever typed `victim@exämple.com`. A link works only while the
|
|
11
|
+
* account keeps this address.
|
|
12
|
+
*/
|
|
13
|
+
email: string;
|
|
14
|
+
/**
|
|
15
|
+
* The stored password hash, or `null` for an account without a password
|
|
16
|
+
* (a reset then sets one). A link only works while this is unchanged, so
|
|
17
|
+
* any password change invalidates the links sent before it.
|
|
18
|
+
*/
|
|
19
|
+
passwordHash: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** What `PasswordResetHandler.send()` delivers. */
|
|
22
|
+
export interface PasswordResetLink {
|
|
23
|
+
userId: string;
|
|
24
|
+
email: string;
|
|
25
|
+
/** `passwordReset.url` with `?token=…`. */
|
|
26
|
+
url: string;
|
|
27
|
+
expiresAt: Date;
|
|
28
|
+
}
|
|
29
|
+
export interface PasswordResetOptions {
|
|
30
|
+
/** Page that receives `?token=…`, asks for a new password and POSTs both, e.g. `https://app.example.com/reset-password`. */
|
|
31
|
+
url: string;
|
|
32
|
+
/** Lifetime of a link. Default `'1h'`. */
|
|
33
|
+
ttl?: Duration;
|
|
34
|
+
/** Clock in epoch milliseconds, for tests. */
|
|
35
|
+
now?: () => number;
|
|
36
|
+
}
|
|
37
|
+
export interface ResetPasswordOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Signs the user in on this browser afterwards, through
|
|
40
|
+
* `SignInService.signIn()` (so a user with an authenticator gets a
|
|
41
|
+
* pending session). Default `false`.
|
|
42
|
+
*/
|
|
43
|
+
signIn?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface PasswordResetResult {
|
|
46
|
+
userId: string;
|
|
47
|
+
/** With `signIn: true`: the new session, as `SignInService.signIn()` returns it. */
|
|
48
|
+
signedIn?: IssuedSession;
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Duration } from './duration.interface.js';
|
|
2
|
+
export interface RefreshTokenOptions {
|
|
3
|
+
/** Lifetime of one refresh token. Default `'30d'`. */
|
|
4
|
+
ttl?: Duration;
|
|
5
|
+
/** Maximum lifetime of a family, however often it rotates. Default `'90d'`. */
|
|
6
|
+
absoluteTtl?: Duration;
|
|
7
|
+
/** Clock in epoch milliseconds, for tests. */
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export type RefreshTokenFailure = 'invalid' | 'expired' | 'reused';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface RefreshTokenRecord {
|
|
2
|
+
/** SHA-256 of the token. */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Every rotation stays in the family of the original sign-in. */
|
|
5
|
+
familyId: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
expiresAt: Date;
|
|
9
|
+
/** The family cannot be extended past this, however often it rotates. */
|
|
10
|
+
familyExpiresAt: Date;
|
|
11
|
+
usedAt?: Date;
|
|
12
|
+
/**
|
|
13
|
+
* Claims fixed at sign-in (`amr`, `auth_time`, …) that every token of the
|
|
14
|
+
* family carries, so access tokens minted on refresh describe the same
|
|
15
|
+
* authentication: a sign-in that passed MFA stays MFA-verified.
|
|
16
|
+
*/
|
|
17
|
+
claims?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Storage for refresh tokens. Implement it on your database or Redis, and
|
|
21
|
+
* register the provider with
|
|
22
|
+
* `AuthenticationStorage.registerSource({ refreshTokens: this })`.
|
|
23
|
+
*
|
|
24
|
+
* `markRefreshTokenUsed()` is what detects a stolen token, and must be one
|
|
25
|
+
* conditional write (`UPDATE … SET used_at = ? WHERE id = ? AND used_at IS
|
|
26
|
+
* NULL`), never a read followed by a write. A family's revocation must
|
|
27
|
+
* cover tokens saved *after* it: a refresh that lost the race to a reuse
|
|
28
|
+
* may still save its successor. Each method's comment has its rule, and
|
|
29
|
+
* `authenticationStoreContract()` from `@nestjs/authentication/testing`
|
|
30
|
+
* checks them.
|
|
31
|
+
*
|
|
32
|
+
* Optional fields come back absent (`undefined`), never `null`. Times come
|
|
33
|
+
* from `TokenService`'s clock, never the store's.
|
|
34
|
+
*/
|
|
35
|
+
export interface RefreshTokenStore {
|
|
36
|
+
/** The token, or `undefined`. Used and expired tokens are returned too: `TokenService` checks them. */
|
|
37
|
+
getRefreshToken(id: string): Promise<RefreshTokenRecord | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Saves a new token (a fresh random id: a plain insert). A good place to
|
|
40
|
+
* delete tokens whose `familyExpiresAt` has passed, so the store stays
|
|
41
|
+
* bounded (and with them their family's revocation, which no longer
|
|
42
|
+
* matters).
|
|
43
|
+
*/
|
|
44
|
+
saveRefreshToken(record: RefreshTokenRecord): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Sets `usedAt = at` iff the token exists and is unused, in one
|
|
47
|
+
* conditional write; `true` if this call did. Of several concurrent
|
|
48
|
+
* calls, exactly one gets `true`: the others are a reuse, and revoke the
|
|
49
|
+
* family.
|
|
50
|
+
*/
|
|
51
|
+
markRefreshTokenUsed(id: string, at: Date): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Revokes the family: `isRefreshTokenFamilyRevoked()` answers `true` from
|
|
54
|
+
* now on, including for tokens of the family saved later. Idempotent.
|
|
55
|
+
*/
|
|
56
|
+
revokeRefreshTokenFamily(familyId: string): Promise<void>;
|
|
57
|
+
/** Whether the family was revoked (by `revokeRefreshTokenFamily()` or `revokeUserRefreshTokens()`). */
|
|
58
|
+
isRefreshTokenFamilyRevoked(familyId: string): Promise<boolean>;
|
|
59
|
+
/** Revokes every family the user has tokens in now. Families started later are not affected. */
|
|
60
|
+
revokeUserRefreshTokens(userId: string): Promise<void>;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MfaState } from './authentication-result.interface.js';
|
|
2
|
+
export interface SessionRecord {
|
|
3
|
+
/** SHA-256 of the cookie token. The token itself is never stored. */
|
|
4
|
+
id: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
/** Absolute expiry: activity never extends a session past this. */
|
|
8
|
+
expiresAt: Date;
|
|
9
|
+
/** Last activity, for the idle (sliding) timeout. */
|
|
10
|
+
lastActiveAt: Date;
|
|
11
|
+
mfa?: MfaState;
|
|
12
|
+
/** App data: user agent, IP, device name, for "your sessions" pages. */
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Where sessions live. Implement it on your database or Redis, and register
|
|
17
|
+
* the provider with `AuthenticationStorage.registerSource({ sessions: this })`.
|
|
18
|
+
* Each method's comment has its rule, and `authenticationStoreContract()`
|
|
19
|
+
* from `@nestjs/authentication/testing` checks them.
|
|
20
|
+
*
|
|
21
|
+
* Optional fields come back absent (`undefined`), never `null`. Times come
|
|
22
|
+
* from `SessionService`'s clock, never the store's.
|
|
23
|
+
*/
|
|
24
|
+
export interface SessionStore {
|
|
25
|
+
/** The session, or `undefined`. Expired sessions may be returned: `SessionService` checks expiry. */
|
|
26
|
+
getSession(id: string): Promise<SessionRecord | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Saves a new session (a fresh random id: a plain insert). A good place
|
|
29
|
+
* to delete sessions whose `expiresAt` has passed, so the store stays
|
|
30
|
+
* bounded.
|
|
31
|
+
*/
|
|
32
|
+
createSession(record: SessionRecord): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Moves `lastActiveAt` forward, **only if the session still exists** and
|
|
35
|
+
* only forward: one conditional write, `UPDATE … SET last_active_at = ?
|
|
36
|
+
* WHERE id = ? AND last_active_at < ?`, `SET … XX` in Redis. A read
|
|
37
|
+
* followed by a write lets a request that read the session before a
|
|
38
|
+
* sign-out or rotation deleted it bring it back. Changes no other field.
|
|
39
|
+
*/
|
|
40
|
+
touchSession(id: string, lastActiveAt: Date): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Deletes the session, and resolves whether this call deleted it: one
|
|
43
|
+
* statement (`DELETE … WHERE id = ?` and its row count, Redis `DEL` and its
|
|
44
|
+
* count), `false` when it was already gone. `SessionService.rotate()`
|
|
45
|
+
* relies on it: of a rotation and a revocation of one session (or two
|
|
46
|
+
* rotations), only one may win.
|
|
47
|
+
*/
|
|
48
|
+
deleteSession(id: string): Promise<boolean>;
|
|
49
|
+
/** The user's sessions, in any order (`[]` when none), expired ones included. */
|
|
50
|
+
listUserSessions(userId: string): Promise<SessionRecord[]>;
|
|
51
|
+
/** Deletes every session of the user ("sign out everywhere"). */
|
|
52
|
+
deleteUserSessions(userId: string): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from 'node:http';
|
|
2
|
+
import type { CookieAttributes } from '../session/cookies.util.js';
|
|
3
|
+
import type { Duration } from './duration.interface.js';
|
|
4
|
+
import type { SessionRecord } from './session-store.interface.js';
|
|
5
|
+
/** The request a session is created from, as Express and Fastify both provide it. */
|
|
6
|
+
export interface SessionRequest {
|
|
7
|
+
headers: IncomingHttpHeaders;
|
|
8
|
+
ip?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Default `__Host-sid`, or `sid` when `cookie` rules the `__Host-` prefix
|
|
13
|
+
* out (`secure: false`, a `domain`, or a `path` other than `/`).
|
|
14
|
+
*/
|
|
15
|
+
cookieName?: string;
|
|
16
|
+
/** Absolute lifetime: activity never extends a session past it. Default `'7d'`. */
|
|
17
|
+
absoluteTtl?: Duration;
|
|
18
|
+
/** Idle timeout, slid by activity. Default `'1d'`. `0` disables it. */
|
|
19
|
+
idleTtl?: Duration;
|
|
20
|
+
/** Write `lastActiveAt` at most this often, which saves a write per request. Default `'1m'`. */
|
|
21
|
+
touchInterval?: Duration;
|
|
22
|
+
/**
|
|
23
|
+
* `HttpOnly` is always set. `secure` defaults to `true`, which browsers
|
|
24
|
+
* accept from `http://localhost` too; turn it off only for plain HTTP on
|
|
25
|
+
* another host.
|
|
26
|
+
*/
|
|
27
|
+
cookie?: CookieAttributes;
|
|
28
|
+
/**
|
|
29
|
+
* Origins besides the application's own whose pages may use the session
|
|
30
|
+
* cookie for unsafe requests (POST, …) and sign in, e.g.
|
|
31
|
+
* `['https://app.example.com']` for a web app on another origin than the
|
|
32
|
+
* API. The same list as `app.enableCsrfProtection({ trustedOrigins })`.
|
|
33
|
+
*/
|
|
34
|
+
trustedOrigins?: string[];
|
|
35
|
+
/**
|
|
36
|
+
* What each new session stores next to the user, read from the request
|
|
37
|
+
* that signs in (password, magic link, OIDC alike): a user agent, an IP
|
|
38
|
+
* address, a device name, for "where you're signed in" pages.
|
|
39
|
+
*/
|
|
40
|
+
metadata?: (request: SessionRequest) => Record<string, unknown>;
|
|
41
|
+
/** Clock in epoch milliseconds, for tests. */
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}
|
|
44
|
+
export interface IssuedSession {
|
|
45
|
+
session: SessionRecord;
|
|
46
|
+
/** The raw token. Only the `Set-Cookie` value should leave the server. */
|
|
47
|
+
token: string;
|
|
48
|
+
/** `Set-Cookie` header value. `SignInService` sets it on the HTTP response itself. */
|
|
49
|
+
cookie: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SignInOptions {
|
|
2
|
+
/**
|
|
3
|
+
* How the user proved who they are, recorded on the `sign-in` event:
|
|
4
|
+
* `password`, `magic-link`, `oidc:google`.
|
|
5
|
+
*/
|
|
6
|
+
method?: string;
|
|
7
|
+
/** Stored with the session, over what `session.metadata` returns for the request. */
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface IssueTokensOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Claims that describe the sign-in (`amr`, `auth_time`, …): this access
|
|
4
|
+
* token and every one `refresh()` mints carry them. Keep roles and other
|
|
5
|
+
* facts that can change out of them: they are fixed for the family's life.
|
|
6
|
+
* The service owns the `amr` values that mean a second factor (`mfa`,
|
|
7
|
+
* `otp`, `hwk`): it adds `mfa` itself once a code was verified, and drops
|
|
8
|
+
* them from `claims.amr` with a warning (the rest, `pwd`, `sso`, is kept).
|
|
9
|
+
*/
|
|
10
|
+
claims?: Record<string, unknown>;
|
|
11
|
+
/** How the user proved who they are, recorded on the `sign-in` event: `password`, `magic-link`. */
|
|
12
|
+
method?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The code the client sent for a user with a confirmed authenticator: a
|
|
15
|
+
* TOTP `code` or a `recoveryCode`. Ignored for other users.
|
|
16
|
+
*/
|
|
17
|
+
secondFactor?: {
|
|
18
|
+
code?: string;
|
|
19
|
+
recoveryCode?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** What a token endpoint returns to a client that signed in. */
|
|
23
|
+
export interface TokenPair {
|
|
24
|
+
/** A JWT signed with the `accessToken` options. */
|
|
25
|
+
accessToken: string;
|
|
26
|
+
/** Opaque, single use: `refresh()` exchanges it for a new pair. */
|
|
27
|
+
refreshToken: string;
|
|
28
|
+
/** Seconds until the access token expires (OAuth's `expires_in`). */
|
|
29
|
+
expiresIn: number;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `amr` values (RFC 8176) that mean a second factor was presented:
|
|
3
|
+
* `mfa` (what `TokenService` records), `otp` and `hwk`. `JwtBearerProvider`
|
|
4
|
+
* treats a token carrying one as MFA-verified, so `TokenService` never lets
|
|
5
|
+
* an app put one into a token by itself.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MFA_AMR_VALUES: readonly string[];
|
|
8
|
+
/** Whether an `amr` claim says a second factor was presented. */
|
|
9
|
+
export declare function hasMfaAmr(amr: unknown): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `amr` values (RFC 8176) that mean a second factor was presented:
|
|
3
|
+
* `mfa` (what `TokenService` records), `otp` and `hwk`. `JwtBearerProvider`
|
|
4
|
+
* treats a token carrying one as MFA-verified, so `TokenService` never lets
|
|
5
|
+
* an app put one into a token by itself.
|
|
6
|
+
*/
|
|
7
|
+
export const MFA_AMR_VALUES = ['mfa', 'otp', 'hwk'];
|
|
8
|
+
/** Whether an `amr` claim says a second factor was presented. */
|
|
9
|
+
export function hasMfaAmr(amr) {
|
|
10
|
+
return Array.isArray(amr) && amr.some((method) => typeof method === 'string' && MFA_AMR_VALUES.includes(method));
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RefreshTokenRecord, RefreshTokenStore } from '../interfaces/refresh-token-store.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Process-local store: the default when no source is registered, and a
|
|
4
|
+
* test double. Families past their absolute expiry are dropped as new
|
|
5
|
+
* tokens are written, so an app that runs for months does not grow without
|
|
6
|
+
* bound.
|
|
7
|
+
*/
|
|
8
|
+
export declare class InMemoryRefreshTokenStore implements RefreshTokenStore {
|
|
9
|
+
private readonly tokens;
|
|
10
|
+
private readonly revoked;
|
|
11
|
+
private sweepAt;
|
|
12
|
+
getRefreshToken(id: string): Promise<{
|
|
13
|
+
id: string;
|
|
14
|
+
familyId: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
expiresAt: Date;
|
|
18
|
+
familyExpiresAt: Date;
|
|
19
|
+
usedAt?: Date;
|
|
20
|
+
claims?: Record<string, unknown>;
|
|
21
|
+
} | undefined>;
|
|
22
|
+
saveRefreshToken(record: RefreshTokenRecord): Promise<void>;
|
|
23
|
+
markRefreshTokenUsed(id: string, at: Date): Promise<boolean>;
|
|
24
|
+
revokeRefreshTokenFamily(familyId: string): Promise<void>;
|
|
25
|
+
isRefreshTokenFamilyRevoked(familyId: string): Promise<boolean>;
|
|
26
|
+
revokeUserRefreshTokens(userId: string): Promise<void>;
|
|
27
|
+
/** Drops families past their absolute expiry (every token of a family shares it), and their revocation. */
|
|
28
|
+
private sweep;
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
import { Injectable } from '@nestjs/common';
|
|
8
|
+
/**
|
|
9
|
+
* Process-local store: the default when no source is registered, and a
|
|
10
|
+
* test double. Families past their absolute expiry are dropped as new
|
|
11
|
+
* tokens are written, so an app that runs for months does not grow without
|
|
12
|
+
* bound.
|
|
13
|
+
*/
|
|
14
|
+
let InMemoryRefreshTokenStore = class InMemoryRefreshTokenStore {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.tokens = new Map();
|
|
17
|
+
this.revoked = new Set();
|
|
18
|
+
this.sweepAt = 1_024;
|
|
19
|
+
}
|
|
20
|
+
async getRefreshToken(id) {
|
|
21
|
+
const record = this.tokens.get(id);
|
|
22
|
+
return record && { ...record };
|
|
23
|
+
}
|
|
24
|
+
async saveRefreshToken(record) {
|
|
25
|
+
this.tokens.set(record.id, { ...record });
|
|
26
|
+
// The new token's own clock, so tests with a fake one agree.
|
|
27
|
+
if (this.tokens.size >= this.sweepAt) {
|
|
28
|
+
this.sweep(record.createdAt.getTime());
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async markRefreshTokenUsed(id, at) {
|
|
32
|
+
const record = this.tokens.get(id);
|
|
33
|
+
if (!record || record.usedAt) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
record.usedAt = at;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
async revokeRefreshTokenFamily(familyId) {
|
|
40
|
+
this.revoked.add(familyId);
|
|
41
|
+
}
|
|
42
|
+
async isRefreshTokenFamilyRevoked(familyId) {
|
|
43
|
+
return this.revoked.has(familyId);
|
|
44
|
+
}
|
|
45
|
+
async revokeUserRefreshTokens(userId) {
|
|
46
|
+
for (const record of this.tokens.values()) {
|
|
47
|
+
if (record.userId === userId) {
|
|
48
|
+
this.revoked.add(record.familyId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Drops families past their absolute expiry (every token of a family shares it), and their revocation. */
|
|
53
|
+
sweep(now) {
|
|
54
|
+
for (const [id, record] of this.tokens) {
|
|
55
|
+
if (record.familyExpiresAt.getTime() > now) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
this.tokens.delete(id);
|
|
59
|
+
this.revoked.delete(record.familyId);
|
|
60
|
+
}
|
|
61
|
+
this.sweepAt = Math.max(1_024, this.tokens.size * 2);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
InMemoryRefreshTokenStore = __decorate([
|
|
65
|
+
Injectable()
|
|
66
|
+
], InMemoryRefreshTokenStore);
|
|
67
|
+
export { InMemoryRefreshTokenStore };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
import type { JwksClientOptions } from '../interfaces/jwt-options.interface.js';
|
|
3
|
+
import { type JwsHeader } from './jws.util.js';
|
|
4
|
+
/**
|
|
5
|
+
* A cached JSON Web Key Set, shared by bearer-token verification and OIDC
|
|
6
|
+
* ID tokens.
|
|
7
|
+
*
|
|
8
|
+
* - Keys are cached for `cacheTtl`; an unknown `kid` triggers a refetch
|
|
9
|
+
* (key rotation), at most once per `cooldown`.
|
|
10
|
+
* - Concurrent refreshes share one request.
|
|
11
|
+
* - A failed refresh keeps serving the previous keys. With no keys to
|
|
12
|
+
* serve, it is an outage (a plain `Error`, so a 500, never a 401 that
|
|
13
|
+
* would make clients drop valid tokens), retried at most once per
|
|
14
|
+
* `cooldown`.
|
|
15
|
+
* - Keys marked `use` ≠ `sig`, with `key_ops` lacking `verify`, of
|
|
16
|
+
* unsupported types, or whose `alg` disagrees with the token are ignored.
|
|
17
|
+
*/
|
|
18
|
+
export declare class JwksClient {
|
|
19
|
+
readonly uri: string;
|
|
20
|
+
private keys;
|
|
21
|
+
private fetchedAt;
|
|
22
|
+
private attemptedAt;
|
|
23
|
+
private failure?;
|
|
24
|
+
private inflight?;
|
|
25
|
+
private readonly options;
|
|
26
|
+
/** Number of network fetches, for tests and metrics. */
|
|
27
|
+
fetchCount: number;
|
|
28
|
+
constructor(uri: string, options?: JwksClientOptions);
|
|
29
|
+
getKey(header: Pick<JwsHeader, 'alg' | 'kid'>): Promise<KeyObject>;
|
|
30
|
+
private find;
|
|
31
|
+
private refresh;
|
|
32
|
+
private load;
|
|
33
|
+
private now;
|
|
34
|
+
}
|