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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +328 -7
  2. package/dist/client-proof.d.ts +10 -1
  3. package/dist/client-proof.js +136 -11
  4. package/dist/client-proof.js.map +1 -1
  5. package/dist/client.d.ts +101 -1
  6. package/dist/client.js +65 -0
  7. package/dist/client.js.map +1 -1
  8. package/dist/config.d.ts +122 -0
  9. package/dist/config.js +53 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/crypto.d.ts +1 -1
  12. package/dist/errors.d.ts +159 -3
  13. package/dist/errors.js +95 -2
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.d.ts +52 -12
  16. package/dist/index.js +104 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/{machine-principals-CaEFq61K.d.ts → machine-principals-CdEgxOB1.d.ts} +2049 -771
  19. package/dist/nextjs/api.js +329 -37
  20. package/dist/nextjs/api.js.map +1 -1
  21. package/dist/nextjs/server.d.ts +59 -24
  22. package/dist/nextjs/server.js +105 -11
  23. package/dist/nextjs/server.js.map +1 -1
  24. package/dist/server.d.ts +415 -332
  25. package/dist/server.js +2933 -1545
  26. package/dist/server.js.map +1 -1
  27. package/dist/{session-Dfwu5g2W.d.ts → session-BbhAGZtA.d.ts} +57 -1
  28. package/dist/{types-DYyhze28.d.ts → types-CTdoTOxM.d.ts} +24 -1
  29. package/migrations/20260918184037_happy_mordo/migration.sql +4 -0
  30. package/migrations/20260918184037_happy_mordo/snapshot.json +6000 -0
  31. package/migrations/20260918184152_dear_rictor/migration.sql +3 -0
  32. package/migrations/20260918184152_dear_rictor/snapshot.json +6039 -0
  33. package/migrations/20260919023107_even_mikhail_rasputin/migration.sql +20 -0
  34. package/migrations/20260919023107_even_mikhail_rasputin/snapshot.json +6300 -0
  35. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
+ import { S as SessionBindingType, K as KeyAlgorithmType, h as KeyPlatformType, l as SocialProvider } from './types-CTdoTOxM.js';
1
2
  import * as _simplewebauthn_server from '@simplewebauthn/server';
2
3
  import { RegistrationResponseJSON, AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/server';
3
4
  import * as _spfn_core_route from '@spfn/core/route';
4
- import { K as KeyAlgorithmType, h as KeyPlatformType, j as SocialProvider } from './types-DYyhze28.js';
5
5
  import * as _sinclair_typebox from '@sinclair/typebox';
6
6
  import { Static } from '@sinclair/typebox';
7
7
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
@@ -90,6 +90,66 @@ interface UserProfile {
90
90
  profile: ProfileInfo | null;
91
91
  }
92
92
 
93
+ /**
94
+ * @spfn/auth - What a sign-in answers with
95
+ *
96
+ * One module for the shape every path that starts a session returns, and for the
97
+ * helper that fills in its binding half. It is here rather than in
98
+ * `auth.service.ts` because three of its readers are upstream of that file —
99
+ * `key.service` decides the second-factor step-up, `mfa.service` resolves it —
100
+ * and a type living with one of its producers would put those services in a
101
+ * cycle with each other.
102
+ */
103
+
104
+ /**
105
+ * The second-factor challenge a stepped-up sign-in hands back (#95).
106
+ *
107
+ * `secret` is the 32 random bytes the challenge was minted from, and it is the
108
+ * only form of it that ever leaves the server — the row is addressed by its
109
+ * hash. It authorizes exactly one thing, `POST /_auth/mfa/verify` for this one
110
+ * registration, and it is not a bearer credential for anything else.
111
+ */
112
+ interface MfaChallengeHandle {
113
+ secret: string;
114
+ /** Epoch milliseconds the challenge stops verifying at. */
115
+ expiresAtMillis: number;
116
+ }
117
+ /**
118
+ * What a sign-in answers with, on every path that starts a session.
119
+ *
120
+ * **One type with a required discriminant, not a union.** A sign-in on an
121
+ * account with a second factor and a device it has never seen answers 202 with
122
+ * `mfaRequired: true` and a challenge instead of a session (#95), and the two
123
+ * answers have to be one declared type: `authApi.login` infers its result from
124
+ * this declaration, so a union would make every existing `result.userId` in
125
+ * every consuming app stop compiling, and the mobile contract's grammar has no
126
+ * union type either — `DeviceAuthPollResponse` was flattened the same way and
127
+ * for the same reason. Narrow on `mfaRequired` before reading `userId`.
128
+ *
129
+ * `sessionBinding` and `keyExpiresAtMillis` are the carrier #97 needed. The
130
+ * Next.js proxy generated the device key and sealed the cookie, but only the
131
+ * backend knows whether the account asked for a bound session and when the key
132
+ * it just registered runs out — so the sign-in says it here and the interceptor
133
+ * copies both into `SessionData`. A response without them seals an unbound
134
+ * session, which is what every account that did not opt in gets and what every
135
+ * path predating that change keeps getting.
136
+ */
137
+ interface LoginResult {
138
+ /** true means no session was started: verify the challenge below first. */
139
+ mfaRequired: boolean;
140
+ /** Present exactly when `mfaRequired` is true. */
141
+ challenge?: MfaChallengeHandle;
142
+ userId?: string;
143
+ publicId?: string;
144
+ email?: string;
145
+ phone?: string;
146
+ passwordChangeRequired?: boolean;
147
+ /** `'passkey'` when the key registered by this sign-in is bound. Absent otherwise. */
148
+ sessionBinding?: SessionBindingType;
149
+ /** Epoch milliseconds that key expires at. Only sent alongside `sessionBinding`. */
150
+ keyExpiresAtMillis?: number;
151
+ }
152
+
93
153
  /**
94
154
  * @spfn/auth - Auth Service
95
155
  *
@@ -112,6 +172,12 @@ interface RegisterParams {
112
172
  ip?: string;
113
173
  /** `user-agent` of the request, already truncated at the route. */
114
174
  userAgent?: string;
175
+ /**
176
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
177
+ * helper. Carried for shape rather than effect: a brand-new account is on the
178
+ * default `session_binding: 'none'`, so its first key is never bound.
179
+ */
180
+ webProxy?: boolean;
115
181
  }
116
182
  interface RegisterResult {
117
183
  userId: string;
@@ -134,14 +200,10 @@ interface LoginParams {
134
200
  ip?: string;
135
201
  /** `user-agent` of the request, already truncated at the route. */
136
202
  userAgent?: string;
203
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
204
+ webProxy?: boolean;
137
205
  }
138
- interface LoginResult {
139
- userId: string;
140
- publicId: string;
141
- email?: string;
142
- phone?: string;
143
- passwordChangeRequired: boolean;
144
- }
206
+
145
207
  interface LogoutParams {
146
208
  userId: number;
147
209
  keyId: string;
@@ -254,11 +316,14 @@ declare const DeviceAuthPollResponseSchema: _sinclair_typebox.TUnion<[_sinclair_
254
316
  intervalMillis: _sinclair_typebox.TInteger;
255
317
  }>, _sinclair_typebox.TObject<{
256
318
  status: _sinclair_typebox.TLiteral<"approved">;
319
+ mfaRequired: _sinclair_typebox.TBoolean;
257
320
  userId: _sinclair_typebox.TString;
258
321
  publicId: _sinclair_typebox.TString;
259
322
  email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
260
323
  phone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
261
324
  passwordChangeRequired: _sinclair_typebox.TBoolean;
325
+ sessionBinding: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"none" | "passkey">[]>>;
326
+ keyExpiresAtMillis: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
262
327
  }>]>;
263
328
  declare const PasswordSchema: _sinclair_typebox.TString;
264
329
  declare const TargetTypeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
@@ -476,6 +541,8 @@ interface CompletePasswordResetParams {
476
541
  ip?: string;
477
542
  /** `user-agent` of the request, already truncated at the route. */
478
543
  userAgent?: string;
544
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
545
+ webProxy?: boolean;
479
546
  }
480
547
  /**
481
548
  * Step 3 — set the new password, which is what completes the reset.
@@ -488,8 +555,15 @@ interface CompletePasswordResetParams {
488
555
  * A refusal that is the user's to fix — a password the policy rejects, a body
489
556
  * with no device key — leaves the setup session usable, so the fix is retyping
490
557
  * the password rather than asking for a fresh email.
558
+ *
559
+ * The answer carries the binding fields, like every other path that starts a
560
+ * session. A reset on an account that opted in registers a bound key — 24 hours,
561
+ * renewable only by a passkey — and this route is on the sealing interceptor's
562
+ * list, so a body that did not say so would have the proxy seal a cookie
563
+ * believing the session unbound: no user-agent check for the life of that key,
564
+ * and a sign-out a day later instead of the renewal prompt.
491
565
  */
492
- declare function completePasswordResetService(params: CompletePasswordResetParams): Promise<RegisterResult>;
566
+ declare function completePasswordResetService(params: CompletePasswordResetParams): Promise<LoginResult>;
493
567
 
494
568
  /**
495
569
  * @spfn/auth - Device Auth Service
@@ -564,16 +638,35 @@ interface PollDeviceAuthParams {
564
638
  ip?: string;
565
639
  /** `user-agent` of the request, already truncated at the route. */
566
640
  userAgent?: string;
641
+ /**
642
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
643
+ * helper. A waiting device polls the backend itself, so this is false there
644
+ * and the key it collects is unbound — which is what a device with no browser
645
+ * to run a WebAuthn ceremony in needs.
646
+ */
647
+ webProxy?: boolean;
567
648
  }
568
649
  /** Nobody has answered yet. Not an error — the waiting device waits. */
569
650
  interface DeviceAuthPendingResult {
570
651
  status: 'pending';
571
652
  intervalMillis: number;
572
653
  }
573
- /** Approved and spent: the key is registered and this is the login it produced. */
654
+ /**
655
+ * Approved and spent: the key is registered and this is the login it produced.
656
+ *
657
+ * `LoginResult`'s login fields are optional because a sign-in may answer a
658
+ * second-factor challenge instead of a session (#95). This branch never can —
659
+ * the owner already said yes on a device that is signed in, which is itself the
660
+ * second factor — so the three it always carries are narrowed back to required
661
+ * and a consumer of the approved branch reads exactly what it always read.
662
+ */
574
663
  type DeviceAuthApprovedResult = {
575
664
  status: 'approved';
576
- } & LoginResult;
665
+ } & LoginResult & {
666
+ userId: string;
667
+ publicId: string;
668
+ passwordChangeRequired: boolean;
669
+ };
577
670
  type PollDeviceAuthResult = DeviceAuthPendingResult | DeviceAuthApprovedResult;
578
671
  /**
579
672
  * Park a new device's key and hand back the codes it needs.
@@ -1033,6 +1126,8 @@ interface FinishPasskeyLoginParams {
1033
1126
  ip?: string;
1034
1127
  /** `user-agent` of the request, already truncated at the route. */
1035
1128
  userAgent?: string;
1129
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
1130
+ webProxy?: boolean;
1036
1131
  }
1037
1132
  /**
1038
1133
  * Step 2 of sign-in — verify the assertion, then sign in exactly as a password
@@ -1092,394 +1187,101 @@ declare function revokePasskeyService(params: RevokePasskeyParams): Promise<{
1092
1187
  }>;
1093
1188
 
1094
1189
  /**
1095
- * @spfn/auth - Second-Factor Verification Entity
1096
- *
1097
- * When each device last proved the account's second factor. One row per device
1098
- * key, which is what makes the step-up window mean "this device, recently"
1099
- * rather than "somebody, somewhere, recently".
1190
+ * Auth provider type
1100
1191
  *
1101
- * `key_id` is the primary key and a foreign key onto `user_public_keys.key_id`,
1102
- * so the row dies with the device it belongs to — a revoked key cannot leave a
1103
- * verification behind for the next key that reuses its id, and deleting an
1104
- * account takes these with it. A rotation is the one moment the row moves
1105
- * rather than dying: rotating is already proof of the same device, so
1106
- * `rotateKeyService` and the login rotation carry the verification across, or
1107
- * the window would silently expire every time the proxy rotated a key.
1192
+ * 직접 인증(email/phone) + 등록 가능한 모든 소셜 provider(SOCIAL_PROVIDERS).
1108
1193
  */
1194
+ declare const AuthProviderSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">, ..._sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]]>;
1195
+ /** The doors `auth.login` can name, as a type. */
1196
+ type AuthLoginProvider = Static<typeof AuthLoginProviderSchema>;
1109
1197
  /**
1110
- * Which credential satisfied the step-up.
1198
+ * Login provider type
1111
1199
  *
1112
- * Recorded for the owner-facing audit an app may build on it, and for support:
1113
- * "recovery" is the one that should be rare, and an account stepping up with
1114
- * recovery codes repeatedly has lost its authenticator.
1200
+ * AuthProviderSchema + `'device'` and `'passkey'`.
1201
+ *
1202
+ * `'device'` is how a device-code login names itself: the account was proven on
1203
+ * another device that was already signed in, so no credential was presented here
1204
+ * and none of the values above describes it. `'passkey'` is a WebAuthn assertion
1205
+ * — a credential of the account, but not one of the sign-up channels.
1206
+ *
1207
+ * A separate union rather than a widened AuthProviderSchema. Neither is a way to
1208
+ * register — a device-code request can only ever be approved by an existing
1209
+ * account, and a passkey has to be enrolled from a session that already exists —
1210
+ * and neither is something a provider can unlink, so the two events that mean
1211
+ * those things must not start accepting them.
1115
1212
  */
1116
- declare const MFA_VERIFICATION_METHODS: readonly ["totp", "recovery", "passkey"];
1117
- type MfaVerificationMethod = typeof MFA_VERIFICATION_METHODS[number];
1118
- declare const mfaVerifications: drizzle_orm_pg_core.PgTableWithColumns<{
1119
- name: "mfa_verifications";
1120
- schema: string;
1121
- columns: {
1122
- keyId: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
1123
- name: string;
1124
- tableName: "mfa_verifications";
1125
- dataType: "string";
1126
- data: string;
1127
- driverParam: string;
1128
- notNull: true;
1129
- hasDefault: false;
1130
- isPrimaryKey: false;
1131
- isAutoincrement: false;
1132
- hasRuntimeDefault: false;
1133
- enumValues: undefined;
1134
- identity: undefined;
1135
- generated: undefined;
1136
- }>;
1137
- userId: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
1138
- name: string;
1139
- tableName: "mfa_verifications";
1140
- dataType: "number int53";
1141
- data: number;
1142
- driverParam: string | number;
1143
- notNull: true;
1144
- hasDefault: false;
1145
- isPrimaryKey: false;
1146
- isAutoincrement: false;
1147
- hasRuntimeDefault: false;
1148
- enumValues: undefined;
1149
- identity: undefined;
1150
- generated: undefined;
1151
- }>;
1152
- method: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["totp", "recovery", "passkey"] & [string, ...string[]]>>, {
1153
- name: string;
1154
- tableName: "mfa_verifications";
1155
- dataType: "string enum";
1156
- data: "totp" | "recovery" | "passkey";
1157
- driverParam: string;
1158
- notNull: true;
1159
- hasDefault: false;
1160
- isPrimaryKey: false;
1161
- isAutoincrement: false;
1162
- hasRuntimeDefault: false;
1163
- enumValues: ["totp", "recovery", "passkey"] & [string, ...string[]];
1164
- identity: undefined;
1165
- generated: undefined;
1166
- }>;
1167
- verifiedAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>>, {
1168
- name: string;
1169
- tableName: "mfa_verifications";
1170
- dataType: "object date";
1171
- data: Date;
1172
- driverParam: string;
1173
- notNull: true;
1174
- hasDefault: true;
1175
- isPrimaryKey: false;
1176
- isAutoincrement: false;
1177
- hasRuntimeDefault: false;
1178
- enumValues: undefined;
1179
- identity: undefined;
1180
- generated: undefined;
1181
- }>;
1182
- };
1183
- dialect: "pg";
1184
- }>;
1185
- type MfaVerification = typeof mfaVerifications.$inferSelect;
1186
- type NewMfaVerification = typeof mfaVerifications.$inferInsert;
1187
-
1213
+ declare const AuthLoginProviderSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">, _sinclair_typebox.TLiteral<"device">, _sinclair_typebox.TLiteral<"passkey">, ..._sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]]>;
1188
1214
  /**
1189
- * @spfn/auth - Second Factor Service
1190
- *
1191
- * An optional second step the owner enrols, that the package then asks for at
1192
- * the moments a stolen first credential would be used. Two forms: a TOTP
1193
- * authenticator app, or a passkey the owner already enrolled and has marked as
1194
- * satisfying a step-up. Ten single-use recovery codes come with either.
1215
+ * auth.login - 로그인 성공 이벤트
1195
1216
  *
1196
- * The rule the whole feature hangs on is the one in `assertStepUp`: an account
1197
- * that never enrolled is answered exactly as it is today, on every route. That
1198
- * is why the enrolment check comes first and returns, before any passkey
1199
- * configuration is read and before any second query is made — an app with no
1200
- * passkeys configured must still be able to change a password.
1217
+ * 발행 시점:
1218
+ * - 이메일/전화 로그인 성공 시
1219
+ * - OAuth 기존 사용자 로그인 시
1220
+ * - 기기 코드 승인이 소비되어 새 기기 키가 등록될 때 (provider: 'device')
1201
1221
  *
1202
- * The step-up window is per **device key**, not per account: "this device
1203
- * proved the second factor recently" is the claim a sensitive change needs, and
1204
- * a verification made on a phone must not authorize a change from a laptop. A
1205
- * key rotation carries the row across, because rotating is already proof of the
1206
- * same device.
1222
+ * `mfaEnrolled` is computed as the event is emitted (#95) and is the whole of
1223
+ * the package's opinion about the second factor: it never blocks an account
1224
+ * that has none, and this is the hook an app uses to offer enrolment at a first
1225
+ * login. It says nothing about *which* factor and carries no secret.
1207
1226
  *
1208
- * A secret and a recovery code appear in exactly three response bodies —
1209
- * `totp/enroll`, `totp/confirm` and `recovery/regenerate` — and nowhere else:
1210
- * not in `status`, not in an event, and not in a log line.
1227
+ * @example
1228
+ * ```typescript
1229
+ * authLoginEvent.subscribe(async (payload) => {
1230
+ * await analytics.trackLogin(payload.userId, payload.provider);
1231
+ * if (!payload.mfaEnrolled) await suggestSecondFactor(payload.userId);
1232
+ * });
1233
+ * ```
1211
1234
  */
1212
-
1235
+ declare const authLoginEvent: _spfn_core_event.EventDef<{
1236
+ email?: string | undefined;
1237
+ phone?: string | undefined;
1238
+ userId: string;
1239
+ provider: "email" | "phone" | "passkey" | "google" | "apple" | "github" | "kakao" | "naver" | "superself" | "device";
1240
+ mfaEnrolled: boolean;
1241
+ }>;
1213
1242
  /**
1214
- * Does this account have a second factor at all?
1243
+ * Where a device key was registered — the door the new device came through.
1215
1244
  *
1216
- * One statement (`repositories/mfa-enrolment.repository.ts`), because every
1217
- * sensitive route and every device registration asks it, including the
1218
- * overwhelming majority that will answer false.
1245
+ * Required on `RegisterPublicKeyParams` rather than optional with a default: a
1246
+ * new *call site* for key registration must choose one, and a default would let
1247
+ * it inherit somebody else's answer silently. `'register'` and `'signup-link'`
1248
+ * both arrive at `createVerifiedAccount`, so the two name themselves there;
1249
+ * `'invitation'` is the one path that stores a key without the key service.
1219
1250
  */
