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

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.
@@ -1,7 +1,7 @@
1
1
  import * as _simplewebauthn_server from '@simplewebauthn/server';
2
- import { RegistrationResponseJSON, AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/server';
2
+ import { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/server';
3
+ import { S as SessionBindingType, K as KeyAlgorithmType, h as KeyPlatformType, l as SocialProvider } from './types-CTdoTOxM.js';
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';
@@ -91,561 +91,29 @@ interface UserProfile {
91
91
  }
92
92
 
93
93
  /**
94
- * @spfn/auth - Auth Service
95
- *
96
- * Core authentication logic: registration, login, logout, password management
97
- */
98
-
99
- interface RegisterParams {
100
- email?: string;
101
- phone?: string;
102
- verificationToken: string;
103
- password: string;
104
- publicKey: string;
105
- keyId: string;
106
- fingerprint: string;
107
- algorithm?: KeyAlgorithmType;
108
- deviceName?: string;
109
- platform?: KeyPlatformType;
110
- metadata?: Record<string, unknown>;
111
- /** Client address of the request, from `deviceProvenance` at the route. */
112
- ip?: string;
113
- /** `user-agent` of the request, already truncated at the route. */
114
- userAgent?: string;
115
- }
116
- interface RegisterResult {
117
- userId: string;
118
- publicId: string;
119
- email?: string;
120
- phone?: string;
121
- }
122
- interface LoginParams {
123
- email?: string;
124
- phone?: string;
125
- password: string;
126
- publicKey: string;
127
- keyId: string;
128
- fingerprint: string;
129
- oldKeyId?: string;
130
- algorithm?: KeyAlgorithmType;
131
- deviceName?: string;
132
- platform?: KeyPlatformType;
133
- /** Client address of the request, from `deviceProvenance` at the route. */
134
- ip?: string;
135
- /** `user-agent` of the request, already truncated at the route. */
136
- userAgent?: string;
137
- }
138
- interface LoginResult {
139
- userId: string;
140
- publicId: string;
141
- email?: string;
142
- phone?: string;
143
- passwordChangeRequired: boolean;
144
- }
145
- interface LogoutParams {
146
- userId: number;
147
- keyId: string;
148
- }
149
- interface ChangePasswordParams {
150
- userId: number;
151
- /**
152
- * The device key this request is signed with.
153
- *
154
- * Only read to measure the second-factor window of an enrolled account —
155
- * an account with nothing enrolled is answered exactly as before, so this
156
- * adds no refusal for anybody who has not opted in.
157
- */
158
- keyId: string;
159
- currentPassword?: string;
160
- newPassword: string;
161
- passwordHash?: string;
162
- }
163
- /**
164
- * Register a new user account
165
- */
166
- declare function registerService(params: RegisterParams): Promise<RegisterResult>;
167
- /**
168
- * Authenticate user and create session
169
- */
170
- declare function loginService(params: LoginParams): Promise<LoginResult>;
171
- /**
172
- * Logout user (revoke current key)
173
- */
174
- declare function logoutService(params: LogoutParams): Promise<void>;
175
- /**
176
- * Change user password
177
- *
178
- * An enrolled account steps up first (#95): a stolen session must not be able
179
- * to take the account over by setting a new password. An unenrolled account is
180
- * unaffected — including the OAuth-only account with no password and a key
181
- * older than ten minutes, which still sets a first password and gets a 200.
182
- */
183
- declare function changePasswordService(params: ChangePasswordParams): Promise<void>;
184
-
185
- declare const EmailSchema: _sinclair_typebox.TString;
186
- declare const PhoneSchema: _sinclair_typebox.TString;
187
- /**
188
- * Optional device labels a client may send when registering a key.
189
- *
190
- * Display only: the key list uses them to tell one device from another, and
191
- * nothing is authorized or refused by either value, so a client that lies about
192
- * them gains nothing. Both are omitted by every key registered before they
193
- * existed, hence optional rather than defaulted.
194
- */
195
- declare const DeviceNameSchema: _sinclair_typebox.TString;
196
- declare const PlatformSchema: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>;
197
- /**
198
- * Key material as a device sends it, bounded.
199
- *
200
- * The bounds exist for the one route that takes this material from a caller who
201
- * has not authenticated and cannot: `POST /_auth/device/start` persists what it
202
- * is given, and a correctly fingerprinted megabyte of base64 would sit in
203
- * `device_authorizations` until something swept it — and nothing sweeps it.
204
- *
205
- * The numbers are what real key material measures, with room to spare. The
206
- * package's own generators produce SPKI DER in base64: 124 characters for
207
- * ES256 (P-256), 392 for RS256 (RSA-2048). An RSA-4096 key would be 736, an
208
- * RSA-8192 key about 1400, and the same 4096-bit key PEM-armoured about 800 —
209
- * so 2048 admits every shape of key anyone could reasonably present, while a
210
- * megabyte is refused three orders of magnitude before it reaches a row.
211
- *
212
- * `keyId` is a UUID (36) everywhere this package generates one; 64 leaves room
213
- * for a client that prefixes or namespaces its own. `fingerprint` is SHA-256
214
- * hex, exactly 64, and nothing else can ever verify against the public key —
215
- * 128 is the length a longer digest would need, and no more.
216
- */
217
- declare const PublicKeySchema: _sinclair_typebox.TString;
218
- declare const KeyIdSchema: _sinclair_typebox.TString;
219
- declare const FingerprintSchema: _sinclair_typebox.TString;
220
- /**
221
- * The code a person reads off the waiting device and types on their own.
222
- *
223
- * Loose on purpose: 8 characters plus an optional dash is what is shown, but the
224
- * server folds whitespace, dashes and lower case away before looking anything up,
225
- * so refusing those spellings here would refuse a code that is on screen. The
226
- * bounds exist to stop an unbounded string reaching the database, not to spell
227
- * out the format — `USER_CODE_ALPHABET` is the only thing that can match a row.
228
- */
229
- declare const UserCodeSchema: _sinclair_typebox.TString;
230
- /**
231
- * What `POST /_auth/device/poll` answers with.
232
- *
233
- * A union, because the two answers are different kinds of thing rather than one
234
- * shape with optional fields: pending says "ask again in this long", approved is
235
- * a completed login carrying exactly what `/_auth/login` returns. `status` is the
236
- * discriminant, so a generated client narrows on it instead of testing which
237
- * fields happen to be present.
94
+ * @spfn/auth - Passkeys Entity
238
95
  *
239
- * The mobile contract has no union type, so it exports this as one object with
240
- * `status` required and every branch field optional — see
241
- * `deviceAuthorization.pollStatusRule` in the bundle. `intervalMillis` is an
242
- * integer for the same reason: that grammar carries no floating-point scalar,
243
- * and a count of milliseconds never needed one.
96
+ * A WebAuthn credential the account owner enrolled on one of their devices.
97
+ * It is a *credential*, not a session: an assertion proves who is asking, and
98
+ * the ordinary device key in `user_public_keys` is what the request afterwards
99
+ * is signed with. The two tables therefore never stand in for each other.
244
100
  *
245
- * That integer is a promise two things keep, because nothing validates a response
246
- * against this schema on the way out. `configureDeviceAuth` refuses an interval
247
- * that is not a whole number of milliseconds, so the only value this branch can
248
- * carry is one; and `contract-export.test.ts` reads this schema to check the
249
- * exported declaration, so writing `Type.Number` here fails the suite instead of
250
- * publishing an integer the server does not send.
251
- */
252
- declare const DeviceAuthPollResponseSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TObject<{
253
- status: _sinclair_typebox.TLiteral<"pending">;
254
- intervalMillis: _sinclair_typebox.TInteger;
255
- }>, _sinclair_typebox.TObject<{
256
- status: _sinclair_typebox.TLiteral<"approved">;
257
- userId: _sinclair_typebox.TString;
258
- publicId: _sinclair_typebox.TString;
259
- email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
260
- phone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
261
- passwordChangeRequired: _sinclair_typebox.TBoolean;
262
- }>]>;
263
- declare const PasswordSchema: _sinclair_typebox.TString;
264
- declare const TargetTypeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
265
- type VerificationTargetType = Static<typeof TargetTypeSchema>;
266
- declare const VERIFICATION_TARGET_TYPES: readonly ["email", "phone"];
267
- declare const VerificationPurposeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">, _sinclair_typebox.TLiteral<"account_deletion">]>;
268
- type VerificationPurpose = Static<typeof VerificationPurposeSchema>;
269
- declare const VERIFICATION_PURPOSES: readonly ["registration", "login", "password_reset", "email_change", "phone_change", "account_deletion"];
270
-
271
- /**
272
- * @spfn/auth - Verification Service
101
+ * Nothing here is a bearer value, so nothing is hashed. `publicKey` is public by
102
+ * construction and `credentialId` is a handle the authenticator hands to any
103
+ * origin that asks — storing either in the clear costs nothing, and the lookup
104
+ * on `credentialId` has to be a plain equality match on an indexed column.
273
105
  *
274
- * Handles OTP code generation, validation, and delivery
275
- */
276
-
277
- interface SendVerificationCodeParams {
278
- target: string;
279
- targetType: VerificationTargetType;
280
- purpose: VerificationPurpose;
281
- }
282
- interface SendVerificationCodeResult {
283
- success: boolean;
284
- expiresAt: string;
285
- }
286
- interface VerifyCodeParams {
287
- target: string;
288
- targetType: VerificationTargetType;
289
- code: string;
290
- purpose: VerificationPurpose;
291
- }
292
- interface VerifyCodeResult {
293
- valid: boolean;
294
- verificationToken: string;
295
- }
296
- /**
297
- * Send verification code via email or SMS
298
- */
299
- declare function sendVerificationCodeService(params: SendVerificationCodeParams): Promise<SendVerificationCodeResult>;
300
- /**
301
- * Verify OTP code and return verification token
106
+ * Revocation is soft, and `credentialId` stays unique across live and revoked
107
+ * rows alike: a credential someone cut off must never become enrollable again,
108
+ * on this account or on another one.
302
109
  */
303
- declare function verifyCodeService(params: VerifyCodeParams): Promise<{
304
- valid: boolean;
305
- verificationToken: string;
306
- }>;
307
-
308
110
  /**
309
- * @spfn/auth - Verified-Email Signup Service
310
- *
311
- * A signup where the address is proven before a password exists:
312
- *
313
- * request -> a one-time link is emailed
314
- * confirm -> the link is exchanged for a short-lived password-setup session
315
- * password -> the account is created, the device registered, the user signed in
316
- *
317
- * The link token and the setup secret are bearer credentials, so neither is ever
318
- * stored. Only their SHA-256 hashes are, and lookup is by hash. A database dump
319
- * therefore yields nothing that can be presented to either step.
111
+ * Whether the credential can leave the authenticator that minted it.
320
112
  *
321
- * The six-digit-code registration path is untouched and remains the default; this
322
- * is a second entry point to the same account creation, not a replacement.
323
- */
324
-
325
- interface RequestSignupLinkParams {
326
- email: string;
327
- returnPath?: string;
328
- }
329
- interface RequestSignupLinkResult {
330
- success: boolean;
331
- expiresAt: string;
332
- }
333
- /**
334
- * Step 1 — issue a confirmation link for an address.
335
- *
336
- * Answers identically whether or not the address already has an account. When it
337
- * does, the owner gets a notice instead of a usable link, through the same
338
- * dedupe window the six-digit-code path uses.
339
- *
340
- * Requesting again is how a resend works: every live link for the address is
341
- * superseded first, so the newest link is the only one that opens, and any setup
342
- * session already opened from an older link dies with it.
343
- *
344
- * Neither branch sends mail: both hand it to `auth.link-mail`, so the answer
345
- * costs the same database work whichever one ran. With no pg-boss initialised
346
- * the mail still goes out on this request — see `lib/link-mail-delivery.ts`.
347
- */
348
- declare function requestSignupLinkService(params: RequestSignupLinkParams): Promise<RequestSignupLinkResult>;
349
- interface ConfirmSignupLinkParams {
350
- token: string;
351
- }
352
- interface ConfirmSignupLinkResult {
353
- email: string;
354
- returnPath: string | null;
355
- /** Handed to the proxy interceptor, which moves it into an HttpOnly cookie. */
356
- setupSecret: string;
357
- setupExpiresAt: string;
358
- }
359
- /**
360
- * Step 2 — exchange a link for a password-setup session.
361
- *
362
- * Nothing binds the row to a device or a browser, which is what lets someone
363
- * request the link on a laptop and open it on a phone.
364
- */
365
- declare function confirmSignupLinkService(params: ConfirmSignupLinkParams): Promise<ConfirmSignupLinkResult>;
366
- interface CompleteSignupParams {
367
- setupSecret?: string;
368
- password: string;
369
- publicKey: string;
370
- keyId: string;
371
- fingerprint: string;
372
- algorithm?: KeyAlgorithmType;
373
- deviceName?: string;
374
- platform?: KeyPlatformType;
375
- metadata?: Record<string, unknown>;
376
- /** Client address of the request, from `deviceProvenance` at the route. */
377
- ip?: string;
378
- /** `user-agent` of the request, already truncated at the route. */
379
- userAgent?: string;
380
- }
381
- /**
382
- * Step 3 — set the password, which is what creates the account.
383
- *
384
- * Run under `Transactional()`: the user row, the device key and the completion
385
- * mark commit together. A device-key failure must not leave an account nobody
386
- * can sign into, and a completion mark must not survive a rolled-back account.
387
- *
388
- * A refusal that is the user's to fix — a weak password, an app policy that
389
- * rejects the registration — leaves the setup session usable, so the fix is
390
- * retyping the password rather than requesting a fresh email.
391
- */
392
- declare function completeSignupService(params: CompleteSignupParams): Promise<RegisterResult>;
393
-
394
- /**
395
- * @spfn/auth - Password Reset Service
396
- *
397
- * Getting back into an account whose password is gone, using the address the
398
- * account already proved:
399
- *
400
- * request -> a one-time link is emailed
401
- * confirm -> the link is exchanged for a short-lived password-setup session
402
- * complete -> the new password is written, everything else is signed out,
403
- * and the browser that reset is signed in on a fresh device key
404
- *
405
- * Mirrors the verified-email signup slice deliberately — same credentials, same
406
- * hashing, same supersede-on-resend, same interceptor moves — with two
407
- * differences that matter.
408
- *
409
- * First, the request answers identically for *every* input and sends mail only
410
- * to an account that can be reset. Signup can afford to tell an existing owner
411
- * "you already have an account"; a reset cannot send anything to a stranger's
412
- * mailbox, because the mail itself would be the answer to "does this address
413
- * have an account here".
414
- *
415
- * Second, completing it is a credential change on a live account, so it carries
416
- * the same blast radius as `changePasswordService`: pending device
417
- * authorizations are denied and every active key is revoked. Whoever was signed
418
- * in on the old password is signed out, including the attacker the reset was
419
- * needed for.
420
- */
421
-
422
- interface RequestPasswordResetParams {
423
- email: string;
424
- returnPath?: string;
425
- }
426
- interface RequestPasswordResetResult {
427
- success: boolean;
428
- expiresAt: string;
429
- }
430
- /**
431
- * Step 1 — issue a reset link for an address.
432
- *
433
- * Answers identically for every input: the same status, the same two fields, and
434
- * an `expiresAt` computed the same way whether or not a row was written. An
435
- * address with no account, an account that cannot be reset, and an account that
436
- * can are indistinguishable to the caller — only the first of the three gets
437
- * mail, and it goes to the owner.
438
- *
439
- * Requesting again is how a resend works: every live link for the account is
440
- * superseded first, so the newest link is the only one that opens, and any setup
441
- * session already opened from an older link dies with it.
442
- *
443
- * The eligible branch does not send the mail either — it hands it to
444
- * `auth.link-mail` — so the two branches differ by a few database writes and not
445
- * by a mail provider's round trip. With no pg-boss initialised the mail still
446
- * goes out on this request; see `lib/link-mail-delivery.ts`.
447
- */
448
- declare function requestPasswordResetService(params: RequestPasswordResetParams): Promise<RequestPasswordResetResult>;
449
- interface ConfirmPasswordResetParams {
450
- token: string;
451
- }
452
- interface ConfirmPasswordResetResult {
453
- email: string;
454
- returnPath: string | null;
455
- /** Handed to the proxy interceptor, which moves it into an HttpOnly cookie. */
456
- setupSecret: string;
457
- setupExpiresAt: string;
458
- }
459
- /**
460
- * Step 2 — exchange a link for a password-setup session.
461
- *
462
- * Nothing binds the row to a device or a browser, which is what lets someone ask
463
- * for the link on a laptop and open it on a phone.
464
- */
465
- declare function confirmPasswordResetService(params: ConfirmPasswordResetParams): Promise<ConfirmPasswordResetResult>;
466
- interface CompletePasswordResetParams {
467
- setupSecret?: string;
468
- password: string;
469
- publicKey: string;
470
- keyId: string;
471
- fingerprint: string;
472
- algorithm?: KeyAlgorithmType;
473
- deviceName?: string;
474
- platform?: KeyPlatformType;
475
- /** Client address of the request, from `deviceProvenance` at the route. */
476
- ip?: string;
477
- /** `user-agent` of the request, already truncated at the route. */
478
- userAgent?: string;
479
- }
480
- /**
481
- * Step 3 — set the new password, which is what completes the reset.
482
- *
483
- * Run under `Transactional()`: the password, the revocations, the new device key
484
- * and the completion mark commit together. A key registration that failed after
485
- * the revoke-all would otherwise leave an account with a new password and
486
- * nothing signed in.
487
- *
488
- * A refusal that is the user's to fix — a password the policy rejects, a body
489
- * with no device key — leaves the setup session usable, so the fix is retyping
490
- * the password rather than asking for a fresh email.
491
- */
492
- declare function completePasswordResetService(params: CompletePasswordResetParams): Promise<RegisterResult>;
493
-
494
- /**
495
- * @spfn/auth - Device Auth Service
496
- *
497
- * Device-code login: a device with no key on file yet shows a short code, the
498
- * account owner types that code on a device that is already signed in, and the
499
- * waiting device's key is registered on approval.
500
- *
501
- * There is no token to hand over. Every request in this system is signed by the
502
- * calling device's own key, so "logging a device in" means one thing — getting
503
- * its public key into `user_public_keys` under the right account. That is what
504
- * the poll does, and it is why the poll returns exactly what `loginService`
505
- * returns: from the client's side the two ways in are indistinguishable.
506
- *
507
- * | state ↓ op → | info | approve | deny | poll |
508
- * | --- | --- | --- | --- | --- |
509
- * | pending | device details | → approved | → denied | pending |
510
- * | approved | AlreadyHandled | AlreadyHandled | AlreadyHandled | key registered, → consumed |
511
- * | denied | AlreadyHandled | AlreadyHandled | AlreadyHandled | Denied |
512
- * | consumed | NotFound | NotFound | NotFound | NotFound |
513
- * | expired | Expired | Expired | Expired | Expired |
514
- * | unknown | NotFound | NotFound | NotFound | NotFound |
515
- *
516
- * A global revocation — revoke-all, a password change, a deletion request —
517
- * refuses the account's live records too, as `denied`, so they land in that row
518
- * of the table. See `denyAllActiveByUserId`; the three callers are the three
519
- * places that revoke every key at once.
520
- */
521
-
522
- interface StartDeviceAuthParams {
523
- publicKey: string;
524
- keyId: string;
525
- fingerprint: string;
526
- algorithm?: KeyAlgorithmType;
527
- /** Device label shown to the approver. Display only — nothing is authorized by it. */
528
- deviceName?: string;
529
- platform?: KeyPlatformType;
530
- }
531
- interface StartDeviceAuthResult {
532
- /** Returned once. The waiting device polls with it; the server stores only its hash. */
533
- deviceCode: string;
534
- /** `XXXX-XXXX`, for the waiting device's screen and nowhere else. */
535
- userCode: string;
536
- expiresAtMillis: number;
537
- /** Milliseconds the waiting device should wait between polls. */
538
- intervalMillis: number;
539
- }
540
- interface DeviceAuthInfoParams {
541
- userCode: string;
542
- }
543
- /** What the approver is shown about the device asking to be let in. */
544
- interface DeviceAuthInfoResult {
545
- deviceName?: string;
546
- /** One of `KEY_PLATFORM`, which is what the route accepts and the column stores. */
547
- platform?: KeyPlatformType;
548
- /** First bytes of the pending key's fingerprint, as the device list truncates it. */
549
- fingerprintPrefix: string;
550
- requestedAtMillis: number;
551
- expiresAtMillis: number;
552
- }
553
- interface ApproveDeviceAuthParams {
554
- userCode: string;
555
- /** The approver, read from their session. Never from a request body. */
556
- userId: number;
557
- }
558
- interface DenyDeviceAuthParams {
559
- userCode: string;
560
- }
561
- interface PollDeviceAuthParams {
562
- deviceCode: string;
563
- /** Client address of the request, from `deviceProvenance` at the route. */
564
- ip?: string;
565
- /** `user-agent` of the request, already truncated at the route. */
566
- userAgent?: string;
567
- }
568
- /** Nobody has answered yet. Not an error — the waiting device waits. */
569
- interface DeviceAuthPendingResult {
570
- status: 'pending';
571
- intervalMillis: number;
572
- }
573
- /** Approved and spent: the key is registered and this is the login it produced. */
574
- type DeviceAuthApprovedResult = {
575
- status: 'approved';
576
- } & LoginResult;
577
- type PollDeviceAuthResult = DeviceAuthPendingResult | DeviceAuthApprovedResult;
578
- /**
579
- * Park a new device's key and hand back the codes it needs.
580
- *
581
- * The caller is unauthenticated by definition — this is what a device does before
582
- * it has any way to prove anything — so nothing here is attributed to an account.
583
- * The record gains an owner only when someone approves it.
584
- */
585
- declare function startDeviceAuthService(params: StartDeviceAuthParams): Promise<StartDeviceAuthResult>;
586
- /**
587
- * What the approver sees before deciding.
588
- *
589
- * This is the whole defence against being talked into approving someone else's
590
- * device: the answer names the device that is waiting, so the person holding the
591
- * phone can see that it is not theirs. An approval screen that showed only the
592
- * code would be asking them to confirm a number they were just told.
593
- */
594
- declare function getDeviceAuthInfoService(params: DeviceAuthInfoParams): Promise<DeviceAuthInfoResult>;
595
- /**
596
- * Bind the record to the approving account.
597
- *
598
- * The key is not registered here. The waiting device may never come back, and a
599
- * key registered for a device that stopped listening is a signing credential
600
- * nobody asked for — so approval records the decision and the poll acts on it.
601
- *
602
- * Answers with the same device description `info` returns, so a client that let
603
- * a user approve without looking first can still show them what they just let
604
- * in — which is the moment someone talked into approving an attacker's device
605
- * has to notice and revoke it.
606
- */
607
- declare function approveDeviceAuthService(params: ApproveDeviceAuthParams): Promise<DeviceAuthInfoResult>;
608
- /**
609
- * Refuse the record, so the waiting device is told no instead of timing out.
610
- *
611
- * Denying binds no user: the point of refusing is that the account owner wants
612
- * nothing to do with the request.
613
- */
614
- declare function denyDeviceAuthService(params: DenyDeviceAuthParams): Promise<void>;
615
- /**
616
- * The waiting device asking whether anyone has answered.
617
- *
618
- * Approved is the one branch with a side effect, and it is a one-shot: the record
619
- * is spent by a conditional update that names `approved`, so of two polls that
620
- * arrive together exactly one registers the key. The loser matches nothing and is
621
- * answered as if the code were unknown — which by then it is.
622
- */
623
- declare function pollDeviceAuthService(params: PollDeviceAuthParams): Promise<PollDeviceAuthResult>;
624
-
625
- /**
626
- * @spfn/auth - Passkeys Entity
627
- *
628
- * A WebAuthn credential the account owner enrolled on one of their devices.
629
- * It is a *credential*, not a session: an assertion proves who is asking, and
630
- * the ordinary device key in `user_public_keys` is what the request afterwards
631
- * is signed with. The two tables therefore never stand in for each other.
632
- *
633
- * Nothing here is a bearer value, so nothing is hashed. `publicKey` is public by
634
- * construction and `credentialId` is a handle the authenticator hands to any
635
- * origin that asks — storing either in the clear costs nothing, and the lookup
636
- * on `credentialId` has to be a plain equality match on an indexed column.
637
- *
638
- * Revocation is soft, and `credentialId` stays unique across live and revoked
639
- * rows alike: a credential someone cut off must never become enrollable again,
640
- * on this account or on another one.
641
- */
642
- /**
643
- * Whether the credential can leave the authenticator that minted it.
644
- *
645
- * `multiDevice` is a synced passkey (iCloud Keychain, Google Password Manager);
646
- * `singleDevice` is bound to one authenticator. Reported by the authenticator at
647
- * enrollment and shown in the management list, because "this one is only on that
648
- * phone" is what the owner needs to know before revoking the other entry.
113
+ * `multiDevice` is a synced passkey (iCloud Keychain, Google Password Manager);
114
+ * `singleDevice` is bound to one authenticator. Reported by the authenticator at
115
+ * enrollment and shown in the management list, because "this one is only on that
116
+ * phone" is what the owner needs to know before revoking the other entry.
649
117
  */
650
118
  declare const PASSKEY_DEVICE_TYPES: readonly ["singleDevice", "multiDevice"];
651
119
  type PasskeyDeviceType = typeof PASSKEY_DEVICE_TYPES[number];
@@ -898,8 +366,677 @@ declare const passkeys: drizzle_orm_pg_core.PgTableWithColumns<{
898
366
  };
899
367
  dialect: "pg";
900
368
  }>;
