@spfn/auth 0.3.0-beta.22 → 0.3.0-beta.23

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/README.md CHANGED
@@ -135,7 +135,7 @@ real secret values out of band, never commit them.
135
135
  | `DATABASE_URL` | both | yes | Postgres connection |
136
136
  | `SPFN_AUTH_VERIFICATION_TOKEN_SECRET` | `.env.server` | yes | OTP / verification token signing |
137
137
  | `SPFN_AUTH_SESSION_SECRET` | `.env.local` | yes | ≥32 chars, AES-256 session cookie encryption (validated: entropy/unique-char checks) |
138
- | `SPFN_AUTH_TOKEN_ENCRYPTION_KEYS` | `.env.server` | web OAuth | OAuth token keyring: comma-separated `<keyId>:<base64-32-byte-key>` entries; first key is active |
138
+ | `SPFN_AUTH_TOKEN_ENCRYPTION_KEYS` | `.env.server` | web OAuth, **MFA** | At-rest keyring: comma-separated `<keyId>:<base64-32-byte-key>` entries; first key is active. Required by any app offering a [second factor](#second-factor-mfa), social login or not |
139
139
  | `SPFN_API_URL` | `.env.local` | — | default `http://localhost:8790` |
140
140
  | `SPFN_AUTH_SESSION_TTL` | both | — | default `7d` (e.g. `7d`, `12h`, `45m`) |
141
141
  | `SPFN_AUTH_JWT_SECRET` / `SPFN_AUTH_JWT_EXPIRES_IN` | `.env.server` | — | legacy server-signed JWT mode only |
@@ -171,6 +171,8 @@ real secret values out of band, never commit them.
171
171
  | `SPFN_AUTH_PASSKEY_RP_ID` / `_RP_NAME` / `_ORIGINS` | `.env.server` | — | relying party for passkeys; defaults derive from `{NEXT_PUBLIC_SPFN_APP_URL\|\|SPFN_APP_URL}` and are **checked at boot** — see [Passkeys](#passkeys-webauthn) |
172
172
  | `SPFN_AUTH_PASSKEY_USER_VERIFICATION` | `.env.server` | — | `preferred` (default) or `required`; `discouraged` refuses boot |
173
173
  | `SPFN_AUTH_PASSKEY_CHALLENGE_TTL_SECONDS` / `_RECENT_AUTH_MINUTES` | `.env.server` | — | defaults `300` / `10` — see [Passkeys](#passkeys-webauthn) |
174
+ | `SPFN_AUTH_MFA_ISSUER` | `.env.server` | — | name the authenticator app files the account under; defaults to the passkey relying-party name, then the app URL host — see [Second factor](#second-factor-mfa) |
175
+ | `SPFN_AUTH_MFA_STEP_UP_MINUTES` | `.env.server` | — | default `10`; how recently an enrolled account's device must have proved its second factor for a sensitive change — see [Second factor](#second-factor-mfa) |
174
176
  | `NEXT_PUBLIC_SPFN_API_URL` / `NEXT_PUBLIC_SPFN_APP_URL` | `.env.local` | — | browser-facing URLs for OAuth redirects |
175
177
 
176
178
  Read validated values via `import { env } from '@spfn/auth/config'` (a proxy validated at
@@ -214,6 +216,14 @@ routes use `.skip(['auth'])`; the rest require `Authorization: Bearer <client-si
214
216
  | `listPasskeys` | POST `/_auth/passkeys/list` | yes | the caller's enrolled passkeys |
215
217
  | `renamePasskey` | POST `/_auth/passkeys/rename` | yes | rename one |
216
218
  | `revokePasskey` | POST `/_auth/passkeys/revoke` | yes | retire one (refused if it is the last way in) |
219
+ | `mfaTotpEnroll` | POST `/_auth/mfa/totp/enroll` | yes | mint a TOTP secret — see [Second factor](#second-factor-mfa) |
220
+ | `mfaTotpConfirm` | POST `/_auth/mfa/totp/confirm` | yes | spend the first code; answers the ten recovery codes |
221
+ | `mfaDisable` | POST `/_auth/mfa/disable` | yes + step-up | remove the second factor (204 either way) |
222
+ | `mfaMarkPasskey` | POST `/_auth/mfa/passkey/mark` | yes + step-up | mark or unmark a passkey as the second factor |
223
+ | `mfaRegenerateRecoveryCodes` | POST `/_auth/mfa/recovery/regenerate` | yes + step-up | ten fresh codes; every earlier one stops verifying |
224
+ | `mfaStatus` | GET `/_auth/mfa/status` | yes | `{ enrolled, methods, recoveryCodesRemaining }`; no secret |
225
+ | `mfaStepUp` | POST `/_auth/mfa/step-up` | yes | re-prove the second factor on this device |
226
+ | `mfaStepUpOptions` | POST `/_auth/mfa/step-up/options` | yes | options for a step-up by passkey |
217
227
  | `logout` | POST `/_auth/logout` | yes | revoke current key |
218
228
  | `rotateKey` | POST `/_auth/keys/rotate` | yes | rotate public key before 90-day expiry |
219
229
  | `listKeys` | POST `/_auth/keys/list` | yes | the caller's registered devices — see [Registered devices](#registered-devices-key-management) |
@@ -978,6 +988,131 @@ The behaviour above is asserted row by row in
978
988
 
979
989
  Configuration rows C1–C6 are in `src/__tests__/unit/passkey-config.test.ts`.
980
990
 
991
+ ### Second factor (MFA)
992
+
993
+ Optional, and optional in the strong sense: an account that never enrols sees exactly the
994
+ behaviour it saw before this existed, on every route. Nothing here blocks anybody — the
995
+ package asks for a second factor only from people who asked it to.
996
+
997
+ Two forms. A **TOTP** authenticator app (RFC 6238, SHA-1, 30-second steps, six digits, one
998
+ step of drift), or a **passkey** the owner already enrolled through `/_auth/passkeys/*` and
999
+ has marked as a second factor. Either one comes with ten single-use recovery codes.
1000
+
1001
+ ```
1002
+ enrol → POST /_auth/mfa/totp/enroll → { secret, otpauthUri }, shown once
1003
+ confirm → POST /_auth/mfa/totp/confirm → { recoveryCodes }, ten of them, shown once
1004
+ → or POST /_auth/mfa/passkey/mark → an existing passkey becomes the second factor
1005
+ inspect → GET /_auth/mfa/status → { enrolled, methods, recoveryCodesRemaining }
1006
+ step up → POST /_auth/mfa/step-up → 204, this device's window reopens
1007
+ remove → POST /_auth/mfa/disable → 204
1008
+ ```
1009
+
1010
+ #### Prerequisite: the encryption keyring
1011
+
1012
+ A TOTP secret is encrypted at rest with **`SPFN_AUTH_TOKEN_ENCRYPTION_KEYS`** — the same
1013
+ keyring the OAuth tokens use, in the same `enc:v2:<keyId>:` frame, under its own additional
1014
+ authenticated data so a row cannot be moved between accounts. That variable is listed above
1015
+ as "web OAuth", and it is now also required by any app offering a second factor, **including
1016
+ an app with no social login at all**. `totp/enroll` answers a 500 configuration error while
1017
+ it is unset, and a key id dropped from the keyring answers the same way rather than the 401 a
1018
+ wrong code gets — an operator has to be able to tell a broken deploy from a person misreading
1019
+ their phone. A row written under a key that has since been retired is re-encrypted in place
1020
+ the next time its owner verifies, so a retired key drains as people use their second factor.
1021
+
1022
+ #### Enrolling
1023
+
1024
+ `totp/enroll` mints a 20-byte secret and returns it as RFC 4648 base32 (upper case, no
1025
+ padding) plus the `otpauth://` URI an authenticator app scans. Nothing is enrolled yet:
1026
+ calling it again replaces the pending secret, and a secret nobody confirms is swept away a day
1027
+ later by `auth.mfa.sweep`. `totp/confirm` spends the first code, which is what turns the
1028
+ enrolment into a second factor and issues the recovery codes.
1029
+
1030
+ A submitted code has its spaces and dashes stripped, so `123 456` and `123-456` are the same
1031
+ code. Five wrong codes discard the pending secret — the sixth attempt says there is nothing to
1032
+ confirm, and a fresh `totp/enroll` is the remedy and what resets the counter. A **confirmed**
1033
+ enrolment is never discarded that way; `totp/enroll` on one is a 409, because replacing a
1034
+ working second factor is `disable` followed by a fresh enrolment, both step-up gated.
1035
+
1036
+ The **same code cannot be spent twice**, which is what makes it single-use: the newest step
1037
+ the account has spent is remembered, and a code presented again inside its own thirty seconds
1038
+ is refused. That includes the legitimate case of a second device signing in during the same
1039
+ step — it gets a 401 with the same body as a wrong code, and the client should **retry on the
1040
+ next step** rather than treat it as a bad credential.
1041
+
1042
+ #### Recovery codes
1043
+
1044
+ Ten codes, format `xxxxx-xxxxx`, shown once at confirmation and once at each regeneration.
1045
+ They are stored as **password hashes** rather than as the unsalted SHA-256 the link flows use:
1046
+ a code a human transcribes is short enough that a leaked dump of unsalted hashes would fall to
1047
+ an offline sweep. `recovery/regenerate` raises the generation, so every code from before it
1048
+ stops verifying with the same body as one that never existed. `status` reports how many of the
1049
+ current generation are unspent, which is what an app warns on at two remaining.
1050
+
1051
+ #### The step-up window
1052
+
1053
+ For an **enrolled** account, four kinds of change ask for the second factor again:
1054
+
1055
+ | route | what it changes |
1056
+ |-------|-----------------|
1057
+ | `PUT /_auth/password` | the password, and every other session with it |
1058
+ | `POST /_auth/keys/revoke-all` | every device |
1059
+ | `POST /_auth/mfa/disable`, `recovery/regenerate`, `passkey/mark`, `totp/enroll` | the second factor itself |
1060
+ | `POST /_auth/passkeys/register/options`, `passkeys/revoke` | the account's credentials |
1061
+
1062
+ The rule is per **device key**: this device must have proved the second factor within
1063
+ `SPFN_AUTH_MFA_STEP_UP_MINUTES` (default 10). Otherwise the answer is **403
1064
+ `STEP_UP_REQUIRED`**, and the client sends the user to `POST /_auth/mfa/step-up` — a TOTP
1065
+ code, a recovery code, or an assertion from a marked passkey (options from
1066
+ `POST /_auth/mfa/step-up/options`) — and retries. 403 rather than 401 on purpose, and for the
1067
+ reason `RECENT_AUTH_REQUIRED` is: a 401 on an authenticated route is what a web client reads
1068
+ as "the session is gone", so it would sign the user out instead of asking for a code.
1069
+
1070
+ A key **rotation carries the window across**, because rotating is already proof of the same
1071
+ device — otherwise the web proxy, which rotates at every login, would expire it constantly.
1072
+
1073
+ The two passkey routes keep their own `RECENT_AUTH_REQUIRED` rule unchanged and run it after
1074
+ the step-up: the two guards are independent, and an unenrolled account meets exactly the rule
1075
+ it met before. Unmarking the last second-factor passkey is likewise independent of
1076
+ `LAST_RECOVERY_CREDENTIAL` — removing a mark is not removing a way into the account, so
1077
+ `passkey/mark false` succeeds where `passkeys/revoke` on the same credential is still refused.
1078
+
1079
+ Two sign-ins deliberately produce a session with no verification of its own: a
1080
+ [device-code login](#device-code-login) and a passkey sign-in. Both are exempt at
1081
+ *registration* and both still step up for a sensitive change, which is what
1082
+ `POST /_auth/mfa/step-up` is for. Marking a passkey as a second factor is likewise not proving
1083
+ it, so the device that marks one steps up before it may change the second factor again.
1084
+
1085
+ #### Telling people it exists
1086
+
1087
+ `authLoginEvent` and `authDeviceRegisteredEvent` carry **`mfaEnrolled: boolean`**, computed as
1088
+ the event is emitted. That is the whole of the package's opinion: subscribe and offer
1089
+ enrolment at a first login or when a new device appears. Nothing is ever blocked on it.
1090
+
1091
+ #### Errors
1092
+
1093
+ | error | status | `code` | when |
1094
+ |-------|--------|--------|------|
1095
+ | `MfaVerificationFailedError` | 401 | — | a wrong or stale code, a spent step, a used / old-generation / foreign recovery code, or an assertion from an unmarked passkey |
1096
+ | `MfaNotEnrolledError` | 400 | — | `confirm` with no pending secret (the five-strike deletion included), or `regenerate` on an account with no second factor |
1097
+ | `StepUpRequiredError` | 403 | `STEP_UP_REQUIRED` | an enrolled account's device is outside the window |
1098
+ | `MfaAlreadyEnrolledError` | 409 | — | `totp/enroll` on a confirmed enrolment |
1099
+ | `MfaConfigError` | 500 | — | `SPFN_AUTH_TOKEN_ENCRYPTION_KEYS` unset, or a stored secret naming a key id no longer in it |
1100
+
1101
+ None of these is a mobile-contract error: the enrolment routes are not contract operations.
1102
+
1103
+ #### The case table
1104
+
1105
+ Asserted row by row in `src/__tests__/integration/mfa-enrolment.test.ts` (enrolment) and
1106
+ `mfa-step-up.test.ts` (the window); each `it` is named for its row.
1107
+ `mfa-unenrolled-regression.test.ts` pins the status and the body shape an **unenrolled**
1108
+ account gets from `login`, `changePassword`, `keys/revoke-all` and `passkeys/revoke`.
1109
+
1110
+ #### The sweep
1111
+
1112
+ `auth.mfa.sweep` runs daily at 07:00 and deletes enrolments still unconfirmed after 24 hours.
1113
+ It is carried by `authJobRouter` beside the other sweeps; pass `mfaSweepCron` to
1114
+ `createAuthJobRouter()` to move it. A confirmed enrolment is never touched.
1115
+
981
1116
  ### Writing protected routes (route DSL)
982
1117
 
983
1118
  This is the current SPFN route DSL — `route.<method>().input().use().skip().handler()` registered
@@ -1689,6 +1824,11 @@ and not what it began on, so a stolen password used on a new machine was silent
1689
1824
  Send it with a [sign-out-everywhere link](#the-sign-out-everywhere-link), which is the action the
1690
1825
  notice should offer.
1691
1826
 
1827
+ Both `authLoginEvent` and `authDeviceRegisteredEvent` carry `mfaEnrolled: boolean`, computed
1828
+ as the event is emitted. It is the hook an app uses to offer a [second
1829
+ factor](#second-factor-mfa) at a first login or when a new device appears; the package itself
1830
+ never blocks an account that has none.
1831
+
1692
1832
  Key **rotation** is deliberately not announced — replacing the key of a device that is already
1693
1833
  signed in is not a new device, and a notice for it would teach the owner to ignore the ones that
1694
1834
  matter. A login that names an `oldKeyId` is only a rotation when that key was actually revoked: an
@@ -1700,7 +1840,8 @@ Payload types: `AuthLoginPayload`, `AuthRegisterPayload`, `AuthPasswordResetPayl
1700
1840
  `InvitationAcceptedPayload`, `AuthDeletionRequestedPayload`, `AuthDeletionCancelledPayload`,
1701
1841
  `AuthDeletionCompletedPayload`, `OAuthUnlinkedPayload` (`auth.oauth.unlinked` — provider-side
1702
1842
  disconnect, see the OAuth unlink-notify section), `PasskeyEnrolledPayload`,
1703
- `PasskeyRevokedPayload`. These events also bind to `@spfn/core/job`
1843
+ `PasskeyRevokedPayload`. `AuthLoginPayload` and `AuthDeviceRegisteredPayload` both gained
1844
+ `mfaEnrolled` in 0.3.0-beta.23. These events also bind to `@spfn/core/job`
1704
1845
  jobs via `.on(event)`.
1705
1846
 
1706
1847
  ## Registration gate (`beforeRegister`)
package/dist/config.d.ts CHANGED
@@ -357,6 +357,25 @@ declare const authEnvSchema: {
357
357
  } & {
358
358
  key: "SPFN_AUTH_PASSKEY_RECENT_AUTH_MINUTES";
359
359
  };
360
+ SPFN_AUTH_MFA_ISSUER: {
361
+ description: string;
362
+ required: boolean;
363
+ examples: string[];
364
+ type: "string";
365
+ validator: (value: string) => string;
366
+ } & {
367
+ key: "SPFN_AUTH_MFA_ISSUER";
368
+ };
369
+ SPFN_AUTH_MFA_STEP_UP_MINUTES: {
370
+ description: string;
371
+ default: number;
372
+ required: boolean;
373
+ examples: number[];
374
+ type: "number";
375
+ validator: (value: string) => number;
376
+ } & {
377
+ key: "SPFN_AUTH_MFA_STEP_UP_MINUTES";
378
+ };
360
379
  SPFN_API_URL: {
361
380
  description: string;
362
381
  default: string;
@@ -942,6 +961,25 @@ declare const env: _spfn_core_env.InferEnvType<{
942
961
  } & {
943
962
  key: "SPFN_AUTH_PASSKEY_RECENT_AUTH_MINUTES";
944
963
  };
964
+ SPFN_AUTH_MFA_ISSUER: {
965
+ description: string;
966
+ required: boolean;
967
+ examples: string[];
968
+ type: "string";
969
+ validator: (value: string) => string;
970
+ } & {
971
+ key: "SPFN_AUTH_MFA_ISSUER";
972
+ };
973
+ SPFN_AUTH_MFA_STEP_UP_MINUTES: {
974
+ description: string;
975
+ default: number;
976
+ required: boolean;
977
+ examples: number[];
978
+ type: "number";
979
+ validator: (value: string) => number;
980
+ } & {
981
+ key: "SPFN_AUTH_MFA_STEP_UP_MINUTES";
982
+ };
945
983
  SPFN_API_URL: {
946
984
  description: string;
947
985
  default: string;
package/dist/config.js CHANGED
@@ -343,6 +343,24 @@ var authEnvSchema = defineEnvSchema({
343
343
  })
344
344
  },
345
345
  // ============================================================================
346
+ // Second factor (MFA)
347
+ // ============================================================================
348
+ SPFN_AUTH_MFA_ISSUER: {
349
+ ...envString({
350
+ description: "Name the authenticator app files this account under, carried in the otpauth:// URI. Defaults to the passkey relying-party name, then to the host of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Changing it after people have enrolled only relabels the entry in their app; the codes keep working.",
351
+ required: false,
352
+ examples: ["Acme", "Acme Staging"]
353
+ })
354
+ },
355
+ SPFN_AUTH_MFA_STEP_UP_MINUTES: {
356
+ ...envNumber({
357
+ description: "How recently an enrolled account must have proved its second factor on the calling device for a sensitive change (password change, sign out everywhere, disabling MFA, passkey management) to go through. Older than this and the request is 403 STEP_UP_REQUIRED until POST /_auth/mfa/step-up succeeds. Unenrolled accounts are unaffected.",
358
+ default: 10,
359
+ required: false,
360
+ examples: [5, 10, 30]
361
+ })
362
+ },
363
+ // ============================================================================
346
364
  // API Configuration
347
365
  // ============================================================================
348
366
  SPFN_API_URL: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/index.ts","../src/config/schema.ts"],"sourcesContent":["/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { authEnvSchema } from './schema';\n\nexport { authEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n */\nconst registry = createEnvRegistry(authEnvSchema);\nexport const env = registry.validate();\n","/**\n * Auth Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/auth.\n * This provides type safety, validation, and documentation for Auth configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envString,\n envNumber,\n envBoolean,\n envEnum,\n createSecureSecretParser,\n createPasswordParser,\n} from '@spfn/core/env';\n\n/**\n * Auth environment variable schema\n *\n * Defines all Auth environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { envSchema } from '@spfn/auth/config';\n *\n * // Access schema information\n * console.log(envSchema.SPFN_AUTH_SESSION_SECRET.description);\n * console.log(envSchema.SPFN_AUTH_JWT_EXPIRES_IN.default);\n * ```\n */\nexport const authEnvSchema = defineEnvSchema({\n // ============================================================================\n // Session Configuration\n // ============================================================================\n SPFN_AUTH_SESSION_SECRET: {\n ...envString({\n description: 'Session encryption secret (minimum 32 characters for AES-256)',\n required: true,\n fallbackKeys: ['SESSION_SECRET'],\n validator: createSecureSecretParser({\n minLength: 32,\n minUniqueChars: 16,\n minEntropy: 3.5,\n }),\n sensitive: true,\n nextjs: true, // Required for Next.js RSC session validation\n examples: [\n 'my-super-secret-session-key-at-least-32-chars-long',\n 'use-a-cryptographically-secure-random-string-here',\n ],\n }),\n },\n\n SPFN_AUTH_SESSION_TTL: {\n ...envString({\n description: 'Session TTL (time to live) - supports duration strings like \\'7d\\', \\'12h\\', \\'45m\\'',\n default: '7d',\n required: false,\n nextjs: true, // May be needed for session validation in Next.js RSC\n examples: ['7d', '30d', '12h', '45m', '3600'],\n }),\n },\n\n // ============================================================================\n // JWT Configuration\n // ============================================================================\n SPFN_AUTH_JWT_SECRET: {\n ...envString({\n description: 'JWT signing secret for server-signed tokens (legacy mode)',\n default: 'dev-secret-key-change-in-production',\n required: false,\n examples: [\n 'your-jwt-secret-key-here',\n 'use-different-from-session-secret',\n ],\n }),\n },\n\n SPFN_AUTH_JWT_EXPIRES_IN: {\n ...envString({\n description: 'JWT token expiration time (e.g., \\'7d\\', \\'24h\\', \\'1h\\')',\n default: '7d',\n required: false,\n examples: ['7d', '24h', '1h', '30m'],\n }),\n },\n\n // ============================================================================\n // Security Configuration\n // ============================================================================\n SPFN_AUTH_COOKIE_SECURE: {\n ...envBoolean({\n description: 'Override cookie Secure flag. Defaults to NODE_ENV === \"production\". Set to false for HTTP-only environments (e.g. bastion over plain HTTP).',\n required: false,\n nextjs: true,\n examples: [true, false],\n }),\n },\n\n SPFN_AUTH_CSRF: {\n ...envString({\n description: 'CSRF protection for cookie-session mutations in the Next.js proxy: off | warn | enforce. Unset behaves as \"warn\" (log what would be refused, allow it through). configureAuth({ csrf: { mode } }) takes precedence.',\n required: false,\n nextjs: true, // The check runs in the Next.js proxy\n examples: ['enforce', 'warn', 'off'],\n }),\n },\n\n SPFN_AUTH_BCRYPT_SALT_ROUNDS: {\n ...envNumber({\n description: 'Bcrypt salt rounds (cost factor, higher = more secure but slower)',\n default: 12,\n required: false,\n examples: [10, 12, 14],\n }),\n key: 'SPFN_AUTH_BCRYPT_SALT_ROUNDS',\n },\n\n SPFN_AUTH_VERIFICATION_TOKEN_SECRET: {\n ...envString({\n description: 'Verification token secret for email verification, password reset, etc.',\n required: true,\n examples: [\n 'your-verification-token-secret',\n 'can-be-different-from-jwt-secret',\n ],\n }),\n },\n\n SPFN_AUTH_TOKEN_ENCRYPTION_KEYS: {\n ...envString({\n description: 'Backend-only OAuth token encryption keyring. Comma-separated <keyId>:<base64-encoded 32-byte key> entries; the first key encrypts new values and remaining keys decrypt during rotation.',\n required: false,\n sensitive: true,\n examples: [\n 'v2:<base64-encoded-32-byte-key>,v1:<previous-base64-encoded-32-byte-key>',\n ],\n }),\n },\n\n // ============================================================================\n // Admin Account Configuration\n // ============================================================================\n SPFN_AUTH_ADMIN_ACCOUNTS: {\n ...envString({\n description: 'JSON array of admin accounts (recommended for multiple admins)',\n required: false,\n examples: [\n '[{\"email\":\"admin@example.com\",\"password\":\"secure-pass\",\"role\":\"admin\"}]',\n '[{\"email\":\"super@example.com\",\"password\":\"pass1\",\"role\":\"superadmin\"},{\"email\":\"admin@example.com\",\"password\":\"pass2\",\"role\":\"admin\"}]',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_EMAILS: {\n ...envString({\n description: 'Comma-separated list of admin emails (legacy CSV format)',\n required: false,\n examples: [\n 'admin@example.com,user@example.com',\n 'super@example.com,admin@example.com,user@example.com',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_PASSWORDS: {\n ...envString({\n description: 'Comma-separated list of admin passwords (legacy CSV format)',\n required: false,\n examples: [\n 'admin-pass,user-pass',\n 'super-pass,admin-pass,user-pass',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_ROLES: {\n ...envString({\n description: 'Comma-separated list of admin roles (legacy CSV format)',\n required: false,\n examples: [\n 'admin,user',\n 'superadmin,admin,user',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_EMAIL: {\n ...envString({\n description: 'Single admin email (simplest format)',\n required: false,\n examples: ['admin@example.com'],\n }),\n },\n\n SPFN_AUTH_ADMIN_PASSWORD: {\n ...envString({\n description: 'Single admin password (simplest format)',\n required: false,\n validator: createPasswordParser({\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumber: true,\n requireSpecial: true,\n }),\n sensitive: true,\n examples: ['SecureAdmin123!'],\n }),\n },\n\n // ============================================================================\n // Username Configuration\n // ============================================================================\n SPFN_AUTH_RESERVED_USERNAMES: {\n ...envString({\n description: 'Comma-separated list of reserved usernames that cannot be registered',\n required: false,\n default: 'admin,root,system,support,help,moderator,superadmin',\n examples: [\n 'admin,root,system,support,help',\n 'admin,root,system,support,help,moderator,superadmin,operator',\n ],\n }),\n },\n\n SPFN_AUTH_USERNAME_MIN_LENGTH: {\n ...envNumber({\n description: 'Minimum username length',\n default: 3,\n required: false,\n examples: [2, 3, 4],\n }),\n },\n\n SPFN_AUTH_USERNAME_MAX_LENGTH: {\n ...envNumber({\n description: 'Maximum username length',\n default: 30,\n required: false,\n examples: [20, 30, 50],\n }),\n },\n\n // ============================================================================\n // Verified-email signup\n // ============================================================================\n SPFN_AUTH_SIGNUP_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long an emailed signup confirmation link stays valid. Long enough to survive a mail delay, short enough that a link left in an inbox stops working.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_SIGNUP_SETUP_TTL_MINUTES: {\n ...envNumber({\n description: 'How long the password-setup session opened by a confirmation link stays valid. Covers one sitting at the password form, not an abandoned tab.',\n default: 15,\n required: false,\n examples: [10, 15, 30],\n }),\n },\n\n SPFN_AUTH_SIGNUP_CONFIRM_PATH: {\n ...envString({\n description: 'App page the emailed confirmation link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/signup/email/confirm; it is a page in your app, not an API route.',\n default: '/signup/confirm',\n required: false,\n examples: ['/signup/confirm', '/auth/confirm', '/join/verify'],\n }),\n },\n\n // ============================================================================\n // Password reset (verified email)\n // ============================================================================\n SPFN_AUTH_PASSWORD_RESET_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long an emailed password reset link stays valid. Long enough to survive a mail delay, short enough that a link left in an inbox stops working.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_PASSWORD_RESET_SETUP_TTL_MINUTES: {\n ...envNumber({\n description: 'How long the password-setup session opened by a reset link stays valid. Covers one sitting at the new-password form, not an abandoned tab.',\n default: 15,\n required: false,\n examples: [10, 15, 30],\n }),\n },\n\n SPFN_AUTH_PASSWORD_RESET_CONFIRM_PATH: {\n ...envString({\n description: 'App page the emailed reset link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/password/reset/confirm; it is a page in your app, not an API route.',\n default: '/password/reset',\n required: false,\n examples: ['/password/reset', '/auth/reset', '/forgot/new-password'],\n }),\n },\n\n // ============================================================================\n // Signed sign-out-everywhere link\n // ============================================================================\n SPFN_AUTH_REVOKE_ALL_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long a signed sign-out-everywhere link stays valid. The same default as the other two link flows: long enough to survive a mail delay, short enough that a link left in an inbox stops working — and this one signs every device out.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_REVOKE_ALL_CONFIRM_PATH: {\n ...envString({\n description: 'App page the sign-out-everywhere link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/keys/revoke-all/confirm to describe the link, then to /_auth/keys/revoke-all/consume when the owner confirms; it is a page in your app, not an API route.',\n default: '/account/revoke-all',\n required: false,\n examples: ['/account/revoke-all', '/security/sign-out-everywhere'],\n }),\n },\n\n // ============================================================================\n // Link mail delivery\n // ============================================================================\n SPFN_AUTH_LINK_MAIL_DELIVERY: {\n ...envEnum(['auto', 'inline', 'queued'] as const, {\n description: 'Who sends signup-link, password-reset and account-exists mail. \\'auto\\' (default) queues it on auth.link-mail when pg-boss is initialised and sends it on the request path when it is not; \\'queued\\' always queues and surfaces an enqueue failure; \\'inline\\' always sends on the request path, which makes how long the request took reveal whether the address has an account.',\n default: 'auto',\n required: false,\n examples: ['auto', 'inline', 'queued'],\n }),\n },\n\n // ============================================================================\n // Passkeys (WebAuthn)\n // ============================================================================\n SPFN_AUTH_PASSKEY_RP_ID: {\n ...envString({\n description: 'Domain passkeys are bound to — a registrable domain with no protocol and no port. Defaults to the host of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Set it explicitly when the app is served from several hosts that share a domain; every origin below must be that host or a subdomain of it. Changing it orphans every passkey already enrolled.',\n required: false,\n examples: ['example.com', 'app.example.com', 'localhost'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_RP_NAME: {\n ...envString({\n description: \"Name the authenticator's own prompt shows the user. Defaults to the relying party ID.\",\n required: false,\n examples: ['Acme', 'Acme Staging'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_ORIGINS: {\n ...envString({\n description: 'Comma-separated full origins allowed to run a passkey ceremony. Defaults to the origin of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Each must be https (http only for localhost) and must be the relying party ID or a subdomain of it. Checked at boot: a value that breaks either rule refuses to start, because it would otherwise surface as the browser refusing every ceremony.',\n required: false,\n examples: ['https://app.example.com', 'https://app.example.com,https://admin.example.com', 'http://localhost:3000'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_USER_VERIFICATION: {\n ...envString({\n description: \"How hard the authenticator must prove the person is present: 'preferred' or 'required'. 'discouraged' is refused at boot — a passkey is the whole credential here, so an assertion that skipped user verification would sign someone in on an unlocked device alone.\",\n default: 'preferred',\n required: false,\n examples: ['preferred', 'required'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_CHALLENGE_TTL_SECONDS: {\n ...envNumber({\n description: 'How long the challenge minted by a passkey options call stays presentable. One ceremony at the authenticator, not an abandoned tab.',\n default: 300,\n required: false,\n examples: [120, 300, 600],\n }),\n },\n\n SPFN_AUTH_PASSKEY_RECENT_AUTH_MINUTES: {\n ...envNumber({\n description: 'How recently the calling device key must have been registered for enrolling or revoking a passkey to go through without the current password. Older than this and the request needs `currentPassword`; an account with no password has to sign in again.',\n default: 10,\n required: false,\n examples: [5, 10, 30],\n }),\n },\n\n // ============================================================================\n // API Configuration\n // ============================================================================\n SPFN_API_URL: {\n ...envString({\n description: 'Internal API URL for server-to-server communication',\n default: 'http://localhost:8790',\n required: false,\n examples: [\n 'https://api.example.com',\n 'http://localhost:8790',\n ],\n }),\n },\n\n NEXT_PUBLIC_SPFN_API_URL: {\n ...envString({\n description: 'Public-facing API URL used for browser-facing redirects. Falls back to SPFN_API_URL if not set.',\n required: false,\n examples: [\n 'https://api.example.com',\n 'http://localhost:8790',\n ],\n }),\n },\n\n SPFN_APP_URL: {\n ...envString({\n description: 'Next.js application URL (internal). Used for server-to-server communication.',\n default: 'http://localhost:3000',\n required: false,\n examples: [\n 'https://app.example.com',\n 'http://localhost:3000',\n ],\n }),\n },\n\n NEXT_PUBLIC_SPFN_APP_URL: {\n ...envString({\n description: 'Public-facing Next.js app URL for browser redirects (e.g. OAuth redirect). Falls back to SPFN_APP_URL if not set.',\n required: false,\n examples: [\n 'https://app.example.com',\n 'http://localhost:3000',\n ],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Google\n // ============================================================================\n SPFN_AUTH_GOOGLE_CLIENT_ID: {\n ...envString({\n description: 'Google OAuth 2.0 Client ID. When set, Google OAuth routes are automatically enabled.',\n required: false,\n examples: ['123456789-abc123.apps.googleusercontent.com'],\n }),\n },\n\n SPFN_AUTH_GOOGLE_CLIENT_SECRET: {\n ...envString({\n description: 'Google OAuth 2.0 Client Secret',\n required: false,\n sensitive: true,\n examples: ['GOCSPX-abcdefghijklmnop'],\n }),\n },\n\n SPFN_AUTH_GOOGLE_SCOPES: {\n ...envString({\n description: 'Comma-separated Google OAuth scopes. Defaults to \"email,profile\" if not set.',\n required: false,\n examples: [\n 'email,profile',\n 'email,profile,https://www.googleapis.com/auth/gmail.readonly',\n 'email,profile,https://www.googleapis.com/auth/calendar.readonly',\n ],\n }),\n },\n\n SPFN_AUTH_GOOGLE_REDIRECT_URI: {\n ...envString({\n description: 'Google OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/google/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: [\n 'https://app.example.com/_auth/oauth/google/callback',\n 'http://localhost:3000/_auth/oauth/google/callback',\n ],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Kakao\n // ============================================================================\n SPFN_AUTH_KAKAO_CLIENT_ID: {\n ...envString({\n description: 'Kakao Login REST API key. Used as the OAuth client_id.',\n required: false,\n examples: ['your-kakao-rest-api-key'],\n }),\n },\n\n SPFN_AUTH_KAKAO_CLIENT_SECRET: {\n ...envString({\n description: 'Kakao Login client secret. Required when the Kakao client-secret feature is enabled.',\n required: false,\n sensitive: true,\n examples: ['your-kakao-client-secret'],\n }),\n },\n\n SPFN_AUTH_KAKAO_ADMIN_KEY: {\n ...envString({\n description: 'Kakao app admin key. Required to verify the User Unlinked webhook (Authorization: KakaoAK header).',\n required: false,\n sensitive: true,\n examples: ['your-kakao-admin-key'],\n }),\n },\n\n SPFN_AUTH_KAKAO_SCOPES: {\n ...envString({\n description: 'Comma-separated Kakao consent scopes. Defaults to account_email.',\n required: false,\n examples: ['account_email'],\n }),\n },\n\n SPFN_AUTH_KAKAO_REDIRECT_URI: {\n ...envString({\n description: 'Kakao OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/kakao/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/kakao/callback'],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Naver\n // ============================================================================\n SPFN_AUTH_NAVER_CLIENT_ID: {\n ...envString({\n description: 'Naver Login OAuth client ID.',\n required: false,\n examples: ['your-naver-client-id'],\n }),\n },\n\n SPFN_AUTH_NAVER_CLIENT_SECRET: {\n ...envString({\n description: 'Naver Login OAuth client secret.',\n required: false,\n sensitive: true,\n examples: ['your-naver-client-secret'],\n }),\n },\n\n SPFN_AUTH_NAVER_REDIRECT_URI: {\n ...envString({\n description: 'Naver OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/naver/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/naver/callback'],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - GitHub\n // ============================================================================\n SPFN_AUTH_GITHUB_CLIENT_ID: {\n ...envString({\n description: 'GitHub OAuth app client ID. When set, GitHub OAuth routes are automatically enabled.',\n required: false,\n examples: ['Iv1.abc123def456'],\n }),\n },\n\n SPFN_AUTH_GITHUB_CLIENT_SECRET: {\n ...envString({\n description: 'GitHub OAuth app client secret.',\n required: false,\n sensitive: true,\n examples: ['your-github-client-secret'],\n }),\n },\n\n SPFN_AUTH_GITHUB_SCOPES: {\n ...envString({\n description: 'Comma-separated GitHub OAuth scopes. Defaults to \"read:user,user:email\".',\n required: false,\n examples: ['read:user,user:email'],\n }),\n },\n\n SPFN_AUTH_GITHUB_REDIRECT_URI: {\n ...envString({\n description: 'GitHub OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/github/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/github/callback'],\n }),\n },\n\n SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK: {\n ...envString({\n description: 'Boot-time check of the four SPFN_AUTH_<PROVIDER>_REDIRECT_URI overrides: each one that is set must sit on the web app origin ({NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}) at /_auth/oauth/<provider>/callback, because the callback CSRF cookie is host-only. \"off\" is the only value that disables the check — unset, \"on\" and anything else all run it.',\n required: false,\n examples: ['off'],\n }),\n },\n\n // ============================================================================\n // Native Social Login (mobile/web id_token verification)\n //\n // 네이티브 SDK가 받은 id_token을 서버가 JWKS로 검증하는 경로 전용 설정.\n // authorization code 교환을 하지 않으므로 client secret이 필요 없다.\n // audience(aud)로 허용할 client id 목록만 지정한다.\n // ============================================================================\n SPFN_AUTH_GOOGLE_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Google client IDs accepted as id_token audience for native sign-in (iOS, Android, web). When set, Google native sign-in is enabled. SPFN_AUTH_GOOGLE_CLIENT_ID is also accepted automatically.',\n required: false,\n examples: [\n '123-ios.apps.googleusercontent.com,123-android.apps.googleusercontent.com',\n ],\n }),\n },\n\n SPFN_AUTH_APPLE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Apple client IDs accepted as id_token audience for native sign-in (iOS bundle ID, web/Android Services ID). When set, Apple native sign-in is enabled.',\n required: false,\n examples: [\n 'com.example.app,com.example.app.service',\n ],\n }),\n },\n\n SPFN_AUTH_KAKAO_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Kakao app keys accepted as id_token audience for native sign-in (native app key). SPFN_AUTH_KAKAO_CLIENT_ID (REST API key) is also accepted automatically, so native sign-in is available when either variable is set. Requires OpenID Connect to be enabled in the Kakao developer console.',\n required: false,\n examples: [\n 'your-kakao-native-app-key',\n ],\n }),\n },\n\n SPFN_AUTH_NAVER_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Naver client IDs accepted as id_token audience for native sign-in. SPFN_AUTH_NAVER_CLIENT_ID is also accepted automatically, and one Naver application has a single client ID covering web and app environments — set this only when the app uses a separate application.',\n required: false,\n examples: [\n 'your-naver-app-client-id',\n ],\n }),\n },\n\n SPFN_AUTH_OAUTH_SUCCESS_URL: {\n ...envString({\n description: 'OAuth callback page URL. This page should use OAuthCallback component to finalize session.',\n required: false,\n default: '/auth/callback',\n examples: [\n '/auth/callback',\n 'https://app.example.com/auth/callback',\n ],\n }),\n },\n\n SPFN_AUTH_OAUTH_ERROR_URL: {\n ...envString({\n description: 'URL to redirect after OAuth error. Use {error} placeholder for error message.',\n required: false,\n default: '/auth/error?error={error}',\n examples: [\n 'https://app.example.com/auth/error?error={error}',\n 'http://localhost:3000/auth/error?error={error}',\n ],\n }),\n },\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/config/index.ts","../src/config/schema.ts"],"sourcesContent":["/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { authEnvSchema } from './schema';\n\nexport { authEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n */\nconst registry = createEnvRegistry(authEnvSchema);\nexport const env = registry.validate();\n","/**\n * Auth Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/auth.\n * This provides type safety, validation, and documentation for Auth configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envString,\n envNumber,\n envBoolean,\n envEnum,\n createSecureSecretParser,\n createPasswordParser,\n} from '@spfn/core/env';\n\n/**\n * Auth environment variable schema\n *\n * Defines all Auth environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { envSchema } from '@spfn/auth/config';\n *\n * // Access schema information\n * console.log(envSchema.SPFN_AUTH_SESSION_SECRET.description);\n * console.log(envSchema.SPFN_AUTH_JWT_EXPIRES_IN.default);\n * ```\n */\nexport const authEnvSchema = defineEnvSchema({\n // ============================================================================\n // Session Configuration\n // ============================================================================\n SPFN_AUTH_SESSION_SECRET: {\n ...envString({\n description: 'Session encryption secret (minimum 32 characters for AES-256)',\n required: true,\n fallbackKeys: ['SESSION_SECRET'],\n validator: createSecureSecretParser({\n minLength: 32,\n minUniqueChars: 16,\n minEntropy: 3.5,\n }),\n sensitive: true,\n nextjs: true, // Required for Next.js RSC session validation\n examples: [\n 'my-super-secret-session-key-at-least-32-chars-long',\n 'use-a-cryptographically-secure-random-string-here',\n ],\n }),\n },\n\n SPFN_AUTH_SESSION_TTL: {\n ...envString({\n description: 'Session TTL (time to live) - supports duration strings like \\'7d\\', \\'12h\\', \\'45m\\'',\n default: '7d',\n required: false,\n nextjs: true, // May be needed for session validation in Next.js RSC\n examples: ['7d', '30d', '12h', '45m', '3600'],\n }),\n },\n\n // ============================================================================\n // JWT Configuration\n // ============================================================================\n SPFN_AUTH_JWT_SECRET: {\n ...envString({\n description: 'JWT signing secret for server-signed tokens (legacy mode)',\n default: 'dev-secret-key-change-in-production',\n required: false,\n examples: [\n 'your-jwt-secret-key-here',\n 'use-different-from-session-secret',\n ],\n }),\n },\n\n SPFN_AUTH_JWT_EXPIRES_IN: {\n ...envString({\n description: 'JWT token expiration time (e.g., \\'7d\\', \\'24h\\', \\'1h\\')',\n default: '7d',\n required: false,\n examples: ['7d', '24h', '1h', '30m'],\n }),\n },\n\n // ============================================================================\n // Security Configuration\n // ============================================================================\n SPFN_AUTH_COOKIE_SECURE: {\n ...envBoolean({\n description: 'Override cookie Secure flag. Defaults to NODE_ENV === \"production\". Set to false for HTTP-only environments (e.g. bastion over plain HTTP).',\n required: false,\n nextjs: true,\n examples: [true, false],\n }),\n },\n\n SPFN_AUTH_CSRF: {\n ...envString({\n description: 'CSRF protection for cookie-session mutations in the Next.js proxy: off | warn | enforce. Unset behaves as \"warn\" (log what would be refused, allow it through). configureAuth({ csrf: { mode } }) takes precedence.',\n required: false,\n nextjs: true, // The check runs in the Next.js proxy\n examples: ['enforce', 'warn', 'off'],\n }),\n },\n\n SPFN_AUTH_BCRYPT_SALT_ROUNDS: {\n ...envNumber({\n description: 'Bcrypt salt rounds (cost factor, higher = more secure but slower)',\n default: 12,\n required: false,\n examples: [10, 12, 14],\n }),\n key: 'SPFN_AUTH_BCRYPT_SALT_ROUNDS',\n },\n\n SPFN_AUTH_VERIFICATION_TOKEN_SECRET: {\n ...envString({\n description: 'Verification token secret for email verification, password reset, etc.',\n required: true,\n examples: [\n 'your-verification-token-secret',\n 'can-be-different-from-jwt-secret',\n ],\n }),\n },\n\n SPFN_AUTH_TOKEN_ENCRYPTION_KEYS: {\n ...envString({\n description: 'Backend-only OAuth token encryption keyring. Comma-separated <keyId>:<base64-encoded 32-byte key> entries; the first key encrypts new values and remaining keys decrypt during rotation.',\n required: false,\n sensitive: true,\n examples: [\n 'v2:<base64-encoded-32-byte-key>,v1:<previous-base64-encoded-32-byte-key>',\n ],\n }),\n },\n\n // ============================================================================\n // Admin Account Configuration\n // ============================================================================\n SPFN_AUTH_ADMIN_ACCOUNTS: {\n ...envString({\n description: 'JSON array of admin accounts (recommended for multiple admins)',\n required: false,\n examples: [\n '[{\"email\":\"admin@example.com\",\"password\":\"secure-pass\",\"role\":\"admin\"}]',\n '[{\"email\":\"super@example.com\",\"password\":\"pass1\",\"role\":\"superadmin\"},{\"email\":\"admin@example.com\",\"password\":\"pass2\",\"role\":\"admin\"}]',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_EMAILS: {\n ...envString({\n description: 'Comma-separated list of admin emails (legacy CSV format)',\n required: false,\n examples: [\n 'admin@example.com,user@example.com',\n 'super@example.com,admin@example.com,user@example.com',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_PASSWORDS: {\n ...envString({\n description: 'Comma-separated list of admin passwords (legacy CSV format)',\n required: false,\n examples: [\n 'admin-pass,user-pass',\n 'super-pass,admin-pass,user-pass',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_ROLES: {\n ...envString({\n description: 'Comma-separated list of admin roles (legacy CSV format)',\n required: false,\n examples: [\n 'admin,user',\n 'superadmin,admin,user',\n ],\n }),\n },\n\n SPFN_AUTH_ADMIN_EMAIL: {\n ...envString({\n description: 'Single admin email (simplest format)',\n required: false,\n examples: ['admin@example.com'],\n }),\n },\n\n SPFN_AUTH_ADMIN_PASSWORD: {\n ...envString({\n description: 'Single admin password (simplest format)',\n required: false,\n validator: createPasswordParser({\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumber: true,\n requireSpecial: true,\n }),\n sensitive: true,\n examples: ['SecureAdmin123!'],\n }),\n },\n\n // ============================================================================\n // Username Configuration\n // ============================================================================\n SPFN_AUTH_RESERVED_USERNAMES: {\n ...envString({\n description: 'Comma-separated list of reserved usernames that cannot be registered',\n required: false,\n default: 'admin,root,system,support,help,moderator,superadmin',\n examples: [\n 'admin,root,system,support,help',\n 'admin,root,system,support,help,moderator,superadmin,operator',\n ],\n }),\n },\n\n SPFN_AUTH_USERNAME_MIN_LENGTH: {\n ...envNumber({\n description: 'Minimum username length',\n default: 3,\n required: false,\n examples: [2, 3, 4],\n }),\n },\n\n SPFN_AUTH_USERNAME_MAX_LENGTH: {\n ...envNumber({\n description: 'Maximum username length',\n default: 30,\n required: false,\n examples: [20, 30, 50],\n }),\n },\n\n // ============================================================================\n // Verified-email signup\n // ============================================================================\n SPFN_AUTH_SIGNUP_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long an emailed signup confirmation link stays valid. Long enough to survive a mail delay, short enough that a link left in an inbox stops working.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_SIGNUP_SETUP_TTL_MINUTES: {\n ...envNumber({\n description: 'How long the password-setup session opened by a confirmation link stays valid. Covers one sitting at the password form, not an abandoned tab.',\n default: 15,\n required: false,\n examples: [10, 15, 30],\n }),\n },\n\n SPFN_AUTH_SIGNUP_CONFIRM_PATH: {\n ...envString({\n description: 'App page the emailed confirmation link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/signup/email/confirm; it is a page in your app, not an API route.',\n default: '/signup/confirm',\n required: false,\n examples: ['/signup/confirm', '/auth/confirm', '/join/verify'],\n }),\n },\n\n // ============================================================================\n // Password reset (verified email)\n // ============================================================================\n SPFN_AUTH_PASSWORD_RESET_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long an emailed password reset link stays valid. Long enough to survive a mail delay, short enough that a link left in an inbox stops working.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_PASSWORD_RESET_SETUP_TTL_MINUTES: {\n ...envNumber({\n description: 'How long the password-setup session opened by a reset link stays valid. Covers one sitting at the new-password form, not an abandoned tab.',\n default: 15,\n required: false,\n examples: [10, 15, 30],\n }),\n },\n\n SPFN_AUTH_PASSWORD_RESET_CONFIRM_PATH: {\n ...envString({\n description: 'App page the emailed reset link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/password/reset/confirm; it is a page in your app, not an API route.',\n default: '/password/reset',\n required: false,\n examples: ['/password/reset', '/auth/reset', '/forgot/new-password'],\n }),\n },\n\n // ============================================================================\n // Signed sign-out-everywhere link\n // ============================================================================\n SPFN_AUTH_REVOKE_ALL_LINK_TTL_MINUTES: {\n ...envNumber({\n description: 'How long a signed sign-out-everywhere link stays valid. The same default as the other two link flows: long enough to survive a mail delay, short enough that a link left in an inbox stops working — and this one signs every device out.',\n default: 30,\n required: false,\n examples: [15, 30, 60],\n }),\n },\n\n SPFN_AUTH_REVOKE_ALL_CONFIRM_PATH: {\n ...envString({\n description: 'App page the sign-out-everywhere link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/keys/revoke-all/confirm to describe the link, then to /_auth/keys/revoke-all/consume when the owner confirms; it is a page in your app, not an API route.',\n default: '/account/revoke-all',\n required: false,\n examples: ['/account/revoke-all', '/security/sign-out-everywhere'],\n }),\n },\n\n // ============================================================================\n // Link mail delivery\n // ============================================================================\n SPFN_AUTH_LINK_MAIL_DELIVERY: {\n ...envEnum(['auto', 'inline', 'queued'] as const, {\n description: 'Who sends signup-link, password-reset and account-exists mail. \\'auto\\' (default) queues it on auth.link-mail when pg-boss is initialised and sends it on the request path when it is not; \\'queued\\' always queues and surfaces an enqueue failure; \\'inline\\' always sends on the request path, which makes how long the request took reveal whether the address has an account.',\n default: 'auto',\n required: false,\n examples: ['auto', 'inline', 'queued'],\n }),\n },\n\n // ============================================================================\n // Passkeys (WebAuthn)\n // ============================================================================\n SPFN_AUTH_PASSKEY_RP_ID: {\n ...envString({\n description: 'Domain passkeys are bound to — a registrable domain with no protocol and no port. Defaults to the host of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Set it explicitly when the app is served from several hosts that share a domain; every origin below must be that host or a subdomain of it. Changing it orphans every passkey already enrolled.',\n required: false,\n examples: ['example.com', 'app.example.com', 'localhost'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_RP_NAME: {\n ...envString({\n description: \"Name the authenticator's own prompt shows the user. Defaults to the relying party ID.\",\n required: false,\n examples: ['Acme', 'Acme Staging'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_ORIGINS: {\n ...envString({\n description: 'Comma-separated full origins allowed to run a passkey ceremony. Defaults to the origin of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Each must be https (http only for localhost) and must be the relying party ID or a subdomain of it. Checked at boot: a value that breaks either rule refuses to start, because it would otherwise surface as the browser refusing every ceremony.',\n required: false,\n examples: ['https://app.example.com', 'https://app.example.com,https://admin.example.com', 'http://localhost:3000'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_USER_VERIFICATION: {\n ...envString({\n description: \"How hard the authenticator must prove the person is present: 'preferred' or 'required'. 'discouraged' is refused at boot — a passkey is the whole credential here, so an assertion that skipped user verification would sign someone in on an unlocked device alone.\",\n default: 'preferred',\n required: false,\n examples: ['preferred', 'required'],\n }),\n },\n\n SPFN_AUTH_PASSKEY_CHALLENGE_TTL_SECONDS: {\n ...envNumber({\n description: 'How long the challenge minted by a passkey options call stays presentable. One ceremony at the authenticator, not an abandoned tab.',\n default: 300,\n required: false,\n examples: [120, 300, 600],\n }),\n },\n\n SPFN_AUTH_PASSKEY_RECENT_AUTH_MINUTES: {\n ...envNumber({\n description: 'How recently the calling device key must have been registered for enrolling or revoking a passkey to go through without the current password. Older than this and the request needs `currentPassword`; an account with no password has to sign in again.',\n default: 10,\n required: false,\n examples: [5, 10, 30],\n }),\n },\n\n // ============================================================================\n // Second factor (MFA)\n // ============================================================================\n SPFN_AUTH_MFA_ISSUER: {\n ...envString({\n description: 'Name the authenticator app files this account under, carried in the otpauth:// URI. Defaults to the passkey relying-party name, then to the host of {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. Changing it after people have enrolled only relabels the entry in their app; the codes keep working.',\n required: false,\n examples: ['Acme', 'Acme Staging'],\n }),\n },\n\n SPFN_AUTH_MFA_STEP_UP_MINUTES: {\n ...envNumber({\n description: 'How recently an enrolled account must have proved its second factor on the calling device for a sensitive change (password change, sign out everywhere, disabling MFA, passkey management) to go through. Older than this and the request is 403 STEP_UP_REQUIRED until POST /_auth/mfa/step-up succeeds. Unenrolled accounts are unaffected.',\n default: 10,\n required: false,\n examples: [5, 10, 30],\n }),\n },\n\n // ============================================================================\n // API Configuration\n // ============================================================================\n SPFN_API_URL: {\n ...envString({\n description: 'Internal API URL for server-to-server communication',\n default: 'http://localhost:8790',\n required: false,\n examples: [\n 'https://api.example.com',\n 'http://localhost:8790',\n ],\n }),\n },\n\n NEXT_PUBLIC_SPFN_API_URL: {\n ...envString({\n description: 'Public-facing API URL used for browser-facing redirects. Falls back to SPFN_API_URL if not set.',\n required: false,\n examples: [\n 'https://api.example.com',\n 'http://localhost:8790',\n ],\n }),\n },\n\n SPFN_APP_URL: {\n ...envString({\n description: 'Next.js application URL (internal). Used for server-to-server communication.',\n default: 'http://localhost:3000',\n required: false,\n examples: [\n 'https://app.example.com',\n 'http://localhost:3000',\n ],\n }),\n },\n\n NEXT_PUBLIC_SPFN_APP_URL: {\n ...envString({\n description: 'Public-facing Next.js app URL for browser redirects (e.g. OAuth redirect). Falls back to SPFN_APP_URL if not set.',\n required: false,\n examples: [\n 'https://app.example.com',\n 'http://localhost:3000',\n ],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Google\n // ============================================================================\n SPFN_AUTH_GOOGLE_CLIENT_ID: {\n ...envString({\n description: 'Google OAuth 2.0 Client ID. When set, Google OAuth routes are automatically enabled.',\n required: false,\n examples: ['123456789-abc123.apps.googleusercontent.com'],\n }),\n },\n\n SPFN_AUTH_GOOGLE_CLIENT_SECRET: {\n ...envString({\n description: 'Google OAuth 2.0 Client Secret',\n required: false,\n sensitive: true,\n examples: ['GOCSPX-abcdefghijklmnop'],\n }),\n },\n\n SPFN_AUTH_GOOGLE_SCOPES: {\n ...envString({\n description: 'Comma-separated Google OAuth scopes. Defaults to \"email,profile\" if not set.',\n required: false,\n examples: [\n 'email,profile',\n 'email,profile,https://www.googleapis.com/auth/gmail.readonly',\n 'email,profile,https://www.googleapis.com/auth/calendar.readonly',\n ],\n }),\n },\n\n SPFN_AUTH_GOOGLE_REDIRECT_URI: {\n ...envString({\n description: 'Google OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/google/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: [\n 'https://app.example.com/_auth/oauth/google/callback',\n 'http://localhost:3000/_auth/oauth/google/callback',\n ],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Kakao\n // ============================================================================\n SPFN_AUTH_KAKAO_CLIENT_ID: {\n ...envString({\n description: 'Kakao Login REST API key. Used as the OAuth client_id.',\n required: false,\n examples: ['your-kakao-rest-api-key'],\n }),\n },\n\n SPFN_AUTH_KAKAO_CLIENT_SECRET: {\n ...envString({\n description: 'Kakao Login client secret. Required when the Kakao client-secret feature is enabled.',\n required: false,\n sensitive: true,\n examples: ['your-kakao-client-secret'],\n }),\n },\n\n SPFN_AUTH_KAKAO_ADMIN_KEY: {\n ...envString({\n description: 'Kakao app admin key. Required to verify the User Unlinked webhook (Authorization: KakaoAK header).',\n required: false,\n sensitive: true,\n examples: ['your-kakao-admin-key'],\n }),\n },\n\n SPFN_AUTH_KAKAO_SCOPES: {\n ...envString({\n description: 'Comma-separated Kakao consent scopes. Defaults to account_email.',\n required: false,\n examples: ['account_email'],\n }),\n },\n\n SPFN_AUTH_KAKAO_REDIRECT_URI: {\n ...envString({\n description: 'Kakao OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/kakao/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/kakao/callback'],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - Naver\n // ============================================================================\n SPFN_AUTH_NAVER_CLIENT_ID: {\n ...envString({\n description: 'Naver Login OAuth client ID.',\n required: false,\n examples: ['your-naver-client-id'],\n }),\n },\n\n SPFN_AUTH_NAVER_CLIENT_SECRET: {\n ...envString({\n description: 'Naver Login OAuth client secret.',\n required: false,\n sensitive: true,\n examples: ['your-naver-client-secret'],\n }),\n },\n\n SPFN_AUTH_NAVER_REDIRECT_URI: {\n ...envString({\n description: 'Naver OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/naver/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/naver/callback'],\n }),\n },\n\n // ============================================================================\n // OAuth Configuration - GitHub\n // ============================================================================\n SPFN_AUTH_GITHUB_CLIENT_ID: {\n ...envString({\n description: 'GitHub OAuth app client ID. When set, GitHub OAuth routes are automatically enabled.',\n required: false,\n examples: ['Iv1.abc123def456'],\n }),\n },\n\n SPFN_AUTH_GITHUB_CLIENT_SECRET: {\n ...envString({\n description: 'GitHub OAuth app client secret.',\n required: false,\n sensitive: true,\n examples: ['your-github-client-secret'],\n }),\n },\n\n SPFN_AUTH_GITHUB_SCOPES: {\n ...envString({\n description: 'Comma-separated GitHub OAuth scopes. Defaults to \"read:user,user:email\".',\n required: false,\n examples: ['read:user,user:email'],\n }),\n },\n\n SPFN_AUTH_GITHUB_REDIRECT_URI: {\n ...envString({\n description: 'GitHub OAuth callback URL. Defaults to {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}/_auth/oauth/github/callback. The override must stay on the web app origin at this exact path — the CSRF cookie for the callback is host-only and the app rewrites /_auth/:path* to the API, so a callback that lands anywhere else is refused for CSRF. Checked at boot: a value off the web app origin or off the callback path refuses to start. The one case for an override elsewhere is the direct POST /_auth/oauth/start flow on a split deployment (no Next.js interceptor, so its CSRF cookie is on the API host), which also needs SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK=off.',\n required: false,\n examples: ['https://app.example.com/_auth/oauth/github/callback'],\n }),\n },\n\n SPFN_AUTH_OAUTH_CALLBACK_ORIGIN_CHECK: {\n ...envString({\n description: 'Boot-time check of the four SPFN_AUTH_<PROVIDER>_REDIRECT_URI overrides: each one that is set must sit on the web app origin ({NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}) at /_auth/oauth/<provider>/callback, because the callback CSRF cookie is host-only. \"off\" is the only value that disables the check — unset, \"on\" and anything else all run it.',\n required: false,\n examples: ['off'],\n }),\n },\n\n // ============================================================================\n // Native Social Login (mobile/web id_token verification)\n //\n // 네이티브 SDK가 받은 id_token을 서버가 JWKS로 검증하는 경로 전용 설정.\n // authorization code 교환을 하지 않으므로 client secret이 필요 없다.\n // audience(aud)로 허용할 client id 목록만 지정한다.\n // ============================================================================\n SPFN_AUTH_GOOGLE_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Google client IDs accepted as id_token audience for native sign-in (iOS, Android, web). When set, Google native sign-in is enabled. SPFN_AUTH_GOOGLE_CLIENT_ID is also accepted automatically.',\n required: false,\n examples: [\n '123-ios.apps.googleusercontent.com,123-android.apps.googleusercontent.com',\n ],\n }),\n },\n\n SPFN_AUTH_APPLE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Apple client IDs accepted as id_token audience for native sign-in (iOS bundle ID, web/Android Services ID). When set, Apple native sign-in is enabled.',\n required: false,\n examples: [\n 'com.example.app,com.example.app.service',\n ],\n }),\n },\n\n SPFN_AUTH_KAKAO_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Kakao app keys accepted as id_token audience for native sign-in (native app key). SPFN_AUTH_KAKAO_CLIENT_ID (REST API key) is also accepted automatically, so native sign-in is available when either variable is set. Requires OpenID Connect to be enabled in the Kakao developer console.',\n required: false,\n examples: [\n 'your-kakao-native-app-key',\n ],\n }),\n },\n\n SPFN_AUTH_NAVER_NATIVE_CLIENT_IDS: {\n ...envString({\n description: 'Comma-separated Naver client IDs accepted as id_token audience for native sign-in. SPFN_AUTH_NAVER_CLIENT_ID is also accepted automatically, and one Naver application has a single client ID covering web and app environments — set this only when the app uses a separate application.',\n required: false,\n examples: [\n 'your-naver-app-client-id',\n ],\n }),\n },\n\n SPFN_AUTH_OAUTH_SUCCESS_URL: {\n ...envString({\n description: 'OAuth callback page URL. This page should use OAuthCallback component to finalize session.',\n required: false,\n default: '/auth/callback',\n examples: [\n '/auth/callback',\n 'https://app.example.com/auth/callback',\n ],\n }),\n },\n\n SPFN_AUTH_OAUTH_ERROR_URL: {\n ...envString({\n description: 'URL to redirect after OAuth error. Use {error} placeholder for error message.',\n required: false,\n default: '/auth/error?error={error}',\n examples: [\n 'https://app.example.com/auth/error?error={error}',\n 'http://localhost:3000/auth/error?error={error}',\n ],\n }),\n },\n});\n"],"mappings":"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","names":[]}
package/dist/errors.d.ts CHANGED
@@ -686,6 +686,91 @@ declare class OAuth2GrantNotFoundError extends NotFoundError {
686
686
  details?: Record<string, any>;
687
687
  });
688
688
  }
689
+ /**
690
+ * MFA Already Enrolled Error (409)
691
+ *
692
+ * Thrown when `totp/enroll` is called on an account whose TOTP is already
693
+ * confirmed. Replacing a working second factor is `disable` followed by a fresh
694
+ * enrolment, both step-up gated — an enrol that quietly overwrote a confirmed
695
+ * secret would be a way to swap someone's authenticator for your own.
696
+ */
697
+ declare class MfaAlreadyEnrolledError extends ConflictError {
698
+ constructor(data?: {
699
+ message?: string;
700
+ details?: Record<string, any>;
701
+ });
702
+ }
703
+ /**
704
+ * MFA Not Enrolled Error (400)
705
+ *
706
+ * Thrown when a call needs an enrolment that is not there: `totp/confirm` with
707
+ * no pending secret — including the row its own five failed attempts deleted —
708
+ * or `recovery/regenerate` on an account with no second factor. `disable` is
709
+ * deliberately not one of them; it answers 204 either way, because "make sure
710
+ * MFA is off" has already succeeded.
711
+ */
712
+ declare class MfaNotEnrolledError extends ValidationError {
713
+ constructor(data?: {
714
+ message?: string;
715
+ details?: Record<string, any>;
716
+ });
717
+ }
718
+ /**
719
+ * MFA Verification Failed Error (401)
720
+ *
721
+ * Thrown when a submitted code does not verify: a wrong or stale TOTP, a code
722
+ * from a step already spent, a recovery code that is used, from an older
723
+ * generation or another account's, or an assertion from a passkey the owner
724
+ * never marked as a second factor.
725
+ *
726
+ * One body for all of them. Which one applies describes state the caller is
727
+ * guessing at, and the remedy — look at the authenticator again — is the same.
728
+ */
729
+ declare class MfaVerificationFailedError extends UnauthorizedError {
730
+ constructor(data?: {
731
+ message?: string;
732
+ details?: Record<string, any>;
733
+ });
734
+ }
735
+ /**
736
+ * Step Up Required Error (403)
737
+ *
738
+ * Thrown when an enrolled account asks for a sensitive change from a device
739
+ * whose second factor was last verified longer ago than the step-up window. A
740
+ * stolen session alone therefore cannot change a password, sign every device
741
+ * out, or take the second factor off.
742
+ *
743
+ * 403 and not 401, on the same reasoning as `RecentAuthenticationRequiredError`,
744
+ * which it sits beside: a 401 on an authenticated route is what a web client
745
+ * reads as "the session is gone", so it would sign the user out instead of
746
+ * asking for a code. Clients branch on `code` to send the user to
747
+ * `POST /_auth/mfa/step-up` and retry.
748
+ */
749
+ declare class StepUpRequiredError extends ForbiddenError {
750
+ readonly code = "STEP_UP_REQUIRED";
751
+ constructor(data?: {
752
+ message?: string;
753
+ details?: Record<string, any>;
754
+ });
755
+ }
756
+ /**
757
+ * MFA Config Error (500)
758
+ *
759
+ * Thrown when the at-rest keyring cannot serve a second factor:
760
+ * `SPFN_AUTH_TOKEN_ENCRYPTION_KEYS` is unset, or a stored secret names a key id
761
+ * that has been dropped from it.
762
+ *
763
+ * Deliberately not the 401 a wrong code gets. The two have nothing in common: a
764
+ * wrong code is one person looking at the wrong line of their authenticator,
765
+ * and a broken keyring is every enrolled user locked out at once by a deploy.
766
+ * An operator has to be able to tell them apart from the response alone.
767
+ */
768
+ declare class MfaConfigError extends HttpError {
769
+ constructor(data: {
770
+ message: string;
771
+ details?: Record<string, any>;
772
+ });
773
+ }
689
774
 
690
775
  type authErrors_AccountAlreadyExistsError = AccountAlreadyExistsError;
691
776
  declare const authErrors_AccountAlreadyExistsError: typeof AccountAlreadyExistsError;
@@ -737,6 +822,14 @@ type authErrors_KeyNotFoundError = KeyNotFoundError;
737
822
  declare const authErrors_KeyNotFoundError: typeof KeyNotFoundError;
738
823
  type authErrors_LastRecoveryCredentialError = LastRecoveryCredentialError;
739
824
  declare const authErrors_LastRecoveryCredentialError: typeof LastRecoveryCredentialError;
825
+ type authErrors_MfaAlreadyEnrolledError = MfaAlreadyEnrolledError;
826
+ declare const authErrors_MfaAlreadyEnrolledError: typeof MfaAlreadyEnrolledError;
827
+ type authErrors_MfaConfigError = MfaConfigError;
828
+ declare const authErrors_MfaConfigError: typeof MfaConfigError;
829
+ type authErrors_MfaNotEnrolledError = MfaNotEnrolledError;
830
+ declare const authErrors_MfaNotEnrolledError: typeof MfaNotEnrolledError;
831
+ type authErrors_MfaVerificationFailedError = MfaVerificationFailedError;
832
+ declare const authErrors_MfaVerificationFailedError: typeof MfaVerificationFailedError;
740
833
  type authErrors_NativeSignInUnsupportedError = NativeSignInUnsupportedError;
741
834
  declare const authErrors_NativeSignInUnsupportedError: typeof NativeSignInUnsupportedError;
742
835
  type authErrors_NonceKeyBindingError = NonceKeyBindingError;
@@ -771,6 +864,8 @@ type authErrors_ReservedUsernameError = ReservedUsernameError;
771
864
  declare const authErrors_ReservedUsernameError: typeof ReservedUsernameError;
772
865
  type authErrors_RevokeAllLinkError = RevokeAllLinkError;
773
866
  declare const authErrors_RevokeAllLinkError: typeof RevokeAllLinkError;
867
+ type authErrors_StepUpRequiredError = StepUpRequiredError;
868
+ declare const authErrors_StepUpRequiredError: typeof StepUpRequiredError;
774
869
  type authErrors_TokenExpiredError = TokenExpiredError;
775
870
  declare const authErrors_TokenExpiredError: typeof TokenExpiredError;
776
871
  type authErrors_UnverifiedEmailLinkError = UnverifiedEmailLinkError;
@@ -782,7 +877,7 @@ declare const authErrors_VerificationTokenPurposeMismatchError: typeof Verificat
782
877
  type authErrors_VerificationTokenTargetMismatchError = VerificationTokenTargetMismatchError;
783
878
  declare const authErrors_VerificationTokenTargetMismatchError: typeof VerificationTokenTargetMismatchError;
784
879
  declare namespace authErrors {
785
- export { authErrors_AccountAlreadyExistsError as AccountAlreadyExistsError, authErrors_AccountDisabledError as AccountDisabledError, authErrors_AccountPendingDeletionError as AccountPendingDeletionError, authErrors_DeletionAlreadyRequestedError as DeletionAlreadyRequestedError, authErrors_DeletionNotRequestedError as DeletionNotRequestedError, authErrors_DeviceAuthAlreadyHandledError as DeviceAuthAlreadyHandledError, authErrors_DeviceAuthDeniedError as DeviceAuthDeniedError, authErrors_DeviceAuthExpiredError as DeviceAuthExpiredError, authErrors_DeviceAuthNotFoundError as DeviceAuthNotFoundError, authErrors_ImmediateDeletionNotAllowedError as ImmediateDeletionNotAllowedError, authErrors_InsufficientPermissionsError as InsufficientPermissionsError, authErrors_InsufficientRoleError as InsufficientRoleError, authErrors_InvalidCredentialsError as InvalidCredentialsError, authErrors_InvalidKeyFingerprintError as InvalidKeyFingerprintError, authErrors_InvalidSignupLinkError as InvalidSignupLinkError, authErrors_InvalidSignupSetupSessionError as InvalidSignupSetupSessionError, authErrors_InvalidSocialTokenError as InvalidSocialTokenError, authErrors_InvalidTokenError as InvalidTokenError, authErrors_InvalidVerificationCodeError as InvalidVerificationCodeError, authErrors_InvalidVerificationTokenError as InvalidVerificationTokenError, authErrors_KeyAlgorithmMismatchError as KeyAlgorithmMismatchError, authErrors_KeyExpiredError as KeyExpiredError, authErrors_KeyIdAlreadyRegisteredError as KeyIdAlreadyRegisteredError, authErrors_KeyNotFoundError as KeyNotFoundError, authErrors_LastRecoveryCredentialError as LastRecoveryCredentialError, authErrors_NativeSignInUnsupportedError as NativeSignInUnsupportedError, authErrors_NonceKeyBindingError as NonceKeyBindingError, authErrors_OAuth2AuthorizeRedirectError as OAuth2AuthorizeRedirectError, authErrors_OAuth2GrantNotFoundError as OAuth2GrantNotFoundError, authErrors_OAuth2RedirectUriMismatchError as OAuth2RedirectUriMismatchError, authErrors_OAuth2UnknownClientError as OAuth2UnknownClientError, authErrors_PasskeyAlreadyRegisteredError as PasskeyAlreadyRegisteredError, authErrors_PasskeyChallengeError as PasskeyChallengeError, authErrors_PasskeyConfigError as PasskeyConfigError, authErrors_PasskeyNotFoundError as PasskeyNotFoundError, authErrors_PasskeyVerificationError as PasskeyVerificationError, authErrors_PasswordResetLinkError as PasswordResetLinkError, authErrors_PasswordResetSessionError as PasswordResetSessionError, authErrors_RecentAuthenticationRequiredError as RecentAuthenticationRequiredError, authErrors_RegistrationRejectedError as RegistrationRejectedError, authErrors_ReservedUsernameError as ReservedUsernameError, authErrors_RevokeAllLinkError as RevokeAllLinkError, authErrors_TokenExpiredError as TokenExpiredError, authErrors_UnverifiedEmailLinkError as UnverifiedEmailLinkError, authErrors_UsernameAlreadyTakenError as UsernameAlreadyTakenError, authErrors_VerificationTokenPurposeMismatchError as VerificationTokenPurposeMismatchError, authErrors_VerificationTokenTargetMismatchError as VerificationTokenTargetMismatchError };
880
+ export { authErrors_AccountAlreadyExistsError as AccountAlreadyExistsError, authErrors_AccountDisabledError as AccountDisabledError, authErrors_AccountPendingDeletionError as AccountPendingDeletionError, authErrors_DeletionAlreadyRequestedError as DeletionAlreadyRequestedError, authErrors_DeletionNotRequestedError as DeletionNotRequestedError, authErrors_DeviceAuthAlreadyHandledError as DeviceAuthAlreadyHandledError, authErrors_DeviceAuthDeniedError as DeviceAuthDeniedError, authErrors_DeviceAuthExpiredError as DeviceAuthExpiredError, authErrors_DeviceAuthNotFoundError as DeviceAuthNotFoundError, authErrors_ImmediateDeletionNotAllowedError as ImmediateDeletionNotAllowedError, authErrors_InsufficientPermissionsError as InsufficientPermissionsError, authErrors_InsufficientRoleError as InsufficientRoleError, authErrors_InvalidCredentialsError as InvalidCredentialsError, authErrors_InvalidKeyFingerprintError as InvalidKeyFingerprintError, authErrors_InvalidSignupLinkError as InvalidSignupLinkError, authErrors_InvalidSignupSetupSessionError as InvalidSignupSetupSessionError, authErrors_InvalidSocialTokenError as InvalidSocialTokenError, authErrors_InvalidTokenError as InvalidTokenError, authErrors_InvalidVerificationCodeError as InvalidVerificationCodeError, authErrors_InvalidVerificationTokenError as InvalidVerificationTokenError, authErrors_KeyAlgorithmMismatchError as KeyAlgorithmMismatchError, authErrors_KeyExpiredError as KeyExpiredError, authErrors_KeyIdAlreadyRegisteredError as KeyIdAlreadyRegisteredError, authErrors_KeyNotFoundError as KeyNotFoundError, authErrors_LastRecoveryCredentialError as LastRecoveryCredentialError, authErrors_MfaAlreadyEnrolledError as MfaAlreadyEnrolledError, authErrors_MfaConfigError as MfaConfigError, authErrors_MfaNotEnrolledError as MfaNotEnrolledError, authErrors_MfaVerificationFailedError as MfaVerificationFailedError, authErrors_NativeSignInUnsupportedError as NativeSignInUnsupportedError, authErrors_NonceKeyBindingError as NonceKeyBindingError, authErrors_OAuth2AuthorizeRedirectError as OAuth2AuthorizeRedirectError, authErrors_OAuth2GrantNotFoundError as OAuth2GrantNotFoundError, authErrors_OAuth2RedirectUriMismatchError as OAuth2RedirectUriMismatchError, authErrors_OAuth2UnknownClientError as OAuth2UnknownClientError, authErrors_PasskeyAlreadyRegisteredError as PasskeyAlreadyRegisteredError, authErrors_PasskeyChallengeError as PasskeyChallengeError, authErrors_PasskeyConfigError as PasskeyConfigError, authErrors_PasskeyNotFoundError as PasskeyNotFoundError, authErrors_PasskeyVerificationError as PasskeyVerificationError, authErrors_PasswordResetLinkError as PasswordResetLinkError, authErrors_PasswordResetSessionError as PasswordResetSessionError, authErrors_RecentAuthenticationRequiredError as RecentAuthenticationRequiredError, authErrors_RegistrationRejectedError as RegistrationRejectedError, authErrors_ReservedUsernameError as ReservedUsernameError, authErrors_RevokeAllLinkError as RevokeAllLinkError, authErrors_StepUpRequiredError as StepUpRequiredError, authErrors_TokenExpiredError as TokenExpiredError, authErrors_UnverifiedEmailLinkError as UnverifiedEmailLinkError, authErrors_UsernameAlreadyTakenError as UsernameAlreadyTakenError, authErrors_VerificationTokenPurposeMismatchError as VerificationTokenPurposeMismatchError, authErrors_VerificationTokenTargetMismatchError as VerificationTokenTargetMismatchError };
786
881
  }
787
882
 
788
883
  /**
@@ -791,4 +886,4 @@ declare namespace authErrors {
791
886
 
792
887
  declare const authErrorRegistry: ErrorRegistry;
793
888
 
794
- export { AccountAlreadyExistsError, AccountDisabledError, AccountPendingDeletionError, authErrors as AuthError, DeletionAlreadyRequestedError, DeletionNotRequestedError, DeviceAuthAlreadyHandledError, DeviceAuthDeniedError, DeviceAuthExpiredError, DeviceAuthNotFoundError, ImmediateDeletionNotAllowedError, InsufficientPermissionsError, InsufficientRoleError, InvalidCredentialsError, InvalidKeyFingerprintError, InvalidSignupLinkError, InvalidSignupSetupSessionError, InvalidSocialTokenError, InvalidTokenError, InvalidVerificationCodeError, InvalidVerificationTokenError, KeyAlgorithmMismatchError, KeyExpiredError, KeyIdAlreadyRegisteredError, KeyNotFoundError, LastRecoveryCredentialError, NativeSignInUnsupportedError, NonceKeyBindingError, OAuth2AuthorizeRedirectError, OAuth2GrantNotFoundError, OAuth2RedirectUriMismatchError, OAuth2UnknownClientError, PasskeyAlreadyRegisteredError, PasskeyChallengeError, PasskeyConfigError, PasskeyNotFoundError, PasskeyVerificationError, PasswordResetLinkError, PasswordResetSessionError, RecentAuthenticationRequiredError, RegistrationRejectedError, ReservedUsernameError, RevokeAllLinkError, TokenExpiredError, UnverifiedEmailLinkError, UsernameAlreadyTakenError, VerificationTokenPurposeMismatchError, VerificationTokenTargetMismatchError, authErrorRegistry };
889
+ export { AccountAlreadyExistsError, AccountDisabledError, AccountPendingDeletionError, authErrors as AuthError, DeletionAlreadyRequestedError, DeletionNotRequestedError, DeviceAuthAlreadyHandledError, DeviceAuthDeniedError, DeviceAuthExpiredError, DeviceAuthNotFoundError, ImmediateDeletionNotAllowedError, InsufficientPermissionsError, InsufficientRoleError, InvalidCredentialsError, InvalidKeyFingerprintError, InvalidSignupLinkError, InvalidSignupSetupSessionError, InvalidSocialTokenError, InvalidTokenError, InvalidVerificationCodeError, InvalidVerificationTokenError, KeyAlgorithmMismatchError, KeyExpiredError, KeyIdAlreadyRegisteredError, KeyNotFoundError, LastRecoveryCredentialError, MfaAlreadyEnrolledError, MfaConfigError, MfaNotEnrolledError, MfaVerificationFailedError, NativeSignInUnsupportedError, NonceKeyBindingError, OAuth2AuthorizeRedirectError, OAuth2GrantNotFoundError, OAuth2RedirectUriMismatchError, OAuth2UnknownClientError, PasskeyAlreadyRegisteredError, PasskeyChallengeError, PasskeyConfigError, PasskeyNotFoundError, PasskeyVerificationError, PasswordResetLinkError, PasswordResetSessionError, RecentAuthenticationRequiredError, RegistrationRejectedError, ReservedUsernameError, RevokeAllLinkError, StepUpRequiredError, TokenExpiredError, UnverifiedEmailLinkError, UsernameAlreadyTakenError, VerificationTokenPurposeMismatchError, VerificationTokenTargetMismatchError, authErrorRegistry };