1220
- declare function mfaEnrolledForUser(userId: number): Promise<boolean>;
1221
- interface AssertStepUpParams {
1222
- userId: number;
1223
- /** The device key this request is signed with — the window is its window. */
1224
- keyId: string;
1225
- /** Override the configured window. Callers needing a tighter one pass it. */
1226
- maxAgeMs?: number;
1227
- }
1251
+ declare const DeviceRegistrationChannelSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"register">, _sinclair_typebox.TLiteral<"signup-link">, _sinclair_typebox.TLiteral<"invitation">, _sinclair_typebox.TLiteral<"password">, _sinclair_typebox.TLiteral<"oauth">, _sinclair_typebox.TLiteral<"oauth-native">, _sinclair_typebox.TLiteral<"device-code">, _sinclair_typebox.TLiteral<"password-reset">, _sinclair_typebox.TLiteral<"passkey">, _sinclair_typebox.TLiteral<"renewal">]>;
1252
+ /** The ten doors a device key is registered through. */
1253
+ type DeviceRegistrationChannel = Static<typeof DeviceRegistrationChannelSchema>;
1228
1254
  /**
1229
- * Refuse a sensitive change unless this device proved the second factor recently.
1255
+ * auth.device.registered — a new device key was added to an account
1230
1256
  *
1231
- * **No-op for an account with no second factor.** That is the contract of the
1232
- * whole feature: `changePassword` and `revokeAllKeys` have no recency gate
1233
- * today, and adding one for people who never opted in would be a new refusal on
1234
- * routes that never had one. Two of the callers do have a gate of their own —
1235
- * the passkey routes' `assertRecentAuthentication` — and they still run it
1236
- * afterwards; this adds a rule for enrolled accounts rather than replacing one.
1257
+ * 발행 시점:
1258
+ * - a key row was created for an account and the transaction that created it
1259
+ * committed, on every one of the ten channels above
1237
1260
  *
1238
- * The unenrolled path therefore costs one indexed lookup and touches no passkey
1239
- * configuration, which is what lets an app with no passkeys at all call it.
1261
+ * This is the notice an account owner needs and could not get before: a stolen
1262
+ * password used to sign in on a new device was silent, because a login event
1263
+ * says a session began and not what it began on. Rotation is deliberately not
1264
+ * announced — replacing the key of a device that is already signed in is not a
1265
+ * new device, and a notice for it would train the owner to ignore the ones that
1266
+ * matter.
1240
1267
  *
1241
- * @throws StepUpRequiredError when the account is enrolled and the window has passed
1242
- */
1243
- declare function assertStepUp(params: AssertStepUpParams): Promise<void>;
1244
- /**
1245
- * Carry a device's verification onto the key that replaces it.
1268
+ * `ip` and `userAgent` are what the registering request said about itself. Both
1269
+ * are unauthenticated display material: nothing is decided by them, and a field
1270
+ * is absent rather than carrying a placeholder when the request resolved none.
1246
1271
  *
1247
- * Called from the two rotation seams — `rotateKeyService`, and the `oldKeyId`
1248
- * rotation every login path runs through `registerPublicKeyService`. Without
1249
- * it the window would expire silently on every rotation, which the web proxy
1250
- * does at each login: a user who stepped up a minute ago would be asked again
1251
- * with nothing to connect it to.
1252
- */
1253
- declare function carryStepUpVerification(userId: number, fromKeyId: string, toKeyId: string): Promise<void>;
1254
- interface TotpEnrolmentResult {
1255
- /** The base32 secret, shown once. Never logged, never returned again. */
1256
- secret: string;
1257
- /** The same secret as the URI an authenticator app scans. */
1258
- otpauthUri: string;
1259
- }
1260
- /**
1261
- * Mint a secret and park it unconfirmed.
1272
+ * Neither the full fingerprint nor the public key is carried. The prefix is
1273
+ * enough to point at one entry of `listKeys`, which is what a notice needs.
1262
1274
  *
1263
- * Calling this twice replaces the unconfirmed row and its failure counter — a
1264
- * user who closed the app before scanning starts over, which is also the
1265
- * documented remedy for a row that spent its five confirm attempts. A
1266
- * **confirmed** enrolment is refused instead: replacing a working second factor
1267
- * is `disable` followed by a fresh enrolment, both step-up gated, so nobody
1268
- * holding only a stolen session can swap an authenticator for their own.
1275
+ * `mfaEnrolled` is computed as the event is emitted (#95), so a notice about a
1276
+ * new device can also be the moment an app offers a second factor to the
1277
+ * accounts that have none.
1269
1278
  *
1270
- * @throws MfaAlreadyEnrolledError when a confirmed enrolment already exists
1271
- */
1272
- declare function startTotpEnrolmentService(userId: number): Promise<TotpEnrolmentResult>;
1273
- interface ConfirmTotpParams {
1274
- userId: number;
1275
- /** The device this enrolment is being confirmed from — it gets the first verification. */
1276
- keyId: string;
1277
- code: string;
1278
- }
1279
- interface ConfirmTotpResult {
1280
- /** The ten plaintext codes, shown once. */
1281
- recoveryCodes: string[];
1282
- }
1283
- /**
1284
- * Spend the first code, which is what turns an enrolment into a second factor.
1285
- *
1286
- * Five wrong codes delete the unconfirmed row: at that point the person is
1287
- * reading the wrong entry in their app, and a fresh `enroll` is both the remedy
1288
- * and what resets the counter. A confirmed row is never deleted this way.
1289
- *
1290
- * The route deliberately runs this outside a transaction, so the counter
1291
- * survives the refusal that raised it; the success path opens its own, because
1292
- * the confirmation, the first verification and the recovery codes have to
1293
- * commit together or not at all.
1294
- *
1295
- * @throws MfaNotEnrolledError | MfaVerificationFailedError
1296
- */
1297
- declare function confirmTotpEnrolmentService(params: ConfirmTotpParams): Promise<ConfirmTotpResult>;
1298
- /**
1299
- * Replace the account's recovery codes, retiring every earlier one.
1300
- *
1301
- * The old generation's rows stay, unspent and unreachable: a code written down
1302
- * last year is refused with the same body as one that never existed, and the
1303
- * record of which generation a spent code came from survives.
1304
- *
1305
- * @throws MfaNotEnrolledError on an account with no second factor
1306
- */
1307
- declare function regenerateRecoveryCodesService(userId: number): Promise<{
1308
- recoveryCodes: string[];
1309
- }>;
1310
- /**
1311
- * Take the second factor off the account entirely.
1312
- *
1313
- * Idempotent: an account with nothing enrolled is a success, because "make sure
1314
- * MFA is off" has already happened. The passkeys themselves are untouched —
1315
- * only their second-factor marks go, since a credential that signs the owner in
1316
- * is not something this route may remove.
1317
- */
1318
- declare function disableMfaService(userId: number): Promise<void>;
1319
- interface MarkPasskeyParams {
1320
- userId: number;
1321
- passkeyId: string;
1322
- secondFactor: boolean;
1323
- }
1324
- /**
1325
- * Mark or unmark one of the caller's passkeys as their second factor.
1326
- *
1327
- * Unmarking the last one is allowed, and leaves the account unenrolled. It is
1328
- * deliberately independent of `assertNotLastRecoveryCredential`, which guards
1329
- * `passkeys/revoke`: that rule protects a way *into* the account, and a
1330
- * second-factor mark is not one — the credential still signs the owner in
1331
- * afterwards, unchanged.
1332
- *
1333
- * @throws PasskeyNotFoundError for a credential that is not theirs, or revoked
1334
- */
1335
- declare function markPasskeySecondFactorService(params: MarkPasskeyParams): Promise<MfaStatus>;
1336
- /** What the account surface shows about the second factor. No secret is in it. */
1337
- interface MfaStatus {
1338
- enrolled: boolean;
1339
- /** Which second factors are live: `'totp'`, `'passkey'`, or both. */
1340
- methods: ('totp' | 'passkey')[];
1341
- /** Unspent codes of the current generation, for the "2 left" warning. */
1342
- recoveryCodesRemaining: number;
1343
- }
1344
- /**
1345
- * The account's second-factor state.
1346
- *
1347
- * Carries no secret, no otpauth URI and no recovery code — only the counts and
1348
- * names an account screen needs. An unconfirmed enrolment is not a method: it
1349
- * gates nothing, so reporting it would tell the owner they are protected when
1350
- * they are not.
1351
- */
1352
- declare function mfaStatusService(userId: number): Promise<MfaStatus>;
1353
- /**
1354
- * Options for a step-up by passkey assertion.
1355
- *
1356
- * `allowCredentials` is empty, as it is for a sign-in and for the same reason
1357
- * (D3): the discoverable credential on the device names itself, and the owner
1358
- * and the second-factor mark are checked when the assertion comes back. The
1359
- * challenge is minted with kind `'mfa'` and this account's id, so it cannot be
1360
- * presented to `passkeys/login/verify` and a sign-in challenge cannot be
1361
- * presented here.
1362
- */
1363
- declare function startStepUpService(userId: number): Promise<PublicKeyCredentialRequestOptionsJSON>;
1364
- interface StepUpParams {
1365
- userId: number;
1366
- /** The device the verification is recorded against. */
1367
- keyId: string;
1368
- code?: string;
1369
- recoveryCode?: string;
1370
- response?: AuthenticationResponseJSON;
1371
- }
1372
- /**
1373
- * Re-prove the second factor on this device, refreshing its window.
1374
- *
1375
- * The escape hatch the exempt registration channels need: a device-code
1376
- * approval and a passkey sign-in register a key with no verification against
1377
- * it, by design, so the session they create would otherwise fail every
1378
- * sensitive change with no way forward.
1379
- *
1380
- * @throws ValidationError when the body names none or more than one input
1381
- * @throws MfaNotEnrolledError | MfaVerificationFailedError
1382
- */
1383
- declare function stepUpService(params: StepUpParams): Promise<void>;
1384
- /**
1385
- * Check exactly one of the three proofs, and say which one it was.
1386
- *
1387
- * Exactly one: two inputs in a body is a caller trying combinations, and none
1388
- * is a malformed request. Neither is a failed verification, so both are a 400
1389
- * rather than the uniform 401 a wrong proof gets.
1390
- *
1391
- * @throws ValidationError | MfaVerificationFailedError
1392
- */
1393
- declare function verifySecondFactor(params: StepUpParams): Promise<MfaVerificationMethod>;
1394
- /**
1395
- * Drop enrolments nobody ever confirmed.
1396
- *
1397
- * A secret handed out and abandoned is a credential sitting in a table doing
1398
- * nothing; a day is long enough for anyone who meant to finish.
1399
- *
1400
- * @returns number of rows deleted
1401
- */
1402
- declare function sweepUnconfirmedMfaService(): Promise<{
1403
- deleted: number;
1404
- }>;
1405
-
1406
- /**
1407
- * Auth provider type
1408
- *
1409
- * 직접 인증(email/phone) + 등록 가능한 모든 소셜 provider(SOCIAL_PROVIDERS).
1410
- */
1411
- declare const AuthProviderSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">, ..._sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]]>;
1412
- /**
1413
- * auth.login - 로그인 성공 이벤트
1414
- *
1415
- * 발행 시점:
1416
- * - 이메일/전화 로그인 성공 시
1417
- * - OAuth 기존 사용자 로그인 시
1418
- * - 기기 코드 승인이 소비되어 새 기기 키가 등록될 때 (provider: 'device')
1419
- *
1420
- * `mfaEnrolled` is computed as the event is emitted (#95) and is the whole of
1421
- * the package's opinion about the second factor: it never blocks an account
1422
- * that has none, and this is the hook an app uses to offer enrolment at a first
1423
- * login. It says nothing about *which* factor and carries no secret.
1424
- *
1425
- * @example
1426
- * ```typescript
1427
- * authLoginEvent.subscribe(async (payload) => {
1428
- * await analytics.trackLogin(payload.userId, payload.provider);
1429
- * if (!payload.mfaEnrolled) await suggestSecondFactor(payload.userId);
1430
- * });
1431
- * ```
1432
- */
1433
- declare const authLoginEvent: _spfn_core_event.EventDef<{
1434
- email?: string | undefined;
1435
- phone?: string | undefined;
1436
- userId: string;
1437
- provider: "email" | "phone" | "google" | "apple" | "github" | "kakao" | "naver" | "superself" | "passkey" | "device";
1438
- mfaEnrolled: boolean;
1439
- }>;
1440
- /**
1441
- * Where a device key was registered — the door the new device came through.
1442
- *
1443
- * Required on `RegisterPublicKeyParams` rather than optional with a default: a
1444
- * new *call site* for key registration must choose one, and a default would let
1445
- * it inherit somebody else's answer silently. `'register'` and `'signup-link'`
1446
- * both arrive at `createVerifiedAccount`, so the two name themselves there;
1447
- * `'invitation'` is the one path that stores a key without the key service.
1448
- */
1449
- declare const DeviceRegistrationChannelSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"register">, _sinclair_typebox.TLiteral<"signup-link">, _sinclair_typebox.TLiteral<"invitation">, _sinclair_typebox.TLiteral<"password">, _sinclair_typebox.TLiteral<"oauth">, _sinclair_typebox.TLiteral<"oauth-native">, _sinclair_typebox.TLiteral<"device-code">, _sinclair_typebox.TLiteral<"password-reset">, _sinclair_typebox.TLiteral<"passkey">]>;
1450
- /** The nine doors a device key is registered through. */
1451
- type DeviceRegistrationChannel = Static<typeof DeviceRegistrationChannelSchema>;
1452
- /**
1453
- * auth.device.registered — a new device key was added to an account
1454
- *
1455
- * 발행 시점:
1456
- * - a key row was created for an account and the transaction that created it
1457
- * committed, on every one of the nine channels above
1458
- *
1459
- * This is the notice an account owner needs and could not get before: a stolen
1460
- * password used to sign in on a new device was silent, because a login event
1461
- * says a session began and not what it began on. Rotation is deliberately not
1462
- * announced — replacing the key of a device that is already signed in is not a
1463
- * new device, and a notice for it would train the owner to ignore the ones that
1464
- * matter.
1465
- *
1466
- * `ip` and `userAgent` are what the registering request said about itself. Both
1467
- * are unauthenticated display material: nothing is decided by them, and a field
1468
- * is absent rather than carrying a placeholder when the request resolved none.
1469
- *
1470
- * Neither the full fingerprint nor the public key is carried. The prefix is
1471
- * enough to point at one entry of `listKeys`, which is what a notice needs.
1472
- *
1473
- * `mfaEnrolled` is computed as the event is emitted (#95), so a notice about a
1474
- * new device can also be the moment an app offers a second factor to the
1475
- * accounts that have none.
1476
- *
1477
- * @example
1478
- * ```typescript
1479
- * authDeviceRegisteredEvent.subscribe(async ({ userId, deviceName, ip, channel }) => {
1480
- * await notifyOwner(userId, `A new device signed in (${deviceName ?? channel})`);
1481
- * });
1482
- * ```
1279
+ * @example
1280
+ * ```typescript
1281
+ * authDeviceRegisteredEvent.subscribe(async ({ userId, deviceName, ip, channel }) => {
1282
+ * await notifyOwner(userId, `A new device signed in (${deviceName ?? channel})`);
1283
+ * });
1284
+ * ```
1483
1285
  */