901
- type Passkey = typeof passkeys.$inferSelect;
902
- type NewPasskey = typeof passkeys.$inferInsert;
369
+ type Passkey = typeof passkeys.$inferSelect;
370
+ type NewPasskey = typeof passkeys.$inferInsert;
371
+
372
+ /**
373
+ * @spfn/auth - Auth Service
374
+ *
375
+ * Core authentication logic: registration, login, logout, password management
376
+ */
377
+
378
+ interface RegisterParams {
379
+ email?: string;
380
+ phone?: string;
381
+ verificationToken: string;
382
+ password: string;
383
+ publicKey: string;
384
+ keyId: string;
385
+ fingerprint: string;
386
+ algorithm?: KeyAlgorithmType;
387
+ deviceName?: string;
388
+ platform?: KeyPlatformType;
389
+ metadata?: Record<string, unknown>;
390
+ /** Client address of the request, from `deviceProvenance` at the route. */
391
+ ip?: string;
392
+ /** `user-agent` of the request, already truncated at the route. */
393
+ userAgent?: string;
394
+ /**
395
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
396
+ * helper. Carried for shape rather than effect: a brand-new account is on the
397
+ * default `session_binding: 'none'`, so its first key is never bound.
398
+ */
399
+ webProxy?: boolean;
400
+ }
401
+ interface RegisterResult {
402
+ userId: string;
403
+ publicId: string;
404
+ email?: string;
405
+ phone?: string;
406
+ }
407
+ interface LoginParams {
408
+ email?: string;
409
+ phone?: string;
410
+ password: string;
411
+ publicKey: string;
412
+ keyId: string;
413
+ fingerprint: string;
414
+ oldKeyId?: string;
415
+ algorithm?: KeyAlgorithmType;
416
+ deviceName?: string;
417
+ platform?: KeyPlatformType;
418
+ /** Client address of the request, from `deviceProvenance` at the route. */
419
+ ip?: string;
420
+ /** `user-agent` of the request, already truncated at the route. */
421
+ userAgent?: string;
422
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
423
+ webProxy?: boolean;
424
+ }
425
+ /**
426
+ * What a sign-in answers with, on every path that starts a session.
427
+ *
428
+ * The last two fields are the carrier #97 needed. The Next.js proxy generated
429
+ * the device key and sealed the cookie, but only the backend knows whether the
430
+ * account asked for a bound session and when the key it just registered runs
431
+ * out — so the sign-in says it here and the interceptor copies both into
432
+ * `SessionData`. A response without them seals an unbound session, which is what
433
+ * every account that did not opt in gets and what every path predating this
434
+ * change keeps getting.
435
+ */
436
+ interface LoginResult {
437
+ userId: string;
438
+ publicId: string;
439
+ email?: string;
440
+ phone?: string;
441
+ passwordChangeRequired: boolean;
442
+ /** `'passkey'` when the key registered by this sign-in is bound. Absent otherwise. */
443
+ sessionBinding?: SessionBindingType;
444
+ /** Epoch milliseconds that key expires at. Only sent alongside `sessionBinding`. */
445
+ keyExpiresAtMillis?: number;
446
+ }
447
+ /**
448
+ * The binding half of a sign-in answer, as a type.
449
+ *
450
+ * Named because more than one result carries it: a password reset registers a
451
+ * device key exactly as a sign-in does, so its answer has to say so too or the
452
+ * proxy seals a cookie that does not know the key it holds is short-lived.
453
+ */
454
+ type LoginBindingFields = Pick<LoginResult, 'sessionBinding' | 'keyExpiresAtMillis'>;
455
+ interface LogoutParams {
456
+ userId: number;
457
+ keyId: string;
458
+ }
459
+ interface ChangePasswordParams {
460
+ userId: number;
461
+ /**
462
+ * The device key this request is signed with.
463
+ *
464
+ * Only read to measure the second-factor window of an enrolled account —
465
+ * an account with nothing enrolled is answered exactly as before, so this
466
+ * adds no refusal for anybody who has not opted in.
467
+ */
468
+ keyId: string;
469
+ currentPassword?: string;
470
+ newPassword: string;
471
+ passwordHash?: string;
472
+ }
473
+ /**
474
+ * Register a new user account
475
+ */
476
+ declare function registerService(params: RegisterParams): Promise<RegisterResult>;
477
+ /**
478
+ * Authenticate user and create session
479
+ */
480
+ declare function loginService(params: LoginParams): Promise<LoginResult>;
481
+ /**
482
+ * Logout user (revoke current key)
483
+ */
484
+ declare function logoutService(params: LogoutParams): Promise<void>;
485
+ /**
486
+ * Change user password
487
+ *
488
+ * An enrolled account steps up first (#95): a stolen session must not be able
489
+ * to take the account over by setting a new password. An unenrolled account is
490
+ * unaffected — including the OAuth-only account with no password and a key
491
+ * older than ten minutes, which still sets a first password and gets a 200.
492
+ */
493
+ declare function changePasswordService(params: ChangePasswordParams): Promise<void>;
494
+
495
+ /**
496
+ * @spfn/auth - Session Renewal Service
497
+ *
498
+ * What a bound session does when its key runs out: prove, with a fresh WebAuthn
499
+ * assertion, that the person who enrolled the passkey is still at the machine,
500
+ * and get a new short-lived key sealed into the cookie.
501
+ *
502
+ * Neither step is public. The expiring key is named by `expiredKeyId`, and that
503
+ * value reaches the service from `authenticateForRenewal` — the `keyId` of a
504
+ * bearer JWT this very key signed — rather than from the request body, so a
505
+ * caller who does not hold the private half cannot name a key at all. The
506
+ * assertion still has to be signed by a passkey that key's owner enrolled: the
507
+ * signature proves the cookie, and the cookie is the thing that may have been
508
+ * copied.
509
+ *
510
+ * The admission below is run again here all the same. The middleware and the
511
+ * service ask the same four questions of the row, and a service that trusted its
512
+ * caller to have asked them would be one refactor away from not being asked at
513
+ * all.
514
+ *
515
+ * Every refusal is the same refusal. A key that never existed, a stranger's key,
516
+ * an unbound key, a revoked one, one past its grace, an inactive account, a spent
517
+ * challenge, an assertion that did not verify — all `SessionRenewalRefusedError`,
518
+ * with the same body, because anything finer would answer "is this key id live"
519
+ * to whoever asked.
520
+ *
521
+ * Renewal announces nothing. No `auth.login`, no `auth.device.registered`, and
522
+ * `lastLoginAt` does not move: this is the same person on the same device
523
+ * continuing the session they already had, and a subscriber mailing "new sign-in"
524
+ * once a day per device would train its reader to ignore the notice that matters.
525
+ * A `lastLoginAt` that moved every day would make dormant-account detection
526
+ * meaningless for exactly the accounts that turned this protection on.
527
+ */
528
+
529
+ interface StartSessionRenewParams {
530
+ /** The key that ran out, read off the JWT the request was signed with. */
531
+ expiredKeyId: string;
532
+ }
533
+ interface FinishSessionRenewParams extends StartSessionRenewParams {
534
+ /** The assertion, from `navigator.credentials.get()`. */
535
+ response: AuthenticationResponseJSON;
536
+ /**
537
+ * The new key pair, in the vocabulary the Next.js login interceptor already
538
+ * writes: `renew/verify` is on that interceptor's path list, so these arrive
539
+ * exactly as they do on a login.
540
+ */
541
+ keyId: string;
542
+ publicKey: string;
543
+ fingerprint: string;
544
+ algorithm?: KeyAlgorithmType;
545
+ }
546
+ /**
547
+ * What a completed renewal answers: a sign-in result, plus the new key's id.
548
+ *
549
+ * The id is the one thing a renewal has that a sign-in does not need to say —
550
+ * `renewSession()` promises it to the app, which has no other way to learn it
551
+ * (the key pair is minted in the proxy and the private half never leaves the
552
+ * cookie). It is not a contract operation, so nothing generated reads it.
553
+ */
554
+ interface SessionRenewResult extends LoginResult {
555
+ /** The key this renewal registered, the one the session now signs with. */
556
+ keyId: string;
557
+ }
558
+ /**
559
+ * Step 1 — the challenge the authenticator signs.
560
+ *
561
+ * `allowCredentials` is empty and the account lives only on the challenge row.
562
+ * See `startRenewalCeremonyService`.
563
+ *
564
+ * @throws SessionRenewalRefusedError 갱신할 수 없는 키·계정일 때 (모든 사유 동일)
565
+ */
566
+ declare function startSessionRenewService(params: StartSessionRenewParams): Promise<PublicKeyCredentialRequestOptionsJSON>;
567
+ /**
568
+ * Step 2 — verify the assertion, put a new bound key in place of the old one.
569
+ *
570
+ * The revocation runs first and its answer is the race winner: two verifies that
571
+ * both got past their own challenges meet at the same conditional UPDATE, and
572
+ * only the one that actually revoked the key goes on to register a replacement.
573
+ *
574
+ * The new key inherits the old row's provenance, so the device list keeps saying
575
+ * where this device first appeared rather than re-stamping itself every day. Its
576
+ * expiry is a fresh window from now — renewal is a renewal, not an extension of
577
+ * what the old key had.
578
+ *
579
+ * @throws SessionRenewalRefusedError 갱신할 수 없을 때 (증명 실패 포함, 모든 사유 동일)
580
+ */
581
+ declare function finishSessionRenewService(params: FinishSessionRenewParams): Promise<SessionRenewResult>;
582
+
583
+ declare const EmailSchema: _sinclair_typebox.TString;
584
+ declare const PhoneSchema: _sinclair_typebox.TString;
585
+ /**
586
+ * Optional device labels a client may send when registering a key.
587
+ *
588
+ * Display only: the key list uses them to tell one device from another, and
589
+ * nothing is authorized or refused by either value, so a client that lies about
590
+ * them gains nothing. Both are omitted by every key registered before they
591
+ * existed, hence optional rather than defaulted.
592
+ */
593
+ declare const DeviceNameSchema: _sinclair_typebox.TString;
594
+ declare const PlatformSchema: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>;
595
+ /**
596
+ * Key material as a device sends it, bounded.
597
+ *
598
+ * The bounds exist for the one route that takes this material from a caller who
599
+ * has not authenticated and cannot: `POST /_auth/device/start` persists what it
600
+ * is given, and a correctly fingerprinted megabyte of base64 would sit in
601
+ * `device_authorizations` until something swept it — and nothing sweeps it.
602
+ *
603
+ * The numbers are what real key material measures, with room to spare. The
604
+ * package's own generators produce SPKI DER in base64: 124 characters for
605
+ * ES256 (P-256), 392 for RS256 (RSA-2048). An RSA-4096 key would be 736, an
606
+ * RSA-8192 key about 1400, and the same 4096-bit key PEM-armoured about 800 —
607
+ * so 2048 admits every shape of key anyone could reasonably present, while a
608
+ * megabyte is refused three orders of magnitude before it reaches a row.
609
+ *
610
+ * `keyId` is a UUID (36) everywhere this package generates one; 64 leaves room
611
+ * for a client that prefixes or namespaces its own. `fingerprint` is SHA-256
612
+ * hex, exactly 64, and nothing else can ever verify against the public key —
613
+ * 128 is the length a longer digest would need, and no more.
614
+ */
615
+ declare const PublicKeySchema: _sinclair_typebox.TString;
616
+ declare const KeyIdSchema: _sinclair_typebox.TString;
617
+ declare const FingerprintSchema: _sinclair_typebox.TString;
618
+ /**
619
+ * The code a person reads off the waiting device and types on their own.
620
+ *
621
+ * Loose on purpose: 8 characters plus an optional dash is what is shown, but the
622
+ * server folds whitespace, dashes and lower case away before looking anything up,
623
+ * so refusing those spellings here would refuse a code that is on screen. The
624
+ * bounds exist to stop an unbounded string reaching the database, not to spell
625
+ * out the format — `USER_CODE_ALPHABET` is the only thing that can match a row.
626
+ */
627
+ declare const UserCodeSchema: _sinclair_typebox.TString;
628
+ /**
629
+ * What `POST /_auth/device/poll` answers with.
630
+ *
631
+ * A union, because the two answers are different kinds of thing rather than one
632
+ * shape with optional fields: pending says "ask again in this long", approved is
633
+ * a completed login carrying exactly what `/_auth/login` returns. `status` is the
634
+ * discriminant, so a generated client narrows on it instead of testing which
635
+ * fields happen to be present.
636
+ *
637
+ * The mobile contract has no union type, so it exports this as one object with
638
+ * `status` required and every branch field optional — see
639
+ * `deviceAuthorization.pollStatusRule` in the bundle. `intervalMillis` is an
640
+ * integer for the same reason: that grammar carries no floating-point scalar,
641
+ * and a count of milliseconds never needed one.
642
+ *
643
+ * That integer is a promise two things keep, because nothing validates a response
644
+ * against this schema on the way out. `configureDeviceAuth` refuses an interval
645
+ * that is not a whole number of milliseconds, so the only value this branch can
646
+ * carry is one; and `contract-export.test.ts` reads this schema to check the
647
+ * exported declaration, so writing `Type.Number` here fails the suite instead of
648
+ * publishing an integer the server does not send.
649
+ */
650
+ declare const DeviceAuthPollResponseSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TObject<{
651
+ status: _sinclair_typebox.TLiteral<"pending">;
652
+ intervalMillis: _sinclair_typebox.TInteger;
653
+ }>, _sinclair_typebox.TObject<{
654
+ status: _sinclair_typebox.TLiteral<"approved">;
655
+ userId: _sinclair_typebox.TString;
656
+ publicId: _sinclair_typebox.TString;
657
+ email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
658
+ phone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
659
+ passwordChangeRequired: _sinclair_typebox.TBoolean;
660
+ sessionBinding: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"none" | "passkey">[]>>;
661
+ keyExpiresAtMillis: _sinclair_typebox.TOptional<_sinclair_typebox.TInteger>;
662
+ }>]>;
663
+ declare const PasswordSchema: _sinclair_typebox.TString;
664
+ declare const TargetTypeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
665
+ type VerificationTargetType = Static<typeof TargetTypeSchema>;
666
+ declare const VERIFICATION_TARGET_TYPES: readonly ["email", "phone"];
667
+ declare const VerificationPurposeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">, _sinclair_typebox.TLiteral<"account_deletion">]>;
668
+ type VerificationPurpose = Static<typeof VerificationPurposeSchema>;
669
+ declare const VERIFICATION_PURPOSES: readonly ["registration", "login", "password_reset", "email_change", "phone_change", "account_deletion"];
670
+
671
+ /**
672
+ * @spfn/auth - Verification Service
673
+ *
674
+ * Handles OTP code generation, validation, and delivery
675
+ */
676
+
677
+ interface SendVerificationCodeParams {
678
+ target: string;
679
+ targetType: VerificationTargetType;
680
+ purpose: VerificationPurpose;
681
+ }
682
+ interface SendVerificationCodeResult {
683
+ success: boolean;
684
+ expiresAt: string;
685
+ }
686
+ interface VerifyCodeParams {
687
+ target: string;
688
+ targetType: VerificationTargetType;
689
+ code: string;
690
+ purpose: VerificationPurpose;
691
+ }
692
+ interface VerifyCodeResult {
693
+ valid: boolean;
694
+ verificationToken: string;
695
+ }
696
+ /**
697
+ * Send verification code via email or SMS
698
+ */
699
+ declare function sendVerificationCodeService(params: SendVerificationCodeParams): Promise<SendVerificationCodeResult>;
700
+ /**
701
+ * Verify OTP code and return verification token
702
+ */
703
+ declare function verifyCodeService(params: VerifyCodeParams): Promise<{
704
+ valid: boolean;
705
+ verificationToken: string;
706
+ }>;
707
+
708
+ /**
709
+ * @spfn/auth - Verified-Email Signup Service
710
+ *
711
+ * A signup where the address is proven before a password exists:
712
+ *
713
+ * request -> a one-time link is emailed
714
+ * confirm -> the link is exchanged for a short-lived password-setup session
715
+ * password -> the account is created, the device registered, the user signed in
716
+ *
717
+ * The link token and the setup secret are bearer credentials, so neither is ever
718
+ * stored. Only their SHA-256 hashes are, and lookup is by hash. A database dump
719
+ * therefore yields nothing that can be presented to either step.
720
+ *
721
+ * The six-digit-code registration path is untouched and remains the default; this
722
+ * is a second entry point to the same account creation, not a replacement.
723
+ */
724
+
725
+ interface RequestSignupLinkParams {
726
+ email: string;
727
+ returnPath?: string;
728
+ }
729
+ interface RequestSignupLinkResult {
730
+ success: boolean;
731
+ expiresAt: string;
732
+ }
733
+ /**
734
+ * Step 1 — issue a confirmation link for an address.
735
+ *
736
+ * Answers identically whether or not the address already has an account. When it
737
+ * does, the owner gets a notice instead of a usable link, through the same
738
+ * dedupe window the six-digit-code path uses.
739
+ *
740
+ * Requesting again is how a resend works: every live link for the address is
741
+ * superseded first, so the newest link is the only one that opens, and any setup
742
+ * session already opened from an older link dies with it.
743
+ *
744
+ * Neither branch sends mail: both hand it to `auth.link-mail`, so the answer
745
+ * costs the same database work whichever one ran. With no pg-boss initialised
746
+ * the mail still goes out on this request — see `lib/link-mail-delivery.ts`.
747
+ */
748
+ declare function requestSignupLinkService(params: RequestSignupLinkParams): Promise<RequestSignupLinkResult>;
749
+ interface ConfirmSignupLinkParams {
750
+ token: string;
751
+ }
752
+ interface ConfirmSignupLinkResult {
753
+ email: string;
754
+ returnPath: string | null;
755
+ /** Handed to the proxy interceptor, which moves it into an HttpOnly cookie. */
756
+ setupSecret: string;
757
+ setupExpiresAt: string;
758
+ }
759
+ /**
760
+ * Step 2 — exchange a link for a password-setup session.
761
+ *
762
+ * Nothing binds the row to a device or a browser, which is what lets someone
763
+ * request the link on a laptop and open it on a phone.
764
+ */
765
+ declare function confirmSignupLinkService(params: ConfirmSignupLinkParams): Promise<ConfirmSignupLinkResult>;
766
+ interface CompleteSignupParams {
767
+ setupSecret?: string;
768
+ password: string;
769
+ publicKey: string;
770
+ keyId: string;
771
+ fingerprint: string;
772
+ algorithm?: KeyAlgorithmType;
773
+ deviceName?: string;
774
+ platform?: KeyPlatformType;
775
+ metadata?: Record<string, unknown>;
776
+ /** Client address of the request, from `deviceProvenance` at the route. */
777
+ ip?: string;
778
+ /** `user-agent` of the request, already truncated at the route. */
779
+ userAgent?: string;
780
+ }
781
+ /**
782
+ * Step 3 — set the password, which is what creates the account.
783
+ *
784
+ * Run under `Transactional()`: the user row, the device key and the completion
785
+ * mark commit together. A device-key failure must not leave an account nobody
786
+ * can sign into, and a completion mark must not survive a rolled-back account.
787
+ *
788
+ * A refusal that is the user's to fix — a weak password, an app policy that
789
+ * rejects the registration — leaves the setup session usable, so the fix is
790
+ * retyping the password rather than requesting a fresh email.
791
+ */
792
+ declare function completeSignupService(params: CompleteSignupParams): Promise<RegisterResult>;
793
+
794
+ /**
795
+ * @spfn/auth - Password Reset Service
796
+ *
797
+ * Getting back into an account whose password is gone, using the address the
798
+ * account already proved:
799
+ *
800
+ * request -> a one-time link is emailed
801
+ * confirm -> the link is exchanged for a short-lived password-setup session
802
+ * complete -> the new password is written, everything else is signed out,
803
+ * and the browser that reset is signed in on a fresh device key
804
+ *
805
+ * Mirrors the verified-email signup slice deliberately — same credentials, same
806
+ * hashing, same supersede-on-resend, same interceptor moves — with two
807
+ * differences that matter.
808
+ *
809
+ * First, the request answers identically for *every* input and sends mail only
810
+ * to an account that can be reset. Signup can afford to tell an existing owner
811
+ * "you already have an account"; a reset cannot send anything to a stranger's
812
+ * mailbox, because the mail itself would be the answer to "does this address
813
+ * have an account here".
814
+ *
815
+ * Second, completing it is a credential change on a live account, so it carries
816
+ * the same blast radius as `changePasswordService`: pending device
817
+ * authorizations are denied and every active key is revoked. Whoever was signed
818
+ * in on the old password is signed out, including the attacker the reset was
819
+ * needed for.
820
+ */
821
+
822
+ interface RequestPasswordResetParams {
823
+ email: string;
824
+ returnPath?: string;
825
+ }
826
+ interface RequestPasswordResetResult {
827
+ success: boolean;
828
+ expiresAt: string;
829
+ }
830
+ /**
831
+ * Step 1 — issue a reset link for an address.
832
+ *
833
+ * Answers identically for every input: the same status, the same two fields, and
834
+ * an `expiresAt` computed the same way whether or not a row was written. An
835
+ * address with no account, an account that cannot be reset, and an account that
836
+ * can are indistinguishable to the caller — only the first of the three gets
837
+ * mail, and it goes to the owner.
838
+ *
839
+ * Requesting again is how a resend works: every live link for the account is
840
+ * superseded first, so the newest link is the only one that opens, and any setup
841
+ * session already opened from an older link dies with it.
842
+ *
843
+ * The eligible branch does not send the mail either — it hands it to
844
+ * `auth.link-mail` — so the two branches differ by a few database writes and not
845
+ * by a mail provider's round trip. With no pg-boss initialised the mail still
846
+ * goes out on this request; see `lib/link-mail-delivery.ts`.
847
+ */
848
+ declare function requestPasswordResetService(params: RequestPasswordResetParams): Promise<RequestPasswordResetResult>;
849
+ interface ConfirmPasswordResetParams {
850
+ token: string;
851
+ }
852
+ interface ConfirmPasswordResetResult {
853
+ email: string;
854
+ returnPath: string | null;
855
+ /** Handed to the proxy interceptor, which moves it into an HttpOnly cookie. */
856
+ setupSecret: string;
857
+ setupExpiresAt: string;
858
+ }
859
+ /**
860
+ * Step 2 — exchange a link for a password-setup session.
861
+ *
862
+ * Nothing binds the row to a device or a browser, which is what lets someone ask
863
+ * for the link on a laptop and open it on a phone.
864
+ */
865
+ declare function confirmPasswordResetService(params: ConfirmPasswordResetParams): Promise<ConfirmPasswordResetResult>;
866
+ interface CompletePasswordResetParams {
867
+ setupSecret?: string;
868
+ password: string;
869
+ publicKey: string;
870
+ keyId: string;
871
+ fingerprint: string;
872
+ algorithm?: KeyAlgorithmType;
873
+ deviceName?: string;
874
+ platform?: KeyPlatformType;
875
+ /** Client address of the request, from `deviceProvenance` at the route. */
876
+ ip?: string;
877
+ /** `user-agent` of the request, already truncated at the route. */
878
+ userAgent?: string;
879
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
880
+ webProxy?: boolean;
881
+ }
882
+ /**
883
+ * Step 3 — set the new password, which is what completes the reset.
884
+ *
885
+ * Run under `Transactional()`: the password, the revocations, the new device key
886
+ * and the completion mark commit together. A key registration that failed after
887
+ * the revoke-all would otherwise leave an account with a new password and
888
+ * nothing signed in.
889
+ *
890
+ * A refusal that is the user's to fix — a password the policy rejects, a body
891
+ * with no device key — leaves the setup session usable, so the fix is retyping
892
+ * the password rather than asking for a fresh email.
893
+ *
894
+ * The answer carries the binding fields, like every other path that starts a
895
+ * session. A reset on an account that opted in registers a bound key — 24 hours,
896
+ * renewable only by a passkey — and this route is on the sealing interceptor's
897
+ * list, so a body that did not say so would have the proxy seal a cookie
898
+ * believing the session unbound: no user-agent check for the life of that key,
899
+ * and a sign-out a day later instead of the renewal prompt.
900
+ */
901
+ declare function completePasswordResetService(params: CompletePasswordResetParams): Promise<RegisterResult & LoginBindingFields>;
902
+
903
+ /**
904
+ * @spfn/auth - Device Auth Service
905
+ *
906
+ * Device-code login: a device with no key on file yet shows a short code, the
907
+ * account owner types that code on a device that is already signed in, and the
908
+ * waiting device's key is registered on approval.
909
+ *
910
+ * There is no token to hand over. Every request in this system is signed by the
911
+ * calling device's own key, so "logging a device in" means one thing — getting
912
+ * its public key into `user_public_keys` under the right account. That is what
913
+ * the poll does, and it is why the poll returns exactly what `loginService`
914
+ * returns: from the client's side the two ways in are indistinguishable.
915
+ *
916
+ * | state ↓ op → | info | approve | deny | poll |
917
+ * | --- | --- | --- | --- | --- |
918
+ * | pending | device details | → approved | → denied | pending |
919
+ * | approved | AlreadyHandled | AlreadyHandled | AlreadyHandled | key registered, → consumed |
920
+ * | denied | AlreadyHandled | AlreadyHandled | AlreadyHandled | Denied |
921
+ * | consumed | NotFound | NotFound | NotFound | NotFound |
922
+ * | expired | Expired | Expired | Expired | Expired |
923
+ * | unknown | NotFound | NotFound | NotFound | NotFound |
924
+ *
925
+ * A global revocation — revoke-all, a password change, a deletion request —
926
+ * refuses the account's live records too, as `denied`, so they land in that row
927
+ * of the table. See `denyAllActiveByUserId`; the three callers are the three
928
+ * places that revoke every key at once.
929
+ */
930
+
931
+ interface StartDeviceAuthParams {
932
+ publicKey: string;
933
+ keyId: string;
934
+ fingerprint: string;
935
+ algorithm?: KeyAlgorithmType;
936
+ /** Device label shown to the approver. Display only — nothing is authorized by it. */
937
+ deviceName?: string;
938
+ platform?: KeyPlatformType;
939
+ }
940
+ interface StartDeviceAuthResult {
941
+ /** Returned once. The waiting device polls with it; the server stores only its hash. */
942
+ deviceCode: string;
943
+ /** `XXXX-XXXX`, for the waiting device's screen and nowhere else. */
944
+ userCode: string;
945
+ expiresAtMillis: number;
946
+ /** Milliseconds the waiting device should wait between polls. */
947
+ intervalMillis: number;
948
+ }
949
+ interface DeviceAuthInfoParams {
950
+ userCode: string;
951
+ }
952
+ /** What the approver is shown about the device asking to be let in. */
953
+ interface DeviceAuthInfoResult {
954
+ deviceName?: string;
955
+ /** One of `KEY_PLATFORM`, which is what the route accepts and the column stores. */
956
+ platform?: KeyPlatformType;
957
+ /** First bytes of the pending key's fingerprint, as the device list truncates it. */
958
+ fingerprintPrefix: string;
959
+ requestedAtMillis: number;
960
+ expiresAtMillis: number;
961
+ }
962
+ interface ApproveDeviceAuthParams {
963
+ userCode: string;
964
+ /** The approver, read from their session. Never from a request body. */
965
+ userId: number;
966
+ }
967
+ interface DenyDeviceAuthParams {
968
+ userCode: string;
969
+ }
970
+ interface PollDeviceAuthParams {
971
+ deviceCode: string;
972
+ /** Client address of the request, from `deviceProvenance` at the route. */
973
+ ip?: string;
974
+ /** `user-agent` of the request, already truncated at the route. */
975
+ userAgent?: string;
976
+ /**
977
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
978
+ * helper. A waiting device polls the backend itself, so this is false there
979
+ * and the key it collects is unbound — which is what a device with no browser
980
+ * to run a WebAuthn ceremony in needs.
981
+ */
982
+ webProxy?: boolean;
983
+ }
984
+ /** Nobody has answered yet. Not an error — the waiting device waits. */
985
+ interface DeviceAuthPendingResult {
986
+ status: 'pending';
987
+ intervalMillis: number;
988
+ }
989
+ /** Approved and spent: the key is registered and this is the login it produced. */
990
+ type DeviceAuthApprovedResult = {
991
+ status: 'approved';
992
+ } & LoginResult;
993
+ type PollDeviceAuthResult = DeviceAuthPendingResult | DeviceAuthApprovedResult;
994
+ /**
995
+ * Park a new device's key and hand back the codes it needs.
996
+ *
997
+ * The caller is unauthenticated by definition — this is what a device does before
998
+ * it has any way to prove anything — so nothing here is attributed to an account.
999
+ * The record gains an owner only when someone approves it.
1000
+ */
1001
+ declare function startDeviceAuthService(params: StartDeviceAuthParams): Promise<StartDeviceAuthResult>;
1002
+ /**
1003
+ * What the approver sees before deciding.
1004
+ *
1005
+ * This is the whole defence against being talked into approving someone else's
1006
+ * device: the answer names the device that is waiting, so the person holding the
1007
+ * phone can see that it is not theirs. An approval screen that showed only the
1008
+ * code would be asking them to confirm a number they were just told.
1009
+ */
1010
+ declare function getDeviceAuthInfoService(params: DeviceAuthInfoParams): Promise<DeviceAuthInfoResult>;
1011
+ /**
1012
+ * Bind the record to the approving account.
1013
+ *
1014
+ * The key is not registered here. The waiting device may never come back, and a
1015
+ * key registered for a device that stopped listening is a signing credential
1016
+ * nobody asked for — so approval records the decision and the poll acts on it.
1017
+ *
1018
+ * Answers with the same device description `info` returns, so a client that let
1019
+ * a user approve without looking first can still show them what they just let
1020
+ * in — which is the moment someone talked into approving an attacker's device
1021
+ * has to notice and revoke it.
1022
+ */
1023
+ declare function approveDeviceAuthService(params: ApproveDeviceAuthParams): Promise<DeviceAuthInfoResult>;
1024
+ /**
1025
+ * Refuse the record, so the waiting device is told no instead of timing out.
1026
+ *
1027
+ * Denying binds no user: the point of refusing is that the account owner wants
1028
+ * nothing to do with the request.
1029
+ */
1030
+ declare function denyDeviceAuthService(params: DenyDeviceAuthParams): Promise<void>;
1031
+ /**
1032
+ * The waiting device asking whether anyone has answered.
1033
+ *
1034
+ * Approved is the one branch with a side effect, and it is a one-shot: the record
1035
+ * is spent by a conditional update that names `approved`, so of two polls that
1036
+ * arrive together exactly one registers the key. The loser matches nothing and is
1037
+ * answered as if the code were unknown — which by then it is.
1038
+ */
1039
+ declare function pollDeviceAuthService(params: PollDeviceAuthParams): Promise<PollDeviceAuthResult>;
903
1040
 
