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