1484
1286
  declare const authDeviceRegisteredEvent: _spfn_core_event.EventDef<{
1485
1287
  deviceName?: string | undefined;
@@ -1489,10 +1291,10 @@ declare const authDeviceRegisteredEvent: _spfn_core_event.EventDef<{
1489
1291
  keyId: string;
1490
1292
  algorithm: string;
1491
1293
  userId: string;
1294
+ mfaEnrolled: boolean;
1492
1295
  fingerprintPrefix: string;
1493
1296
  createdAtMillis: number;
1494
- mfaEnrolled: boolean;
1495
- channel: "password" | "register" | "oauth-native" | "passkey" | "oauth" | "invitation" | "signup-link" | "password-reset" | "device-code";
1297
+ channel: "password" | "register" | "passkey" | "oauth-native" | "renewal" | "signup-link" | "invitation" | "oauth" | "device-code" | "password-reset";
1496
1298
  }>;
1497
1299
  /**
1498
1300
  * auth.register - 회원가입 성공 이벤트
@@ -1674,58 +1476,760 @@ type AuthDeletionCompletedPayload = typeof authDeletionCompletedEvent._payload;
1674
1476
  type OAuthUnlinkedPayload = typeof oauthUnlinkedEvent._payload;
1675
1477
 
1676
1478
  /**
1677
- * @spfn/auth - Key Service
1479
+ * The four doors a second factor is asked for at.
1480
+ *
1481
+ * A subset of `DeviceRegistrationChannel`, and the subset is the decision: these
1482
+ * are the four where a stolen first credential — a password, a social account, a
1483
+ * mailbox — is enough to reach a new device on its own. `device-code` and
1484
+ * `passkey` are exempt because each already carried a second proof, and
1485
+ * `register`, `signup-link`, `invitation` and `renewal` cannot apply (a
1486
+ * brand-new account has nothing enrolled, and a renewal replaces a key on a
1487
+ * device that is already signed in).
1488
+ */
1489
+ declare const MFA_CHALLENGE_CHANNELS: readonly ["password", "oauth", "oauth-native", "password-reset"];
1490
+ type MfaChallengeChannel = typeof MFA_CHALLENGE_CHANNELS[number];
1491
+ /** How many wrong proofs a challenge survives before it is spent for good. */
1492
+ declare const MFA_CHALLENGE_ATTEMPT_LIMIT = 5;
1493
+ /**
1494
+ * The `authLoginEvent` payload this registration would have emitted.
1678
1495
  *
1679
- * Handles public key registration, rotation, and revocation
1496
+ * Carried rather than recomputed because it cannot be recomputed: the app's own
1497
+ * `metadata` reached the sign-in in a sealed OAuth state or a request body that
1498
+ * is gone by the time `verify` runs, and the provider is the social provider on
1499
+ * the OAuth channels rather than anything the user row says. Null on
1500
+ * `password-reset`, which announces a reset and never a login.
1501
+ *
1502
+ * `mfaEnrolled` is not stored: the account is enrolled by construction — that is
1503
+ * why there is a challenge — and re-reading it at verify keeps the payload
1504
+ * honest if the second factor went away in between.
1680
1505
  */
1681
-
1682
- interface RegisterPublicKeyParams {
1683
- userId: number;
1684
- keyId: string;
1685
- publicKey: string;
1686
- fingerprint: string;
1687
- algorithm?: KeyAlgorithmType;
1688
- /** Device label for the key list. Display only — nothing is authorized by it. */
1689
- deviceName?: string;
1690
- platform?: KeyPlatformType;
1691
- /**
1692
- * Which door this device came through. Required, so that a registration path
1693
- * added later has to say what it is rather than inherit an answer.
1694
- */
1695
- channel: DeviceRegistrationChannel;
1696
- /** Client address of the registering request, absent when none resolved. */
1697
- ip?: string;
1698
- /** `user-agent` of the registering request, already truncated. */
1699
- userAgent?: string;
1700
- /**
1701
- * The key this one replaces on the same device, when a revocation actually
1702
- * happened. Its presence is what makes this a rotation rather than a new
1703
- * device, so no event is announced for it.
1704
- *
1705
- * Only ever set from a `revokeKeyService` that returned true: an `oldKeyId`
1706
- * naming somebody else's key, an already-revoked key or nothing at all
1707
- * revokes nothing, and treating that as a rotation would be a way to
1708
- * register a device with the owner's notice switched off.
1709
- */
1710
- replacesKeyId?: string;
1711
- }
1712
- interface RotateKeyParams {
1713
- userId: number;
1714
- oldKeyId: string;
1715
- newKeyId: string;
1716
- newPublicKey: string;
1717
- fingerprint: string;
1718
- algorithm?: KeyAlgorithmType;
1719
- /** Omitted: the replaced key's label carries over, so rotation keeps its name. */
1720
- deviceName?: string;
1721
- platform?: KeyPlatformType;
1722
- }
1723
- interface RotateKeyResult {
1724
- success: boolean;
1725
- keyId: string;
1506
+ interface DeferredLoginEvent {
1507
+ provider: AuthLoginProvider;
1508
+ email?: string;
1509
+ phone?: string;
1510
+ metadata?: Record<string, unknown>;
1726
1511
  }
1727
- interface RevokeKeyParams {
1728
- userId: number;
1512
+ declare const mfaChallenges: drizzle_orm_pg_core.PgTableWithColumns<{
1513
+ name: "mfa_challenges";
1514
+ schema: string;
1515
+ columns: {
1516
+ createdAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>, {
1517
+ name: string;
1518
+ tableName: "mfa_challenges";
1519
+ dataType: "object date";
1520
+ data: Date;
1521
+ driverParam: string;
1522
+ notNull: true;
1523
+ hasDefault: true;
1524
+ isPrimaryKey: false;
1525
+ isAutoincrement: false;
1526
+ hasRuntimeDefault: false;
1527
+ enumValues: undefined;
1528
+ identity: undefined;
1529
+ generated: undefined;
1530
+ }>;
1531
+ updatedAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>, {
1532
+ name: string;
1533
+ tableName: "mfa_challenges";
1534
+ dataType: "object date";
1535
+ data: Date;
1536
+ driverParam: string;
1537
+ notNull: true;
1538
+ hasDefault: true;
1539
+ isPrimaryKey: false;
1540
+ isAutoincrement: false;
1541
+ hasRuntimeDefault: false;
1542
+ enumValues: undefined;
1543
+ identity: undefined;
1544
+ generated: undefined;
1545
+ }>;
1546
+ id: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
1547
+ name: string;
1548
+ tableName: "mfa_challenges";
1549
+ dataType: "number int53";
1550
+ data: number;
1551
+ driverParam: number;
1552
+ notNull: true;
1553
+ hasDefault: true;
1554
+ isPrimaryKey: false;
1555
+ isAutoincrement: false;
1556
+ hasRuntimeDefault: false;
1557
+ enumValues: undefined;
1558
+ identity: undefined;
1559
+ generated: undefined;
1560
+ }>;
1561
+ userId: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
1562
+ name: string;
1563
+ tableName: "mfa_challenges";
1564
+ dataType: "number int53";
1565
+ data: number;
1566
+ driverParam: string | number;
1567
+ notNull: true;
1568
+ hasDefault: false;
1569
+ isPrimaryKey: false;
1570
+ isAutoincrement: false;
1571
+ hasRuntimeDefault: false;
1572
+ enumValues: undefined;
1573
+ identity: undefined;
1574
+ generated: undefined;
1575
+ }>;
1576
+ challengeHash: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
1577
+ name: string;
1578
+ tableName: "mfa_challenges";
1579
+ dataType: "string";
1580
+ data: string;
1581
+ driverParam: string;
1582
+ notNull: true;
1583
+ hasDefault: false;
1584
+ isPrimaryKey: false;
1585
+ isAutoincrement: false;
1586
+ hasRuntimeDefault: false;
1587
+ enumValues: undefined;
1588
+ identity: undefined;
1589
+ generated: undefined;
1590
+ }>;
1591
+ keyId: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
1592
+ name: string;
1593
+ tableName: "mfa_challenges";
1594
+ dataType: "string";
1595
+ data: string;
1596
+ driverParam: string;
1597
+ notNull: true;
1598
+ hasDefault: false;
1599
+ isPrimaryKey: false;
1600
+ isAutoincrement: false;
1601
+ hasRuntimeDefault: false;
1602
+ enumValues: undefined;
1603
+ identity: undefined;
1604
+ generated: undefined;
1605
+ }>;
1606
+ channel: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["password", "oauth", "oauth-native", "password-reset"] & [string, ...string[]]>>, {
1607
+ name: string;
1608
+ tableName: "mfa_challenges";
1609
+ dataType: "string enum";
1610
+ data: "password" | "oauth-native" | "oauth" | "password-reset";
1611
+ driverParam: string;
1612
+ notNull: true;
1613
+ hasDefault: false;
1614
+ isPrimaryKey: false;
1615
+ isAutoincrement: false;
1616
+ hasRuntimeDefault: false;
1617
+ enumValues: ["password", "oauth", "oauth-native", "password-reset"] & [string, ...string[]];
1618
+ identity: undefined;
1619
+ generated: undefined;
1620
+ }>;
1621
+ keyEpoch: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgIntegerBuilder>, {
1622
+ name: string;
1623
+ tableName: "mfa_challenges";
1624
+ dataType: "number int32";
1625
+ data: number;
1626
+ driverParam: string | number;
1627
+ notNull: true;
1628
+ hasDefault: false;
1629
+ isPrimaryKey: false;
1630
+ isAutoincrement: false;
1631
+ hasRuntimeDefault: false;
1632
+ enumValues: undefined;
1633
+ identity: undefined;
1634
+ generated: undefined;
1635
+ }>;
1636
+ attempts: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgIntegerBuilder>>, {
1637
+ name: string;
1638
+ tableName: "mfa_challenges";
1639
+ dataType: "number int32";
1640
+ data: number;
1641
+ driverParam: string | number;
1642
+ notNull: true;
1643
+ hasDefault: true;
1644
+ isPrimaryKey: false;
1645
+ isAutoincrement: false;
1646
+ hasRuntimeDefault: false;
1647
+ enumValues: undefined;
1648
+ identity: undefined;
1649
+ generated: undefined;
1650
+ }>;
1651
+ expiresAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>, {
1652
+ name: string;
1653
+ tableName: "mfa_challenges";
1654
+ dataType: "object date";
1655
+ data: Date;
1656
+ driverParam: string;
1657
+ notNull: true;
1658
+ hasDefault: false;
1659
+ isPrimaryKey: false;
1660
+ isAutoincrement: false;
1661
+ hasRuntimeDefault: false;
1662
+ enumValues: undefined;
1663
+ identity: undefined;
1664
+ generated: undefined;
1665
+ }>;
1666
+ verifiedAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.PgTimestampBuilder, {
1667
+ name: string;
1668
+ tableName: "mfa_challenges";
1669
+ dataType: "object date";
1670
+ data: Date;
1671
+ driverParam: string;
1672
+ notNull: false;
1673
+ hasDefault: false;
1674
+ isPrimaryKey: false;
1675
+ isAutoincrement: false;
1676
+ hasRuntimeDefault: false;
1677
+ enumValues: undefined;
1678
+ identity: undefined;
1679
+ generated: undefined;
1680
+ }>;
1681
+ loginEvent: drizzle_orm_pg_core.PgBuildColumn<"mfa_challenges", drizzle_orm_pg_core.Set$Type<drizzle_orm_pg_core.PgJsonbBuilder, DeferredLoginEvent>, {
1682
+ name: string;
1683
+ tableName: "mfa_challenges";
1684
+ dataType: "object json";
1685
+ data: DeferredLoginEvent;
1686
+ driverParam: unknown;
1687
+ notNull: false;
1688
+ hasDefault: false;
1689
+ isPrimaryKey: false;
1690
+ isAutoincrement: false;
1691
+ hasRuntimeDefault: false;
1692
+ enumValues: undefined;
1693
+ identity: undefined;
1694
+ generated: undefined;
1695
+ }>;
1696
+ };
1697
+ dialect: "pg";
1698
+ }>;
1699
+ type MfaChallenge = typeof mfaChallenges.$inferSelect;
1700
+ type NewMfaChallenge = typeof mfaChallenges.$inferInsert;
1701
+
1702
+ /**
1703
+ * @spfn/auth - Second-Factor Verification Entity
1704
+ *
1705
+ * When each device last proved the account's second factor. One row per device
1706
+ * key, which is what makes the step-up window mean "this device, recently"
1707
+ * rather than "somebody, somewhere, recently".
1708
+ *
1709
+ * `key_id` is the primary key and a foreign key onto `user_public_keys.key_id`,
1710
+ * so the row dies with the device it belongs to — a revoked key cannot leave a
1711
+ * verification behind for the next key that reuses its id, and deleting an
1712
+ * account takes these with it. A rotation is the one moment the row moves
1713
+ * rather than dying: rotating is already proof of the same device, so
1714
+ * `rotateKeyService` and the login rotation carry the verification across, or
1715
+ * the window would silently expire every time the proxy rotated a key.
1716
+ */
1717
+ /**
1718
+ * Which credential satisfied the step-up.
1719
+ *
1720
+ * Recorded for the owner-facing audit an app may build on it, and for support:
1721
+ * "recovery" is the one that should be rare, and an account stepping up with
1722
+ * recovery codes repeatedly has lost its authenticator.
1723
+ */
1724
+ declare const MFA_VERIFICATION_METHODS: readonly ["totp", "recovery", "passkey"];
1725
+ type MfaVerificationMethod = typeof MFA_VERIFICATION_METHODS[number];
1726
+ declare const mfaVerifications: drizzle_orm_pg_core.PgTableWithColumns<{
1727
+ name: "mfa_verifications";
1728
+ schema: string;
1729
+ columns: {
1730
+ keyId: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
1731
+ name: string;
1732
+ tableName: "mfa_verifications";
1733
+ dataType: "string";
1734
+ data: string;
1735
+ driverParam: string;
1736
+ notNull: true;
1737
+ hasDefault: false;
1738
+ isPrimaryKey: false;
1739
+ isAutoincrement: false;
1740
+ hasRuntimeDefault: false;
1741
+ enumValues: undefined;
1742
+ identity: undefined;
1743
+ generated: undefined;
1744
+ }>;
1745
+ userId: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
1746
+ name: string;
1747
+ tableName: "mfa_verifications";
1748
+ dataType: "number int53";
1749
+ data: number;
1750
+ driverParam: string | number;
1751
+ notNull: true;
1752
+ hasDefault: false;
1753
+ isPrimaryKey: false;
1754
+ isAutoincrement: false;
1755
+ hasRuntimeDefault: false;
1756
+ enumValues: undefined;
1757
+ identity: undefined;
1758
+ generated: undefined;
1759
+ }>;
1760
+ method: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["totp", "recovery", "passkey"] & [string, ...string[]]>>, {
1761
+ name: string;
1762
+ tableName: "mfa_verifications";
1763
+ dataType: "string enum";
1764
+ data: "passkey" | "totp" | "recovery";
1765
+ driverParam: string;
1766
+ notNull: true;
1767
+ hasDefault: false;
1768
+ isPrimaryKey: false;
1769
+ isAutoincrement: false;
1770
+ hasRuntimeDefault: false;
1771
+ enumValues: ["totp", "recovery", "passkey"] & [string, ...string[]];
1772
+ identity: undefined;
1773
+ generated: undefined;
1774
+ }>;
1775
+ verifiedAt: drizzle_orm_pg_core.PgBuildColumn<"mfa_verifications", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>>, {
1776
+ name: string;
1777
+ tableName: "mfa_verifications";
1778
+ dataType: "object date";
1779
+ data: Date;
1780
+ driverParam: string;
1781
+ notNull: true;
1782
+ hasDefault: true;
1783
+ isPrimaryKey: false;
1784
+ isAutoincrement: false;
1785
+ hasRuntimeDefault: false;
1786
+ enumValues: undefined;
1787
+ identity: undefined;
1788
+ generated: undefined;
1789
+ }>;
1790
+ };
1791
+ dialect: "pg";
1792
+ }>;
1793
+ type MfaVerification = typeof mfaVerifications.$inferSelect;
1794
+ type NewMfaVerification = typeof mfaVerifications.$inferInsert;
1795
+
1796
+ /**
1797
+ * @spfn/auth - Second Factor Service
1798
+ *
1799
+ * An optional second step the owner enrols, that the package then asks for at
1800
+ * the moments a stolen first credential would be used. Two forms: a TOTP
1801
+ * authenticator app, or a passkey the owner already enrolled and has marked as
1802
+ * satisfying a step-up. Ten single-use recovery codes come with either.
1803
+ *
1804
+ * The rule the whole feature hangs on is the one in `assertStepUp`: an account
1805
+ * that never enrolled is answered exactly as it is today, on every route. That
1806
+ * is why the enrolment check comes first and returns, before any passkey
1807
+ * configuration is read and before any second query is made — an app with no
1808
+ * passkeys configured must still be able to change a password.
1809
+ *
1810
+ * The step-up window is per **device key**, not per account: "this device
1811
+ * proved the second factor recently" is the claim a sensitive change needs, and
1812
+ * a verification made on a phone must not authorize a change from a laptop. A
1813
+ * key rotation carries the row across, because rotating is already proof of the
1814
+ * same device.
1815
+ *
1816
+ * A secret and a recovery code appear in exactly three response bodies —
1817
+ * `totp/enroll`, `totp/confirm` and `recovery/regenerate` — and nowhere else:
1818
+ * not in `status`, not in an event, and not in a log line.
1819
+ */
1820
+
1821
+ /**
1822
+ * Does this account have a second factor at all?
1823
+ *
1824
+ * One statement (`repositories/mfa-enrolment.repository.ts`), because every
1825
+ * sensitive route and every device registration asks it, including the
1826
+ * overwhelming majority that will answer false.
1827
+ */
1828
+ declare function mfaEnrolledForUser(userId: number): Promise<boolean>;
1829
+ interface AssertStepUpParams {
1830
+ userId: number;
1831
+ /** The device key this request is signed with — the window is its window. */
1832
+ keyId: string;
1833
+ /** Override the configured window. Callers needing a tighter one pass it. */
1834
+ maxAgeMs?: number;
1835
+ }
1836
+ /**
1837
+ * Refuse a sensitive change unless this device proved the second factor recently.
1838
+ *
1839
+ * **No-op for an account with no second factor.** That is the contract of the
1840
+ * whole feature: `changePassword` and `revokeAllKeys` have no recency gate
1841
+ * today, and adding one for people who never opted in would be a new refusal on
1842
+ * routes that never had one. Two of the callers do have a gate of their own —
1843
+ * the passkey routes' `assertRecentAuthentication` — and they still run it
1844
+ * afterwards; this adds a rule for enrolled accounts rather than replacing one.
1845
+ *
1846
+ * The unenrolled path therefore costs one indexed lookup and touches no passkey
1847
+ * configuration, which is what lets an app with no passkeys at all call it.
1848
+ *
1849
+ * @throws StepUpRequiredError when the account is enrolled and the window has passed
1850
+ */
1851
+ declare function assertStepUp(params: AssertStepUpParams): Promise<void>;
1852
+ /**
1853
+ * Carry a device's verification onto the key that replaces it.
1854
+ *
1855
+ * Called from the two rotation seams — `rotateKeyService`, and the `oldKeyId`
1856
+ * rotation every login path runs through `registerPublicKeyService`. Without
1857
+ * it the window would expire silently on every rotation, which the web proxy
1858
+ * does at each login: a user who stepped up a minute ago would be asked again
1859
+ * with nothing to connect it to.
1860
+ */
1861
+ declare function carryStepUpVerification(userId: number, fromKeyId: string, toKeyId: string): Promise<void>;
1862
+ interface OpenStepUpChallengeParams {
1863
+ userId: number;
1864
+ /** The inactive key this challenge would activate. */
1865
+ keyId: string;
1866
+ channel: MfaChallengeChannel;
1867
+ /** The account's key generation right now — the challenge dies with it. */
1868
+ keyEpoch: number;
1869
+ /** The login announcement the 202 is holding back, when the channel has one. */
1870
+ loginEvent?: DeferredLoginEvent;
1871
+ }
1872
+ /**
1873
+ * Mint the challenge a stopped registration hands back.
1874
+ *
1875
+ * The secret is returned once and never stored: only its hash reaches the row,
1876
+ * so a database dump does not yield a spendable challenge, and `verify` finds a
1877
+ * row only for a caller who already had the secret. The row id is not in the
1878
+ * answer at all — it is a sequence, and a sequence on an unauthenticated route
1879
+ * is a thing an attacker walks.
1880
+ */
1881
+ declare function openStepUpChallengeService(params: OpenStepUpChallengeParams): Promise<MfaChallengeHandle>;
1882
+ /**
1883
+ * The challenge already outstanding for this key, re-secreted, or null.
1884
+ *
1885
+ * Registering the same keyId twice is an ordinary path, not a collision: a
1886
+ * native client reuses its keyId, and an OAuth state replayed from the back
1887
+ * button carries the one it was sealed with. Answering 409 there would refuse a
1888
+ * caller for holding a key that is their own and is waiting on them.
1889
+ *
1890
+ * The row is reused rather than replaced, so the retry inherits the attempts
1891
+ * already spent and the expiry already ticking — retrying is not a way around
1892
+ * either. Only the secret is new, because the first one exists nowhere: the row
1893
+ * holds its hash, and that is the property that keeps a database dump from
1894
+ * yielding a spendable challenge.
1895
+ */
1896
+ declare function resumeStepUpChallengeService(userId: number, keyId: string): Promise<MfaChallengeHandle | null>;
1897
+ interface VerifyMfaChallengeParams {
1898
+ /** The secret from the 202 body, the callback query, or the pending page. */
1899
+ challenge: string;
1900
+ code?: string;
1901
+ recoveryCode?: string;
1902
+ response?: AuthenticationResponseJSON;
1903
+ }
1904
+ /**
1905
+ * The sign-in the challenge was standing in for, plus what the proxy needs.
1906
+ *
1907
+ * `keyId` and `challengeHash` are for the Next.js interceptor and nothing else:
1908
+ * it seals a session only when both match the pending cookie it baked at the
1909
+ * 202, which is what stops a cookie minted for one flow from sealing a session
1910
+ * around another flow's key. Neither is a credential — the hash is what the
1911
+ * server already stores, and the key is inactive to anyone without the private
1912
+ * half the proxy is holding.
1913
+ */
1914
+ interface VerifyMfaChallengeResult extends LoginResult {
1915
+ keyId: string;
1916
+ challengeHash: string;
1917
+ }
1918
+ /**
1919
+ * Spend a new-device challenge, which activates the key and starts the session.
1920
+ *
1921
+ * Unauthenticated by construction: the key this would activate is the only one
1922
+ * the caller has and it cannot sign anything yet. The challenge secret is the
1923
+ * whole credential, and it authorizes exactly this — no other route reads it.
1924
+ *
1925
+ * The verified mark is a conditional UPDATE, so two requests carrying the same
1926
+ * secret produce one session and one 401. The key is activated only after that
1927
+ * mark is won, which is what makes "a pending challenge never yields a usable
1928
+ * key" true even under a race.
1929
+ *
1930
+ * The binding the answer carries was decided at **registration**, not here, and
1931
+ * is read back off the key row. It has to be: the decision reads the owner's
1932
+ * `session_binding` setting together with whether the request came through the
1933
+ * trusted Next.js proxy, and this route is unauthenticated and may be called
1934
+ * from anywhere — deciding it here would let a direct caller ask for a cookie
1935
+ * that believes a bound key is an ordinary one. A bound account that steps up
1936
+ * therefore gets exactly the binding and the expiry its sign-in registered, and
1937
+ * the ten minutes a challenge may sit for come out of that key's short life.
1938
+ *
1939
+ * @throws ValidationError when the body names none or more than one proof
1940
+ * @throws MfaVerificationFailedError for every other refusal, in one body
1941
+ */
1942
+ declare function verifyMfaChallengeService(params: VerifyMfaChallengeParams): Promise<VerifyMfaChallengeResult>;
1943
+ /**
1944
+ * Options for finishing a new-device step-up with a passkey.
1945
+ *
1946
+ * The step-up challenge stands in for the session this caller does not have yet:
1947
+ * it is what names the account, so the WebAuthn challenge can be minted for the
1948
+ * right owner without the request having to say who that is. `allowCredentials`
1949
+ * is empty for the reason it is everywhere else here (D3), and the ceremony kind
1950
+ * is `'mfa'`, so what comes back cannot be spent as a sign-in.
1951
+ *
1952
+ * @throws MfaVerificationFailedError when the challenge cannot be spent
1953
+ */
1954
+ declare function startMfaChallengeAssertionService(challenge: string): Promise<PublicKeyCredentialRequestOptionsJSON>;
1955
+ /**
1956
+ * Drop expired and spent challenges, and the keys they were holding.
1957
+ *
1958
+ * A pending key is unusable by construction, but it is still a key row on an
1959
+ * account nobody is watching, and its challenge is what the owner would be shown
1960
+ * if anything ever listed it. Both go once the challenge can no longer do
1961
+ * anything. A spent one is kept for the same span as an expired one, so a replay
1962
+ * inside the window is answered "already verified" from a row rather than
1963
+ * "unknown" from an absence — the same 401 either way, but the record survives
1964
+ * long enough to be read in a log.
1965
+ *
1966
+ * @returns number of challenge rows deleted
1967
+ */
1968
+ declare function sweepMfaChallengesService(): Promise<{
1969
+ deleted: number;
1970
+ }>;
1971
+ interface TotpEnrolmentResult {
1972
+ /** The base32 secret, shown once. Never logged, never returned again. */
1973
+ secret: string;
1974
+ /** The same secret as the URI an authenticator app scans. */
1975
+ otpauthUri: string;
1976
+ }
1977
+ /**
1978
+ * Mint a secret and park it unconfirmed.
1979
+ *
1980
+ * Calling this twice replaces the unconfirmed row and its failure counter — a
1981
+ * user who closed the app before scanning starts over, which is also the
1982
+ * documented remedy for a row that spent its five confirm attempts. A
1983
+ * **confirmed** enrolment is refused instead: replacing a working second factor
1984
+ * is `disable` followed by a fresh enrolment, both step-up gated, so nobody
1985
+ * holding only a stolen session can swap an authenticator for their own.
1986
+ *
1987
+ * @throws MfaAlreadyEnrolledError when a confirmed enrolment already exists
1988
+ */
1989
+ declare function startTotpEnrolmentService(userId: number): Promise<TotpEnrolmentResult>;
1990
+ interface ConfirmTotpParams {
1991
+ userId: number;
1992
+ /** The device this enrolment is being confirmed from — it gets the first verification. */
1993
+ keyId: string;
1994
+ code: string;
1995
+ }
1996
+ interface ConfirmTotpResult {
1997
+ /** The ten plaintext codes, shown once. */
1998
+ recoveryCodes: string[];
1999
+ }
2000
+ /**
2001
+ * Spend the first code, which is what turns an enrolment into a second factor.
2002
+ *
2003
+ * Five wrong codes delete the unconfirmed row: at that point the person is
2004
+ * reading the wrong entry in their app, and a fresh `enroll` is both the remedy
2005
+ * and what resets the counter. A confirmed row is never deleted this way.
2006
+ *
2007
+ * The route deliberately runs this outside a transaction, so the counter
2008
+ * survives the refusal that raised it; the success path opens its own, because
2009
+ * the confirmation, the first verification and the recovery codes have to
2010
+ * commit together or not at all.
2011
+ *
2012
+ * @throws MfaNotEnrolledError | MfaVerificationFailedError
2013
+ */
2014
+ declare function confirmTotpEnrolmentService(params: ConfirmTotpParams): Promise<ConfirmTotpResult>;
2015
+ /**
2016
+ * Replace the account's recovery codes, retiring every earlier one.
2017
+ *
2018
+ * The old generation's rows stay, unspent and unreachable: a code written down
2019
+ * last year is refused with the same body as one that never existed, and the
2020
+ * record of which generation a spent code came from survives.
2021
+ *
2022
+ * @throws MfaNotEnrolledError on an account with no second factor
2023
+ */
2024
+ declare function regenerateRecoveryCodesService(userId: number): Promise<{
2025
+ recoveryCodes: string[];
2026
+ }>;
2027
+ /**
2028
+ * Take the second factor off the account entirely.
2029
+ *
2030
+ * Idempotent: an account with nothing enrolled is a success, because "make sure
2031
+ * MFA is off" has already happened. The passkeys themselves are untouched —
2032
+ * only their second-factor marks go, since a credential that signs the owner in
2033
+ * is not something this route may remove.
2034
+ */
2035
+ declare function disableMfaService(userId: number): Promise<void>;
2036
+ interface MarkPasskeyParams {
2037
+ userId: number;
2038
+ passkeyId: string;
2039
+ secondFactor: boolean;
2040
+ }
2041
+ /**
2042
+ * Mark or unmark one of the caller's passkeys as their second factor.
2043
+ *
2044
+ * Unmarking the last one is allowed, and leaves the account unenrolled. It is
2045
+ * deliberately independent of `assertNotLastRecoveryCredential`, which guards
2046
+ * `passkeys/revoke`: that rule protects a way *into* the account, and a
2047
+ * second-factor mark is not one — the credential still signs the owner in
2048
+ * afterwards, unchanged.
2049
+ *
2050
+ * @throws PasskeyNotFoundError for a credential that is not theirs, or revoked
2051
+ */
2052
+ declare function markPasskeySecondFactorService(params: MarkPasskeyParams): Promise<MfaStatus>;
2053
+ /** What the account surface shows about the second factor. No secret is in it. */
2054
+ interface MfaStatus {
2055
+ enrolled: boolean;
2056
+ /** Which second factors are live: `'totp'`, `'passkey'`, or both. */
2057
+ methods: ('totp' | 'passkey')[];
2058
+ /** Unspent codes of the current generation, for the "2 left" warning. */
2059
+ recoveryCodesRemaining: number;
2060
+ }
2061
+ /**
2062
+ * The account's second-factor state.
2063
+ *
2064
+ * Carries no secret, no otpauth URI and no recovery code — only the counts and
2065
+ * names an account screen needs. An unconfirmed enrolment is not a method: it
2066
+ * gates nothing, so reporting it would tell the owner they are protected when
2067
+ * they are not.
2068
+ */
2069
+ declare function mfaStatusService(userId: number): Promise<MfaStatus>;
2070
+ /**
2071
+ * Options for a step-up by passkey assertion.
2072
+ *
2073
+ * `allowCredentials` is empty, as it is for a sign-in and for the same reason
2074
+ * (D3): the discoverable credential on the device names itself, and the owner
2075
+ * and the second-factor mark are checked when the assertion comes back. The
2076
+ * challenge is minted with kind `'mfa'` and this account's id, so it cannot be
2077
+ * presented to `passkeys/login/verify` and a sign-in challenge cannot be
2078
+ * presented here.
2079
+ */
2080
+ declare function startStepUpService(userId: number): Promise<PublicKeyCredentialRequestOptionsJSON>;
2081
+ interface StepUpParams {
2082
+ userId: number;
2083
+ /** The device the verification is recorded against. */
2084
+ keyId: string;
2085
+ code?: string;
2086
+ recoveryCode?: string;
2087
+ response?: AuthenticationResponseJSON;
2088
+ }
2089
+ /**
2090
+ * Re-prove the second factor on this device, refreshing its window.
2091
+ *
2092
+ * The escape hatch the exempt registration channels need: a device-code
2093
+ * approval and a passkey sign-in register a key with no verification against
2094
+ * it, by design, so the session they create would otherwise fail every
2095
+ * sensitive change with no way forward.
2096
+ *
2097
+ * @throws ValidationError when the body names none or more than one input
2098
+ * @throws MfaNotEnrolledError | MfaVerificationFailedError
2099
+ */
2100
+ declare function stepUpService(params: StepUpParams): Promise<void>;
2101
+ /**
2102
+ * Check exactly one of the three proofs, and say which one it was — or null.
2103
+ *
2104
+ * Exactly one: two inputs in a body is a caller trying combinations, and none
2105
+ * is a malformed request. Neither is a failed verification, so both are a 400
2106
+ * rather than the uniform 401 a wrong proof gets.
2107
+ *
2108
+ * Null rather than a throw for the failure itself, because one caller has
2109
+ * bookkeeping to do before it refuses: the new-device challenge counts the
2110
+ * attempt, and counting it inside a `catch` around the thing that threw would be
2111
+ * two control flows for one answer.
2112
+ *
2113
+ * @throws ValidationError when the body names none or more than one input
2114
+ */
2115
+ declare function attemptSecondFactor(params: StepUpParams): Promise<MfaVerificationMethod | null>;
2116
+ /**
2117
+ * `attemptSecondFactor`, for the callers whose only answer to a wrong proof is
2118
+ * the uniform 401.
2119
+ *
2120
+ * @throws ValidationError | MfaVerificationFailedError
2121
+ */
2122
+ declare function verifySecondFactor(params: StepUpParams): Promise<MfaVerificationMethod>;
2123
+ /**
2124
+ * Drop enrolments nobody ever confirmed.
2125
+ *
2126
+ * A secret handed out and abandoned is a credential sitting in a table doing
2127
+ * nothing; a day is long enough for anyone who meant to finish.
2128
+ *
2129
+ * @returns number of rows deleted
2130
+ */
2131
+ declare function sweepUnconfirmedMfaService(): Promise<{
2132
+ deleted: number;
2133
+ }>;
2134
+
2135
+ /**
2136
+ * @spfn/auth - Key Service
2137
+ *
2138
+ * Handles public key registration, rotation, and revocation
2139
+ */
2140
+
2141
+ interface RegisterPublicKeyParams {
2142
+ userId: number;
2143
+ keyId: string;
2144
+ publicKey: string;
2145
+ fingerprint: string;
2146
+ algorithm?: KeyAlgorithmType;
2147
+ /** Device label for the key list. Display only — nothing is authorized by it. */
2148
+ deviceName?: string;
2149
+ platform?: KeyPlatformType;
2150
+ /**
2151
+ * Which door this device came through. Required, so that a registration path
2152
+ * added later has to say what it is rather than inherit an answer.
2153
+ */
2154
+ channel: DeviceRegistrationChannel;
2155
+ /** Client address of the registering request, absent when none resolved. */
2156
+ ip?: string;
2157
+ /** `user-agent` of the registering request, already truncated. */
2158
+ userAgent?: string;
2159
+ /**
2160
+ * Whether this key is bound to a passkey, as the registering path decided.
2161
+ *
2162
+ * Decided by the caller from two facts it is the only one holding: the
2163
+ * owner's `session_binding` setting, and whether `proxy-guard` recognised the
2164
+ * request as coming through the trusted Next.js proxy (`decideKeyBinding`).
2165
+ * Never read off a request body, and `platform` is not consulted — that field
2166
+ * is display-only, usually absent on a proxy-made key, and a native client
2167
+ * may declare `'web'` freely. Omitted means `'none'`, which is what every
2168
+ * path that has no opinion gets.
2169
+ */
2170
+ binding?: SessionBindingType;
2171
+ /**
2172
+ * The key this one replaces on the same device, when a revocation actually
2173
+ * happened. Its presence is what makes this a rotation rather than a new
2174
+ * device, so no event is announced for it.
2175
+ *
2176
+ * Only ever set from a `revokeKeyService` that returned true: an `oldKeyId`
2177
+ * naming somebody else's key, an already-revoked key or nothing at all
2178
+ * revokes nothing, and treating that as a rotation would be a way to
2179
+ * register a device with the owner's notice switched off.
2180
+ */
2181
+ replacesKeyId?: string;
2182
+ /**
2183
+ * What to announce if this registration is stopped and later verified (#95).
2184
+ *
2185
+ * Read only on a channel that can step up, and only when it does. The login
2186
+ * event has to fire once and with the original channel's payload, and the
2187
+ * app's own `metadata` and the social provider that carried it are gone by
2188
+ * the time `POST /_auth/mfa/verify` runs — so the announcement rides the
2189
+ * challenge row rather than being guessed at from the user row.
2190
+ */
2191
+ loginEvent?: DeferredLoginEvent;
2192
+ }
2193
+ /**
2194
+ * What a registration settled on: a key, or a second factor still to prove.
2195
+ *
2196
+ * The `pending` branch is the 202 (#95). Its callers answer it to the client and
2197
+ * stop — no session, no event, no `lastLoginAt` — and everything they would have
2198
+ * done happens at `verify` instead.
2199
+ */
2200
+ type RegisterPublicKeyResult = ({
2201
+ pending: false;
2202
+ } & RegisteredKeyBinding) | {
2203
+ pending: true;
2204
+ challenge: MfaChallengeHandle;
2205
+ };
2206
+ /**
2207
+ * The binding half of a registration that could not have been stepped up.
2208
+ *
2209
+ * Only four channels can answer `pending`, so a caller on any other one is
2210
+ * reading a case its channel cannot reach. Throwing rather than defaulting is
2211
+ * the honest answer if that ever stops being true: a silent `{}` would seal an
2212
+ * unbound session for a key nobody had proved, which is the one outcome this
2213
+ * whole feature exists to prevent.
2214
+ */
2215
+ declare function registeredBinding(result: RegisterPublicKeyResult): RegisteredKeyBinding;
2216
+ interface RotateKeyParams {
2217
+ userId: number;
2218
+ oldKeyId: string;
2219
+ newKeyId: string;
2220
+ newPublicKey: string;
2221
+ fingerprint: string;
2222
+ algorithm?: KeyAlgorithmType;
2223
+ /** Omitted: the replaced key's label carries over, so rotation keeps its name. */
2224
+ deviceName?: string;
2225
+ platform?: KeyPlatformType;
2226
+ }
2227
+ interface RotateKeyResult {
2228
+ success: boolean;
2229
+ keyId: string;
2230
+ }
2231
+ interface RevokeKeyParams {
2232
+ userId: number;
1729
2233
  keyId: string;
1730
2234
  reason: string;
1731
2235
  }