904
1041
  /**
905
1042
  * @spfn/auth - Passkey Service
@@ -1033,6 +1170,8 @@ interface FinishPasskeyLoginParams {
1033
1170
  ip?: string;
1034
1171
  /** `user-agent` of the request, already truncated at the route. */
1035
1172
  userAgent?: string;
1173
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
1174
+ webProxy?: boolean;
1036
1175
  }
1037
1176
  /**
1038
1177
  * Step 2 of sign-in — verify the assertion, then sign in exactly as a password
@@ -1153,7 +1292,7 @@ declare const mfaVerifications: drizzle_orm_pg_core.PgTableWithColumns<{
1153
1292
  name: string;
1154
1293
  tableName: "mfa_verifications";
1155
1294
  dataType: "string enum";
1156
- data: "totp" | "recovery" | "passkey";
1295
+ data: "passkey" | "totp" | "recovery";
1157
1296
  driverParam: string;
1158
1297
  notNull: true;
1159
1298
  hasDefault: false;
@@ -1434,7 +1573,7 @@ declare const authLoginEvent: _spfn_core_event.EventDef<{
1434
1573
  email?: string | undefined;
1435
1574
  phone?: string | undefined;
1436
1575
  userId: string;
1437
- provider: "email" | "phone" | "google" | "apple" | "github" | "kakao" | "naver" | "superself" | "passkey" | "device";
1576
+ provider: "email" | "phone" | "passkey" | "google" | "apple" | "github" | "kakao" | "naver" | "superself" | "device";
1438
1577
  mfaEnrolled: boolean;
1439
1578
  }>;
1440
1579
  /**
@@ -1446,15 +1585,15 @@ declare const authLoginEvent: _spfn_core_event.EventDef<{
1446
1585
  * both arrive at `createVerifiedAccount`, so the two name themselves there;
1447
1586
  * `'invitation'` is the one path that stores a key without the key service.
1448
1587
  */
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. */
1588
+ 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">]>;
1589
+ /** The ten doors a device key is registered through. */
1451
1590
  type DeviceRegistrationChannel = Static<typeof DeviceRegistrationChannelSchema>;
