@spfn/auth 0.2.1 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1032 -2397
- package/dist/{authenticate-eucncHxN.d.ts → authenticate-DlTGaBT8.d.ts} +545 -82
- package/dist/client-proof.d.ts +606 -0
- package/dist/client-proof.js +1842 -0
- package/dist/client-proof.js.map +1 -0
- package/dist/config.d.ts +319 -3
- package/dist/config.js +155 -5
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +180 -3
- package/dist/errors.js +116 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +122 -18
- package/dist/index.js +129 -8
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +404 -96
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/server.d.ts +5 -4
- package/dist/nextjs/server.js +165 -26
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +2130 -1016
- package/dist/server.js +4916 -739
- package/dist/server.js.map +1 -1
- package/dist/session-DTHahDQ9.d.ts +53 -0
- package/dist/types-DYyhze28.d.ts +98 -0
- package/dist/wire-version-CtzMKvBB.d.ts +134 -0
- package/migrations/20251125021229_premium_famine/snapshot.json +2641 -0
- package/migrations/20260225130050_smooth_the_fury/snapshot.json +2686 -0
- package/migrations/20260308141417_deep_iceman/snapshot.json +2686 -0
- package/migrations/20260308151309_perfect_deathbird/snapshot.json +2731 -0
- package/migrations/20260308201135_concerned_rawhide_kid/snapshot.json +2786 -0
- package/migrations/20260629103209_lethal_lifeguard/migration.sql +32 -0
- package/migrations/20260629103209_lethal_lifeguard/snapshot.json +2786 -0
- package/migrations/20260709073531_easy_hardball/migration.sql +24 -0
- package/migrations/20260709073531_easy_hardball/snapshot.json +3119 -0
- package/migrations/20260714081434_glossy_major_mapleleaf/migration.sql +1 -0
- package/migrations/20260714081434_glossy_major_mapleleaf/snapshot.json +3112 -0
- package/migrations/20260804105939_amazing_bushwacker/migration.sql +3 -0
- package/migrations/20260804105939_amazing_bushwacker/snapshot.json +3112 -0
- package/migrations/20260804110033_fat_piledriver/migration.sql +2 -0
- package/migrations/20260804110033_fat_piledriver/snapshot.json +3138 -0
- package/migrations/20260805143152_vengeful_ravenous/migration.sql +4 -0
- package/migrations/20260805143152_vengeful_ravenous/snapshot.json +3190 -0
- package/package.json +54 -40
- package/migrations/meta/0000_snapshot.json +0 -1632
- package/migrations/meta/0001_snapshot.json +0 -1660
- package/migrations/meta/0002_snapshot.json +0 -1660
- package/migrations/meta/0003_snapshot.json +0 -1689
- package/migrations/meta/0004_snapshot.json +0 -1721
- package/migrations/meta/_journal.json +0 -41
- /package/migrations/{0000_premium_famine.sql → 20251125021229_premium_famine/migration.sql} +0 -0
- /package/migrations/{0001_smooth_the_fury.sql → 20260225130050_smooth_the_fury/migration.sql} +0 -0
- /package/migrations/{0002_deep_iceman.sql → 20260308141417_deep_iceman/migration.sql} +0 -0
- /package/migrations/{0003_perfect_deathbird.sql → 20260308151309_perfect_deathbird/migration.sql} +0 -0
- /package/migrations/{0004_concerned_rawhide_kid.sql → 20260308201135_concerned_rawhide_kid/migration.sql} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _spfn_core_route from '@spfn/core/route';
|
|
2
|
+
import { K as KeyAlgorithmType, h as KeyPlatformType, j as SocialProvider } from './types-DYyhze28.js';
|
|
2
3
|
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
3
4
|
import { Static } from '@sinclair/typebox';
|
|
5
|
+
import { Context } from 'hono';
|
|
4
6
|
import { User } from '@spfn/auth/server';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -40,6 +42,7 @@ interface AuthSession {
|
|
|
40
42
|
email: string | null;
|
|
41
43
|
emailVerified: boolean;
|
|
42
44
|
phoneVerified: boolean;
|
|
45
|
+
hasPassword: boolean;
|
|
43
46
|
role: Role;
|
|
44
47
|
permissions: Permission[];
|
|
45
48
|
}
|
|
@@ -83,65 +86,12 @@ interface UserProfile {
|
|
|
83
86
|
profile: ProfileInfo | null;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
/**
|
|
87
|
-
* @spfn/auth - Shared Types
|
|
88
|
-
*
|
|
89
|
-
* Common types and constants used across the auth package
|
|
90
|
-
*/
|
|
91
|
-
/**
|
|
92
|
-
* Supported JWT signature algorithms
|
|
93
|
-
*
|
|
94
|
-
* - ES256: ECDSA with P-256 and SHA-256 (recommended, smaller keys)
|
|
95
|
-
* - RS256: RSA with SHA-256 (fallback, larger keys)
|
|
96
|
-
*/
|
|
97
|
-
declare const KEY_ALGORITHM: readonly ["ES256", "RS256"];
|
|
98
|
-
/**
|
|
99
|
-
* Key algorithm type derived from the const array
|
|
100
|
-
*/
|
|
101
|
-
type KeyAlgorithmType = typeof KEY_ALGORITHM[number];
|
|
102
|
-
/**
|
|
103
|
-
* Invitation status enum values
|
|
104
|
-
* Single source of truth for all invitation statuses
|
|
105
|
-
*/
|
|
106
|
-
declare const INVITATION_STATUSES: readonly ["pending", "accepted", "expired", "cancelled"];
|
|
107
|
-
/**
|
|
108
|
-
* Invitation status type derived from the const array
|
|
109
|
-
*/
|
|
110
|
-
type InvitationStatus = typeof INVITATION_STATUSES[number];
|
|
111
|
-
/**
|
|
112
|
-
* User status enum values
|
|
113
|
-
* Single source of truth for all user statuses
|
|
114
|
-
*/
|
|
115
|
-
declare const USER_STATUSES: readonly ["active", "inactive", "suspended"];
|
|
116
|
-
/**
|
|
117
|
-
* User status type derived from the const array
|
|
118
|
-
*/
|
|
119
|
-
type UserStatus = typeof USER_STATUSES[number];
|
|
120
|
-
/**
|
|
121
|
-
* Social provider enum values
|
|
122
|
-
* Single source of truth for supported OAuth providers
|
|
123
|
-
*/
|
|
124
|
-
declare const SOCIAL_PROVIDERS: readonly ["google", "github", "kakao", "naver"];
|
|
125
|
-
/**
|
|
126
|
-
* Social provider type derived from the const array
|
|
127
|
-
*/
|
|
128
|
-
type SocialProvider = typeof SOCIAL_PROVIDERS[number];
|
|
129
|
-
|
|
130
89
|
/**
|
|
131
90
|
* @spfn/auth - Auth Service
|
|
132
91
|
*
|
|
133
92
|
* Core authentication logic: registration, login, logout, password management
|
|
134
93
|
*/
|
|
135
94
|
|
|
136
|
-
interface CheckAccountExistsParams {
|
|
137
|
-
email?: string;
|
|
138
|
-
phone?: string;
|
|
139
|
-
}
|
|
140
|
-
interface CheckAccountExistsResult {
|
|
141
|
-
exists: boolean;
|
|
142
|
-
identifier: string;
|
|
143
|
-
identifierType: 'email' | 'phone';
|
|
144
|
-
}
|
|
145
95
|
interface RegisterParams {
|
|
146
96
|
email?: string;
|
|
147
97
|
phone?: string;
|
|
@@ -151,6 +101,8 @@ interface RegisterParams {
|
|
|
151
101
|
keyId: string;
|
|
152
102
|
fingerprint: string;
|
|
153
103
|
algorithm?: KeyAlgorithmType;
|
|
104
|
+
deviceName?: string;
|
|
105
|
+
platform?: KeyPlatformType;
|
|
154
106
|
metadata?: Record<string, unknown>;
|
|
155
107
|
}
|
|
156
108
|
interface RegisterResult {
|
|
@@ -168,6 +120,8 @@ interface LoginParams {
|
|
|
168
120
|
fingerprint: string;
|
|
169
121
|
oldKeyId?: string;
|
|
170
122
|
algorithm?: KeyAlgorithmType;
|
|
123
|
+
deviceName?: string;
|
|
124
|
+
platform?: KeyPlatformType;
|
|
171
125
|
}
|
|
172
126
|
interface LoginResult {
|
|
173
127
|
userId: string;
|
|
@@ -182,14 +136,10 @@ interface LogoutParams {
|
|
|
182
136
|
}
|
|
183
137
|
interface ChangePasswordParams {
|
|
184
138
|
userId: number;
|
|
185
|
-
currentPassword
|
|
139
|
+
currentPassword?: string;
|
|
186
140
|
newPassword: string;
|
|
187
141
|
passwordHash?: string;
|
|
188
142
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Check if an account exists by email or phone
|
|
191
|
-
*/
|
|
192
|
-
declare function checkAccountExistsService(params: CheckAccountExistsParams): Promise<CheckAccountExistsResult>;
|
|
193
143
|
/**
|
|
194
144
|
* Register a new user account
|
|
195
145
|
*/
|
|
@@ -209,13 +159,23 @@ declare function changePasswordService(params: ChangePasswordParams): Promise<vo
|
|
|
209
159
|
|
|
210
160
|
declare const EmailSchema: _sinclair_typebox.TString;
|
|
211
161
|
declare const PhoneSchema: _sinclair_typebox.TString;
|
|
162
|
+
/**
|
|
163
|
+
* Optional device labels a client may send when registering a key.
|
|
164
|
+
*
|
|
165
|
+
* Display only: the key list uses them to tell one device from another, and
|
|
166
|
+
* nothing is authorized or refused by either value, so a client that lies about
|
|
167
|
+
* them gains nothing. Both are omitted by every key registered before they
|
|
168
|
+
* existed, hence optional rather than defaulted.
|
|
169
|
+
*/
|
|
170
|
+
declare const DeviceNameSchema: _sinclair_typebox.TString;
|
|
171
|
+
declare const PlatformSchema: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>;
|
|
212
172
|
declare const PasswordSchema: _sinclair_typebox.TString;
|
|
213
173
|
declare const TargetTypeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
|
|
214
174
|
type VerificationTargetType = Static<typeof TargetTypeSchema>;
|
|
215
175
|
declare const VERIFICATION_TARGET_TYPES: readonly ["email", "phone"];
|
|
216
|
-
declare const VerificationPurposeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">]>;
|
|
176
|
+
declare const VerificationPurposeSchema: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">, _sinclair_typebox.TLiteral<"account_deletion">]>;
|
|
217
177
|
type VerificationPurpose = Static<typeof VerificationPurposeSchema>;
|
|
218
|
-
declare const VERIFICATION_PURPOSES: readonly ["registration", "login", "password_reset", "email_change", "phone_change"];
|
|
178
|
+
declare const VERIFICATION_PURPOSES: readonly ["registration", "login", "password_reset", "email_change", "phone_change", "account_deletion"];
|
|
219
179
|
|
|
220
180
|
/**
|
|
221
181
|
* @spfn/auth - Verification Service
|
|
@@ -266,6 +226,9 @@ interface RegisterPublicKeyParams {
|
|
|
266
226
|
publicKey: string;
|
|
267
227
|
fingerprint: string;
|
|
268
228
|
algorithm?: KeyAlgorithmType;
|
|
229
|
+
/** Device label for the key list. Display only — nothing is authorized by it. */
|
|
230
|
+
deviceName?: string;
|
|
231
|
+
platform?: KeyPlatformType;
|
|
269
232
|
}
|
|
270
233
|
interface RotateKeyParams {
|
|
271
234
|
userId: number;
|
|
@@ -274,6 +237,9 @@ interface RotateKeyParams {
|
|
|
274
237
|
newPublicKey: string;
|
|
275
238
|
fingerprint: string;
|
|
276
239
|
algorithm?: KeyAlgorithmType;
|
|
240
|
+
/** Omitted: the replaced key's label carries over, so rotation keeps its name. */
|
|
241
|
+
deviceName?: string;
|
|
242
|
+
platform?: KeyPlatformType;
|
|
277
243
|
}
|
|
278
244
|
interface RotateKeyResult {
|
|
279
245
|
success: boolean;
|
|
@@ -284,8 +250,61 @@ interface RevokeKeyParams {
|
|
|
284
250
|
keyId: string;
|
|
285
251
|
reason: string;
|
|
286
252
|
}
|
|
253
|
+
interface RevokeAllKeysParams {
|
|
254
|
+
userId: number;
|
|
255
|
+
/** The key the request itself is signed with — spared unless includeCurrent. */
|
|
256
|
+
currentKeyId: string;
|
|
257
|
+
/** true signs the caller out too. Default false: "my other devices". */
|
|
258
|
+
includeCurrent?: boolean;
|
|
259
|
+
reason: string;
|
|
260
|
+
}
|
|
261
|
+
interface RevokeAllKeysResult {
|
|
262
|
+
revokedCount: number;
|
|
263
|
+
currentKeyRevoked: boolean;
|
|
264
|
+
}
|
|
265
|
+
/** One registered device as the account surface shows it. */
|
|
266
|
+
interface KeySummary {
|
|
267
|
+
keyId: string;
|
|
268
|
+
deviceName?: string;
|
|
269
|
+
platform?: string;
|
|
270
|
+
algorithm: KeyAlgorithmType;
|
|
271
|
+
/** First bytes of the fingerprint — enough to tell two entries apart. */
|
|
272
|
+
fingerprintPrefix: string;
|
|
273
|
+
/**
|
|
274
|
+
* Milliseconds since the Unix epoch, not an ISO string.
|
|
275
|
+
*
|
|
276
|
+
* One representation of a moment across the whole surface: a generated Swift
|
|
277
|
+
* or Kotlin client reads an integer with no date formatter, and
|
|
278
|
+
* `ISO8601DateFormatter` rejecting fractional seconds by default stops being
|
|
279
|
+
* a way for the two SDKs to disagree about the same value.
|
|
280
|
+
*/
|
|
281
|
+
createdAtMillis: number;
|
|
282
|
+
lastUsedAtMillis?: number;
|
|
283
|
+
expiresAtMillis?: number;
|
|
284
|
+
/** The TTL has run out. The key still reads as active; authenticate refuses it. */
|
|
285
|
+
isExpired: boolean;
|
|
286
|
+
/** False once revoked. Only ever false when the caller asked for revoked keys. */
|
|
287
|
+
isActive: boolean;
|
|
288
|
+
/** When it was revoked, for the "what did I cut off, and when" reading. */
|
|
289
|
+
revokedAtMillis?: number;
|
|
290
|
+
}
|
|
291
|
+
interface ListKeysParams {
|
|
292
|
+
userId: number;
|
|
293
|
+
/** Also return keys already revoked. Default false: only what can still sign. */
|
|
294
|
+
includeRevoked?: boolean;
|
|
295
|
+
}
|
|
296
|
+
/** How much of the fingerprint the list returns. */
|
|
297
|
+
declare const KEY_FINGERPRINT_PREFIX_LENGTH = 8;
|
|
287
298
|
/**
|
|
288
299
|
* Register a new public key for a user
|
|
300
|
+
*
|
|
301
|
+
* `keyId` is UNIQUE across all users, so the lookup must ignore `isActive` —
|
|
302
|
+
* filtering on it misses a revoked row and the insert then fails on the unique
|
|
303
|
+
* index, rolling the whole login transaction back into a 500. Reuse is refused
|
|
304
|
+
* with a domain error instead, telling the client to generate a fresh keyId.
|
|
305
|
+
*
|
|
306
|
+
* @throws KeyIdAlreadyRegisteredError keyId가 이미 쓰인 값일 때 (자기 폐기 키 재사용 · 남의 키)
|
|
307
|
+
* @throws InvalidKeyFingerprintError fingerprint가 publicKey와 맞지 않을 때
|
|
289
308
|
*/
|
|
290
309
|
declare function registerPublicKeyService(params: RegisterPublicKeyParams): Promise<void>;
|
|
291
310
|
/**
|
|
@@ -293,9 +312,36 @@ declare function registerPublicKeyService(params: RegisterPublicKeyParams): Prom
|
|
|
293
312
|
*/
|
|
294
313
|
declare function rotateKeyService(params: RotateKeyParams): Promise<RotateKeyResult>;
|
|
295
314
|
/**
|
|
296
|
-
* Revoke a user's public key
|
|
315
|
+
* Revoke a user's public key.
|
|
316
|
+
*
|
|
317
|
+
* Returns false when the key does not belong to this user, so a caller acting
|
|
318
|
+
* on a key id from outside (the device list) can answer "not found" instead of
|
|
319
|
+
* reporting a revocation that never happened. The repository already scopes the
|
|
320
|
+
* update by userId, so someone else's key is never touched either way.
|
|
321
|
+
*/
|
|
322
|
+
declare function revokeKeyService(params: RevokeKeyParams): Promise<boolean>;
|
|
323
|
+
/**
|
|
324
|
+
* List the caller's active keys — one entry per device that can sign for them.
|
|
325
|
+
*
|
|
326
|
+
* `isExpired` is computed rather than stored: an expired key keeps `isActive`
|
|
327
|
+
* true (nothing flips it), and `authenticate` refuses it at request time. A list
|
|
328
|
+
* that showed it as simply "active" would be telling the user something the
|
|
329
|
+
* server does not act on.
|
|
330
|
+
*
|
|
331
|
+
* The fingerprint is truncated. Its full value is what a native sign-in must
|
|
332
|
+
* send as its nonce (issue #63), and an account page has no use for it beyond
|
|
333
|
+
* telling two entries apart.
|
|
334
|
+
*/
|
|
335
|
+
declare function listKeysService(params: ListKeysParams): Promise<KeySummary[]>;
|
|
336
|
+
/**
|
|
337
|
+
* Revoke every active key the user has, optionally sparing the current one.
|
|
338
|
+
*
|
|
339
|
+
* The caller's own key is spared by default, so "sign out my other devices"
|
|
340
|
+
* does not also end the session making the request. Passing
|
|
341
|
+
* `includeCurrent: true` is the full sign-out, which until now was reachable
|
|
342
|
+
* only as a side effect of changing a password.
|
|
297
343
|
*/
|
|
298
|
-
declare function
|
|
344
|
+
declare function revokeAllKeysService(params: RevokeAllKeysParams): Promise<RevokeAllKeysResult>;
|
|
299
345
|
|
|
300
346
|
/**
|
|
301
347
|
* @spfn/auth - RBAC Type Definitions
|
|
@@ -406,6 +452,161 @@ declare function issueOneTimeTokenService(userId: string): Promise<IssueOneTimeT
|
|
|
406
452
|
*/
|
|
407
453
|
declare function verifyOneTimeTokenService(token: string): Promise<string | null>;
|
|
408
454
|
|
|
455
|
+
/**
|
|
456
|
+
* OAuth Provider 추상화
|
|
457
|
+
*
|
|
458
|
+
* Provider별로 하드코딩된 분기를 제거하기 위한 공통 인터페이스와 registry.
|
|
459
|
+
* - 내장 provider(google)는 패키지 로드 시점에 자기 등록(dogfood)
|
|
460
|
+
* - 외부 패키지(@superself/auth 등)는 registerOAuthProvider()로 런타임 등록
|
|
461
|
+
*
|
|
462
|
+
* @spfn/auth는 토큰 issuer가 아니라 소비(client) 측이므로, 이 추상화는
|
|
463
|
+
* web 흐름("authorize URL 생성 → code 교환 → 사용자 정보 정규화")과
|
|
464
|
+
* native 흐름(네이티브/웹 SDK가 받은 id_token 직접 검증)을 다룬다.
|
|
465
|
+
*/
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Provider 사용자 정보를 공통 형태로 정규화한 신원
|
|
469
|
+
*
|
|
470
|
+
* provider별 응답 형태(snake_case 등)를 service에 노출하지 않기 위한 경계.
|
|
471
|
+
*/
|
|
472
|
+
interface NormalizedIdentity {
|
|
473
|
+
providerUserId: string;
|
|
474
|
+
email: string | null;
|
|
475
|
+
emailVerified: boolean;
|
|
476
|
+
name?: string;
|
|
477
|
+
avatar?: string;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 정규화된 OAuth 토큰 응답
|
|
481
|
+
*
|
|
482
|
+
* @property expiresIn - access token 만료까지 남은 초(seconds)
|
|
483
|
+
*/
|
|
484
|
+
interface OAuthTokens {
|
|
485
|
+
accessToken: string;
|
|
486
|
+
refreshToken?: string;
|
|
487
|
+
expiresIn: number;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* 네이티브 id_token 검증 옵션
|
|
491
|
+
*/
|
|
492
|
+
interface NativeVerifyOptions {
|
|
493
|
+
/** 클라이언트가 생성한 raw nonce. provider별 규약(raw 또는 SHA-256 해시)으로 대조된다. */
|
|
494
|
+
nonce: string;
|
|
495
|
+
/**
|
|
496
|
+
* 같은 로그인에서 SDK가 함께 받은 provider access token (선택).
|
|
497
|
+
*
|
|
498
|
+
* id_token만으로는 알 수 없는 claim을 provider API로 보강하려는 provider가 쓴다
|
|
499
|
+
* (카카오: id_token에 email_verified가 없어 /v2/user/me의 이메일 유효·인증 플래그를 본다).
|
|
500
|
+
*
|
|
501
|
+
* ⚠️ 클라이언트가 보낸 검증되지 않은 값이다. 다른 사용자의 토큰일 수 있으므로,
|
|
502
|
+
* 이 값으로 조회한 신원은 반드시 id_token의 sub와 대조한 뒤에만 신뢰해야 한다.
|
|
503
|
+
*/
|
|
504
|
+
accessToken?: string;
|
|
505
|
+
}
|
|
506
|
+
interface OAuthCodeExchangeOptions {
|
|
507
|
+
/** Provider가 callback에 돌려준 원본 state. 일부 provider는 token 교환에도 요구한다. */
|
|
508
|
+
state: string;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Provider가 서비스로 보내는 연동 해제 알림의 원재료
|
|
512
|
+
*
|
|
513
|
+
* provider마다 전달 방식(query/form/JSON, 헤더 인증)이 달라 route가 정규화해 넘긴다.
|
|
514
|
+
* fields는 query string과 body(form/JSON)를 병합한 문자열 맵이다.
|
|
515
|
+
*/
|
|
516
|
+
interface UnlinkNotifyRequest {
|
|
517
|
+
/** Authorization 헤더 원문 (없으면 null) */
|
|
518
|
+
authorization: string | null;
|
|
519
|
+
fields: Record<string, string>;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* 검증에 성공한 연동 해제 알림
|
|
523
|
+
*/
|
|
524
|
+
interface UnlinkNotification {
|
|
525
|
+
providerUserId: string;
|
|
526
|
+
/** provider가 전달한 해제 경로 (kakao referrer_type 등) */
|
|
527
|
+
reason?: string;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* 연동 해제 알림 검증 실패
|
|
531
|
+
*
|
|
532
|
+
* API 에러 응답 체계를 타지 않는다 — route가 잡아 status만 반환한다.
|
|
533
|
+
* (provider 웹훅은 사람이 아닌 provider 서버가 호출자라서 에러 본문이 무의미하다)
|
|
534
|
+
*/
|
|
535
|
+
declare class UnlinkNotifyRejection extends Error {
|
|
536
|
+
readonly status: 400 | 401 | 403;
|
|
537
|
+
constructor(status: 400 | 401 | 403, message: string);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* OAuth provider 구현 인터페이스
|
|
541
|
+
*
|
|
542
|
+
* google, superself 등 모든 provider가 이 형태를 만족해야 registry에 등록된다.
|
|
543
|
+
*/
|
|
544
|
+
interface OAuthProvider {
|
|
545
|
+
id: SocialProvider;
|
|
546
|
+
/**
|
|
547
|
+
* provider가 사용 가능한 상태인지(필수 env 등) 확인
|
|
548
|
+
*/
|
|
549
|
+
isEnabled(): boolean;
|
|
550
|
+
/**
|
|
551
|
+
* provider 로그인 페이지로 보낼 authorization URL 생성
|
|
552
|
+
*
|
|
553
|
+
* @param state - CSRF 방지용 암호화 state
|
|
554
|
+
* @param scopes - 요청할 scope (미지정 시 provider 기본값)
|
|
555
|
+
*/
|
|
556
|
+
getAuthUrl(state: string, scopes?: string[]): string;
|
|
557
|
+
/**
|
|
558
|
+
* authorization code를 토큰으로 교환
|
|
559
|
+
*/
|
|
560
|
+
exchangeCodeForTokens(code: string, options: OAuthCodeExchangeOptions): Promise<OAuthTokens>;
|
|
561
|
+
/**
|
|
562
|
+
* access token으로 사용자 정보를 조회하고 공통 형태로 정규화
|
|
563
|
+
*/
|
|
564
|
+
getUserInfo(accessToken: string): Promise<NormalizedIdentity>;
|
|
565
|
+
/**
|
|
566
|
+
* refresh token으로 access token 갱신 (provider가 지원하는 경우)
|
|
567
|
+
*
|
|
568
|
+
* 저장된 provider 토큰을 이후 API 호출에 재사용할 때 사용한다.
|
|
569
|
+
* 미구현 provider는 갱신 불가로 간주한다.
|
|
570
|
+
*/
|
|
571
|
+
refreshTokens?(refreshToken: string): Promise<OAuthTokens>;
|
|
572
|
+
/**
|
|
573
|
+
* 네이티브/웹 SDK가 받은 id_token을 직접 검증하고 신원을 정규화한다.
|
|
574
|
+
*
|
|
575
|
+
* authorization code 교환 없이 provider JWKS로 서명을 검증하므로 client secret이
|
|
576
|
+
* 필요 없다. native sign-in을 지원하는 provider만 구현한다(Apple은 web SDK 부재로
|
|
577
|
+
* Android·웹도 이 경로를 쓴다).
|
|
578
|
+
*/
|
|
579
|
+
verifyNativeIdToken?(idToken: string, options: NativeVerifyOptions): Promise<NormalizedIdentity>;
|
|
580
|
+
/**
|
|
581
|
+
* Provider발 연동 해제 알림(웹훅)을 검증하고 대상 사용자를 식별한다.
|
|
582
|
+
*
|
|
583
|
+
* 인증 없는 공개 엔드포인트로 들어오므로 provider별 규격(카카오: 어드민 키 헤더,
|
|
584
|
+
* 네이버: HMAC 서명 + AES 복호화)의 검증을 통과해야만 처리된다.
|
|
585
|
+
* 검증 실패는 UnlinkNotifyRejection을 던진다.
|
|
586
|
+
*/
|
|
587
|
+
verifyUnlinkNotification?(request: UnlinkNotifyRequest): Promise<UnlinkNotification>;
|
|
588
|
+
/**
|
|
589
|
+
* 연동 해제 알림 처리 성공 시 응답할 HTTP status (미지정 시 200)
|
|
590
|
+
*
|
|
591
|
+
* 네이버는 204 No Content를 요구한다.
|
|
592
|
+
*/
|
|
593
|
+
unlinkNotifyAckStatus?: 200 | 204;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* OAuth provider 등록 (public)
|
|
597
|
+
*
|
|
598
|
+
* 동일 id로 다시 등록하면 덮어쓴다(외부 패키지의 override 허용).
|
|
599
|
+
*/
|
|
600
|
+
declare function registerOAuthProvider(provider: OAuthProvider): void;
|
|
601
|
+
/**
|
|
602
|
+
* 등록된 provider 조회. 미등록이면 undefined.
|
|
603
|
+
*/
|
|
604
|
+
declare function getOAuthProvider(id: SocialProvider): OAuthProvider | undefined;
|
|
605
|
+
/**
|
|
606
|
+
* 등록된 모든 provider 목록
|
|
607
|
+
*/
|
|
608
|
+
declare function getRegisteredProviders(): OAuthProvider[];
|
|
609
|
+
|
|
409
610
|
/**
|
|
410
611
|
* @spfn/auth - OAuth Service
|
|
411
612
|
*
|
|
@@ -423,6 +624,8 @@ interface OAuthStartParams {
|
|
|
423
624
|
fingerprint: string;
|
|
424
625
|
algorithm: KeyAlgorithmType;
|
|
425
626
|
metadata?: Record<string, unknown>;
|
|
627
|
+
/** CSRF nonce bound into the state; the route sets the matching oauth_csrf cookie. */
|
|
628
|
+
nonce?: string;
|
|
426
629
|
}
|
|
427
630
|
interface OAuthStartResult {
|
|
428
631
|
authUrl: string;
|
|
@@ -431,6 +634,16 @@ interface OAuthCallbackParams {
|
|
|
431
634
|
provider: SocialProvider;
|
|
432
635
|
code: string;
|
|
433
636
|
state: string;
|
|
637
|
+
/**
|
|
638
|
+
* Value(s) of the oauth_csrf cookie from the callback request. One of them
|
|
639
|
+
* must equal the nonce bound into the (encrypted) state — otherwise the flow
|
|
640
|
+
* wasn't initiated by this browser (login CSRF). An array arises because the
|
|
641
|
+
* cookie name carries the PORT suffix of the process that set it (the Next.js
|
|
642
|
+
* web process), which differs from the API process in a split deployment, so
|
|
643
|
+
* the callback collects every spfn_oauth_csrf* candidate. Pass `undefined` or
|
|
644
|
+
* an empty array when absent; verification then fails closed.
|
|
645
|
+
*/
|
|
646
|
+
expectedNonce: string | string[] | undefined;
|
|
434
647
|
}
|
|
435
648
|
interface OAuthCallbackResult {
|
|
436
649
|
redirectUrl: string;
|
|
@@ -438,6 +651,13 @@ interface OAuthCallbackResult {
|
|
|
438
651
|
keyId: string;
|
|
439
652
|
isNewUser: boolean;
|
|
440
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* registry에서 provider를 찾아 사용 가능한지 검증 후 반환
|
|
656
|
+
*
|
|
657
|
+
* 미등록과 비활성을 구분해 디버깅 신호를 남긴다.
|
|
658
|
+
* 라우트 레이어에서도 재사용한다(중복 조회/non-null 단언 제거).
|
|
659
|
+
*/
|
|
660
|
+
declare function requireEnabledProvider(provider: SocialProvider): OAuthProvider;
|
|
441
661
|
/**
|
|
442
662
|
* OAuth 로그인 시작 - Provider 로그인 페이지로 리다이렉트할 URL 생성
|
|
443
663
|
*
|
|
@@ -456,11 +676,11 @@ declare function oauthCallbackService(params: OAuthCallbackParams): Promise<OAut
|
|
|
456
676
|
*/
|
|
457
677
|
declare function buildOAuthErrorUrl(error: string): string;
|
|
458
678
|
/**
|
|
459
|
-
* OAuth provider가 활성화되어 있는지 확인
|
|
679
|
+
* OAuth provider가 등록되어 있고 활성화되어 있는지 확인
|
|
460
680
|
*/
|
|
461
681
|
declare function isOAuthProviderEnabled(provider: SocialProvider): boolean;
|
|
462
682
|
/**
|
|
463
|
-
* 활성화된 모든 OAuth provider 목록
|
|
683
|
+
* 활성화된 모든 OAuth provider 목록 (registry 기반)
|
|
464
684
|
*/
|
|
465
685
|
declare function getEnabledOAuthProviders(): SocialProvider[];
|
|
466
686
|
/**
|
|
@@ -473,6 +693,72 @@ declare function getEnabledOAuthProviders(): SocialProvider[];
|
|
|
473
693
|
* @returns 유효한 Google access token
|
|
474
694
|
*/
|
|
475
695
|
declare function getGoogleAccessToken(userId: number): Promise<string>;
|
|
696
|
+
interface UnlinkNotifyResult {
|
|
697
|
+
/** provider 규격이 요구하는 성공 응답 status */
|
|
698
|
+
ackStatus: 200 | 204;
|
|
699
|
+
/** 대상 소셜 계정을 찾아 삭제했는지 (미존재·이미 삭제면 false) */
|
|
700
|
+
handled: boolean;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Provider발 연동 해제 알림(unlink-notify) 처리
|
|
704
|
+
*
|
|
705
|
+
* 검증(verifyUnlinkNotification)을 통과한 요청만 여기 도달한다.
|
|
706
|
+
* 소셜 계정 연결 row를 삭제해 저장 토큰(access/refresh)까지 함께 파기하고,
|
|
707
|
+
* 후속 정책(계정 탈퇴 연계 등)은 auth.oauth.unlinked 이벤트 구독에 맡긴다.
|
|
708
|
+
*
|
|
709
|
+
* 대상 계정이 없어도 성공으로 응답한다 — provider 재전송·이미 해제된 계정에
|
|
710
|
+
* 대한 알림은 정상 시나리오다.
|
|
711
|
+
*/
|
|
712
|
+
declare function oauthUnlinkNotifyService(provider: SocialProvider, notification: UnlinkNotification): Promise<UnlinkNotifyResult>;
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @spfn/auth - Native Social Login Service
|
|
716
|
+
*
|
|
717
|
+
* 네이티브/웹 SDK가 받은 id_token을 JWKS로 검증하고, 검증된 신원에 클라이언트가 만든
|
|
718
|
+
* 공개키를 등록한다. 토큰은 발급하지 않는다 — 클라이언트가 등록한 키로 client token을
|
|
719
|
+
* 직접 서명해 Bearer로 사용한다(client-signs / server-verifies 모델).
|
|
720
|
+
*
|
|
721
|
+
* 흐름은 두 단계로 분리한다:
|
|
722
|
+
* 1) id_token 검증 — 외부 JWKS 네트워크 조회. DB 트랜잭션 밖에서 수행한다.
|
|
723
|
+
* 2) persist — 사용자 link/create + 공개키 등록을 한 트랜잭션으로. 이벤트는 커밋 후 발행.
|
|
724
|
+
*/
|
|
725
|
+
|
|
726
|
+
interface OAuthNativeParams {
|
|
727
|
+
provider: SocialProvider;
|
|
728
|
+
idToken: string;
|
|
729
|
+
nonce: string;
|
|
730
|
+
publicKey: string;
|
|
731
|
+
keyId: string;
|
|
732
|
+
fingerprint: string;
|
|
733
|
+
algorithm: KeyAlgorithmType;
|
|
734
|
+
/** 키 목록에 보일 기기 라벨 (선택). 표시용이라 권한 판정에 쓰이지 않는다. */
|
|
735
|
+
deviceName?: string;
|
|
736
|
+
platform?: KeyPlatformType;
|
|
737
|
+
/**
|
|
738
|
+
* SDK가 id_token과 함께 받은 provider access token (선택).
|
|
739
|
+
*
|
|
740
|
+
* provider가 id_token만으로 확인할 수 없는 claim을 보강할 때만 쓴다. 없으면 provider는
|
|
741
|
+
* id_token이 담은 정보만으로 신원을 정규화한다.
|
|
742
|
+
*/
|
|
743
|
+
accessToken?: string;
|
|
744
|
+
/** Apple은 첫 로그인에만 이름을 별도로 주므로 클라이언트가 전달할 수 있다. */
|
|
745
|
+
profile?: {
|
|
746
|
+
name?: string;
|
|
747
|
+
};
|
|
748
|
+
metadata?: Record<string, unknown>;
|
|
749
|
+
}
|
|
750
|
+
interface OAuthNativeResult {
|
|
751
|
+
userId: string;
|
|
752
|
+
keyId: string;
|
|
753
|
+
isNewUser: boolean;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* native id_token 로그인 처리
|
|
757
|
+
*
|
|
758
|
+
* @throws NativeSignInUnsupportedError provider가 native sign-in을 지원하지 않을 때
|
|
759
|
+
* @throws InvalidSocialTokenError id_token 검증 실패 시
|
|
760
|
+
*/
|
|
761
|
+
declare function oauthNativeService(params: OAuthNativeParams): Promise<OAuthNativeResult>;
|
|
476
762
|
|
|
477
763
|
/**
|
|
478
764
|
* @spfn/auth - Main Router
|
|
@@ -484,25 +770,19 @@ declare function getGoogleAccessToken(userId: number): Promise<string>;
|
|
|
484
770
|
* Exports all authentication-related routes
|
|
485
771
|
*
|
|
486
772
|
* Routes:
|
|
487
|
-
* - Auth: /_auth/
|
|
773
|
+
* - Auth: /_auth/codes, /_auth/login, /_auth/logout, etc.
|
|
488
774
|
* - OAuth: /_auth/oauth/google, /_auth/oauth/google/callback, etc.
|
|
489
775
|
* - Invitations: /_auth/invitations/*
|
|
490
776
|
* - Users: /_auth/users/*
|
|
777
|
+
* - Deletion: /_auth/deletion/request, /_auth/deletion/cancel
|
|
491
778
|
* - Admin: /_auth/admin/* (superadmin only)
|
|
492
779
|
*/
|
|
493
780
|
declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
494
|
-
checkAccountExists: _spfn_core_route.RouteDef<{
|
|
495
|
-
body: _sinclair_typebox.TUnion<[_sinclair_typebox.TObject<{
|
|
496
|
-
email: _sinclair_typebox.TString;
|
|
497
|
-
}>, _sinclair_typebox.TObject<{
|
|
498
|
-
phone: _sinclair_typebox.TString;
|
|
499
|
-
}>]>;
|
|
500
|
-
}, {}, CheckAccountExistsResult>;
|
|
501
781
|
sendVerificationCode: _spfn_core_route.RouteDef<{
|
|
502
782
|
body: _sinclair_typebox.TObject<{
|
|
503
783
|
target: _sinclair_typebox.TString;
|
|
504
784
|
targetType: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
|
|
505
|
-
purpose: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">]>;
|
|
785
|
+
purpose: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">, _sinclair_typebox.TLiteral<"account_deletion">]>;
|
|
506
786
|
}>;
|
|
507
787
|
}, {}, SendVerificationCodeResult>;
|
|
508
788
|
verifyCode: _spfn_core_route.RouteDef<{
|
|
@@ -510,7 +790,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
510
790
|
target: _sinclair_typebox.TString;
|
|
511
791
|
targetType: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"email">, _sinclair_typebox.TLiteral<"phone">]>;
|
|
512
792
|
code: _sinclair_typebox.TString;
|
|
513
|
-
purpose: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">]>;
|
|
793
|
+
purpose: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"registration">, _sinclair_typebox.TLiteral<"login">, _sinclair_typebox.TLiteral<"password_reset">, _sinclair_typebox.TLiteral<"email_change">, _sinclair_typebox.TLiteral<"phone_change">, _sinclair_typebox.TLiteral<"account_deletion">]>;
|
|
514
794
|
}>;
|
|
515
795
|
}, {}, {
|
|
516
796
|
valid: boolean;
|
|
@@ -530,6 +810,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
530
810
|
keyId: _sinclair_typebox.TString;
|
|
531
811
|
fingerprint: _sinclair_typebox.TString;
|
|
532
812
|
algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
|
|
813
|
+
deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
814
|
+
platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
|
|
533
815
|
}>;
|
|
534
816
|
}, RegisterResult>;
|
|
535
817
|
login: _spfn_core_route.RouteDef<{
|
|
@@ -545,6 +827,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
545
827
|
fingerprint: _sinclair_typebox.TString;
|
|
546
828
|
algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
|
|
547
829
|
oldKeyId: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
830
|
+
deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
831
|
+
platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
|
|
548
832
|
}>;
|
|
549
833
|
}, LoginResult>;
|
|
550
834
|
logout: _spfn_core_route.RouteDef<{}, {}, void>;
|
|
@@ -554,11 +838,33 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
554
838
|
keyId: _sinclair_typebox.TString;
|
|
555
839
|
fingerprint: _sinclair_typebox.TString;
|
|
556
840
|
algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
|
|
841
|
+
deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
842
|
+
platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
|
|
557
843
|
}>;
|
|
558
844
|
}, RotateKeyResult>;
|
|
845
|
+
listKeys: _spfn_core_route.RouteDef<{
|
|
846
|
+
body: _sinclair_typebox.TObject<{
|
|
847
|
+
includeRevoked: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
|
|
848
|
+
}>;
|
|
849
|
+
}, {}, {
|
|
850
|
+
keys: KeySummary[];
|
|
851
|
+
}>;
|
|
852
|
+
revokeKey: _spfn_core_route.RouteDef<{
|
|
853
|
+
body: _sinclair_typebox.TObject<{
|
|
854
|
+
keyId: _sinclair_typebox.TString;
|
|
855
|
+
}>;
|
|
856
|
+
}, {}, {
|
|
857
|
+
keyId: string;
|
|
858
|
+
selfRevoked: boolean;
|
|
859
|
+
}>;
|
|
860
|
+
revokeAllKeys: _spfn_core_route.RouteDef<{
|
|
861
|
+
body: _sinclair_typebox.TObject<{
|
|
862
|
+
includeCurrent: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
|
|
863
|
+
}>;
|
|
864
|
+
}, {}, RevokeAllKeysResult>;
|
|
559
865
|
changePassword: _spfn_core_route.RouteDef<{
|
|
560
866
|
body: _sinclair_typebox.TObject<{
|
|
561
|
-
currentPassword: _sinclair_typebox.TString
|
|
867
|
+
currentPassword: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
562
868
|
newPassword: _sinclair_typebox.TString;
|
|
563
869
|
}>;
|
|
564
870
|
}, {}, void>;
|
|
@@ -580,8 +886,27 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
580
886
|
email: string | null;
|
|
581
887
|
emailVerified: boolean;
|
|
582
888
|
phoneVerified: boolean;
|
|
889
|
+
hasPassword: boolean;
|
|
583
890
|
}>;
|
|
584
891
|
issueOneTimeToken: _spfn_core_route.RouteDef<{}, {}, IssueOneTimeTokenResult>;
|
|
892
|
+
requestAccountDeletion: _spfn_core_route.RouteDef<{
|
|
893
|
+
body: _sinclair_typebox.TObject<{
|
|
894
|
+
password: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
895
|
+
verificationToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
896
|
+
reason: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
897
|
+
immediate: _sinclair_typebox.TOptional<_sinclair_typebox.TBoolean>;
|
|
898
|
+
}>;
|
|
899
|
+
}, {}, {
|
|
900
|
+
purgeScheduledAt: string;
|
|
901
|
+
}>;
|
|
902
|
+
cancelAccountDeletion: _spfn_core_route.RouteDef<{
|
|
903
|
+
body: _sinclair_typebox.TObject<{
|
|
904
|
+
email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
905
|
+
phone: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
906
|
+
password: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
907
|
+
verificationToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
908
|
+
}>;
|
|
909
|
+
}, {}, void>;
|
|
585
910
|
oauthGoogleStart: _spfn_core_route.RouteDef<{
|
|
586
911
|
query: _sinclair_typebox.TObject<{
|
|
587
912
|
state: _sinclair_typebox.TString;
|
|
@@ -597,7 +922,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
597
922
|
}, {}, Response>;
|
|
598
923
|
oauthStart: _spfn_core_route.RouteDef<{
|
|
599
924
|
body: _sinclair_typebox.TObject<{
|
|
600
|
-
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "github" | "kakao" | "naver">[]>;
|
|
925
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
601
926
|
returnUrl: _sinclair_typebox.TString;
|
|
602
927
|
publicKey: _sinclair_typebox.TString;
|
|
603
928
|
keyId: _sinclair_typebox.TString;
|
|
@@ -607,11 +932,12 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
607
932
|
}>;
|
|
608
933
|
}, {}, OAuthStartResult>;
|
|
609
934
|
oauthProviders: _spfn_core_route.RouteDef<{}, {}, {
|
|
610
|
-
providers: ("google" | "github" | "kakao" | "naver")[];
|
|
935
|
+
providers: ("google" | "apple" | "github" | "kakao" | "naver" | "superself")[];
|
|
611
936
|
}>;
|
|
612
937
|
getGoogleOAuthUrl: _spfn_core_route.RouteDef<{
|
|
613
938
|
body: _sinclair_typebox.TObject<{
|
|
614
939
|
returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
940
|
+
metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
|
|
615
941
|
state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
616
942
|
}>;
|
|
617
943
|
}, {}, {
|
|
@@ -629,6 +955,67 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
629
955
|
keyId: string;
|
|
630
956
|
returnUrl: string;
|
|
631
957
|
}>;
|
|
958
|
+
oauthProviderStart: _spfn_core_route.RouteDef<{
|
|
959
|
+
params: _sinclair_typebox.TObject<{
|
|
960
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
961
|
+
}>;
|
|
962
|
+
query: _sinclair_typebox.TObject<{
|
|
963
|
+
state: _sinclair_typebox.TString;
|
|
964
|
+
}>;
|
|
965
|
+
}, {}, Response>;
|
|
966
|
+
oauthProviderCallback: _spfn_core_route.RouteDef<{
|
|
967
|
+
params: _sinclair_typebox.TObject<{
|
|
968
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
969
|
+
}>;
|
|
970
|
+
query: _sinclair_typebox.TObject<{
|
|
971
|
+
code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
972
|
+
state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
973
|
+
error: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
974
|
+
error_description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
975
|
+
}>;
|
|
976
|
+
}, {}, Response>;
|
|
977
|
+
getProviderOAuthUrl: _spfn_core_route.RouteDef<{
|
|
978
|
+
params: _sinclair_typebox.TObject<{
|
|
979
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
980
|
+
}>;
|
|
981
|
+
body: _sinclair_typebox.TObject<{
|
|
982
|
+
returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
983
|
+
metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
|
|
984
|
+
state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
985
|
+
}>;
|
|
986
|
+
}, {}, {
|
|
987
|
+
authUrl: string;
|
|
988
|
+
}>;
|
|
989
|
+
oauthNative: _spfn_core_route.RouteDef<{
|
|
990
|
+
params: _sinclair_typebox.TObject<{
|
|
991
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
992
|
+
}>;
|
|
993
|
+
body: _sinclair_typebox.TObject<{
|
|
994
|
+
idToken: _sinclair_typebox.TString;
|
|
995
|
+
nonce: _sinclair_typebox.TString;
|
|
996
|
+
accessToken: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
997
|
+
publicKey: _sinclair_typebox.TString;
|
|
998
|
+
keyId: _sinclair_typebox.TString;
|
|
999
|
+
fingerprint: _sinclair_typebox.TString;
|
|
1000
|
+
algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
|
|
1001
|
+
deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
1002
|
+
platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
|
|
1003
|
+
profile: _sinclair_typebox.TOptional<_sinclair_typebox.TObject<{
|
|
1004
|
+
name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
1005
|
+
}>>;
|
|
1006
|
+
metadata: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TUnknown>>;
|
|
1007
|
+
}>;
|
|
1008
|
+
}, {}, OAuthNativeResult>;
|
|
1009
|
+
oauthUnlinkNotify: _spfn_core_route.RouteDef<{
|
|
1010
|
+
params: _sinclair_typebox.TObject<{
|
|
1011
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
1012
|
+
}>;
|
|
1013
|
+
}, {}, void | Response>;
|
|
1014
|
+
oauthUnlinkNotifyGet: _spfn_core_route.RouteDef<{
|
|
1015
|
+
params: _sinclair_typebox.TObject<{
|
|
1016
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "apple" | "github" | "kakao" | "naver" | "superself">[]>;
|
|
1017
|
+
}>;
|
|
1018
|
+
}, {}, void | Response>;
|
|
632
1019
|
getInvitation: _spfn_core_route.RouteDef<{
|
|
633
1020
|
params: _sinclair_typebox.TObject<{
|
|
634
1021
|
token: _sinclair_typebox.TString;
|
|
@@ -761,6 +1148,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
761
1148
|
username: _sinclair_typebox.TUnion<[_sinclair_typebox.TString, _sinclair_typebox.TNull]>;
|
|
762
1149
|
}>;
|
|
763
1150
|
}, {}, {
|
|
1151
|
+
deletedAt: Date | null;
|
|
1152
|
+
deletedBy: string | null;
|
|
764
1153
|
createdAt: Date;
|
|
765
1154
|
updatedAt: Date;
|
|
766
1155
|
id: number;
|
|
@@ -771,7 +1160,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
771
1160
|
passwordHash: string | null;
|
|
772
1161
|
passwordChangeRequired: boolean;
|
|
773
1162
|
roleId: number;
|
|
774
|
-
status: "active" | "inactive" | "suspended";
|
|
1163
|
+
status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
|
|
775
1164
|
emailVerifiedAt: Date | null;
|
|
776
1165
|
phoneVerifiedAt: Date | null;
|
|
777
1166
|
lastLoginAt: Date | null;
|
|
@@ -790,8 +1179,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
790
1179
|
}, {}, {
|
|
791
1180
|
roles: {
|
|
792
1181
|
description: string | null;
|
|
793
|
-
id: number;
|
|
794
1182
|
name: string;
|
|
1183
|
+
id: number;
|
|
795
1184
|
displayName: string;
|
|
796
1185
|
isBuiltin: boolean;
|
|
797
1186
|
isSystem: boolean;
|
|
@@ -812,8 +1201,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
812
1201
|
}, {}, {
|
|
813
1202
|
role: {
|
|
814
1203
|
description: string | null;
|
|
815
|
-
id: number;
|
|
816
1204
|
name: string;
|
|
1205
|
+
id: number;
|
|
817
1206
|
displayName: string;
|
|
818
1207
|
isBuiltin: boolean;
|
|
819
1208
|
isSystem: boolean;
|
|
@@ -836,8 +1225,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
836
1225
|
}, {}, {
|
|
837
1226
|
role: {
|
|
838
1227
|
description: string | null;
|
|
839
|
-
id: number;
|
|
840
1228
|
name: string;
|
|
1229
|
+
id: number;
|
|
841
1230
|
displayName: string;
|
|
842
1231
|
isBuiltin: boolean;
|
|
843
1232
|
isSystem: boolean;
|
|
@@ -865,13 +1254,87 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
865
1254
|
}>;
|
|
866
1255
|
}>;
|
|
867
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* The auth-profile registry the authenticate middleware dispatches on.
|
|
1259
|
+
*
|
|
1260
|
+
* A request that names `x-spfn-auth-profile` is answered by the verifier
|
|
1261
|
+
* registered for that profile — an O(1) map lookup, never a per-profile if
|
|
1262
|
+
* chain in the middleware body. A request that names no profile falls through
|
|
1263
|
+
* to the existing Bearer path untouched.
|
|
1264
|
+
*
|
|
1265
|
+
* Every verifier converges on the same `AuthContext` the Bearer path sets, so
|
|
1266
|
+
* downstream permission/tenant code consumes one principal shape and never
|
|
1267
|
+
* branches on how it was authenticated.
|
|
1268
|
+
*
|
|
1269
|
+
* The clientProofV1 verifier reuses the phase-1 admission pieces (header
|
|
1270
|
+
* shape, canonical body, proof-input assembly, ECDSA verification) with two
|
|
1271
|
+
* production substitutions: the key directory is `user_public_keys` via
|
|
1272
|
+
* `keysRepository`, and the replay ledger is the pluggable store from
|
|
1273
|
+
* `client-proof/replay-store` (memory default, Redis opt-in). The admission
|
|
1274
|
+
* order is the contract's — revocation → session → expiry → replay → proof —
|
|
1275
|
+
* and the non-disclosure rule holds: an unregistered keyId shares
|
|
1276
|
+
* PROOF_INVALID with a failed signature, while a revoked or expired key
|
|
1277
|
+
* answers SESSION_REVOKED before the proof is ever examined.
|
|
1278
|
+
*
|
|
1279
|
+
* @module server/middleware/auth-profiles
|
|
1280
|
+
*/
|
|
1281
|
+
|
|
1282
|
+
/** What a verified request leaves in the context — one shape for every scheme. */
|
|
868
1283
|
interface AuthContext {
|
|
869
1284
|
user: User;
|
|
870
1285
|
userId: string;
|
|
871
1286
|
keyId: string;
|
|
872
1287
|
role: string | null;
|
|
873
1288
|
locale: string;
|
|
1289
|
+
/** How the principal was authenticated. Informational — downstream code never branches on it. */
|
|
1290
|
+
scheme: 'bearer' | 'clientProofV1' | 'oneTimeToken';
|
|
874
1291
|
}
|
|
1292
|
+
/** A profile's verifier: admits the request and returns the principal, or throws. */
|
|
1293
|
+
interface AuthProfileVerifier {
|
|
1294
|
+
verify(c: Context): Promise<AuthContext>;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Routes a request to its profile verifier.
|
|
1298
|
+
*
|
|
1299
|
+
* - no profile header → null: the caller continues on the Bearer path;
|
|
1300
|
+
* - profile header + Authorization header → rejected (mixing prohibited);
|
|
1301
|
+
* - unknown profile value → rejected (unknownProfilePolicy: reject).
|
|
1302
|
+
*
|
|
1303
|
+
* Shared by authenticate and optionalAuth so "presented but invalid" refuses
|
|
1304
|
+
* identically on both — only the "presented nothing" outcome differs.
|
|
1305
|
+
*/
|
|
1306
|
+
declare function selectAuthProfile(c: Context): AuthProfileVerifier | null;
|
|
1307
|
+
/**
|
|
1308
|
+
* Loads the user for an authenticated key and applies the account-status
|
|
1309
|
+
* rules. One implementation for every scheme: the Bearer path and the profile
|
|
1310
|
+
* verifiers call this, so a status added here gates both identically.
|
|
1311
|
+
*/
|
|
1312
|
+
declare function resolveAuthenticatedUser(userId: number): Promise<{
|
|
1313
|
+
user: User;
|
|
1314
|
+
role: string | null;
|
|
1315
|
+
locale: string;
|
|
1316
|
+
}>;
|
|
1317
|
+
/** What the profile path produced for one request. */
|
|
1318
|
+
type AuthProfileOutcome = {
|
|
1319
|
+
kind: 'none';
|
|
1320
|
+
} | {
|
|
1321
|
+
kind: 'authenticated';
|
|
1322
|
+
auth: AuthContext;
|
|
1323
|
+
} | {
|
|
1324
|
+
kind: 'refused';
|
|
1325
|
+
response: Response;
|
|
1326
|
+
};
|
|
1327
|
+
/**
|
|
1328
|
+
* The profile path from dispatch to answer — what `authenticate` and
|
|
1329
|
+
* `optionalAuth` both run before their own Bearer code.
|
|
1330
|
+
*
|
|
1331
|
+
* `none` means the request named no profile and the caller continues on the
|
|
1332
|
+
* Bearer path. A refusal comes back as a built response rather than a throw:
|
|
1333
|
+
* the answer a proven call gets is the contract's own envelope, and an error
|
|
1334
|
+
* handed to the generic error handler is classified by its class name instead.
|
|
1335
|
+
*/
|
|
1336
|
+
declare function runAuthProfile(c: Context): Promise<AuthProfileOutcome>;
|
|
1337
|
+
|
|
875
1338
|
declare module 'hono' {
|
|
876
1339
|
interface ContextVariableMap {
|
|
877
1340
|
auth: AuthContext;
|
|
@@ -937,4 +1400,4 @@ declare const authenticate: _spfn_core_route.NamedMiddleware<"auth">;
|
|
|
937
1400
|
*/
|
|
938
1401
|
declare const optionalAuth: _spfn_core_route.NamedMiddleware<"optionalAuth">;
|
|
939
1402
|
|
|
940
|
-
export {
|
|
1403
|
+
export { type UnlinkNotifyResult as $, type AuthInitOptions as A, PasswordSchema as B, type ChangePasswordParams as C, DeviceNameSchema as D, EmailSchema as E, PhoneSchema as F, PlatformSchema as G, type RegisterParams as H, type IssueOneTimeTokenResult as I, type RegisterPublicKeyParams as J, type KeySummary as K, type LoginResult as L, type RevokeAllKeysParams as M, type NativeVerifyOptions as N, type OAuthStartResult as O, type PermissionConfig as P, type RevokeKeyParams as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RotateKeyParams as T, type UserProfile as U, VERIFICATION_PURPOSES as V, type SendVerificationCodeParams as W, TargetTypeSchema as X, type UnlinkNotification as Y, UnlinkNotifyRejection as Z, type UnlinkNotifyRequest as _, type RegisterResult as a, VerificationPurposeSchema as a0, type VerifyCodeParams as a1, type VerifyCodeResult as a2, authenticate as a3, buildOAuthErrorUrl as a4, changePasswordService as a5, getEnabledOAuthProviders as a6, getGoogleAccessToken as a7, getOAuthProvider as a8, getRegisteredProviders as a9, isOAuthProviderEnabled as aa, issueOneTimeTokenService as ab, listKeysService as ac, loginService as ad, logoutService as ae, oauthCallbackService as af, oauthNativeService as ag, oauthStartService as ah, oauthUnlinkNotifyService as ai, optionalAuth as aj, registerOAuthProvider as ak, registerPublicKeyService as al, registerService as am, requireEnabledProvider as an, resolveAuthenticatedUser as ao, revokeAllKeysService as ap, revokeKeyService as aq, rotateKeyService as ar, runAuthProfile as as, selectAuthProfile as at, sendVerificationCodeService as au, verifyCodeService as av, verifyOneTimeTokenService as aw, type RotateKeyResult as b, type RevokeAllKeysResult as c, type OAuthNativeResult as d, type ProfileInfo as e, type AuthSession as f, PERMISSION_CATEGORIES as g, type PermissionCategory as h, VERIFICATION_TARGET_TYPES as i, type VerificationPurpose as j, type VerificationTargetType as k, type OAuthProvider as l, mainAuthRouter as m, type AuthContext as n, type AuthProfileOutcome as o, type AuthProfileVerifier as p, KEY_FINGERPRINT_PREFIX_LENGTH as q, type LoginParams as r, type LogoutParams as s, type NormalizedIdentity as t, type OAuthCallbackParams as u, type OAuthCallbackResult as v, type OAuthCodeExchangeOptions as w, type OAuthNativeParams as x, type OAuthStartParams as y, type OAuthTokens as z };
|