@@ -1785,6 +2289,28 @@ interface KeySummary {
1785
2289
  registeredIp?: string;
1786
2290
  /** `user-agent` of the registering request, on the same terms as above. */
1787
2291
  registeredUserAgent?: string;
2292
+ /**
2293
+ * `'passkey'` when this key is bound to one, absent when it is not.
2294
+ *
2295
+ * Absent rather than `'none'`, so a deployment where nobody opted in answers
2296
+ * exactly the list it always did, and so every consumer reads "unbound" the
2297
+ * same way it reads it off a sign-in response.
2298
+ */
2299
+ binding?: SessionBindingType;
2300
+ /**
2301
+ * When this key was last seen from two client addresses inside the
2302
+ * concurrent-use window, absent when that has never been observed.
2303
+ *
2304
+ * A signal for the owner, not a refusal: addresses change legitimately, so
2305
+ * nothing is blocked by it and a device list that shows it is telling someone
2306
+ * to look rather than telling them something happened. The addresses
2307
+ * themselves are never returned.
2308
+ *
2309
+ * Only meaningful where proxy-guard is configured. Without it every web
2310
+ * request carries the Next.js server's own address, so two browsers on two
2311
+ * continents share one and this never fires.
2312
+ */
2313
+ concurrentUseAtMillis?: number;
1788
2314
  }
1789
2315
  interface ListKeysParams {
1790
2316
  userId: number;
@@ -1793,6 +2319,20 @@ interface ListKeysParams {
1793
2319
  }
1794
2320
  /** How much of the fingerprint the list returns. */
1795
2321
  declare const KEY_FINGERPRINT_PREFIX_LENGTH = 8;
2322
+ /**
2323
+ * What a registration settled on, for the caller that has to tell the browser.
2324
+ *
2325
+ * The sign-in paths put these two into their `LoginResult`, the Next.js proxy
2326
+ * copies them into the sealed cookie, and that is the whole carrier by which the
2327
+ * proxy learns a session is bound and when its key runs out. Returned rather than
2328
+ * looked up again: the row was just written (or just read), so a second query
2329
+ * would be a second answer to a question already settled.
2330
+ */
2331
+ interface RegisteredKeyBinding {
2332
+ binding: SessionBindingType;
2333
+ /** null only for a key registered before expiry was stamped at all. */
2334
+ expiresAt: Date | null;
2335
+ }
1796
2336
  /**
1797
2337
  * Register a new public key for a user
1798
2338
  *
@@ -1801,11 +2341,16 @@ declare const KEY_FINGERPRINT_PREFIX_LENGTH = 8;
1801
2341
  * index, rolling the whole login transaction back into a 500. Reuse is refused
1802
2342
  * with a domain error instead, telling the client to generate a fresh keyId.
1803
2343
  *
2344
+ * An enrolled account arriving on a new device gets the key written **inactive**
2345
+ * and a challenge back (#95). Nothing else happens: no device event, no login
2346
+ * event, no `lastLoginAt` — those are what the challenge is holding, and they
2347
+ * fire at `POST /_auth/mfa/verify` or not at all.
2348
+ *
1804
2349
  * @throws KeyIdAlreadyRegisteredError keyId가 이미 쓰인 값일 때 (자기 폐기 키 재사용 · 남의 키)
1805
2350
  * @throws InvalidKeyFingerprintError fingerprint가 publicKey와 맞지 않을 때
1806
2351
  * @throws KeyAlgorithmMismatchError 키의 SPKI 타입이 선언된 algorithm과 다를 때
1807
2352
  */
1808
- declare function registerPublicKeyService(params: RegisterPublicKeyParams): Promise<void>;
2353
+ declare function registerPublicKeyService(params: RegisterPublicKeyParams): Promise<RegisterPublicKeyResult>;
1809
2354
  /**
1810
2355
  * Rotate user's public key (revoke old, register new)
1811
2356
  *
@@ -2163,12 +2708,23 @@ interface OAuthCallbackParams {
2163
2708
  ip?: string;
2164
2709
  /** `user-agent` of the callback request, already truncated at the route. */
2165
2710
  userAgent?: string;
2711
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
2712
+ webProxy?: boolean;
2166
2713
  }
2167
2714
  interface OAuthCallbackResult {
2168
2715
  redirectUrl: string;
2169
2716
  userId: string;
2170
2717
  keyId: string;
2171
2718
  isNewUser: boolean;
2719
+ /**
2720
+ * The step-up challenge this callback is carrying, when it is carrying one.
2721
+ *
2722
+ * Present exactly when the redirect names `mfaChallenge` instead of
2723
+ * `userId`/`keyId` (#95). Returned beside the URL so a caller that does not
2724
+ * parse the query — a test, or an app mounting its own handler — can tell
2725
+ * the two redirects apart.
2726
+ */
2727
+ mfaChallenge?: string;
2172
2728
  }