1452
1591
  /**
1453
1592
  * auth.device.registered — a new device key was added to an account
1454
1593
  *
1455
1594
  * 발행 시점:
1456
1595
  * - a key row was created for an account and the transaction that created it
1457
- * committed, on every one of the nine channels above
1596
+ * committed, on every one of the ten channels above
1458
1597
  *
1459
1598
  * This is the notice an account owner needs and could not get before: a stolen
1460
1599
  * password used to sign in on a new device was silent, because a login event
@@ -1492,7 +1631,7 @@ declare const authDeviceRegisteredEvent: _spfn_core_event.EventDef<{
1492
1631
  fingerprintPrefix: string;
1493
1632
  createdAtMillis: number;
1494
1633
  mfaEnrolled: boolean;
1495
- channel: "password" | "register" | "oauth-native" | "passkey" | "oauth" | "invitation" | "signup-link" | "password-reset" | "device-code";
1634
+ channel: "password" | "register" | "passkey" | "oauth-native" | "renewal" | "oauth" | "invitation" | "signup-link" | "password-reset" | "device-code";
1496
1635
  }>;
1497
1636
  /**
1498
1637
  * auth.register - 회원가입 성공 이벤트
@@ -1697,6 +1836,18 @@ interface RegisterPublicKeyParams {
1697
1836
  ip?: string;
1698
1837
  /** `user-agent` of the registering request, already truncated. */