2173
2729
  /**
2174
2730
  * registry에서 provider를 찾아 사용 가능한지 검증 후 반환
@@ -2264,16 +2820,35 @@ interface OAuthNativeParams {
2264
2820
  ip?: string;
2265
2821
  /** `user-agent` of the request, already truncated at the route. */
2266
2822
  userAgent?: string;
2823
+ /**
2824
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
2825
+ * helper. Accepted because every registering route spreads the whole
2826
+ * provenance, and read by nothing: a native sign-in never produces a bound
2827
+ * key — see the note at the registration below.
2828
+ */
2829
+ webProxy?: boolean;
2267
2830
  /** Apple은 첫 로그인에만 이름을 별도로 주므로 클라이언트가 전달할 수 있다. */
2268
2831
  profile?: {
2269
2832
  name?: string;
2270
2833
  };
2271
2834
  metadata?: Record<string, unknown>;
2272
2835
  }
2836
+ /**
2837
+ * What a native social sign-in answers with.
2838
+ *
2839
+ * Shaped like `LoginResult` and for the same reason (#95): this channel steps up
2840
+ * too, so the answer is one type carrying a required discriminant and optional
2841
+ * fields rather than a union the typed client and the mobile contract could not
2842
+ * both express. Narrow on `mfaRequired` before reading `userId`.
2843
+ */
2273
2844
  interface OAuthNativeResult {
2274
- userId: string;
2275
- keyId: string;
2276
- isNewUser: boolean;
2845
+ /** true means no key was activated: verify the challenge below first. */
2846
+ mfaRequired: boolean;
2847
+ /** Present exactly when `mfaRequired` is true. */
2848
+ challenge?: MfaChallengeHandle;
2849
+ userId?: string;
2850
+ keyId?: string;
2851
+ isNewUser?: boolean;
2277
2852
  }
2278
2853
  /**
2279
2854
  * native id_token 로그인 처리
@@ -2415,6 +2990,219 @@ declare function revokeOAuth2GrantService(id: number, userId: number): Promise<v
2415
2990
  */
2416
2991
  declare function revokeAllOAuth2GrantsForUser(userId: number): Promise<void>;
2417
2992
 
2993
+ /**
2994
+ * @spfn/auth - Session Binding Service
2995
+ *
2996
+ * The opt-in from #97: an account says its web sessions should run on a key that
2997
+ * expires in hours and can only be renewed by a fresh WebAuthn assertion. A
2998
+ * session cookie copied off that machine — out of a browser profile, out of
2999
+ * DevTools, by malware — still signs like the original until that key runs out,
3000
+ * and then stops, because the copy cannot produce the assertion.
3001
+ *
3002
+ * Three rules shape this file.
3003
+ *
3004
+ * It can only be turned on where the backend can recognise the trusted Next.js
3005
+ * proxy. `clientType` is that recognition and `proxy-guard` is what sets it;
3006
+ * without it every key would be registered unbound and the setting would be a
3007
+ * switch that reports success and protects nothing, so the request is refused as
3008
+ * the configuration error it is.
3009
+ *
3010
+ * Turning it *off* is the privileged direction, which is the reverse of the
3011
+ * usual posture. `assertRecentAuthentication` is satisfied by the age of the
3012
+ * device key this request is signed with — and a cookie copied in the ten
3013
+ * minutes after a sign-in carries exactly that. So leaving `'passkey'` asks for
3014
+ * a credential the copy does not hold: a renewal-grade assertion, or the account
3015
+ * password.
3016
+ *
3017
+ * Enabling twice is idempotent. Recomputing the expiry on a repeat call would
3018
+ * make this route a way to extend a bound key without presenting anything, which
3019
+ * is the thing the short life exists to prevent.
3020
+ */
3021
+
3022
+ /** What every one of the three reads and writes here answers with. */
3023
+ interface SessionBindingResult {
3024
+ mode: SessionBindingType;
3025
+ /**
3026
+ * When the key this request is signed with expires, for a bound session.
3027
+ *
3028
+ * Absent when the account is unbound, and absent for a bound account asking
3029
+ * from a key that is not itself bound — a native client, say, whose key was
3030
+ * registered on a channel the proxy never touched. The response is the input
3031
+ * the Next.js interceptor re-seals the cookie from, so it describes this
3032
+ * session rather than the account.
3033
+ */
3034
+ keyExpiresAtMillis?: number;
3035
+ }
3036
+ interface SessionBindingParams {
3037
+ userId: number;
3038
+ /** The device key this request is signed with — the one binding is applied to. */
3039
+ keyId: string;
3040
+ /** Whether proxy-guard recognised the trusted Next.js proxy. */
3041
+ webProxy?: boolean;
3042
+ }
3043
+ interface DisableSessionBindingParams extends SessionBindingParams {
3044
+ /** A renewal-grade assertion, from `binding/disable/options`. */
3045
+ response?: AuthenticationResponseJSON;
3046
+ /** The account password, for a browser with no passkey to hand. */
3047
+ currentPassword?: string;
3048
+ }
3049
+ /** What binding is on this account, and when this session's key runs out. */
3050
+ declare function getSessionBindingService(params: SessionBindingParams): Promise<SessionBindingResult>;
3051
+ /**
3052
+ * The binding facts of one key, by key id alone.
3053
+ *
3054
+ * For `/_auth/oauth/finalize`, which is the one sealing path with no session and
3055
+ * no `LoginResult` to read them off: the browser arrives at the callback page
3056
+ * holding a key id the OAuth state minted, and the interceptor seals a session
3057
+ * from the answer. Reflecting the caller's own claim about binding would let a
3058
+ * caller ask for an unbound cookie over a bound key, so the row is read instead.
3059
+ *
3060
+ * Scoped by key id and nothing else. The id is a server-minted UUID that only
3061
+ * ever travelled inside the sealed state, so a caller who holds one holds it
3062
+ * because the flow gave it to them; adding the caller's own `userId` to the
3063
+ * lookup would make the answer vary by a second guessable value without
3064
+ * withholding anything from someone who already has the first.
3065
+ */
3066
+ declare function keySessionBindingService(keyId: string): Promise<{
3067
+ sessionBinding?: SessionBindingType;
3068
+ keyExpiresAtMillis?: number;
3069
+ }>;
3070
+ /**
3071
+ * Turn session binding on, and bind the key this request is signed with.
3072
+ *
3073
+ * The current key is bound here rather than only at the next sign-in, because
3074
+ * otherwise the setting would not apply to the session that asked for it until
3075
+ * the person signed in again — and the response is what tells the Next.js proxy
3076
+ * to re-seal the cookie with the new expiry, so the browser and the key row
3077
+ * agree from this moment on.
3078
+ *
3079
+ * @throws SessionBindingUnavailableError proxy-guard가 설정되지 않은 배포일 때
3080
+ * @throws ValidationError 살아 있는 패스키가 하나도 없을 때
3081
+ * @throws RecentAuthenticationRequiredError 최근 인증이 없을 때
3082
+ */
3083
+ declare function enableSessionBindingService(params: SessionBindingParams): Promise<SessionBindingResult>;
3084
+ /**
3085
+ * Turn session binding off, once the caller has proved they are still the owner.
3086
+ *
3087
+ * Every bound key returns to an ordinary ninety-day life in the same call. A row
3088
+ * left marked `'passkey'` would keep expiring in hours with nothing left to
3089
+ * renew it, which is the state the person just asked not to be in.
3090
+ *
3091
+ * @throws RecentAuthenticationRequiredError 새 자격증명 없이 껐을 때
3092
+ */
3093
+ declare function disableSessionBindingService(params: DisableSessionBindingParams): Promise<SessionBindingResult>;
3094
+ /** The challenge the disabling ceremony signs. Same ceremony renewal uses. */
3095
+ declare function startSessionBindingDisableService(userId: number): Promise<PublicKeyCredentialRequestOptionsJSON>;
3096
+
3097
+ /**
3098
+ * @spfn/auth - Session Renewal Service
3099
+ *
3100
+ * What a bound session does when its key runs out: prove, with a fresh WebAuthn
3101
+ * assertion, that the person who enrolled the passkey is still at the machine,
3102
+ * and get a new short-lived key sealed into the cookie.
3103
+ *
3104
+ * Neither step is public. The expiring key is named by `expiredKeyId`, and that
3105
+ * value reaches the service from `authenticateForRenewal` — the `keyId` of a
3106
+ * bearer JWT this very key signed — rather than from the request body, so a
3107
+ * caller who does not hold the private half cannot name a key at all. The
3108
+ * assertion still has to be signed by a passkey that key's owner enrolled: the
3109
+ * signature proves the cookie, and the cookie is the thing that may have been
3110
+ * copied.
3111
+ *
3112
+ * The admission below is run again here all the same. The middleware and the
3113
+ * service ask the same four questions of the row, and a service that trusted its
3114
+ * caller to have asked them would be one refactor away from not being asked at
3115
+ * all.
3116
+ *
3117
+ * Every refusal is the same refusal. A key that never existed, a stranger's key,
3118
+ * an unbound key, a revoked one, one past its grace, an inactive account, a spent
3119
+ * challenge, an assertion that did not verify — all `SessionRenewalRefusedError`,
3120
+ * with the same body, because anything finer would answer "is this key id live"
3121
+ * to whoever asked.
3122
+ *
3123
+ * Renewal announces nothing. No `auth.login`, no `auth.device.registered`, and
3124
+ * `lastLoginAt` does not move: this is the same person on the same device
3125
+ * continuing the session they already had, and a subscriber mailing "new sign-in"
3126
+ * once a day per device would train its reader to ignore the notice that matters.
3127
+ * A `lastLoginAt` that moved every day would make dormant-account detection
3128
+ * meaningless for exactly the accounts that turned this protection on.
3129
+ */
3130
+
3131
+ interface StartSessionRenewParams {
3132
+ /** The key that ran out, read off the JWT the request was signed with. */
3133
+ expiredKeyId: string;
3134
+ }
3135
+ interface FinishSessionRenewParams extends StartSessionRenewParams {
3136
+ /** The assertion, from `navigator.credentials.get()`. */
3137
+ response: AuthenticationResponseJSON;
3138
+ /**
3139
+ * The new key pair, in the vocabulary the Next.js login interceptor already
3140
+ * writes: `renew/verify` is on that interceptor's path list, so these arrive
3141
+ * exactly as they do on a login.
3142
+ */
3143
+ keyId: string;
3144
+ publicKey: string;
3145
+ fingerprint: string;
3146
+ algorithm?: KeyAlgorithmType;
3147
+ }
3148
+ /**
3149
+ * What a completed renewal answers: a sign-in result, plus the new key's id.
3150
+ *
3151
+ * The id is the one thing a renewal has that a sign-in does not need to say —
3152
+ * `renewSession()` promises it to the app, which has no other way to learn it
3153
+ * (the key pair is minted in the proxy and the private half never leaves the
3154
+ * cookie). It is not a contract operation, so nothing generated reads it.
3155
+ */
3156
+ interface SessionRenewResult extends LoginResult {
3157
+ /** The key this renewal registered, the one the session now signs with. */
3158
+ keyId: string;
3159
+ }
3160
+ /**
3161
+ * Step 1 — the challenge the authenticator signs.
3162
+ *
3163
+ * `allowCredentials` is empty and the account lives only on the challenge row.
3164
+ * See `startRenewalCeremonyService`.
3165
+ *
3166
+ * @throws SessionRenewalRefusedError 갱신할 수 없는 키·계정일 때 (모든 사유 동일)
3167
+ */
3168
+ declare function startSessionRenewService(params: StartSessionRenewParams): Promise<PublicKeyCredentialRequestOptionsJSON>;
3169
+ /**
3170
+ * Step 2 — verify the assertion, put a new bound key in place of the old one.
3171
+ *
3172
+ * The revocation runs first and its answer is the race winner: two verifies that
3173
+ * both got past their own challenges meet at the same conditional UPDATE, and
3174
+ * only the one that actually revoked the key goes on to register a replacement.
3175
+ *
3176
+ * The new key inherits the old row's provenance, so the device list keeps saying
3177
+ * where this device first appeared rather than re-stamping itself every day. Its
3178
+ * expiry is a fresh window from now — renewal is a renewal, not an extension of
3179
+ * what the old key had.
3180
+ *
3181
+ * @throws SessionRenewalRefusedError 갱신할 수 없을 때 (증명 실패 포함, 모든 사유 동일)
3182
+ */
3183
+ declare function finishSessionRenewService(params: FinishSessionRenewParams): Promise<SessionRenewResult>;
3184
+
3185
+ /**
3186
+ * What `POST /_auth/oauth/finalize` answers with, in both of its branches.
3187
+ *
3188
+ * One type with a required discriminant rather than a union, on the same
3189
+ * reasoning as `LoginResult` (#95): `authApi.oauthFinalize` infers its result
3190
+ * from this, and a union would make every `result.userId` in every callback page
3191
+ * stop compiling. `mfaRequired` false is the 200 and carries `userId`/`keyId`;
3192
+ * true is the 202 and carries `challenge`.
3193
+ */
3194
+ interface OAuthFinalizeResponse {
3195
+ success: boolean;
3196
+ mfaRequired: boolean;
3197
+ /** The second-factor challenge, echoed back. Present exactly on the 202. */
3198
+ challenge?: string;
3199
+ userId?: string;
3200
+ keyId?: string;
3201
+ returnUrl: string;
3202
+ sessionBinding?: SessionBindingType;
3203
+ keyExpiresAtMillis?: number;
3204
+ }
3205
+
2418
3206
  /**
2419
3207
  * @spfn/auth - Main Router
2420
3208
  *
@@ -2522,7 +3310,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2522
3310
  deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2523
3311
  platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
2524
3312
  }>;
2525
- }, RegisterResult>;
3313
+ }, LoginResult>;
2526
3314
  login: _spfn_core_route.RouteDef<{
2527
3315
  body: _sinclair_typebox.TObject<{
2528
3316
  email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
@@ -2560,7 +3348,10 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2560
3348
  } | {
2561
3349
  email?: string | undefined;
2562
3350
  phone?: string | undefined;
3351
+ sessionBinding?: "none" | "passkey" | undefined;
3352
+ keyExpiresAtMillis?: number | undefined;
2563
3353
  status: "approved";
3354
+ mfaRequired: boolean;
2564
3355
  userId: string;
2565
3356
  publicId: string;
2566
3357
  passwordChangeRequired: boolean;
@@ -2664,6 +3455,19 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2664
3455
  mfaStepUpOptions: _spfn_core_route.RouteDef<{
2665
3456
  body: _sinclair_typebox.TObject<{}>;
2666
3457
  }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
3458
+ mfaVerify: _spfn_core_route.RouteDef<{
3459
+ body: _sinclair_typebox.TObject<{
3460
+ challenge: _sinclair_typebox.TString;
3461
+ code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3462
+ recoveryCode: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3463
+ response: _sinclair_typebox.TOptional<_sinclair_typebox.TUnknown>;
3464
+ }>;
3465
+ }, {}, VerifyMfaChallengeResult>;
3466
+ mfaVerifyOptions: _spfn_core_route.RouteDef<{
3467
+ body: _sinclair_typebox.TObject<{
3468
+ challenge: _sinclair_typebox.TString;
3469
+ }>;
3470
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
2667
3471
  logout: _spfn_core_route.RouteDef<{}, {}, void>;
2668
3472
  rotateKey: _spfn_core_route.RouteDef<{}, {
2669
3473
  body: _sinclair_typebox.TObject<{
@@ -2736,6 +3540,32 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2736
3540
  phoneVerified: boolean;
2737
3541
  hasPassword: boolean;
2738
3542
  }>;
3543
+ setSessionBinding: _spfn_core_route.RouteDef<{
3544
+ body: _sinclair_typebox.TObject<{
3545
+ mode: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"passkey">, _sinclair_typebox.TLiteral<"none">]>;
3546
+ response: _sinclair_typebox.TOptional<_sinclair_typebox.TUnknown>;
3547
+ currentPassword: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3548
+ }>;
3549
+ }, {}, SessionBindingResult>;
3550
+ getSessionBinding: _spfn_core_route.RouteDef<{}, {}, SessionBindingResult>;
3551
+ sessionBindingDisableOptions: _spfn_core_route.RouteDef<{
3552
+ body: _sinclair_typebox.TObject<{}>;
3553
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
3554
+ sessionRenewOptions: _spfn_core_route.RouteDef<{
3555
+ body: _sinclair_typebox.TObject<{}>;
3556
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
3557
+ sessionRenewVerify: _spfn_core_route.RouteDef<{
3558
+ body: _sinclair_typebox.TObject<{
3559
+ response: _sinclair_typebox.TUnknown;
3560
+ }>;
3561
+ }, {
3562
+ body: _sinclair_typebox.TObject<{
3563
+ publicKey: _sinclair_typebox.TString;
3564
+ keyId: _sinclair_typebox.TString;
3565
+ fingerprint: _sinclair_typebox.TString;
3566
+ algorithm: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>>;
3567
+ }>;
3568
+ }, SessionRenewResult>;
2739
3569
  issueOneTimeToken: _spfn_core_route.RouteDef<{}, {}, IssueOneTimeTokenResult>;
2740
3570
  requestAccountDeletion: _spfn_core_route.RouteDef<{
2741
3571
  body: _sinclair_typebox.TObject<{
@@ -2793,396 +3623,804 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2793
3623
  }>;
2794
3624
  oauthFinalize: _spfn_core_route.RouteDef<{
2795
3625
  body: _sinclair_typebox.TObject<{
2796
- userId: _sinclair_typebox.TString;
2797
- keyId: _sinclair_typebox.TString;
3626
+ userId: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3627
+ keyId: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3628
+ mfaChallenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3629
+ returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3630
+ }>;
3631
+ }, {}, OAuthFinalizeResponse>;
3632
+ oauthProviderStart: _spfn_core_route.RouteDef<{
3633
+ params: _sinclair_typebox.TObject<{
3634
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3635
+ }>;
3636
+ query: _sinclair_typebox.TObject<{
3637
+ state: _sinclair_typebox.TString;
3638
+ }>;
3639
+ }, {}, Response>;
3640
+ oauthProviderCallback: _spfn_core_route.RouteDef<{
3641
+ params: _sinclair_typebox.TObject<{
3642
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3643
+ }>;
3644
+ query: _sinclair_typebox.TObject<{
3645
+ code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3646
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3647
+ error: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3648
+ error_description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3649
+ }>;
3650
+ }, {}, Response>;
3651
+ getProviderOAuthUrl: _spfn_core_route.RouteDef<{
3652
+ params: _sinclair_typebox.TObject<{
3653
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3654
+ }>;
3655
+ body: _sinclair_typebox.TObject<{
2798
3656
  returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3657
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
3658
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3659
+ }>;
3660
+ }, {}, {
3661
+ authUrl: string;
3662
+ }>;
3663
+ oauthNative: _spfn_core_route.RouteDef<{
3664
+ params: _sinclair_typebox.TObject<{
3665
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3666
+ }>;
3667
+ body: _sinclair_typebox.TObject<{
3668
+ idToken: _sinclair_typebox.TString;
3669
+ nonce: _sinclair_typebox.TString;
3670
+ accessToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3671
+ publicKey: _sinclair_typebox.TString;
3672
+ keyId: _sinclair_typebox.TString;
3673
+ fingerprint: _sinclair_typebox.TString;
3674
+ algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
3675
+ deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3676
+ platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
3677
+ profile: _sinclair_typebox.TOptional<_sinclair_typebox.TObject<{
3678
+ name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3679
+ }>>;
3680
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
3681
+ }>;
3682
+ }, {}, OAuthNativeResult>;
3683
+ oauthUnlinkNotify: _spfn_core_route.RouteDef<{
3684
+ params: _sinclair_typebox.TObject<{
3685
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3686
+ }>;
3687
+ }, {}, void | Response>;
3688
+ oauthUnlinkNotifyGet: _spfn_core_route.RouteDef<{
3689
+ params: _sinclair_typebox.TObject<{
3690
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3691
+ }>;
3692
+ }, {}, void | Response>;
3693
+ getInvitation: _spfn_core_route.RouteDef<{
3694
+ params: _sinclair_typebox.TObject<{
3695
+ token: _sinclair_typebox.TString;
3696
+ }>;
3697
+ }, {}, {
3698
+ email: string;
3699
+ role: string;
3700
+ roleDisplayName: string;
3701
+ invitedBy: string;
3702
+ expiresAt: string;
3703
+ metadata: Record<string, any> | undefined;
3704
+ }>;
3705
+ acceptInvitation: _spfn_core_route.RouteDef<{
3706
+ body: _sinclair_typebox.TObject<{
3707
+ token: _sinclair_typebox.TString;
3708
+ password: _sinclair_typebox.TString;
3709
+ }>;
3710
+ }, {
3711
+ body: _sinclair_typebox.TObject<{
3712
+ publicKey: _sinclair_typebox.TString;
3713
+ keyId: _sinclair_typebox.TString;
3714
+ fingerprint: _sinclair_typebox.TString;
3715
+ algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
3716
+ }>;
3717
+ }, {
3718
+ userId: number;
3719
+ email: string;
3720
+ role: string;
3721
+ }>;
3722
+ createInvitation: _spfn_core_route.RouteDef<{
3723
+ body: _sinclair_typebox.TObject<{
3724
+ email: _sinclair_typebox.TString;
3725
+ roleId: _sinclair_typebox.TNumber;
3726
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3727
+ expiresAt: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3728
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TAny>;
3729
+ }>;
3730
+ }, {}, {
3731
+ id: number;
3732
+ email: string;
3733
+ token: string;
3734
+ roleId: number;
3735
+ expiresAt: string;
3736
+ invitationUrl: string;
3737
+ }>;
3738
+ listInvitations: _spfn_core_route.RouteDef<{
3739
+ query: _sinclair_typebox.TObject<{
3740
+ status: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"pending" | "accepted" | "expired" | "cancelled">[]>>;
3741
+ page: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3742
+ limit: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3743
+ }>;
3744
+ }, {}, {
3745
+ invitations: {
3746
+ id: number;
3747
+ email: string;
3748
+ token: string;
3749
+ roleId: number;
3750
+ invitedBy: number;
3751
+ status: "pending" | "accepted" | "expired" | "cancelled";
3752
+ expiresAt: Date;
3753
+ acceptedAt: Date | null;
3754
+ cancelledAt: Date | null;
3755
+ metadata: Record<string, any> | null;
3756
+ createdAt: Date;
3757
+ updatedAt: Date;
3758
+ role: {
3759
+ id: number;
3760
+ name: string;
3761
+ displayName: string;
3762
+ };
3763
+ inviter: {
3764
+ id: number;
3765
+ email: string | null;
3766
+ };
3767
+ }[];
3768
+ total: number;
3769
+ page: number;
3770
+ limit: number;
3771
+ totalPages: number;
3772
+ }>;
3773
+ cancelInvitation: _spfn_core_route.RouteDef<{
3774
+ body: _sinclair_typebox.TObject<{
3775
+ id: _sinclair_typebox.TNumber;
3776
+ reason: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3777
+ }>;
3778
+ }, {}, {
3779
+ cancelledAt: string;
3780
+ }>;
3781
+ resendInvitation: _spfn_core_route.RouteDef<{
3782
+ body: _sinclair_typebox.TObject<{
3783
+ id: _sinclair_typebox.TNumber;
3784
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3785
+ }>;
3786
+ }, {}, {
3787
+ expiresAt: string;
3788
+ }>;
3789
+ deleteInvitation: _spfn_core_route.RouteDef<{
3790
+ body: _sinclair_typebox.TObject<{
3791
+ id: _sinclair_typebox.TNumber;
3792
+ }>;
3793
+ }, {}, void>;
3794
+ getUserProfile: _spfn_core_route.RouteDef<{}, {}, UserProfile>;
3795
+ updateUserProfile: _spfn_core_route.RouteDef<{
3796
+ body: _sinclair_typebox.TObject<{
3797
+ displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3798
+ firstName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3799
+ lastName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3800
+ avatarUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3801
+ bio: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3802
+ locale: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3803
+ timezone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3804
+ dateOfBirth: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3805
+ gender: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3806
+ website: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3807
+ location: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3808
+ company: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3809
+ jobTitle: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3810
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TAny>>;
3811
+ }>;
3812
+ }, {}, ProfileInfo>;
3813
+ checkUsername: _spfn_core_route.RouteDef<{
3814
+ query: _sinclair_typebox.TObject<{
3815
+ username: _sinclair_typebox.TString;
3816
+ }>;
3817
+ }, {}, {
3818
+ available: boolean;
3819
+ }>;
3820
+ updateUsername: _spfn_core_route.RouteDef<{
3821
+ body: _sinclair_typebox.TObject<{
3822
+ username: _sinclair_typebox.TUnion<[_sinclair_typebox.TString, _sinclair_typebox.TNull]>;
3823
+ }>;
3824
+ }, {}, {
3825
+ deletedAt: Date | null;
3826
+ deletedBy: string | null;
3827
+ createdAt: Date;
3828
+ updatedAt: Date;
3829
+ id: number;
3830
+ publicId: string;
3831
+ email: string | null;
3832
+ phone: string | null;
3833
+ username: string | null;
3834
+ passwordHash: string | null;
3835
+ passwordChangeRequired: boolean;
3836
+ roleId: number;
3837
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3838
+ emailVerifiedAt: Date | null;
3839
+ phoneVerifiedAt: Date | null;
3840
+ keyEpoch: number;
3841
+ sessionBinding: "none" | "passkey";
3842
+ sessionBindingChangedAt: Date | null;
3843
+ lastLoginAt: Date | null;
3844
+ }>;
3845
+ updateLocale: _spfn_core_route.RouteDef<{
3846
+ body: _sinclair_typebox.TObject<{
3847
+ locale: _sinclair_typebox.TString;
3848
+ }>;
3849
+ }, {}, {
3850
+ locale: string;
3851
+ }>;
3852
+ listRoles: _spfn_core_route.RouteDef<{
3853
+ query: _sinclair_typebox.TObject<{
3854
+ includeInactive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
3855
+ }>;
3856
+ }, {}, {
3857
+ roles: {
3858
+ description: string | null;
3859
+ name: string;
3860
+ id: number;
3861
+ displayName: string;
3862
+ isBuiltin: boolean;
3863
+ isSystem: boolean;
3864
+ isActive: boolean;
3865
+ priority: number;
3866
+ createdAt: Date;
3867
+ updatedAt: Date;
3868
+ }[];
3869
+ }>;
3870
+ createAdminRole: _spfn_core_route.RouteDef<{
3871
+ body: _sinclair_typebox.TObject<{
3872
+ name: _sinclair_typebox.TString;
3873
+ displayName: _sinclair_typebox.TString;
3874
+ description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3875
+ priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3876
+ permissionIds: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TNumber>>;
2799
3877
  }>;
2800
3878
  }, {}, {
2801
- success: boolean;
2802
- userId: string;
2803
- keyId: string;
2804
- returnUrl: string;
3879
+ role: {
3880
+ description: string | null;
3881
+ name: string;
3882
+ id: number;
3883
+ displayName: string;
3884
+ isBuiltin: boolean;
3885
+ isSystem: boolean;
3886
+ isActive: boolean;
3887
+ priority: number;
3888
+ createdAt: Date;
3889
+ updatedAt: Date;
3890
+ };
2805
3891
  }>;
2806
- oauthProviderStart: _spfn_core_route.RouteDef<{
3892
+ updateAdminRole: _spfn_core_route.RouteDef<{
2807
3893
  params: _sinclair_typebox.TObject<{
2808
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3894
+ id: _sinclair_typebox.TNumber;
2809
3895
  }>;
2810
- query: _sinclair_typebox.TObject<{
2811
- state: _sinclair_typebox.TString;
3896
+ body: _sinclair_typebox.TObject<{
3897
+ displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3898
+ description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3899
+ priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3900
+ isActive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
2812
3901
  }>;
2813
- }, {}, Response>;
2814
- oauthProviderCallback: _spfn_core_route.RouteDef<{
3902
+ }, {}, {
3903
+ role: {
3904
+ description: string | null;
3905
+ name: string;
3906
+ id: number;
3907
+ displayName: string;
3908
+ isBuiltin: boolean;
3909
+ isSystem: boolean;
3910
+ isActive: boolean;
3911
+ priority: number;
3912
+ createdAt: Date;
3913
+ updatedAt: Date;
3914
+ };
3915
+ }>;
3916
+ deleteAdminRole: _spfn_core_route.RouteDef<{
2815
3917
  params: _sinclair_typebox.TObject<{
2816
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
2817
- }>;
2818
- query: _sinclair_typebox.TObject<{
2819
- code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2820
- state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2821
- error: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2822
- error_description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3918
+ id: _sinclair_typebox.TNumber;
2823
3919
  }>;
2824
- }, {}, Response>;
2825
- getProviderOAuthUrl: _spfn_core_route.RouteDef<{
3920
+ }, {}, void>;
3921
+ updateUserRole: _spfn_core_route.RouteDef<{
2826
3922
  params: _sinclair_typebox.TObject<{
2827
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3923
+ userId: _sinclair_typebox.TNumber;
2828
3924
  }>;
2829
3925
  body: _sinclair_typebox.TObject<{
2830
- returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2831
- metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
2832
- state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3926
+ roleId: _sinclair_typebox.TNumber;
2833
3927
  }>;
2834
3928
  }, {}, {
2835
- authUrl: string;
3929
+ userId: number;
3930
+ roleId: number;
2836
3931
  }>;
2837
- oauthNative: _spfn_core_route.RouteDef<{
2838
- params: _sinclair_typebox.TObject<{
2839
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
2840
- }>;
3932
+ issueOpsToken: _spfn_core_route.RouteDef<{
2841
3933
  body: _sinclair_typebox.TObject<{
2842
- idToken: _sinclair_typebox.TString;
2843
- nonce: _sinclair_typebox.TString;
2844
- accessToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2845
- publicKey: _sinclair_typebox.TString;
2846
- keyId: _sinclair_typebox.TString;
2847
- fingerprint: _sinclair_typebox.TString;
2848
- algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
2849
- deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2850
- platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
2851
- profile: _sinclair_typebox.TOptional<_sinclair_typebox.TObject<{
2852
- name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2853
- }>>;
2854
- metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
2855
- }>;
2856
- }, {}, OAuthNativeResult>;
2857
- oauthUnlinkNotify: _spfn_core_route.RouteDef<{
2858
- params: _sinclair_typebox.TObject<{
2859
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
2860
- }>;
2861
- }, {}, void | Response>;
2862
- oauthUnlinkNotifyGet: _spfn_core_route.RouteDef<{
2863
- params: _sinclair_typebox.TObject<{
2864
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3934
+ name: _sinclair_typebox.TString;
3935
+ scopes: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
3936
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[_sinclair_typebox.TNumber, _sinclair_typebox.TNull]>>;
2865
3937
  }>;
2866
- }, {}, void | Response>;
2867
- getInvitation: _spfn_core_route.RouteDef<{
3938
+ }, {}, {
3939
+ token: string;
3940
+ opsToken: {
3941
+ id: number;
3942
+ name: string;
3943
+ scopes: string[];
3944
+ expiresAt: string | null;
3945
+ revokedAt: string | null;
3946
+ lastUsedAt: string | null;
3947
+ createdAt: string | null;
3948
+ };
3949
+ }>;
3950
+ listOpsTokens: _spfn_core_route.RouteDef<{}, {}, {
3951
+ opsTokens: {
3952
+ id: number;
3953
+ name: string;
3954
+ scopes: string[];
3955
+ expiresAt: string | null;
3956
+ revokedAt: string | null;
3957
+ lastUsedAt: string | null;
3958
+ createdAt: string | null;
3959
+ }[];
3960
+ }>;
3961
+ revokeOpsToken: _spfn_core_route.RouteDef<{
2868
3962
  params: _sinclair_typebox.TObject<{
2869
- token: _sinclair_typebox.TString;
3963
+ id: _sinclair_typebox.TNumber;
2870
3964
  }>;
2871
3965
  }, {}, {
2872
- email: string;
2873
- role: string;
2874
- roleDisplayName: string;
2875
- invitedBy: string;
2876
- expiresAt: string;
2877
- metadata: Record<string, any> | undefined;
3966
+ opsToken: {
3967
+ id: number;
3968
+ name: string;
3969
+ scopes: string[];
3970
+ expiresAt: string | null;
3971
+ revokedAt: string | null;
3972
+ lastUsedAt: string | null;
3973
+ createdAt: string | null;
3974
+ };
2878
3975
  }>;
2879
- acceptInvitation: _spfn_core_route.RouteDef<{
2880
- body: _sinclair_typebox.TObject<{
2881
- token: _sinclair_typebox.TString;
2882
- password: _sinclair_typebox.TString;
3976
+ registerOAuth2Client: _spfn_core_route.RouteDef<{}, {}, Response>;
3977
+ getOAuth2Authorize: _spfn_core_route.RouteDef<{
3978
+ query: _sinclair_typebox.TObject<{
3979
+ client_id: _sinclair_typebox.TString;
3980
+ redirect_uri: _sinclair_typebox.TString;
3981
+ code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3982
+ code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3983
+ resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3984
+ scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3985
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2883
3986
  }>;
2884
- }, {
3987
+ }, {}, OAuth2ConsentView>;
3988
+ createOAuth2AuthorizationCode: _spfn_core_route.RouteDef<{
2885
3989
  body: _sinclair_typebox.TObject<{
2886
- publicKey: _sinclair_typebox.TString;
2887
- keyId: _sinclair_typebox.TString;
2888
- fingerprint: _sinclair_typebox.TString;
2889
- algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
3990
+ approve: _sinclair_typebox.TBoolean;
3991
+ client_id: _sinclair_typebox.TString;
3992
+ redirect_uri: _sinclair_typebox.TString;
3993
+ code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3994
+ code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3995
+ resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3996
+ scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3997
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2890
3998
  }>;
2891
- }, {
2892
- userId: number;
2893
- email: string;
2894
- role: string;
3999
+ }, {}, OAuth2AuthorizationCodeIssued>;
4000
+ oauth2Token: _spfn_core_route.RouteDef<{}, {}, Response>;
4001
+ oauth2Revoke: _spfn_core_route.RouteDef<{}, {}, Response>;
4002
+ listOAuth2Grants: _spfn_core_route.RouteDef<{}, {}, {
4003
+ grants: OAuth2GrantSummary[];
2895
4004
  }>;
2896
- createInvitation: _spfn_core_route.RouteDef<{
2897
- body: _sinclair_typebox.TObject<{
2898
- email: _sinclair_typebox.TString;
2899
- roleId: _sinclair_typebox.TNumber;
2900
- expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
2901
- expiresAt: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2902
- metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TAny>;
4005
+ revokeOAuth2Grant: _spfn_core_route.RouteDef<{
4006
+ params: _sinclair_typebox.TObject<{
4007
+ id: _sinclair_typebox.TNumber;
2903
4008
  }>;
2904
4009
  }, {}, {
2905
- id: number;
2906
- email: string;
2907
- token: string;
2908
- roleId: number;
2909
- expiresAt: string;
2910
- invitationUrl: string;
4010
+ revoked: boolean;
2911
4011
  }>;
2912
- listInvitations: _spfn_core_route.RouteDef<{
2913
- query: _sinclair_typebox.TObject<{
2914
- status: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"pending" | "accepted" | "expired" | "cancelled">[]>>;
2915
- page: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
2916
- limit: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
4012
+ oauth2AuthorizationServerMetadata: _spfn_core_route.RouteDef<{}, {}, Response>;
4013
+ }>;
4014
+
4015
+ /**
4016
+ * @spfn/auth - User Public Keys Entity
4017
+ *
4018
+ * Stores client-generated public keys for JWT verification
4019
+ * Supports key rotation and multi-key management per user
4020
+ */
4021
+ /**
4022
+ * User Public Keys Table
4023
+ * Each user can have multiple public keys (for rotation)
4024
+ */
4025
+ declare const userPublicKeys: drizzle_orm_pg_core.PgTableWithColumns<{
4026
+ name: "user_public_keys";
4027
+ schema: string;
4028
+ columns: {
4029
+ id: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
4030
+ name: string;
4031
+ tableName: "user_public_keys";
4032
+ dataType: "number int53";
4033
+ data: number;
4034
+ driverParam: number;
4035
+ notNull: true;
4036
+ hasDefault: true;
4037
+ isPrimaryKey: false;
4038
+ isAutoincrement: false;
4039
+ hasRuntimeDefault: false;
4040
+ enumValues: undefined;
4041
+ identity: undefined;
4042
+ generated: undefined;
4043
+ }>;
4044
+ userId: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
4045
+ name: string;
4046
+ tableName: "user_public_keys";
4047
+ dataType: "number int53";
4048
+ data: number;
4049
+ driverParam: string | number;
4050
+ notNull: true;
4051
+ hasDefault: false;
4052
+ isPrimaryKey: false;
4053
+ isAutoincrement: false;
4054
+ hasRuntimeDefault: false;
4055
+ enumValues: undefined;
4056
+ identity: undefined;
4057
+ generated: undefined;
4058
+ }>;
4059
+ keyId: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
4060
+ name: string;
4061
+ tableName: "user_public_keys";
4062
+ dataType: "string";
4063
+ data: string;
4064
+ driverParam: string;
4065
+ notNull: true;
4066
+ hasDefault: false;
4067
+ isPrimaryKey: false;
4068
+ isAutoincrement: false;
4069
+ hasRuntimeDefault: false;
4070
+ enumValues: undefined;
4071
+ identity: undefined;
4072
+ generated: undefined;
4073
+ }>;
4074
+ publicKey: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
4075
+ name: string;
4076
+ tableName: "user_public_keys";
4077
+ dataType: "string";
4078
+ data: string;
4079
+ driverParam: string;
4080
+ notNull: true;
4081
+ hasDefault: false;
4082
+ isPrimaryKey: false;
4083
+ isAutoincrement: false;
4084
+ hasRuntimeDefault: false;
4085
+ enumValues: undefined;
4086
+ identity: undefined;
4087
+ generated: undefined;
2917
4088
  }>;