1699
1838
  userAgent?: string;
1839
+ /**
1840
+ * Whether this key is bound to a passkey, as the registering path decided.
1841
+ *
1842
+ * Decided by the caller from two facts it is the only one holding: the
1843
+ * owner's `session_binding` setting, and whether `proxy-guard` recognised the
1844
+ * request as coming through the trusted Next.js proxy (`decideKeyBinding`).
1845
+ * Never read off a request body, and `platform` is not consulted — that field
1846
+ * is display-only, usually absent on a proxy-made key, and a native client
1847
+ * may declare `'web'` freely. Omitted means `'none'`, which is what every
1848
+ * path that has no opinion gets.
1849
+ */
1850
+ binding?: SessionBindingType;
1700
1851
  /**
1701
1852
  * The key this one replaces on the same device, when a revocation actually
1702
1853
  * happened. Its presence is what makes this a rotation rather than a new
@@ -1785,6 +1936,28 @@ interface KeySummary {
1785
1936
  registeredIp?: string;
1786
1937
  /** `user-agent` of the registering request, on the same terms as above. */
1787
1938
  registeredUserAgent?: string;
1939
+ /**
1940
+ * `'passkey'` when this key is bound to one, absent when it is not.
1941
+ *
1942
+ * Absent rather than `'none'`, so a deployment where nobody opted in answers
1943
+ * exactly the list it always did, and so every consumer reads "unbound" the
1944
+ * same way it reads it off a sign-in response.
1945
+ */
1946
+ binding?: SessionBindingType;
1947
+ /**
1948
+ * When this key was last seen from two client addresses inside the
1949
+ * concurrent-use window, absent when that has never been observed.
1950
+ *
1951
+ * A signal for the owner, not a refusal: addresses change legitimately, so
1952
+ * nothing is blocked by it and a device list that shows it is telling someone
1953
+ * to look rather than telling them something happened. The addresses
1954
+ * themselves are never returned.
1955
+ *
1956
+ * Only meaningful where proxy-guard is configured. Without it every web
1957
+ * request carries the Next.js server's own address, so two browsers on two
1958
+ * continents share one and this never fires.
1959
+ */
1960
+ concurrentUseAtMillis?: number;
1788
1961
  }
1789
1962
  interface ListKeysParams {
1790
1963
  userId: number;
@@ -1793,6 +1966,20 @@ interface ListKeysParams {
1793
1966
  }
1794
1967
  /** How much of the fingerprint the list returns. */
1795
1968
  declare const KEY_FINGERPRINT_PREFIX_LENGTH = 8;
1969
+ /**
1970
+ * What a registration settled on, for the caller that has to tell the browser.
1971
+ *
1972
+ * The sign-in paths put these two into their `LoginResult`, the Next.js proxy
1973
+ * copies them into the sealed cookie, and that is the whole carrier by which the
1974
+ * proxy learns a session is bound and when its key runs out. Returned rather than
1975
+ * looked up again: the row was just written (or just read), so a second query
1976
+ * would be a second answer to a question already settled.
1977
+ */
1978
+ interface RegisteredKeyBinding {
1979
+ binding: SessionBindingType;
1980
+ /** null only for a key registered before expiry was stamped at all. */
1981
+ expiresAt: Date | null;
1982
+ }
1796
1983
  /**
1797
1984
  * Register a new public key for a user
1798
1985
  *
@@ -1805,7 +1992,7 @@ declare const KEY_FINGERPRINT_PREFIX_LENGTH = 8;
1805
1992
  * @throws InvalidKeyFingerprintError fingerprint가 publicKey와 맞지 않을 때
1806
1993
  * @throws KeyAlgorithmMismatchError 키의 SPKI 타입이 선언된 algorithm과 다를 때
1807
1994
  */
1808
- declare function registerPublicKeyService(params: RegisterPublicKeyParams): Promise<void>;
1995
+ declare function registerPublicKeyService(params: RegisterPublicKeyParams): Promise<RegisteredKeyBinding>;
1809
1996
  /**
1810
1997
  * Rotate user's public key (revoke old, register new)
1811
1998
  *
@@ -2163,6 +2350,8 @@ interface OAuthCallbackParams {
2163
2350
  ip?: string;
2164
2351
  /** `user-agent` of the callback request, already truncated at the route. */
2165
2352
  userAgent?: string;
2353
+ /** Whether proxy-guard recognised the trusted Next.js proxy, from the same helper. */
2354
+ webProxy?: boolean;
2166
2355
  }
2167
2356
  interface OAuthCallbackResult {
2168
2357
  redirectUrl: string;
@@ -2264,6 +2453,13 @@ interface OAuthNativeParams {
2264
2453
  ip?: string;
2265
2454
  /** `user-agent` of the request, already truncated at the route. */
2266
2455
  userAgent?: string;
2456
+ /**
2457
+ * Whether proxy-guard recognised the trusted Next.js proxy, from the same
2458
+ * helper. Accepted because every registering route spreads the whole
2459
+ * provenance, and read by nothing: a native sign-in never produces a bound
2460
+ * key — see the note at the registration below.
2461
+ */
2462
+ webProxy?: boolean;
2267
2463
  /** Apple은 첫 로그인에만 이름을 별도로 주므로 클라이언트가 전달할 수 있다. */
2268
2464
  profile?: {
2269
2465
  name?: string;
@@ -2415,6 +2611,110 @@ declare function revokeOAuth2GrantService(id: number, userId: number): Promise<v
2415
2611
  */
2416
2612
  declare function revokeAllOAuth2GrantsForUser(userId: number): Promise<void>;
2417
2613
 
2614
+ /**
2615
+ * @spfn/auth - Session Binding Service
2616
+ *
2617
+ * The opt-in from #97: an account says its web sessions should run on a key that
2618
+ * expires in hours and can only be renewed by a fresh WebAuthn assertion. A
2619
+ * session cookie copied off that machine — out of a browser profile, out of
2620
+ * DevTools, by malware — still signs like the original until that key runs out,
2621
+ * and then stops, because the copy cannot produce the assertion.
2622
+ *
2623
+ * Three rules shape this file.
2624
+ *
2625
+ * It can only be turned on where the backend can recognise the trusted Next.js
2626
+ * proxy. `clientType` is that recognition and `proxy-guard` is what sets it;
2627
+ * without it every key would be registered unbound and the setting would be a
2628
+ * switch that reports success and protects nothing, so the request is refused as
2629
+ * the configuration error it is.
2630
+ *
2631
+ * Turning it *off* is the privileged direction, which is the reverse of the
2632
+ * usual posture. `assertRecentAuthentication` is satisfied by the age of the
2633
+ * device key this request is signed with — and a cookie copied in the ten
2634
+ * minutes after a sign-in carries exactly that. So leaving `'passkey'` asks for
2635
+ * a credential the copy does not hold: a renewal-grade assertion, or the account
2636
+ * password.
2637
+ *
2638
+ * Enabling twice is idempotent. Recomputing the expiry on a repeat call would
2639
+ * make this route a way to extend a bound key without presenting anything, which
2640
+ * is the thing the short life exists to prevent.
2641
+ */
2642
+
2643
+ /** What every one of the three reads and writes here answers with. */
2644
+ interface SessionBindingResult {
2645
+ mode: SessionBindingType;
2646
+ /**
2647
+ * When the key this request is signed with expires, for a bound session.
2648
+ *
2649
+ * Absent when the account is unbound, and absent for a bound account asking
2650
+ * from a key that is not itself bound — a native client, say, whose key was
2651
+ * registered on a channel the proxy never touched. The response is the input
2652
+ * the Next.js interceptor re-seals the cookie from, so it describes this
2653
+ * session rather than the account.
2654
+ */
2655
+ keyExpiresAtMillis?: number;
2656
+ }
2657
+ interface SessionBindingParams {
2658
+ userId: number;
2659
+ /** The device key this request is signed with — the one binding is applied to. */
2660
+ keyId: string;
2661
+ /** Whether proxy-guard recognised the trusted Next.js proxy. */
2662
+ webProxy?: boolean;
2663
+ }
2664
+ interface DisableSessionBindingParams extends SessionBindingParams {
2665
+ /** A renewal-grade assertion, from `binding/disable/options`. */
2666
+ response?: AuthenticationResponseJSON;
2667
+ /** The account password, for a browser with no passkey to hand. */
2668
+ currentPassword?: string;
2669
+ }
2670
+ /** What binding is on this account, and when this session's key runs out. */
2671
+ declare function getSessionBindingService(params: SessionBindingParams): Promise<SessionBindingResult>;
2672
+ /**
2673
+ * The binding facts of one key, by key id alone.
2674
+ *
2675
+ * For `/_auth/oauth/finalize`, which is the one sealing path with no session and
2676
+ * no `LoginResult` to read them off: the browser arrives at the callback page
2677
+ * holding a key id the OAuth state minted, and the interceptor seals a session
2678
+ * from the answer. Reflecting the caller's own claim about binding would let a
2679
+ * caller ask for an unbound cookie over a bound key, so the row is read instead.
2680
+ *
2681
+ * Scoped by key id and nothing else. The id is a server-minted UUID that only
2682
+ * ever travelled inside the sealed state, so a caller who holds one holds it
2683
+ * because the flow gave it to them; adding the caller's own `userId` to the
2684
+ * lookup would make the answer vary by a second guessable value without
2685
+ * withholding anything from someone who already has the first.
2686
+ */
2687
+ declare function keySessionBindingService(keyId: string): Promise<{
2688
+ sessionBinding?: SessionBindingType;
2689
+ keyExpiresAtMillis?: number;
2690
+ }>;
2691
+ /**
2692
+ * Turn session binding on, and bind the key this request is signed with.
2693
+ *
2694
+ * The current key is bound here rather than only at the next sign-in, because
2695
+ * otherwise the setting would not apply to the session that asked for it until
2696
+ * the person signed in again — and the response is what tells the Next.js proxy
2697
+ * to re-seal the cookie with the new expiry, so the browser and the key row
2698
+ * agree from this moment on.
2699
+ *
2700
+ * @throws SessionBindingUnavailableError proxy-guard가 설정되지 않은 배포일 때
2701
+ * @throws ValidationError 살아 있는 패스키가 하나도 없을 때
2702
+ * @throws RecentAuthenticationRequiredError 최근 인증이 없을 때
2703
+ */
2704
+ declare function enableSessionBindingService(params: SessionBindingParams): Promise<SessionBindingResult>;
2705
+ /**
2706
+ * Turn session binding off, once the caller has proved they are still the owner.
2707
+ *
2708
+ * Every bound key returns to an ordinary ninety-day life in the same call. A row
2709
+ * left marked `'passkey'` would keep expiring in hours with nothing left to
2710
+ * renew it, which is the state the person just asked not to be in.
2711
+ *
2712
+ * @throws RecentAuthenticationRequiredError 새 자격증명 없이 껐을 때
2713
+ */
2714
+ declare function disableSessionBindingService(params: DisableSessionBindingParams): Promise<SessionBindingResult>;
2715
+ /** The challenge the disabling ceremony signs. Same ceremony renewal uses. */
2716
+ declare function startSessionBindingDisableService(userId: number): Promise<PublicKeyCredentialRequestOptionsJSON>;
2717
+
2418
2718
  /**
2419
2719
  * @spfn/auth - Main Router
2420
2720
  *
@@ -2522,7 +2822,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2522
2822
  deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2523
2823
  platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
2524
2824
  }>;
2525
- }, RegisterResult>;
2825
+ }, RegisterResult & LoginBindingFields>;
2526
2826
  login: _spfn_core_route.RouteDef<{
2527
2827
  body: _sinclair_typebox.TObject<{
2528
2828
  email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
@@ -2560,6 +2860,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2560
2860
  } | {
2561
2861
  email?: string | undefined;
2562
2862
  phone?: string | undefined;
2863
+ sessionBinding?: "none" | "passkey" | undefined;
2864
+ keyExpiresAtMillis?: number | undefined;
2563
2865
  status: "approved";
2564
2866
  userId: string;
2565
2867
  publicId: string;
@@ -2736,6 +3038,32 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2736
3038
  phoneVerified: boolean;
2737
3039
  hasPassword: boolean;
2738
3040
  }>;
3041
+ setSessionBinding: _spfn_core_route.RouteDef<{
3042
+ body: _sinclair_typebox.TObject<{
3043
+ mode: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"passkey">, _sinclair_typebox.TLiteral<"none">]>;
3044
+ response: _sinclair_typebox.TOptional<_sinclair_typebox.TUnknown>;
3045
+ currentPassword: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3046
+ }>;
3047
+ }, {}, SessionBindingResult>;
3048
+ getSessionBinding: _spfn_core_route.RouteDef<{}, {}, SessionBindingResult>;
3049
+ sessionBindingDisableOptions: _spfn_core_route.RouteDef<{
3050
+ body: _sinclair_typebox.TObject<{}>;
3051
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
3052
+ sessionRenewOptions: _spfn_core_route.RouteDef<{
3053
+ body: _sinclair_typebox.TObject<{}>;
3054
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
3055
+ sessionRenewVerify: _spfn_core_route.RouteDef<{
3056
+ body: _sinclair_typebox.TObject<{
3057
+ response: _sinclair_typebox.TUnknown;
3058
+ }>;
3059
+ }, {
3060
+ body: _sinclair_typebox.TObject<{
3061
+ publicKey: _sinclair_typebox.TString;
3062
+ keyId: _sinclair_typebox.TString;
3063
+ fingerprint: _sinclair_typebox.TString;
3064
+ algorithm: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>>;
3065
+ }>;
3066
+ }, SessionRenewResult>;
2739
3067
  issueOneTimeToken: _spfn_core_route.RouteDef<{}, {}, IssueOneTimeTokenResult>;
2740
3068
  requestAccountDeletion: _spfn_core_route.RouteDef<{
2741
3069
  body: _sinclair_typebox.TObject<{
@@ -2791,398 +3119,797 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
2791
3119
  }, {}, {
2792
3120
  authUrl: string;
2793
3121
  }>;
2794
- oauthFinalize: _spfn_core_route.RouteDef<{
3122
+ oauthFinalize: _spfn_core_route.RouteDef<{
3123
+ body: _sinclair_typebox.TObject<{
3124
+ userId: _sinclair_typebox.TString;
3125
+ keyId: _sinclair_typebox.TString;
3126
+ returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3127
+ }>;
3128
+ }, {}, {
3129
+ sessionBinding?: SessionBindingType;
3130
+ keyExpiresAtMillis?: number;
3131
+ success: boolean;
3132
+ userId: string;
3133
+ keyId: string;
3134
+ returnUrl: string;
3135
+ }>;
3136
+ oauthProviderStart: _spfn_core_route.RouteDef<{
3137
+ params: _sinclair_typebox.TObject<{
3138
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3139
+ }>;
3140
+ query: _sinclair_typebox.TObject<{
3141
+ state: _sinclair_typebox.TString;
3142
+ }>;
3143
+ }, {}, Response>;
3144
+ oauthProviderCallback: _spfn_core_route.RouteDef<{
3145
+ params: _sinclair_typebox.TObject<{
3146
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3147
+ }>;
3148
+ query: _sinclair_typebox.TObject<{
3149
+ code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3150
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3151
+ error: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3152
+ error_description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3153
+ }>;
3154
+ }, {}, Response>;
3155
+ getProviderOAuthUrl: _spfn_core_route.RouteDef<{
3156
+ params: _sinclair_typebox.TObject<{
3157
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3158
+ }>;
3159
+ body: _sinclair_typebox.TObject<{
3160
+ returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3161
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
3162
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3163
+ }>;
3164
+ }, {}, {
3165
+ authUrl: string;
3166
+ }>;
3167
+ oauthNative: _spfn_core_route.RouteDef<{
3168
+ params: _sinclair_typebox.TObject<{
3169
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3170
+ }>;
3171
+ body: _sinclair_typebox.TObject<{
3172
+ idToken: _sinclair_typebox.TString;
3173
+ nonce: _sinclair_typebox.TString;
3174
+ accessToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3175
+ publicKey: _sinclair_typebox.TString;
3176
+ keyId: _sinclair_typebox.TString;
3177
+ fingerprint: _sinclair_typebox.TString;
3178
+ algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
3179
+ deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3180
+ platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
3181
+ profile: _sinclair_typebox.TOptional<_sinclair_typebox.TObject<{
3182
+ name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3183
+ }>>;
3184
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
3185
+ }>;
3186
+ }, {}, OAuthNativeResult>;
3187
+ oauthUnlinkNotify: _spfn_core_route.RouteDef<{
3188
+ params: _sinclair_typebox.TObject<{
3189
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3190
+ }>;
3191
+ }, {}, void | Response>;
3192
+ oauthUnlinkNotifyGet: _spfn_core_route.RouteDef<{
3193
+ params: _sinclair_typebox.TObject<{
3194
+ provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3195
+ }>;
3196
+ }, {}, void | Response>;
3197
+ getInvitation: _spfn_core_route.RouteDef<{
3198
+ params: _sinclair_typebox.TObject<{
3199
+ token: _sinclair_typebox.TString;
3200
+ }>;
3201
+ }, {}, {
3202
+ email: string;
3203
+ role: string;
3204
+ roleDisplayName: string;
3205
+ invitedBy: string;
3206
+ expiresAt: string;
3207
+ metadata: Record<string, any> | undefined;
3208
+ }>;
3209
+ acceptInvitation: _spfn_core_route.RouteDef<{
3210
+ body: _sinclair_typebox.TObject<{
3211
+ token: _sinclair_typebox.TString;
3212
+ password: _sinclair_typebox.TString;
3213
+ }>;
3214
+ }, {
3215
+ body: _sinclair_typebox.TObject<{
3216
+ publicKey: _sinclair_typebox.TString;
3217
+ keyId: _sinclair_typebox.TString;
3218
+ fingerprint: _sinclair_typebox.TString;
3219
+ algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
3220
+ }>;
3221
+ }, {
3222
+ userId: number;
3223
+ email: string;
3224
+ role: string;
3225
+ }>;
3226
+ createInvitation: _spfn_core_route.RouteDef<{
3227
+ body: _sinclair_typebox.TObject<{
3228
+ email: _sinclair_typebox.TString;
3229
+ roleId: _sinclair_typebox.TNumber;
3230
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3231
+ expiresAt: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3232
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TAny>;
3233
+ }>;
3234
+ }, {}, {
3235
+ id: number;
3236
+ email: string;
3237
+ token: string;
3238
+ roleId: number;
3239
+ expiresAt: string;
3240
+ invitationUrl: string;
3241
+ }>;
3242
+ listInvitations: _spfn_core_route.RouteDef<{
3243
+ query: _sinclair_typebox.TObject<{
3244
+ status: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"pending" | "accepted" | "expired" | "cancelled">[]>>;
3245
+ page: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3246
+ limit: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3247
+ }>;
3248
+ }, {}, {
3249
+ invitations: {
3250
+ id: number;
3251
+ email: string;
3252
+ token: string;
3253
+ roleId: number;
3254
+ invitedBy: number;
3255
+ status: "pending" | "accepted" | "expired" | "cancelled";
3256
+ expiresAt: Date;
3257
+ acceptedAt: Date | null;
3258
+ cancelledAt: Date | null;
3259
+ metadata: Record<string, any> | null;
3260
+ createdAt: Date;
3261
+ updatedAt: Date;
3262
+ role: {
3263
+ id: number;
3264
+ name: string;
3265
+ displayName: string;
3266
+ };
3267
+ inviter: {
3268
+ id: number;
3269
+ email: string | null;
3270
+ };
3271
+ }[];
3272
+ total: number;
3273
+ page: number;
3274
+ limit: number;
3275
+ totalPages: number;
3276
+ }>;
3277
+ cancelInvitation: _spfn_core_route.RouteDef<{
3278
+ body: _sinclair_typebox.TObject<{
3279
+ id: _sinclair_typebox.TNumber;
3280
+ reason: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3281
+ }>;
3282
+ }, {}, {
3283
+ cancelledAt: string;
3284
+ }>;
3285
+ resendInvitation: _spfn_core_route.RouteDef<{
3286
+ body: _sinclair_typebox.TObject<{
3287
+ id: _sinclair_typebox.TNumber;
3288
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3289
+ }>;
3290
+ }, {}, {
3291
+ expiresAt: string;
3292
+ }>;
3293
+ deleteInvitation: _spfn_core_route.RouteDef<{
3294
+ body: _sinclair_typebox.TObject<{
3295
+ id: _sinclair_typebox.TNumber;
3296
+ }>;
3297
+ }, {}, void>;
3298
+ getUserProfile: _spfn_core_route.RouteDef<{}, {}, UserProfile>;
3299
+ updateUserProfile: _spfn_core_route.RouteDef<{
3300
+ body: _sinclair_typebox.TObject<{
3301
+ displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3302
+ firstName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3303
+ lastName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3304
+ avatarUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3305
+ bio: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3306
+ locale: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3307
+ timezone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3308
+ dateOfBirth: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3309
+ gender: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3310
+ website: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3311
+ location: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3312
+ company: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3313
+ jobTitle: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3314
+ metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TAny>>;
3315
+ }>;
3316
+ }, {}, ProfileInfo>;
3317
+ checkUsername: _spfn_core_route.RouteDef<{
3318
+ query: _sinclair_typebox.TObject<{
3319
+ username: _sinclair_typebox.TString;
3320
+ }>;
3321
+ }, {}, {
3322
+ available: boolean;
3323
+ }>;
3324
+ updateUsername: _spfn_core_route.RouteDef<{
3325
+ body: _sinclair_typebox.TObject<{
3326
+ username: _sinclair_typebox.TUnion<[_sinclair_typebox.TString, _sinclair_typebox.TNull]>;
3327
+ }>;
3328
+ }, {}, {
3329
+ deletedAt: Date | null;
3330
+ deletedBy: string | null;
3331
+ createdAt: Date;
3332
+ updatedAt: Date;
3333
+ id: number;
3334
+ publicId: string;
3335
+ email: string | null;
3336
+ phone: string | null;
3337
+ username: string | null;
3338
+ passwordHash: string | null;
3339
+ passwordChangeRequired: boolean;
3340
+ roleId: number;
3341
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3342
+ emailVerifiedAt: Date | null;
3343
+ phoneVerifiedAt: Date | null;
3344
+ keyEpoch: number;
3345
+ sessionBinding: "none" | "passkey";
3346
+ sessionBindingChangedAt: Date | null;
3347
+ lastLoginAt: Date | null;
3348
+ }>;
3349
+ updateLocale: _spfn_core_route.RouteDef<{
3350
+ body: _sinclair_typebox.TObject<{
3351
+ locale: _sinclair_typebox.TString;
3352
+ }>;
3353
+ }, {}, {
3354
+ locale: string;
3355
+ }>;
3356
+ listRoles: _spfn_core_route.RouteDef<{
3357
+ query: _sinclair_typebox.TObject<{
3358
+ includeInactive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
3359
+ }>;
3360
+ }, {}, {
3361
+ roles: {
3362
+ description: string | null;
3363
+ name: string;
3364
+ id: number;
3365
+ displayName: string;
3366
+ isBuiltin: boolean;
3367
+ isSystem: boolean;
3368
+ isActive: boolean;
3369
+ priority: number;
3370
+ createdAt: Date;
3371
+ updatedAt: Date;
3372
+ }[];
3373
+ }>;
3374
+ createAdminRole: _spfn_core_route.RouteDef<{
2795
3375
  body: _sinclair_typebox.TObject<{
2796
- userId: _sinclair_typebox.TString;
2797
- keyId: _sinclair_typebox.TString;
2798
- returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3376
+ name: _sinclair_typebox.TString;
3377
+ displayName: _sinclair_typebox.TString;
3378
+ description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3379
+ priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3380
+ permissionIds: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TNumber>>;
2799
3381
  }>;
2800
3382
  }, {}, {
2801
- success: boolean;
2802
- userId: string;
2803
- keyId: string;
2804
- returnUrl: string;
3383
+ role: {
3384
+ description: string | null;
3385
+ name: string;
3386
+ id: number;
3387
+ displayName: string;
3388
+ isBuiltin: boolean;
3389
+ isSystem: boolean;
3390
+ isActive: boolean;
3391
+ priority: number;
3392
+ createdAt: Date;
3393
+ updatedAt: Date;
3394
+ };
2805
3395
  }>;
2806
- oauthProviderStart: _spfn_core_route.RouteDef<{
3396
+ updateAdminRole: _spfn_core_route.RouteDef<{
2807
3397
  params: _sinclair_typebox.TObject<{
2808
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3398
+ id: _sinclair_typebox.TNumber;
2809
3399
  }>;
2810
- query: _sinclair_typebox.TObject<{
2811
- state: _sinclair_typebox.TString;
3400
+ body: _sinclair_typebox.TObject<{
3401
+ displayName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3402
+ description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3403
+ priority: _sinclair_typebox.TOptional<_sinclair_typebox.TNumber>;
3404
+ isActive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
2812
3405
  }>;
2813
- }, {}, Response>;
2814
- oauthProviderCallback: _spfn_core_route.RouteDef<{
3406
+ }, {}, {
3407
+ role: {
3408
+ description: string | null;
3409
+ name: string;
3410
+ id: number;
3411
+ displayName: string;
3412
+ isBuiltin: boolean;
3413
+ isSystem: boolean;
3414
+ isActive: boolean;
3415
+ priority: number;
3416
+ createdAt: Date;
3417
+ updatedAt: Date;
3418
+ };
3419
+ }>;
3420
+ deleteAdminRole: _spfn_core_route.RouteDef<{
2815
3421
  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>;
3422
+ id: _sinclair_typebox.TNumber;
2823
3423
  }>;
2824
- }, {}, Response>;
2825
- getProviderOAuthUrl: _spfn_core_route.RouteDef<{
3424
+ }, {}, void>;
3425
+ updateUserRole: _spfn_core_route.RouteDef<{
2826
3426
  params: _sinclair_typebox.TObject<{
2827
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3427
+ userId: _sinclair_typebox.TNumber;
2828
3428
  }>;
2829
3429
  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>;
3430
+ roleId: _sinclair_typebox.TNumber;
2833
3431
  }>;
2834
3432
  }, {}, {
2835
- authUrl: string;
3433
+ userId: number;
3434
+ roleId: number;
2836
3435
  }>;
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
- }>;
3436
+ issueOpsToken: _spfn_core_route.RouteDef<{
2841
3437
  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>>;
3438
+ name: _sinclair_typebox.TString;
3439
+ scopes: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
3440
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[_sinclair_typebox.TNumber, _sinclair_typebox.TNull]>>;
2855
3441
  }>;
2856
- }, {}, OAuthNativeResult>;
2857
- oauthUnlinkNotify: _spfn_core_route.RouteDef<{
3442
+ }, {}, {
3443
+ token: string;
3444
+ opsToken: {
3445
+ id: number;
3446
+ name: string;
3447
+ scopes: string[];
3448
+ expiresAt: string | null;
3449
+ revokedAt: string | null;
3450
+ lastUsedAt: string | null;
3451
+ createdAt: string | null;
3452
+ };
3453
+ }>;
3454
+ listOpsTokens: _spfn_core_route.RouteDef<{}, {}, {
3455
+ opsTokens: {
3456
+ id: number;
3457
+ name: string;
3458
+ scopes: string[];
3459
+ expiresAt: string | null;
3460
+ revokedAt: string | null;
3461
+ lastUsedAt: string | null;
3462
+ createdAt: string | null;
3463
+ }[];
3464
+ }>;
3465
+ revokeOpsToken: _spfn_core_route.RouteDef<{
2858
3466
  params: _sinclair_typebox.TObject<{
2859
- provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
3467
+ id: _sinclair_typebox.TNumber;
2860
3468
  }>;
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">[]>;
3469
+ }, {}, {
3470
+ opsToken: {
3471
+ id: number;
3472
+ name: string;
3473
+ scopes: string[];
3474
+ expiresAt: string | null;
3475
+ revokedAt: string | null;
3476
+ lastUsedAt: string | null;
3477
+ createdAt: string | null;
3478
+ };
3479
+ }>;
3480
+ registerOAuth2Client: _spfn_core_route.RouteDef<{}, {}, Response>;
3481
+ getOAuth2Authorize: _spfn_core_route.RouteDef<{
3482
+ query: _sinclair_typebox.TObject<{
3483
+ client_id: _sinclair_typebox.TString;
3484
+ redirect_uri: _sinclair_typebox.TString;
3485
+ code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3486
+ code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3487
+ resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3488
+ scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3489
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
2865
3490
  }>;
2866
- }, {}, void | Response>;
2867
- getInvitation: _spfn_core_route.RouteDef<{
3491
+ }, {}, OAuth2ConsentView>;
3492
+ createOAuth2AuthorizationCode: _spfn_core_route.RouteDef<{
3493
+ body: _sinclair_typebox.TObject<{
3494
+ approve: _sinclair_typebox.TBoolean;
3495
+ client_id: _sinclair_typebox.TString;
3496
+ redirect_uri: _sinclair_typebox.TString;
3497
+ code_challenge: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3498
+ code_challenge_method: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3499
+ resource: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3500
+ scope: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3501
+ state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
3502
+ }>;
3503
+ }, {}, OAuth2AuthorizationCodeIssued>;
3504
+ oauth2Token: _spfn_core_route.RouteDef<{}, {}, Response>;
3505
+ oauth2Revoke: _spfn_core_route.RouteDef<{}, {}, Response>;
3506
+ listOAuth2Grants: _spfn_core_route.RouteDef<{}, {}, {
3507
+ grants: OAuth2GrantSummary[];
3508
+ }>;
3509
+ revokeOAuth2Grant: _spfn_core_route.RouteDef<{
2868
3510
  params: _sinclair_typebox.TObject<{
2869
- token: _sinclair_typebox.TString;
3511
+ id: _sinclair_typebox.TNumber;
2870
3512
  }>;
2871
3513
  }, {}, {
2872
- email: string;
2873
- role: string;
2874
- roleDisplayName: string;
2875
- invitedBy: string;
2876
- expiresAt: string;
2877
- metadata: Record<string, any> | undefined;
3514
+ revoked: boolean;
2878
3515
  }>;
2879
- acceptInvitation: _spfn_core_route.RouteDef<{
2880
- body: _sinclair_typebox.TObject<{
2881
- token: _sinclair_typebox.TString;
2882
- password: _sinclair_typebox.TString;
3516
+ oauth2AuthorizationServerMetadata: _spfn_core_route.RouteDef<{}, {}, Response>;
3517
+ }>;
3518
+
3519
+ /**
3520
+ * @spfn/auth - User Public Keys Entity
3521
+ *
3522
+ * Stores client-generated public keys for JWT verification
3523
+ * Supports key rotation and multi-key management per user
3524
+ */
3525
+ /**
3526
+ * User Public Keys Table
3527
+ * Each user can have multiple public keys (for rotation)
3528
+ */
3529
+ declare const userPublicKeys: drizzle_orm_pg_core.PgTableWithColumns<{
3530
+ name: "user_public_keys";
3531
+ schema: string;
3532
+ columns: {
3533
+ id: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>, {
3534
+ name: string;
3535
+ tableName: "user_public_keys";
3536
+ dataType: "number int53";
3537
+ data: number;
3538
+ driverParam: number;
3539
+ notNull: true;
3540
+ hasDefault: true;
3541
+ isPrimaryKey: false;
3542
+ isAutoincrement: false;
3543
+ hasRuntimeDefault: false;
3544
+ enumValues: undefined;
3545
+ identity: undefined;
3546
+ generated: undefined;
2883
3547
  }>;
2884
- }, {
2885
- 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">[]>;
3548
+ userId: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>, {
3549
+ name: string;
3550
+ tableName: "user_public_keys";
3551
+ dataType: "number int53";
3552
+ data: number;
3553
+ driverParam: string | number;
3554
+ notNull: true;
3555
+ hasDefault: false;
3556
+ isPrimaryKey: false;
3557
+ isAutoincrement: false;
3558
+ hasRuntimeDefault: false;
3559
+ enumValues: undefined;
3560
+ identity: undefined;
3561
+ generated: undefined;
2890
3562
  }>;
2891
- }, {
2892
- userId: number;
2893
- email: string;
2894
- role: string;
2895
- }>;
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>;
3563
+ keyId: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
3564
+ name: string;
3565
+ tableName: "user_public_keys";
3566
+ dataType: "string";
3567
+ data: string;
3568
+ driverParam: string;
3569
+ notNull: true;
3570
+ hasDefault: false;
3571
+ isPrimaryKey: false;
3572
+ isAutoincrement: false;
3573
+ hasRuntimeDefault: false;
3574
+ enumValues: undefined;
3575
+ identity: undefined;
3576
+ generated: undefined;
3577
+ }>;
3578
+ publicKey: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
3579
+ name: string;
3580
+ tableName: "user_public_keys";
3581
+ dataType: "string";
3582
+ data: string;
3583
+ driverParam: string;
3584
+ notNull: true;
3585
+ hasDefault: false;
3586
+ isPrimaryKey: false;
3587
+ isAutoincrement: false;
3588
+ hasRuntimeDefault: false;
3589
+ enumValues: undefined;
3590
+ identity: undefined;
3591
+ generated: undefined;
2903
3592
  }>;
2904
- }, {}, {
2905
- id: number;
2906
- email: string;
2907
- token: string;
2908
- roleId: number;
2909
- expiresAt: string;
2910
- invitationUrl: string;
2911
- }>;
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>;
3593
+ 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[]]>>>, {
3594
+ name: string;
3595
+ tableName: "user_public_keys";
3596
+ dataType: "string enum";
3597
+ data: "ES256" | "RS256";
3598
+ driverParam: string;
3599
+ notNull: true;
3600
+ hasDefault: true;
3601
+ isPrimaryKey: false;
3602
+ isAutoincrement: false;
3603
+ hasRuntimeDefault: false;
3604
+ enumValues: ["ES256", "RS256"] & [string, ...string[]];
3605
+ identity: undefined;
3606
+ generated: undefined;
2917
3607
  }>;
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>;
3608
+ fingerprint: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>>, {
3609
+ name: string;
3610
+ tableName: "user_public_keys";
3611
+ dataType: "string";
3612
+ data: string;
3613
+ driverParam: string;
3614
+ notNull: true;
3615
+ hasDefault: false;
3616
+ isPrimaryKey: false;
3617
+ isAutoincrement: false;
3618
+ hasRuntimeDefault: false;
3619
+ enumValues: undefined;
3620
+ identity: undefined;
3621
+ generated: undefined;
2951
3622
  }>;
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>;
3623
+ deviceName: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3624
+ name: string;
3625
+ tableName: "user_public_keys";
3626
+ dataType: "string";
3627
+ data: string;
3628
+ driverParam: string;
3629
+ notNull: false;
3630
+ hasDefault: false;
3631
+ isPrimaryKey: false;
3632
+ isAutoincrement: false;
3633
+ hasRuntimeDefault: false;
3634
+ enumValues: undefined;
3635
+ identity: undefined;
3636
+ generated: undefined;
2959
3637
  }>;