2918
- }, {}, {
2919
- invitations: {
2920
- id: number;
2921
- email: string;
2922
- token: string;
2923
- roleId: number;
2924
- invitedBy: number;
2925
- status: "pending" | "accepted" | "expired" | "cancelled";
2926
- expiresAt: Date;
2927
- acceptedAt: Date | null;
2928
- cancelledAt: Date | null;
2929
- metadata: Record<string, any> | null;
2930
- createdAt: Date;
2931
- updatedAt: Date;
2932
- role: {
2933
- id: number;
2934
- name: string;
2935
- displayName: string;
2936
- };
2937
- inviter: {
2938
- id: number;
2939
- email: string | null;
2940
- };
2941
- }[];
2942
- total: number;
2943
- page: number;
2944
- limit: number;
2945
- totalPages: number;
2946
- }>;
2947
- cancelInvitation: _spfn_core_route.RouteDef<{
2948
- body: _sinclair_typebox.TObject<{
2949
- id: _sinclair_typebox.TNumber;
2950
- reason: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
4089
+ algorithm: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["ES256", "RS256"] & [string, ...string[]]>>>, {
4090
+ name: string;
4091
+ tableName: "user_public_keys";
4092
+ dataType: "string enum";
4093
+ data: "ES256" | "RS256";
4094
+ driverParam: string;
4095
+ notNull: true;
4096
+ hasDefault: true;
4097
+ isPrimaryKey: false;
4098
+ isAutoincrement: false;
4099
+ hasRuntimeDefault: false;
4100
+ enumValues: ["ES256", "RS256"] & [string, ...string[]];
4101
+ identity: undefined;
4102
+ generated: undefined;
2951
4103
  }>;
2952
- }, {}, {
2953
- cancelledAt: string;
2954
- }>;
2955
- resendInvitation: _spfn_core_route.RouteDef<{
2956
- body: _sinclair_typebox.TObject<{
2957
- id: _sinclair_typebox.TNumber;
2958
- expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
4104
+ fingerprint: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
4105
+ name: string;
4106
+ tableName: "user_public_keys";
4107
+ dataType: "string";
4108
+ data: string;
4109
+ driverParam: string;
4110
+ notNull: true;
4111
+ hasDefault: false;
4112
+ isPrimaryKey: false;
4113
+ isAutoincrement: false;
4114
+ hasRuntimeDefault: false;
4115
+ enumValues: undefined;
4116
+ identity: undefined;
4117
+ generated: undefined;
2959
4118
  }>;
2960
- }, {}, {
2961
- expiresAt: string;
2962
- }>;
2963
- deleteInvitation: _spfn_core_route.RouteDef<{
2964
- body: _sinclair_typebox.TObject<{
2965
- id: _sinclair_typebox.TNumber;
4119
+ deviceName: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4120
+ name: string;
4121
+ tableName: "user_public_keys";
4122
+ dataType: "string";
4123
+ data: string;
4124
+ driverParam: string;
4125
+ notNull: false;
4126
+ hasDefault: false;
4127
+ isPrimaryKey: false;
4128
+ isAutoincrement: false;
4129
+ hasRuntimeDefault: false;
4130
+ enumValues: undefined;
4131
+ identity: undefined;
4132
+ generated: undefined;
2966
4133
  }>;
2967
- }, {}, void>;
2968
- getUserProfile: _spfn_core_route.RouteDef<{}, {}, UserProfile>;
2969
- updateUserProfile: _spfn_core_route.RouteDef<{
2970
- body: _sinclair_typebox.TObject<{
2971
- displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2972
- firstName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2973
- lastName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2974
- avatarUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2975
- bio: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2976
- locale: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2977
- timezone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2978
- dateOfBirth: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2979
- gender: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2980
- website: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2981
- location: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2982
- company: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2983
- jobTitle: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2984
- metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TAny>>;
4134
+ platform: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<["ios", "android", "web", "desktop"] & [string, ...string[]]>, {
4135
+ name: string;
4136
+ tableName: "user_public_keys";
4137
+ dataType: "string enum";
4138
+ data: "ios" | "android" | "web" | "desktop";
4139
+ driverParam: string;
4140
+ notNull: false;
4141
+ hasDefault: false;
4142
+ isPrimaryKey: false;
4143
+ isAutoincrement: false;
4144
+ hasRuntimeDefault: false;
4145
+ enumValues: ["ios", "android", "web", "desktop"] & [string, ...string[]];
4146
+ identity: undefined;
4147
+ generated: undefined;
2985
4148
  }>;
2986
- }, {}, ProfileInfo>;
2987
- checkUsername: _spfn_core_route.RouteDef<{
2988
- query: _sinclair_typebox.TObject<{
2989
- username: _sinclair_typebox.TString;
4149
+ registeredIp: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4150
+ name: string;
4151
+ tableName: "user_public_keys";
4152
+ dataType: "string";
4153
+ data: string;
4154
+ driverParam: string;
4155
+ notNull: false;
4156
+ hasDefault: false;
4157
+ isPrimaryKey: false;
4158
+ isAutoincrement: false;
4159
+ hasRuntimeDefault: false;
4160
+ enumValues: undefined;
4161
+ identity: undefined;
4162
+ generated: undefined;
2990
4163
  }>;
2991
- }, {}, {
2992
- available: boolean;
2993
- }>;
2994
- updateUsername: _spfn_core_route.RouteDef<{
2995
- body: _sinclair_typebox.TObject<{
2996
- username: _sinclair_typebox.TUnion<[_sinclair_typebox.TString, _sinclair_typebox.TNull]>;
4164
+ registeredUserAgent: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4165
+ name: string;
4166
+ tableName: "user_public_keys";
4167
+ dataType: "string";
4168
+ data: string;
4169
+ driverParam: string;
4170
+ notNull: false;
4171
+ hasDefault: false;
4172
+ isPrimaryKey: false;
4173
+ isAutoincrement: false;
4174
+ hasRuntimeDefault: false;
4175
+ enumValues: undefined;
4176
+ identity: undefined;
4177
+ generated: undefined;
2997
4178
  }>;
2998
- }, {}, {
2999
- deletedAt: Date | null;
3000
- deletedBy: string | null;
3001
- createdAt: Date;
3002
- updatedAt: Date;
3003
- id: number;
3004
- publicId: string;
3005
- email: string | null;
3006
- phone: string | null;
3007
- username: string | null;
3008
- passwordHash: string | null;
3009
- passwordChangeRequired: boolean;
3010
- roleId: number;
3011
- status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3012
- emailVerifiedAt: Date | null;
3013
- phoneVerifiedAt: Date | null;
3014
- keyEpoch: number;
3015
- lastLoginAt: Date | null;
3016
- }>;
3017
- updateLocale: _spfn_core_route.RouteDef<{
3018
- body: _sinclair_typebox.TObject<{
3019
- locale: _sinclair_typebox.TString;
4179
+ registeredUaFamily: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4180
+ name: string;
4181
+ tableName: "user_public_keys";
4182
+ dataType: "string";
4183
+ data: string;
4184
+ driverParam: string;
4185
+ notNull: false;
4186
+ hasDefault: false;
4187
+ isPrimaryKey: false;
4188
+ isAutoincrement: false;
4189
+ hasRuntimeDefault: false;
4190
+ enumValues: undefined;
4191
+ identity: undefined;
4192
+ generated: undefined;
3020
4193
  }>;
3021
- }, {}, {
3022
- locale: string;
3023
- }>;
3024
- listRoles: _spfn_core_route.RouteDef<{
3025
- query: _sinclair_typebox.TObject<{
3026
- includeInactive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
4194
+ lastSeenIp: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4195
+ name: string;
4196
+ tableName: "user_public_keys";
4197
+ dataType: "string";
4198
+ data: string;
4199
+ driverParam: string;
4200
+ notNull: false;
4201
+ hasDefault: false;
4202
+ isPrimaryKey: false;
4203
+ isAutoincrement: false;
4204
+ hasRuntimeDefault: false;
4205
+ enumValues: undefined;
4206
+ identity: undefined;
4207
+ generated: undefined;
3027
4208
  }>;
3028
- }, {}, {
3029
- roles: {
3030
- description: string | null;
4209
+ lastSeenAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3031
4210
  name: string;
3032
- id: number;
3033
- displayName: string;
3034
- isBuiltin: boolean;
3035
- isSystem: boolean;
3036
- isActive: boolean;
3037
- priority: number;
3038
- createdAt: Date;
3039
- updatedAt: Date;
3040
- }[];
3041
- }>;
3042
- createAdminRole: _spfn_core_route.RouteDef<{
3043
- body: _sinclair_typebox.TObject<{
3044
- name: _sinclair_typebox.TString;
3045
- displayName: _sinclair_typebox.TString;
3046
- description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3047
- priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3048
- permissionIds: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TNumber>>;
4211
+ tableName: "user_public_keys";
4212
+ dataType: "object date";
4213
+ data: Date;
4214
+ driverParam: string;
4215
+ notNull: false;
4216
+ hasDefault: false;
4217
+ isPrimaryKey: false;
4218
+ isAutoincrement: false;
4219
+ hasRuntimeDefault: false;
4220
+ enumValues: undefined;
4221
+ identity: undefined;
4222
+ generated: undefined;
3049
4223
  }>;
3050
- }, {}, {
3051
- role: {
3052
- description: string | null;
4224
+ concurrentUseAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
4225
+ name: string;
4226
+ tableName: "user_public_keys";
4227
+ dataType: "object date";
4228
+ data: Date;
4229
+ driverParam: string;
4230
+ notNull: false;
4231
+ hasDefault: false;
4232
+ isPrimaryKey: false;
4233
+ isAutoincrement: false;
4234
+ hasRuntimeDefault: false;
4235
+ enumValues: undefined;
4236
+ identity: undefined;
4237
+ generated: undefined;
4238
+ }>;
4239
+ binding: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<["none", "passkey"] & [string, ...string[]]>>>, {
4240
+ name: string;
4241
+ tableName: "user_public_keys";
4242
+ dataType: "string enum";
4243
+ data: "none" | "passkey";
4244
+ driverParam: string;
4245
+ notNull: true;
4246
+ hasDefault: true;
4247
+ isPrimaryKey: false;
4248
+ isAutoincrement: false;
4249
+ hasRuntimeDefault: false;
4250
+ enumValues: ["none", "passkey"] & [string, ...string[]];
4251
+ identity: undefined;
4252
+ generated: undefined;
4253
+ }>;
4254
+ clientKind: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<["web", "ios", "android"] & [string, ...string[]]>, {
3053
4255
  name: string;
3054
- id: number;
3055
- displayName: string;
3056
- isBuiltin: boolean;
3057
- isSystem: boolean;
3058
- isActive: boolean;
3059
- priority: number;
3060
- createdAt: Date;
3061
- updatedAt: Date;
3062
- };
3063
- }>;
3064
- updateAdminRole: _spfn_core_route.RouteDef<{
3065
- params: _sinclair_typebox.TObject<{
3066
- id: _sinclair_typebox.TNumber;
4256
+ tableName: "user_public_keys";
4257
+ dataType: "string enum";
4258
+ data: "ios" | "android" | "web";
4259
+ driverParam: string;
4260
+ notNull: false;
4261
+ hasDefault: false;
4262
+ isPrimaryKey: false;
4263
+ isAutoincrement: false;
4264
+ hasRuntimeDefault: false;
4265
+ enumValues: ["web", "ios", "android"] & [string, ...string[]];
4266
+ identity: undefined;
4267
+ generated: undefined;
3067
4268
  }>;
3068
- body: _sinclair_typebox.TObject<{
3069
- displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3070
- description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3071
- priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3072
- isActive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
4269
+ clientVersion: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4270
+ name: string;
4271
+ tableName: "user_public_keys";
4272
+ dataType: "string";
4273
+ data: string;
4274
+ driverParam: string;
4275
+ notNull: false;
4276
+ hasDefault: false;
4277
+ isPrimaryKey: false;
4278
+ isAutoincrement: false;
4279
+ hasRuntimeDefault: false;
4280
+ enumValues: undefined;
4281
+ identity: undefined;
4282
+ generated: undefined;
3073
4283
  }>;
3074
- }, {}, {
3075
- role: {
3076
- description: string | null;
4284
+ clientContractVersion: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3077
4285
  name: string;
3078
- id: number;
3079
- displayName: string;
3080
- isBuiltin: boolean;
3081
- isSystem: boolean;
3082
- isActive: boolean;
3083
- priority: number;
3084
- createdAt: Date;
3085
- updatedAt: Date;
3086
- };
3087
- }>;
3088
- deleteAdminRole: _spfn_core_route.RouteDef<{
3089
- params: _sinclair_typebox.TObject<{
3090
- id: _sinclair_typebox.TNumber;
4286
+ tableName: "user_public_keys";
4287
+ dataType: "string";
4288
+ data: string;
4289
+ driverParam: string;
4290
+ notNull: false;
4291
+ hasDefault: false;
4292
+ isPrimaryKey: false;
4293
+ isAutoincrement: false;
4294
+ hasRuntimeDefault: false;
4295
+ enumValues: undefined;
4296
+ identity: undefined;
4297
+ generated: undefined;
3091
4298
  }>;
3092
- }, {}, void>;
3093
- updateUserRole: _spfn_core_route.RouteDef<{
3094
- params: _sinclair_typebox.TObject<{
3095
- userId: _sinclair_typebox.TNumber;
4299
+ clientSeenAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
4300
+ name: string;
4301
+ tableName: "user_public_keys";
4302
+ dataType: "object date";
4303
+ data: Date;
4304
+ driverParam: string;
4305
+ notNull: false;
4306
+ hasDefault: false;
4307
+ isPrimaryKey: false;
4308
+ isAutoincrement: false;
4309
+ hasRuntimeDefault: false;
4310
+ enumValues: undefined;
4311
+ identity: undefined;
4312
+ generated: undefined;
3096
4313
  }>;
3097
- body: _sinclair_typebox.TObject<{
3098
- roleId: _sinclair_typebox.TNumber;
4314
+ isActive: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
4315
+ name: string;
4316
+ tableName: "user_public_keys";
4317
+ dataType: "boolean";
4318
+ data: boolean;
4319
+ driverParam: boolean;
4320
+ notNull: true;
4321
+ hasDefault: true;
4322
+ isPrimaryKey: false;
4323
+ isAutoincrement: false;
4324
+ hasRuntimeDefault: false;
4325
+ enumValues: undefined;
4326
+ identity: undefined;
4327
+ generated: undefined;
3099
4328
  }>;
3100
- }, {}, {
3101
- userId: number;
3102
- roleId: number;
3103
- }>;
3104
- issueOpsToken: _spfn_core_route.RouteDef<{
3105
- body: _sinclair_typebox.TObject<{
3106
- name: _sinclair_typebox.TString;
3107
- scopes: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
3108
- expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[_sinclair_typebox.TNumber, _sinclair_typebox.TNull]>>;
4329
+ pendingMfaChallengeId: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgBigInt53Builder, {
4330
+ name: string;
4331
+ tableName: "user_public_keys";
4332
+ dataType: "number int53";
4333
+ data: number;
4334
+ driverParam: string | number;
4335
+ notNull: false;
4336
+ hasDefault: false;
4337
+ isPrimaryKey: false;
4338
+ isAutoincrement: false;
4339
+ hasRuntimeDefault: false;
4340
+ enumValues: undefined;
4341
+ identity: undefined;
4342
+ generated: undefined;
3109
4343
  }>;
3110
- }, {}, {
3111
- token: string;
3112
- opsToken: {
3113
- id: number;
4344
+ createdAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTimestampBuilder>>, {
3114
4345
  name: string;
3115
- scopes: string[];
3116
- expiresAt: string | null;
3117
- revokedAt: string | null;
3118
- lastUsedAt: string | null;
3119
- createdAt: string | null;
3120
- };
3121
- }>;
3122
- listOpsTokens: _spfn_core_route.RouteDef<{}, {}, {
3123
- opsTokens: {
3124
- id: number;
4346
+ tableName: "user_public_keys";
4347
+ dataType: "object date";
4348
+ data: Date;
4349
+ driverParam: string;
4350
+ notNull: true;
4351
+ hasDefault: true;
4352
+ isPrimaryKey: false;
4353
+ isAutoincrement: false;
4354
+ hasRuntimeDefault: false;
4355
+ enumValues: undefined;
4356
+ identity: undefined;
4357
+ generated: undefined;
4358
+ }>;
4359
+ lastUsedAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3125
4360
  name: string;
3126
- scopes: string[];
3127
- expiresAt: string | null;
3128
- revokedAt: string | null;
3129
- lastUsedAt: string | null;
3130
- createdAt: string | null;
3131
- }[];
3132
- }>;
3133
- revokeOpsToken: _spfn_core_route.RouteDef<{
3134
- params: _sinclair_typebox.TObject<{
3135
- id: _sinclair_typebox.TNumber;
4361
+ tableName: "user_public_keys";
4362
+ dataType: "object date";
4363
+ data: Date;
4364
+ driverParam: string;
4365
+ notNull: false;
4366
+ hasDefault: false;
4367
+ isPrimaryKey: false;
4368
+ isAutoincrement: false;
4369
+ hasRuntimeDefault: false;
4370
+ enumValues: undefined;
4371
+ identity: undefined;
4372
+ generated: undefined;
3136
4373
  }>;
3137
- }, {}, {
3138
- opsToken: {
3139
- id: number;
4374
+ expiresAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3140
4375
  name: string;
3141
- scopes: string[];
3142
- expiresAt: string | null;
3143
- revokedAt: string | null;
3144
- lastUsedAt: string | null;
3145
- createdAt: string | null;
3146
- };
3147
- }>;
3148
- registerOAuth2Client: _spfn_core_route.RouteDef<{}, {}, Response>;
3149
- getOAuth2Authorize: _spfn_core_route.RouteDef<{
3150
- query: _sinclair_typebox.TObject<{
3151
- client_id: _sinclair_typebox.TString;
3152
- redirect_uri: _sinclair_typebox.TString;
3153
- code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3154
- code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3155
- resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3156
- scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3157
- state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
4376
+ tableName: "user_public_keys";
4377
+ dataType: "object date";
4378
+ data: Date;
4379
+ driverParam: string;
4380
+ notNull: false;
4381
+ hasDefault: false;
4382
+ isPrimaryKey: false;
4383
+ isAutoincrement: false;
4384
+ hasRuntimeDefault: false;
4385
+ enumValues: undefined;
4386
+ identity: undefined;
4387
+ generated: undefined;
3158
4388
  }>;
3159
- }, {}, OAuth2ConsentView>;
3160
- createOAuth2AuthorizationCode: _spfn_core_route.RouteDef<{
3161
- body: _sinclair_typebox.TObject<{
3162
- approve: _sinclair_typebox.TBoolean;
3163
- client_id: _sinclair_typebox.TString;
3164
- redirect_uri: _sinclair_typebox.TString;
3165
- code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3166
- code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3167
- resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3168
- scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3169
- state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
4389
+ revokedAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
4390
+ name: string;
4391
+ tableName: "user_public_keys";
4392
+ dataType: "object date";
4393
+ data: Date;
4394
+ driverParam: string;
4395
+ notNull: false;
4396
+ hasDefault: false;
4397
+ isPrimaryKey: false;
4398
+ isAutoincrement: false;
4399
+ hasRuntimeDefault: false;
4400
+ enumValues: undefined;
4401
+ identity: undefined;
4402
+ generated: undefined;
3170
4403
  }>;
3171
- }, {}, OAuth2AuthorizationCodeIssued>;
3172
- oauth2Token: _spfn_core_route.RouteDef<{}, {}, Response>;
3173
- oauth2Revoke: _spfn_core_route.RouteDef<{}, {}, Response>;
3174
- listOAuth2Grants: _spfn_core_route.RouteDef<{}, {}, {
3175
- grants: OAuth2GrantSummary[];
3176
- }>;
3177
- revokeOAuth2Grant: _spfn_core_route.RouteDef<{
3178
- params: _sinclair_typebox.TObject<{
3179
- id: _sinclair_typebox.TNumber;
4404
+ revokedReason: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
4405
+ name: string;
4406
+ tableName: "user_public_keys";
4407
+ dataType: "string";
4408
+ data: string;
4409
+ driverParam: string;
4410
+ notNull: false;
4411
+ hasDefault: false;
4412
+ isPrimaryKey: false;
4413
+ isAutoincrement: false;
4414
+ hasRuntimeDefault: false;
4415
+ enumValues: undefined;
4416
+ identity: undefined;
4417
+ generated: undefined;
3180
4418
  }>;
3181
- }, {}, {
3182
- revoked: boolean;
3183
- }>;
3184
- oauth2AuthorizationServerMetadata: _spfn_core_route.RouteDef<{}, {}, Response>;
4419
+ };
4420
+ dialect: "pg";
3185
4421
  }>;
4422
+ type UserPublicKey = typeof userPublicKeys.$inferSelect;
4423
+ type NewUserPublicKey = typeof userPublicKeys.$inferInsert;
3186
4424
 
3187
4425
  /**
3188
4426
  * The auth-profile registry the authenticate middleware dispatches on.
@@ -3323,6 +4561,46 @@ declare module 'hono' {
3323
4561
  auth: AuthContext;
3324
4562
  }
3325
4563
  }
4564
+ /** Why a Bearer credential was not admitted, in the order the steps run. */
4565
+ type BearerRefusal = 'absent' | 'machine' | 'undecodable' | 'unknown' | 'expired' | 'token-expired' | 'bad-signature' | 'unverifiable';
4566
+ /** What a Bearer credential resolved to: the key row it named, or why not. */
4567
+ type BearerOutcome = {
4568
+ key: UserPublicKey;
4569
+ } | {
4570
+ refused: BearerRefusal;
4571
+ };
4572
+ /**
4573
+ * Admit the Bearer credential on this request, or say what stopped it.
4574
+ *
4575
+ * The one lookup-and-verify path every Bearer middleware takes — header, machine
4576
+ * discriminator, decode, key row, expiry, signature — kept in one place because
4577
+ * the order is itself a rule: a machine credential never reaches a decode, and a
4578
+ * key row is found before its signature is checked so that an unknown key and a
4579
+ * forged one cost the same work.
4580
+ *
4581
+ * It answers rather than throws, which is what lets two middlewares share it.
4582
+ * `authenticate` turns each refusal into the error that step has always
4583
+ * answered with; `authenticateForRenewal` turns every one of them into a single
4584
+ * refusal, so a caller holding no private key cannot tell a live key id from a
4585
+ * dead one. A shared step that threw would have to be unwound to get there.
4586
+ *
4587
+ * @param c - the Hono context of the request being authenticated
4588
+ * @param admitsExpired - whether a key past its `expiresAt` may still be
4589
+ * admitted. Renewal is the only caller that says yes, and only for a bound key
4590
+ * inside its grace.
4591
+ */
4592
+ declare function admitBearerKey(c: Context, admitsExpired: (key: UserPublicKey) => boolean): Promise<BearerOutcome>;
4593
+ /**
4594
+ * The principal a verified Bearer key resolves to.
4595
+ *
4596
+ * The one place the Bearer path builds an `AuthContext`, so that a middleware
4597
+ * added beside `authenticate` cannot invent a second shape of it.
4598
+ */
4599
+ declare function bearerAuthContext(keyId: string, resolved: {
4600
+ user: User;
4601
+ role: string | null;
4602
+ locale: string;
4603
+ }): AuthContext;
3326
4604
  /**
3327
4605
  * Authentication middleware
3328
4606
  *
@@ -3476,4 +4754,4 @@ declare const machineAuth: _spfn_core_route.NamedMiddleware<"machineAuth">;
3476
4754
  */
3477
4755
  declare const requireMachineScope: _spfn_core_route.NamedMiddlewareFactory<"machineScope", string[]>;
3478
4756
 
3479
- export { type AuthRegisterPayload as $, type AuthInitOptions as A, type ApproveDeviceAuthParams as B, type ConfirmSignupLinkResult as C, type DeviceAuthInfoResult as D, type AssertStepUpParams as E, type FinishPasskeyEnrollmentResult as F, type AuthDeletionCancelledPayload as G, type AuthDeletionCompletedPayload as H, type IssueOneTimeTokenResult as I, type AuthDeletionRequestedPayload as J, type KeySummary as K, type LoginResult as L, type MfaStatus as M, type NewPasskey as N, type OAuthStartResult as O, type PermissionConfig as P, type AuthDeviceRegisteredPayload as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type TotpEnrolmentResult as T, type UserProfile as U, VERIFICATION_PURPOSES as V, type AuthLoginPayload as W, type AuthPasswordResetPayload as X, type AuthProfileOutcome as Y, type AuthProfileVerifier as Z, AuthProviderSchema as _, type RegisterResult as a, type UnlinkNotification as a$, type ChangePasswordParams as a0, type CompletePasswordResetParams as a1, type CompleteSignupParams as a2, type ConfirmPasswordResetParams as a3, type ConfirmSignupLinkParams as a4, type ConfirmTotpParams as a5, type DenyDeviceAuthParams as a6, type DeviceAuthApprovedResult as a7, type DeviceAuthInfoParams as a8, type DeviceAuthPendingResult as a9, type OAuthStartParams as aA, type OAuthTokens as aB, type OAuthUnlinkedPayload as aC, PASSKEY_DEVICE_TYPES as aD, PASSKEY_LABEL_MAX_LENGTH as aE, type PasskeyDeviceType as aF, PasswordSchema as aG, PhoneSchema as aH, PlatformSchema as aI, type PollDeviceAuthParams as aJ, type PollDeviceAuthResult as aK, PublicKeySchema as aL, type RecentAuthenticationParams as aM, type RegisterParams as aN, type RegisterPublicKeyParams as aO, type RenamePasskeyParams as aP, type RequestPasswordResetParams as aQ, type RequestSignupLinkParams as aR, type RevokeAllKeysParams as aS, type RevokeKeyParams as aT, type RevokePasskeyParams as aU, type RotateKeyParams as aV, type SendVerificationCodeParams as aW, type StartDeviceAuthParams as aX, type StartPasskeyEnrollmentParams as aY, type StepUpParams as aZ, TargetTypeSchema as a_, DeviceAuthPollResponseSchema as aa, DeviceNameSchema as ab, type DeviceRegistrationChannel as ac, EmailSchema as ad, FingerprintSchema as ae, type FinishPasskeyEnrollmentParams as af, type FinishPasskeyLoginParams as ag, type InvitationAcceptedPayload as ah, type InvitationCreatedPayload as ai, KEY_FINGERPRINT_PREFIX_LENGTH as aj, KeyIdSchema as ak, type LoginParams as al, type LogoutParams as am, MFA_VERIFICATION_METHODS as an, type MachinePrincipal as ao, type MachineVerifierRegistration as ap, type MarkPasskeyParams as aq, type NativeVerifyOptions as ar, type NewMfaVerification as as, type NormalizedIdentity as at, type OAuth2AuthorizeParams as au, type OAuth2ScopeDescription as av, type OAuthCallbackParams as aw, type OAuthCallbackResult as ax, type OAuthCodeExchangeOptions as ay, type OAuthNativeParams as az, type RequestSignupLinkResult as b, registerAuthProfile as b$, UnlinkNotifyRejection as b0, type UnlinkNotifyRequest as b1, type UnlinkNotifyResult as b2, UserCodeSchema as b3, VerificationPurposeSchema as b4, type VerifyCodeParams as b5, type VerifyCodeResult as b6, approveDeviceAuthService as b7, approveOAuth2AuthorizeService as b8, assertNotLastRecoveryCredential as b9, getGoogleAccessToken as bA, getMachinePrincipal as bB, getOAuthProvider as bC, getRegisteredProviders as bD, invitationAcceptedEvent as bE, invitationCreatedEvent as bF, isOAuthProviderEnabled as bG, issueOneTimeTokenService as bH, listKeysService as bI, listOAuth2GrantsService as bJ, listPasskeysService as bK, loginService as bL, logoutService as bM, machineAuth as bN, markPasskeySecondFactorService as bO, mfaEnrolledForUser as bP, mfaStatusService as bQ, mfaVerifications as bR, oauthCallbackService as bS, oauthNativeService as bT, oauthStartService as bU, oauthUnlinkNotifyService as bV, oauthUnlinkedEvent as bW, optionalAuth as bX, passkeys as bY, pollDeviceAuthService as bZ, regenerateRecoveryCodesService as b_, assertRecentAuthentication as ba, assertStepUp as bb, authDeletionCancelledEvent as bc, authDeletionCompletedEvent as bd, authDeletionRequestedEvent as be, authDeviceRegisteredEvent as bf, authLoginEvent as bg, authPasswordResetEvent as bh, authRegisterEvent as bi, authenticate as bj, buildOAuthErrorUrl as bk, carryStepUpVerification as bl, changePasswordService as bm, completePasswordResetService as bn, completeSignupService as bo, confirmPasswordResetService as bp, confirmSignupLinkService as bq, confirmTotpEnrolmentService as br, denyDeviceAuthService as bs, denyOAuth2AuthorizeService as bt, describeOAuth2AuthorizeRequestService as bu, disableMfaService as bv, finishPasskeyEnrollmentService as bw, finishPasskeyLoginService as bx, getDeviceAuthInfoService as by, getEnabledOAuthProviders as bz, type RequestPasswordResetResult as c, registerMachineVerifier as c0, registerOAuthProvider as c1, registerPublicKeyService as c2, registerService as c3, renamePasskeyService as c4, requestPasswordResetService as c5, requestSignupLinkService as c6, requireEnabledProvider as c7, requireMachineScope as c8, resolveAuthenticatedUser as c9, revokeAllKeysService as ca, revokeAllOAuth2GrantsForUser as cb, revokeKeyService as cc, revokeOAuth2GrantService as cd, revokePasskeyService as ce, rotateKeyService as cf, runAuthProfile as cg, selectAuthProfile as ch, sendVerificationCodeService as ci, startDeviceAuthService as cj, startPasskeyEnrollmentService as ck, startPasskeyLoginService as cl, startStepUpService as cm, startTotpEnrolmentService as cn, stepUpService as co, sweepUnconfirmedMfaService as cp, verifyCodeService as cq, verifyOneTimeTokenService as cr, verifySecondFactor as cs, type ConfirmPasswordResetResult as d, type StartDeviceAuthResult as e, type PasskeySummary as f, type ConfirmTotpResult as g, type RotateKeyResult as h, type RevokeAllKeysResult as i, type OAuthNativeResult as j, type ProfileInfo as k, type OAuth2ConsentView as l, mainAuthRouter as m, type OAuth2AuthorizationCodeIssued as n, type OAuth2GrantSummary as o, type AuthSession as p, PERMISSION_CATEGORIES as q, type PermissionCategory as r, VERIFICATION_TARGET_TYPES as s, type VerificationPurpose as t, type VerificationTargetType as u, type OAuthProvider as v, type Passkey as w, type MfaVerification as x, type MfaVerificationMethod as y, type AuthContext as z };
4757
+ export { type AuthDeletionCompletedPayload as $, type AuthInitOptions as A, type NewPasskey as B, type ConfirmSignupLinkResult as C, type DeviceAuthInfoResult as D, type Passkey as E, type FinishPasskeyEnrollmentResult as F, type MfaVerification as G, type MfaVerificationMethod as H, type IssueOneTimeTokenResult as I, type NewMfaChallenge as J, type KeySummary as K, type LoginResult as L, type MfaStatus as M, type NewUserPublicKey as N, type OAuthStartResult as O, type PermissionConfig as P, type MfaChallenge as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type TotpEnrolmentResult as T, type UserProfile as U, type VerifyMfaChallengeResult as V, type UserPublicKey as W, type AuthContext as X, type ApproveDeviceAuthParams as Y, type AssertStepUpParams as Z, type AuthDeletionCancelledPayload as _, type RegisterResult as a, type PollDeviceAuthParams as a$, type AuthDeletionRequestedPayload as a0, type AuthDeviceRegisteredPayload as a1, type AuthLoginPayload as a2, type AuthPasswordResetPayload as a3, type AuthProfileOutcome as a4, type AuthProfileVerifier as a5, AuthProviderSchema as a6, type AuthRegisterPayload as a7, type BearerOutcome as a8, type BearerRefusal as a9, MFA_CHALLENGE_ATTEMPT_LIMIT as aA, MFA_CHALLENGE_CHANNELS as aB, MFA_VERIFICATION_METHODS as aC, type MachinePrincipal as aD, type MachineVerifierRegistration as aE, type MarkPasskeyParams as aF, type MfaChallengeChannel as aG, type MfaChallengeHandle as aH, type NativeVerifyOptions as aI, type NewMfaVerification as aJ, type NormalizedIdentity as aK, type OAuth2AuthorizeParams as aL, type OAuth2ScopeDescription as aM, type OAuthCallbackParams as aN, type OAuthCallbackResult as aO, type OAuthCodeExchangeOptions as aP, type OAuthNativeParams as aQ, type OAuthStartParams as aR, type OAuthTokens as aS, type OAuthUnlinkedPayload as aT, type OpenStepUpChallengeParams as aU, PASSKEY_DEVICE_TYPES as aV, PASSKEY_LABEL_MAX_LENGTH as aW, type PasskeyDeviceType as aX, PasswordSchema as aY, PhoneSchema as aZ, PlatformSchema as a_, type ChangePasswordParams as aa, type CompletePasswordResetParams as ab, type CompleteSignupParams as ac, type ConfirmPasswordResetParams as ad, type ConfirmSignupLinkParams as ae, type ConfirmTotpParams as af, type DeferredLoginEvent as ag, type DenyDeviceAuthParams as ah, type DeviceAuthApprovedResult as ai, type DeviceAuthInfoParams as aj, type DeviceAuthPendingResult as ak, DeviceAuthPollResponseSchema as al, DeviceNameSchema as am, type DeviceRegistrationChannel as an, type DisableSessionBindingParams as ao, EmailSchema as ap, FingerprintSchema as aq, type FinishPasskeyEnrollmentParams as ar, type FinishPasskeyLoginParams as as, type FinishSessionRenewParams as at, type InvitationAcceptedPayload as au, type InvitationCreatedPayload as av, KEY_FINGERPRINT_PREFIX_LENGTH as aw, KeyIdSchema as ax, type LoginParams as ay, type LogoutParams as az, type RequestSignupLinkResult as b, getEnabledOAuthProviders as b$, type PollDeviceAuthResult as b0, PublicKeySchema as b1, type RecentAuthenticationParams as b2, type RegisterParams as b3, type RegisterPublicKeyParams as b4, type RegisterPublicKeyResult as b5, type RenamePasskeyParams as b6, type RequestPasswordResetParams as b7, type RequestSignupLinkParams as b8, type RevokeAllKeysParams as b9, authDeletionCancelledEvent as bA, authDeletionCompletedEvent as bB, authDeletionRequestedEvent as bC, authDeviceRegisteredEvent as bD, authLoginEvent as bE, authPasswordResetEvent as bF, authRegisterEvent as bG, authenticate as bH, bearerAuthContext as bI, buildOAuthErrorUrl as bJ, carryStepUpVerification as bK, changePasswordService as bL, completePasswordResetService as bM, completeSignupService as bN, confirmPasswordResetService as bO, confirmSignupLinkService as bP, confirmTotpEnrolmentService as bQ, denyDeviceAuthService as bR, denyOAuth2AuthorizeService as bS, describeOAuth2AuthorizeRequestService as bT, disableMfaService as bU, disableSessionBindingService as bV, enableSessionBindingService as bW, finishPasskeyEnrollmentService as bX, finishPasskeyLoginService as bY, finishSessionRenewService as bZ, getDeviceAuthInfoService as b_, type RevokeKeyParams as ba, type RevokePasskeyParams as bb, type RotateKeyParams as bc, type SendVerificationCodeParams as bd, type SessionBindingParams as be, type StartDeviceAuthParams as bf, type StartPasskeyEnrollmentParams as bg, type StartSessionRenewParams as bh, type StepUpParams as bi, TargetTypeSchema as bj, type UnlinkNotification as bk, UnlinkNotifyRejection as bl, type UnlinkNotifyRequest as bm, type UnlinkNotifyResult as bn, UserCodeSchema as bo, VerificationPurposeSchema as bp, type VerifyCodeParams as bq, type VerifyCodeResult as br, type VerifyMfaChallengeParams as bs, admitBearerKey as bt, approveDeviceAuthService as bu, approveOAuth2AuthorizeService as bv, assertNotLastRecoveryCredential as bw, assertRecentAuthentication as bx, assertStepUp as by, attemptSecondFactor as bz, type RequestPasswordResetResult as c, sweepUnconfirmedMfaService as c$, getGoogleAccessToken as c0, getMachinePrincipal as c1, getOAuthProvider as c2, getRegisteredProviders as c3, getSessionBindingService as c4, invitationAcceptedEvent as c5, invitationCreatedEvent as c6, isOAuthProviderEnabled as c7, issueOneTimeTokenService as c8, keySessionBindingService as c9, registeredBinding as cA, renamePasskeyService as cB, requestPasswordResetService as cC, requestSignupLinkService as cD, requireEnabledProvider as cE, requireMachineScope as cF, resolveAuthenticatedUser as cG, resumeStepUpChallengeService as cH, revokeAllKeysService as cI, revokeAllOAuth2GrantsForUser as cJ, revokeKeyService as cK, revokeOAuth2GrantService as cL, revokePasskeyService as cM, rotateKeyService as cN, runAuthProfile as cO, selectAuthProfile as cP, sendVerificationCodeService as cQ, startDeviceAuthService as cR, startMfaChallengeAssertionService as cS, startPasskeyEnrollmentService as cT, startPasskeyLoginService as cU, startSessionBindingDisableService as cV, startSessionRenewService as cW, startStepUpService as cX, startTotpEnrolmentService as cY, stepUpService as cZ, sweepMfaChallengesService as c_, listKeysService as ca, listOAuth2GrantsService as cb, listPasskeysService as cc, loginService as cd, logoutService as ce, machineAuth as cf, markPasskeySecondFactorService as cg, mfaChallenges as ch, mfaEnrolledForUser as ci, mfaStatusService as cj, mfaVerifications as ck, oauthCallbackService as cl, oauthNativeService as cm, oauthStartService as cn, oauthUnlinkNotifyService as co, oauthUnlinkedEvent as cp, openStepUpChallengeService as cq, optionalAuth as cr, passkeys as cs, pollDeviceAuthService as ct, regenerateRecoveryCodesService as cu, registerAuthProfile as cv, registerMachineVerifier as cw, registerOAuthProvider as cx, registerPublicKeyService as cy, registerService as cz, type ConfirmPasswordResetResult as d, userPublicKeys as d0, verifyCodeService as d1, verifyMfaChallengeService as d2, verifyOneTimeTokenService as d3, verifySecondFactor as d4, type StartDeviceAuthResult as e, type PasskeySummary as f, type ConfirmTotpResult as g, type RotateKeyResult as h, type RevokeAllKeysResult as i, type SessionBindingResult as j, type SessionRenewResult as k, type OAuthFinalizeResponse as l, mainAuthRouter as m, type OAuthNativeResult as n, type ProfileInfo as o, type OAuth2ConsentView as p, type OAuth2AuthorizationCodeIssued as q, type OAuth2GrantSummary as r, type AuthSession as s, PERMISSION_CATEGORIES as t, type PermissionCategory as u, VERIFICATION_PURPOSES as v, VERIFICATION_TARGET_TYPES as w, type VerificationPurpose as x, type VerificationTargetType as y, type OAuthProvider as z };