2960
- }, {}, {
2961
- expiresAt: string;
2962
- }>;
2963
- deleteInvitation: _spfn_core_route.RouteDef<{
2964
- body: _sinclair_typebox.TObject<{
2965
- id: _sinclair_typebox.TNumber;
3638
+ platform: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<["ios", "android", "web", "desktop"] & [string, ...string[]]>, {
3639
+ name: string;
3640
+ tableName: "user_public_keys";
3641
+ dataType: "string enum";
3642
+ data: "ios" | "android" | "web" | "desktop";
3643
+ driverParam: string;
3644
+ notNull: false;
3645
+ hasDefault: false;
3646
+ isPrimaryKey: false;
3647
+ isAutoincrement: false;
3648
+ hasRuntimeDefault: false;
3649
+ enumValues: ["ios", "android", "web", "desktop"] & [string, ...string[]];
3650
+ identity: undefined;
3651
+ generated: undefined;
2966
3652
  }>;
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>>;
3653
+ registeredIp: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3654
+ name: string;
3655
+ tableName: "user_public_keys";
3656
+ dataType: "string";
3657
+ data: string;
3658
+ driverParam: string;
3659
+ notNull: false;
3660
+ hasDefault: false;
3661
+ isPrimaryKey: false;
3662
+ isAutoincrement: false;
3663
+ hasRuntimeDefault: false;
3664
+ enumValues: undefined;
3665
+ identity: undefined;
3666
+ generated: undefined;
2985
3667
  }>;
2986
- }, {}, ProfileInfo>;
2987
- checkUsername: _spfn_core_route.RouteDef<{
2988
- query: _sinclair_typebox.TObject<{
2989
- username: _sinclair_typebox.TString;
3668
+ registeredUserAgent: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3669
+ name: string;
3670
+ tableName: "user_public_keys";
3671
+ dataType: "string";
3672
+ data: string;
3673
+ driverParam: string;
3674
+ notNull: false;
3675
+ hasDefault: false;
3676
+ isPrimaryKey: false;
3677
+ isAutoincrement: false;
3678
+ hasRuntimeDefault: false;
3679
+ enumValues: undefined;
3680
+ identity: undefined;
3681
+ generated: undefined;
2990
3682
  }>;
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]>;
3683
+ registeredUaFamily: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3684
+ name: string;
3685
+ tableName: "user_public_keys";
3686
+ dataType: "string";
3687
+ data: string;
3688
+ driverParam: string;
3689
+ notNull: false;
3690
+ hasDefault: false;
3691
+ isPrimaryKey: false;
3692
+ isAutoincrement: false;
3693
+ hasRuntimeDefault: false;
3694
+ enumValues: undefined;
3695
+ identity: undefined;
3696
+ generated: undefined;
2997
3697
  }>;
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;
3698
+ lastSeenIp: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3699
+ name: string;
3700
+ tableName: "user_public_keys";
3701
+ dataType: "string";
3702
+ data: string;
3703
+ driverParam: string;
3704
+ notNull: false;
3705
+ hasDefault: false;
3706
+ isPrimaryKey: false;
3707
+ isAutoincrement: false;
3708
+ hasRuntimeDefault: false;
3709
+ enumValues: undefined;
3710
+ identity: undefined;
3711
+ generated: undefined;
3020
3712
  }>;
3021
- }, {}, {
3022
- locale: string;
3023
- }>;
3024
- listRoles: _spfn_core_route.RouteDef<{
3025
- query: _sinclair_typebox.TObject<{
3026
- includeInactive: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
3713
+ lastSeenAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3714
+ name: string;
3715
+ tableName: "user_public_keys";
3716
+ dataType: "object date";
3717
+ data: Date;
3718
+ driverParam: string;
3719
+ notNull: false;
3720
+ hasDefault: false;
3721
+ isPrimaryKey: false;
3722
+ isAutoincrement: false;
3723
+ hasRuntimeDefault: false;
3724
+ enumValues: undefined;
3725
+ identity: undefined;
3726
+ generated: undefined;
3027
3727
  }>;
3028
- }, {}, {
3029
- roles: {
3030
- description: string | null;
3728
+ concurrentUseAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3031
3729
  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>>;
3730
+ tableName: "user_public_keys";
3731
+ dataType: "object date";
3732
+ data: Date;
3733
+ driverParam: string;
3734
+ notNull: false;
3735
+ hasDefault: false;
3736
+ isPrimaryKey: false;
3737
+ isAutoincrement: false;
3738
+ hasRuntimeDefault: false;
3739
+ enumValues: undefined;
3740
+ identity: undefined;
3741
+ generated: undefined;
3049
3742
  }>;
3050
- }, {}, {
3051
- role: {
3052
- description: string | null;
3743
+ 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[]]>>>, {
3053
3744
  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;
3745
+ tableName: "user_public_keys";
3746
+ dataType: "string enum";
3747
+ data: "none" | "passkey";
3748
+ driverParam: string;
3749
+ notNull: true;
3750
+ hasDefault: true;
3751
+ isPrimaryKey: false;
3752
+ isAutoincrement: false;
3753
+ hasRuntimeDefault: false;
3754
+ enumValues: ["none", "passkey"] & [string, ...string[]];
3755
+ identity: undefined;
3756
+ generated: undefined;
3067
3757
  }>;
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>;
3758
+ clientKind: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<["web", "ios", "android"] & [string, ...string[]]>, {
3759
+ name: string;
3760
+ tableName: "user_public_keys";
3761
+ dataType: "string enum";
3762
+ data: "ios" | "android" | "web";
3763
+ driverParam: string;
3764
+ notNull: false;
3765
+ hasDefault: false;
3766
+ isPrimaryKey: false;
3767
+ isAutoincrement: false;
3768
+ hasRuntimeDefault: false;
3769
+ enumValues: ["web", "ios", "android"] & [string, ...string[]];
3770
+ identity: undefined;
3771
+ generated: undefined;
3073
3772
  }>;
3074
- }, {}, {
3075
- role: {
3076
- description: string | null;
3773
+ clientVersion: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3077
3774
  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;
3775
+ tableName: "user_public_keys";
3776
+ dataType: "string";
3777
+ data: string;
3778
+ driverParam: string;
3779
+ notNull: false;
3780
+ hasDefault: false;
3781
+ isPrimaryKey: false;
3782
+ isAutoincrement: false;
3783
+ hasRuntimeDefault: false;
3784
+ enumValues: undefined;
3785
+ identity: undefined;
3786
+ generated: undefined;
3091
3787
  }>;
3092
- }, {}, void>;
3093
- updateUserRole: _spfn_core_route.RouteDef<{
3094
- params: _sinclair_typebox.TObject<{
3095
- userId: _sinclair_typebox.TNumber;
3788
+ clientContractVersion: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3789
+ name: string;
3790
+ tableName: "user_public_keys";
3791
+ dataType: "string";
3792
+ data: string;
3793
+ driverParam: string;
3794
+ notNull: false;
3795
+ hasDefault: false;
3796
+ isPrimaryKey: false;
3797
+ isAutoincrement: false;
3798
+ hasRuntimeDefault: false;
3799
+ enumValues: undefined;
3800
+ identity: undefined;
3801
+ generated: undefined;
3096
3802
  }>;
3097
- body: _sinclair_typebox.TObject<{
3098
- roleId: _sinclair_typebox.TNumber;
3803
+ clientSeenAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3804
+ name: string;
3805
+ tableName: "user_public_keys";
3806
+ dataType: "object date";
3807
+ data: Date;
3808
+ driverParam: string;
3809
+ notNull: false;
3810
+ hasDefault: false;
3811
+ isPrimaryKey: false;
3812
+ isAutoincrement: false;
3813
+ hasRuntimeDefault: false;
3814
+ enumValues: undefined;
3815
+ identity: undefined;
3816
+ generated: undefined;
3099
3817
  }>;
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]>>;
3818
+ isActive: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBooleanBuilder>>, {
3819
+ name: string;
3820
+ tableName: "user_public_keys";
3821
+ dataType: "boolean";
3822
+ data: boolean;
3823
+ driverParam: boolean;
3824
+ notNull: true;
3825
+ hasDefault: true;
3826
+ isPrimaryKey: false;
3827
+ isAutoincrement: false;
3828
+ hasRuntimeDefault: false;
3829
+ enumValues: undefined;
3830
+ identity: undefined;
3831
+ generated: undefined;
3109
3832
  }>;
3110
- }, {}, {
3111
- token: string;
3112
- opsToken: {
3113
- id: number;
3833
+ 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
3834
  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;
3835
+ tableName: "user_public_keys";
3836
+ dataType: "object date";
3837
+ data: Date;
3838
+ driverParam: string;
3839
+ notNull: true;
3840
+ hasDefault: true;
3841
+ isPrimaryKey: false;
3842
+ isAutoincrement: false;
3843
+ hasRuntimeDefault: false;
3844
+ enumValues: undefined;
3845
+ identity: undefined;
3846
+ generated: undefined;
3847
+ }>;
3848
+ lastUsedAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3125
3849
  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;
3850
+ tableName: "user_public_keys";
3851
+ dataType: "object date";
3852
+ data: Date;
3853
+ driverParam: string;
3854
+ notNull: false;
3855
+ hasDefault: false;
3856
+ isPrimaryKey: false;
3857
+ isAutoincrement: false;
3858
+ hasRuntimeDefault: false;
3859
+ enumValues: undefined;
3860
+ identity: undefined;
3861
+ generated: undefined;
3136
3862
  }>;
3137
- }, {}, {
3138
- opsToken: {
3139
- id: number;
3863
+ expiresAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3140
3864
  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>;
3865
+ tableName: "user_public_keys";
3866
+ dataType: "object date";
3867
+ data: Date;
3868
+ driverParam: string;
3869
+ notNull: false;
3870
+ hasDefault: false;
3871
+ isPrimaryKey: false;
3872
+ isAutoincrement: false;
3873
+ hasRuntimeDefault: false;
3874
+ enumValues: undefined;
3875
+ identity: undefined;
3876
+ generated: undefined;
3158
3877
  }>;
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>;
3878
+ revokedAt: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTimestampBuilder, {
3879
+ name: string;
3880
+ tableName: "user_public_keys";
3881
+ dataType: "object date";
3882
+ data: Date;
3883
+ driverParam: string;
3884
+ notNull: false;
3885
+ hasDefault: false;
3886
+ isPrimaryKey: false;
3887
+ isAutoincrement: false;
3888
+ hasRuntimeDefault: false;
3889
+ enumValues: undefined;
3890
+ identity: undefined;
3891
+ generated: undefined;
3170
3892
  }>;
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;
3893
+ revokedReason: drizzle_orm_pg_core.PgBuildColumn<"user_public_keys", drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>, {
3894
+ name: string;
3895
+ tableName: "user_public_keys";
3896
+ dataType: "string";
3897
+ data: string;
3898
+ driverParam: string;
3899
+ notNull: false;
3900
+ hasDefault: false;
3901
+ isPrimaryKey: false;
3902
+ isAutoincrement: false;
3903
+ hasRuntimeDefault: false;
3904
+ enumValues: undefined;
3905
+ identity: undefined;
3906
+ generated: undefined;
3180
3907
  }>;
3181
- }, {}, {
3182
- revoked: boolean;
3183
- }>;
3184
- oauth2AuthorizationServerMetadata: _spfn_core_route.RouteDef<{}, {}, Response>;
3908
+ };
3909
+ dialect: "pg";
3185
3910
  }>;
3911
+ type UserPublicKey = typeof userPublicKeys.$inferSelect;
3912
+ type NewUserPublicKey = typeof userPublicKeys.$inferInsert;
3186
3913
 
3187
3914
  /**
3188
3915
  * The auth-profile registry the authenticate middleware dispatches on.
@@ -3323,6 +4050,46 @@ declare module 'hono' {
3323
4050
  auth: AuthContext;
3324
4051
  }
3325
4052
  }
4053
+ /** Why a Bearer credential was not admitted, in the order the steps run. */
4054
+ type BearerRefusal = 'absent' | 'machine' | 'undecodable' | 'unknown' | 'expired' | 'token-expired' | 'bad-signature' | 'unverifiable';
4055
+ /** What a Bearer credential resolved to: the key row it named, or why not. */
4056
+ type BearerOutcome = {
4057
+ key: UserPublicKey;
4058
+ } | {
4059
+ refused: BearerRefusal;
4060
+ };
4061
+ /**
4062
+ * Admit the Bearer credential on this request, or say what stopped it.
4063
+ *
4064
+ * The one lookup-and-verify path every Bearer middleware takes — header, machine
4065
+ * discriminator, decode, key row, expiry, signature — kept in one place because
4066
+ * the order is itself a rule: a machine credential never reaches a decode, and a
4067
+ * key row is found before its signature is checked so that an unknown key and a
4068
+ * forged one cost the same work.
4069
+ *
4070
+ * It answers rather than throws, which is what lets two middlewares share it.
4071
+ * `authenticate` turns each refusal into the error that step has always
4072
+ * answered with; `authenticateForRenewal` turns every one of them into a single
4073
+ * refusal, so a caller holding no private key cannot tell a live key id from a
4074
+ * dead one. A shared step that threw would have to be unwound to get there.
4075
+ *
4076
+ * @param c - the Hono context of the request being authenticated
4077
+ * @param admitsExpired - whether a key past its `expiresAt` may still be
4078
+ * admitted. Renewal is the only caller that says yes, and only for a bound key
4079
+ * inside its grace.
4080
+ */
4081
+ declare function admitBearerKey(c: Context, admitsExpired: (key: UserPublicKey) => boolean): Promise<BearerOutcome>;
4082
+ /**
4083
+ * The principal a verified Bearer key resolves to.
4084
+ *
4085
+ * The one place the Bearer path builds an `AuthContext`, so that a middleware
4086
+ * added beside `authenticate` cannot invent a second shape of it.
4087
+ */
4088
+ declare function bearerAuthContext(keyId: string, resolved: {
4089
+ user: User;
4090
+ role: string | null;
4091
+ locale: string;
4092
+ }): AuthContext;
3326
4093
  /**
3327
4094
  * Authentication middleware
3328
4095
  *
@@ -3476,4 +4243,4 @@ declare const machineAuth: _spfn_core_route.NamedMiddleware<"machineAuth">;
3476
4243
  */
3477
4244
  declare const requireMachineScope: _spfn_core_route.NamedMiddlewareFactory<"machineScope", string[]>;
3478
4245
 
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 };
4246
+ export { type AuthLoginPayload as $, type AuthInitOptions as A, type Passkey as B, type ConfirmSignupLinkResult as C, type DeviceAuthInfoResult as D, type MfaVerification as E, type FinishPasskeyEnrollmentResult as F, type MfaVerificationMethod as G, type UserPublicKey as H, type IssueOneTimeTokenResult as I, type AuthContext as J, type KeySummary as K, type LoginBindingFields as L, type MfaStatus as M, type NewUserPublicKey as N, type OAuthStartResult as O, type PermissionConfig as P, type ApproveDeviceAuthParams as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type TotpEnrolmentResult as T, type UserProfile as U, VERIFICATION_PURPOSES as V, type AssertStepUpParams as W, type AuthDeletionCancelledPayload as X, type AuthDeletionCompletedPayload as Y, type AuthDeletionRequestedPayload as Z, type AuthDeviceRegisteredPayload as _, type RegisterResult as a, type RevokeAllKeysParams as a$, type AuthPasswordResetPayload as a0, type AuthProfileOutcome as a1, type AuthProfileVerifier as a2, AuthProviderSchema as a3, type AuthRegisterPayload as a4, type BearerOutcome as a5, type BearerRefusal as a6, type ChangePasswordParams as a7, type CompletePasswordResetParams as a8, type CompleteSignupParams as a9, type NativeVerifyOptions as aA, type NewMfaVerification as aB, type NormalizedIdentity as aC, type OAuth2AuthorizeParams as aD, type OAuth2ScopeDescription as aE, type OAuthCallbackParams as aF, type OAuthCallbackResult as aG, type OAuthCodeExchangeOptions as aH, type OAuthNativeParams as aI, type OAuthStartParams as aJ, type OAuthTokens as aK, type OAuthUnlinkedPayload as aL, PASSKEY_DEVICE_TYPES as aM, PASSKEY_LABEL_MAX_LENGTH as aN, type PasskeyDeviceType as aO, PasswordSchema as aP, PhoneSchema as aQ, PlatformSchema as aR, type PollDeviceAuthParams as aS, type PollDeviceAuthResult as aT, PublicKeySchema as aU, type RecentAuthenticationParams as aV, type RegisterParams as aW, type RegisterPublicKeyParams as aX, type RenamePasskeyParams as aY, type RequestPasswordResetParams as aZ, type RequestSignupLinkParams as a_, type ConfirmPasswordResetParams as aa, type ConfirmSignupLinkParams as ab, type ConfirmTotpParams as ac, type DenyDeviceAuthParams as ad, type DeviceAuthApprovedResult as ae, type DeviceAuthInfoParams as af, type DeviceAuthPendingResult as ag, DeviceAuthPollResponseSchema as ah, DeviceNameSchema as ai, type DeviceRegistrationChannel as aj, type DisableSessionBindingParams as ak, EmailSchema as al, FingerprintSchema as am, type FinishPasskeyEnrollmentParams as an, type FinishPasskeyLoginParams as ao, type FinishSessionRenewParams as ap, type InvitationAcceptedPayload as aq, type InvitationCreatedPayload as ar, KEY_FINGERPRINT_PREFIX_LENGTH as as, KeyIdSchema as at, type LoginParams as au, type LogoutParams as av, MFA_VERIFICATION_METHODS as aw, type MachinePrincipal as ax, type MachineVerifierRegistration as ay, type MarkPasskeyParams as az, type RequestSignupLinkResult as b, listOAuth2GrantsService as b$, type RevokeKeyParams as b0, type RevokePasskeyParams as b1, type RotateKeyParams as b2, type SendVerificationCodeParams as b3, type SessionBindingParams as b4, type StartDeviceAuthParams as b5, type StartPasskeyEnrollmentParams as b6, type StartSessionRenewParams as b7, type StepUpParams as b8, TargetTypeSchema as b9, completePasswordResetService as bA, completeSignupService as bB, confirmPasswordResetService as bC, confirmSignupLinkService as bD, confirmTotpEnrolmentService as bE, denyDeviceAuthService as bF, denyOAuth2AuthorizeService as bG, describeOAuth2AuthorizeRequestService as bH, disableMfaService as bI, disableSessionBindingService as bJ, enableSessionBindingService as bK, finishPasskeyEnrollmentService as bL, finishPasskeyLoginService as bM, finishSessionRenewService as bN, getDeviceAuthInfoService as bO, getEnabledOAuthProviders as bP, getGoogleAccessToken as bQ, getMachinePrincipal as bR, getOAuthProvider as bS, getRegisteredProviders as bT, getSessionBindingService as bU, invitationAcceptedEvent as bV, invitationCreatedEvent as bW, isOAuthProviderEnabled as bX, issueOneTimeTokenService as bY, keySessionBindingService as bZ, listKeysService as b_, type UnlinkNotification as ba, UnlinkNotifyRejection as bb, type UnlinkNotifyRequest as bc, type UnlinkNotifyResult as bd, UserCodeSchema as be, VerificationPurposeSchema as bf, type VerifyCodeParams as bg, type VerifyCodeResult as bh, admitBearerKey as bi, approveDeviceAuthService as bj, approveOAuth2AuthorizeService as bk, assertNotLastRecoveryCredential as bl, assertRecentAuthentication as bm, assertStepUp as bn, authDeletionCancelledEvent as bo, authDeletionCompletedEvent as bp, authDeletionRequestedEvent as bq, authDeviceRegisteredEvent as br, authLoginEvent as bs, authPasswordResetEvent as bt, authRegisterEvent as bu, authenticate as bv, bearerAuthContext as bw, buildOAuthErrorUrl as bx, carryStepUpVerification as by, changePasswordService as bz, type RequestPasswordResetResult as c, listPasskeysService as c0, loginService as c1, logoutService as c2, machineAuth as c3, markPasskeySecondFactorService as c4, mfaEnrolledForUser as c5, mfaStatusService as c6, mfaVerifications as c7, oauthCallbackService as c8, oauthNativeService as c9, sendVerificationCodeService as cA, startDeviceAuthService as cB, startPasskeyEnrollmentService as cC, startPasskeyLoginService as cD, startSessionBindingDisableService as cE, startSessionRenewService as cF, startStepUpService as cG, startTotpEnrolmentService as cH, stepUpService as cI, sweepUnconfirmedMfaService as cJ, userPublicKeys as cK, verifyCodeService as cL, verifyOneTimeTokenService as cM, verifySecondFactor as cN, oauthStartService as ca, oauthUnlinkNotifyService as cb, oauthUnlinkedEvent as cc, optionalAuth as cd, passkeys as ce, pollDeviceAuthService as cf, regenerateRecoveryCodesService as cg, registerAuthProfile as ch, registerMachineVerifier as ci, registerOAuthProvider as cj, registerPublicKeyService as ck, registerService as cl, renamePasskeyService as cm, requestPasswordResetService as cn, requestSignupLinkService as co, requireEnabledProvider as cp, requireMachineScope as cq, resolveAuthenticatedUser as cr, revokeAllKeysService as cs, revokeAllOAuth2GrantsForUser as ct, revokeKeyService as cu, revokeOAuth2GrantService as cv, revokePasskeyService as cw, rotateKeyService as cx, runAuthProfile as cy, selectAuthProfile as cz, type ConfirmPasswordResetResult as d, type LoginResult as e, type StartDeviceAuthResult as f, type PasskeySummary as g, type ConfirmTotpResult as h, type RotateKeyResult as i, type RevokeAllKeysResult as j, type SessionBindingResult as k, type SessionRenewResult 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_TARGET_TYPES as v, type VerificationPurpose as w, type VerificationTargetType as x, type OAuthProvider as y, type NewPasskey as z };