@spfn/auth 0.2.0-beta.74 → 0.2.0-beta.80

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/dist/server.d.ts CHANGED
@@ -1,17 +1,18 @@
1
- import { j as AuthInitOptions, k as OAuthProvider, e as VerificationPurpose, i as PermissionCategory, l as AuthContext } from './authenticate-DXVtYh8X.js';
2
- export { C as ChangePasswordParams, a3 as EmailSchema, I as IssueOneTimeTokenResult, s as LoginParams, L as LoginResult, t as LogoutParams, aa as NativeVerifyOptions, a8 as NormalizedIdentity, Z as OAuthCallbackParams, _ as OAuthCallbackResult, a0 as OAuthNativeParams, c as OAuthNativeResult, Y as OAuthStartParams, O as OAuthStartResult, a9 as OAuthTokens, a5 as PasswordSchema, a4 as PhoneSchema, q as RegisterParams, E as RegisterPublicKeyParams, a as RegisterResult, G as RevokeKeyParams, F as RotateKeyParams, b as RotateKeyResult, w as SendVerificationCodeParams, S as SendVerificationCodeResult, a6 as TargetTypeSchema, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, a7 as VerificationPurposeSchema, V as VerificationTargetType, x as VerifyCodeParams, y as VerifyCodeResult, m as authRouter, a1 as authenticate, N as buildOAuthErrorUrl, p as changePasswordService, W as getEnabledOAuthProviders, X as getGoogleAccessToken, ac as getOAuthProvider, ad as getRegisteredProviders, Q as isOAuthProviderEnabled, H as issueOneTimeTokenService, n as loginService, o as logoutService, M as oauthCallbackService, $ as oauthNativeService, K as oauthStartService, a2 as optionalAuth, ab as registerOAuthProvider, z as registerPublicKeyService, r as registerService, T as requireEnabledProvider, D as revokeKeyService, B as rotateKeyService, u as sendVerificationCodeService, v as verifyCodeService, J as verifyOneTimeTokenService } from './authenticate-DXVtYh8X.js';
1
+ import { j as AuthInitOptions, k as OAuthProvider, e as VerificationPurpose, i as PermissionCategory, l as AuthContext } from './authenticate-ofdEmk6x.js';
2
+ export { C as ChangePasswordParams, a3 as EmailSchema, I as IssueOneTimeTokenResult, s as LoginParams, L as LoginResult, t as LogoutParams, aa as NativeVerifyOptions, a8 as NormalizedIdentity, Z as OAuthCallbackParams, _ as OAuthCallbackResult, ab as OAuthCodeExchangeOptions, a0 as OAuthNativeParams, c as OAuthNativeResult, Y as OAuthStartParams, O as OAuthStartResult, a9 as OAuthTokens, a5 as PasswordSchema, a4 as PhoneSchema, q as RegisterParams, E as RegisterPublicKeyParams, a as RegisterResult, G as RevokeKeyParams, F as RotateKeyParams, b as RotateKeyResult, w as SendVerificationCodeParams, S as SendVerificationCodeResult, a6 as TargetTypeSchema, g as VERIFICATION_PURPOSES, f as VERIFICATION_TARGET_TYPES, a7 as VerificationPurposeSchema, V as VerificationTargetType, x as VerifyCodeParams, y as VerifyCodeResult, m as authRouter, a1 as authenticate, N as buildOAuthErrorUrl, p as changePasswordService, W as getEnabledOAuthProviders, X as getGoogleAccessToken, ad as getOAuthProvider, ae as getRegisteredProviders, Q as isOAuthProviderEnabled, H as issueOneTimeTokenService, n as loginService, o as logoutService, M as oauthCallbackService, $ as oauthNativeService, K as oauthStartService, a2 as optionalAuth, ac as registerOAuthProvider, z as registerPublicKeyService, r as registerService, T as requireEnabledProvider, D as revokeKeyService, B as rotateKeyService, u as sendVerificationCodeService, v as verifyCodeService, J as verifyOneTimeTokenService } from './authenticate-ofdEmk6x.js';
3
3
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
4
- import { K as KeyAlgorithmType, b as InvitationStatus, d as SocialProvider } from './types-BtksCI9X.js';
5
- export { I as INVITATION_STATUSES, a as KEY_ALGORITHM, S as SOCIAL_PROVIDERS, U as USER_STATUSES, c as UserStatus } from './types-BtksCI9X.js';
4
+ import { K as KeyAlgorithmType, c as InvitationStatus, e as SocialProvider, g as AccountDeletionRequestedBy, h as PurgeStrategy } from './types-1BMx0OX1.js';
5
+ export { b as ACCOUNT_DELETION_REQUESTED_BY, A as ACCOUNT_DELETION_REQUEST_STATUSES, f as AccountDeletionRequestStatus, I as INVITATION_STATUSES, a as KEY_ALGORITHM, P as PURGE_STRATEGIES, S as SOCIAL_PROVIDERS, U as USER_STATUSES, d as UserStatus } from './types-1BMx0OX1.js';
6
6
  import { UserProfile as UserProfile$1, ProfileInfo } from '@spfn/auth';
7
7
  import { BaseRepository } from '@spfn/core/db';
8
8
  import { Context } from 'hono';
9
9
  import * as _spfn_core_route from '@spfn/core/route';
10
10
  import { Algorithm } from 'jsonwebtoken';
11
- export { S as SessionData, g as getSessionInfo, s as sealSession, a as shouldRefreshSession, u as unsealSession } from './session-s_hiXmXC.js';
11
+ export { S as SessionData, g as getSessionInfo, s as sealSession, a as shouldRefreshSession, u as unsealSession } from './session-CGxgH3C9.js';
12
12
  import { JWTPayload } from 'jose';
13
13
  import { SSETokenStore, SSETokenManager } from '@spfn/core/event/sse';
14
14
  import * as _spfn_core_logger from '@spfn/core/logger';
15
+ import * as _spfn_core_job from '@spfn/core/job';
15
16
  import * as _spfn_core_event from '@spfn/core/event';
16
17
  import * as _sinclair_typebox from '@sinclair/typebox';
17
18
  import '@spfn/auth/server';
@@ -33,6 +34,40 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
33
34
  name: "users";
34
35
  schema: string;
35
36
  columns: {
37
+ deletedAt: drizzle_orm_pg_core.PgColumn<{
38
+ name: "deleted_at";
39
+ tableName: "users";
40
+ dataType: "date";
41
+ columnType: "PgTimestamp";
42
+ data: Date;
43
+ driverParam: string;
44
+ notNull: false;
45
+ hasDefault: false;
46
+ isPrimaryKey: false;
47
+ isAutoincrement: false;
48
+ hasRuntimeDefault: false;
49
+ enumValues: undefined;
50
+ baseColumn: never;
51
+ identity: undefined;
52
+ generated: undefined;
53
+ }, {}, {}>;
54
+ deletedBy: drizzle_orm_pg_core.PgColumn<{
55
+ name: "deleted_by";
56
+ tableName: "users";
57
+ dataType: "string";
58
+ columnType: "PgText";
59
+ data: string;
60
+ driverParam: string;
61
+ notNull: false;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: [string, ...string[]];
67
+ baseColumn: never;
68
+ identity: undefined;
69
+ generated: undefined;
70
+ }, {}, {}>;
36
71
  createdAt: drizzle_orm_pg_core.PgColumn<{
37
72
  name: "created_at";
38
73
  tableName: "users";
@@ -208,14 +243,14 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
208
243
  tableName: "users";
209
244
  dataType: "string";
210
245
  columnType: "PgText";
211
- data: "active" | "inactive" | "suspended";
246
+ data: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
212
247
  driverParam: string;
213
248
  notNull: true;
214
249
  hasDefault: true;
215
250
  isPrimaryKey: false;
216
251
  isAutoincrement: false;
217
252
  hasRuntimeDefault: false;
218
- enumValues: ["active", "inactive", "suspended"] & [string, ...string[]];
253
+ enumValues: ["active", "inactive", "suspended", "pending_deletion", "deleted"] & [string, ...string[]];
219
254
  baseColumn: never;
220
255
  identity: undefined;
221
256
  generated: undefined;
@@ -287,6 +322,8 @@ type NewUser = typeof users.$inferInsert;
287
322
  * Get user by ID
288
323
  */
289
324
  declare function getUserByIdService(userId: number): Promise<{
325
+ deletedAt: Date | null;
326
+ deletedBy: string | null;
290
327
  createdAt: Date;
291
328
  updatedAt: Date;
292
329
  id: number;
@@ -297,7 +334,7 @@ declare function getUserByIdService(userId: number): Promise<{
297
334
  passwordHash: string | null;
298
335
  passwordChangeRequired: boolean;
299
336
  roleId: number;
300
- status: "active" | "inactive" | "suspended";
337
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
301
338
  emailVerifiedAt: Date | null;
302
339
  phoneVerifiedAt: Date | null;
303
340
  lastLoginAt: Date | null;
@@ -306,6 +343,8 @@ declare function getUserByIdService(userId: number): Promise<{
306
343
  * Get user by email
307
344
  */
308
345
  declare function getUserByEmailService(email: string): Promise<{
346
+ deletedAt: Date | null;
347
+ deletedBy: string | null;
309
348
  createdAt: Date;
310
349
  updatedAt: Date;
311
350
  id: number;
@@ -316,7 +355,7 @@ declare function getUserByEmailService(email: string): Promise<{
316
355
  passwordHash: string | null;
317
356
  passwordChangeRequired: boolean;
318
357
  roleId: number;
319
- status: "active" | "inactive" | "suspended";
358
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
320
359
  emailVerifiedAt: Date | null;
321
360
  phoneVerifiedAt: Date | null;
322
361
  lastLoginAt: Date | null;
@@ -325,6 +364,8 @@ declare function getUserByEmailService(email: string): Promise<{
325
364
  * Get user by phone
326
365
  */
327
366
  declare function getUserByPhoneService(phone: string): Promise<{
367
+ deletedAt: Date | null;
368
+ deletedBy: string | null;
328
369
  createdAt: Date;
329
370
  updatedAt: Date;
330
371
  id: number;
@@ -335,7 +376,7 @@ declare function getUserByPhoneService(phone: string): Promise<{
335
376
  passwordHash: string | null;
336
377
  passwordChangeRequired: boolean;
337
378
  roleId: number;
338
- status: "active" | "inactive" | "suspended";
379
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
339
380
  emailVerifiedAt: Date | null;
340
381
  phoneVerifiedAt: Date | null;
341
382
  lastLoginAt: Date | null;
@@ -363,6 +404,8 @@ declare function checkUsernameAvailableService(username: string): Promise<boolea
363
404
  * @throws UsernameAlreadyTakenError if username is already in use by another user
364
405
  */
365
406
  declare function updateUsernameService(userId: string | number | bigint, username: string | null): Promise<{
407
+ deletedAt: Date | null;
408
+ deletedBy: string | null;
366
409
  createdAt: Date;
367
410
  updatedAt: Date;
368
411
  id: number;
@@ -373,7 +416,7 @@ declare function updateUsernameService(userId: string | number | bigint, usernam
373
416
  passwordHash: string | null;
374
417
  passwordChangeRequired: boolean;
375
418
  roleId: number;
376
- status: "active" | "inactive" | "suspended";
419
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
377
420
  emailVerifiedAt: Date | null;
378
421
  phoneVerifiedAt: Date | null;
379
422
  lastLoginAt: Date | null;
@@ -1581,6 +1624,45 @@ interface VerifyIdTokenParams {
1581
1624
  */
1582
1625
  declare function verifyIdToken(params: VerifyIdTokenParams): Promise<JWTPayload>;
1583
1626
 
1627
+ /**
1628
+ * @spfn/auth - Social Token At-Rest Encryption
1629
+ *
1630
+ * OAuth access/refresh tokens are encrypted with a backend-only rotating keyring.
1631
+ * The active key is the first entry in SPFN_AUTH_TOKEN_ENCRYPTION_KEYS; remaining
1632
+ * keys are decrypt-only grace keys. Ciphertext is bound to its provider account
1633
+ * and token type with AES-GCM additional authenticated data (AAD).
1634
+ *
1635
+ * v2 format: enc:v2:<keyId>:<base64url(iv | authTag | ciphertext)>
1636
+ * v1 format: enc:v1:<base64(iv | authTag | ciphertext)> (legacy session-secret key)
1637
+ *
1638
+ * Never include token or key material in logs or error messages.
1639
+ */
1640
+
1641
+ type OAuthTokenType = 'access' | 'refresh';
1642
+ interface OAuthTokenContext {
1643
+ provider: SocialProvider;
1644
+ providerUserId: string;
1645
+ tokenType: OAuthTokenType;
1646
+ }
1647
+ interface DecryptedOAuthToken {
1648
+ value: string;
1649
+ /** True for plaintext, v1, or ciphertext encrypted with a non-active v2 key. */
1650
+ needsRotation: boolean;
1651
+ }
1652
+ interface OAuthTokenCipher {
1653
+ encrypt(value: string, context: OAuthTokenContext): Promise<string>;
1654
+ decrypt(value: string, context: OAuthTokenContext): Promise<DecryptedOAuthToken>;
1655
+ }
1656
+ declare class EnvironmentKeyringTokenCipher implements OAuthTokenCipher {
1657
+ encrypt(value: string, context: OAuthTokenContext): Promise<string>;
1658
+ decrypt(value: string, context: OAuthTokenContext): Promise<DecryptedOAuthToken>;
1659
+ }
1660
+ /** Configure an external/KMS-backed cipher before the auth server starts. */
1661
+ declare function configureOAuthTokenCipher(cipher: OAuthTokenCipher): void;
1662
+ declare function encryptToken(value: string, context: OAuthTokenContext): Promise<string>;
1663
+ declare function decryptToken(value: string, context: OAuthTokenContext): Promise<DecryptedOAuthToken>;
1664
+ declare function isEncrypted(value: string): boolean;
1665
+
1584
1666
  /**
1585
1667
  * Google OAuthProvider 구현
1586
1668
  *
@@ -1593,18 +1675,362 @@ declare function verifyIdToken(params: VerifyIdTokenParams): Promise<JWTPayload>
1593
1675
  declare const googleProvider: OAuthProvider;
1594
1676
 
1595
1677
  /**
1596
- * Apple OAuthProvider 구현 (native id_token 검증 전용)
1678
+ * Apple OAuthProvider 구현 (native id_token 검증 전용)
1679
+ *
1680
+ * Apple은 Android·웹에 네이티브 SDK를 제공하지 않으므로, web 흐름(Sign in with Apple JS,
1681
+ * Android Custom Tab)도 결국 id_token을 클라이언트가 받아 서버로 보낸다. 서버는 그 id_token을
1682
+ * Apple JWKS로 검증만 한다(authorization code 교환·client secret 없음).
1683
+ *
1684
+ * 따라서 web 메서드(getAuthUrl/exchangeCodeForTokens/getUserInfo)는 지원하지 않는다.
1685
+ *
1686
+ * 이 모듈을 import 하는 것만으로 apple provider가 registry에 자기 등록된다.
1687
+ */
1688
+
1689
+ declare const appleProvider: OAuthProvider;
1690
+
1691
+ /**
1692
+ * Kakao Login OAuthProvider (web authorization-code flow).
1693
+ */
1694
+
1695
+ declare const kakaoProvider: OAuthProvider;
1696
+
1697
+ /**
1698
+ * Naver Login OAuthProvider (web authorization-code flow).
1699
+ */
1700
+
1701
+ declare const naverProvider: OAuthProvider;
1702
+
1703
+ /**
1704
+ * @spfn/auth - Account Deletion Requests Entity
1705
+ *
1706
+ * Audit trail for account deletion/recovery. Rows are never deleted — even after
1707
+ * a hard-delete purge — so "who requested/recovered/purged what, when" survives
1708
+ * the user row itself (개인정보보호법 제21조 3항 분리 보존 원칙).
1709
+ */
1710
+ declare const accountDeletionRequests: drizzle_orm_pg_core.PgTableWithColumns<{
1711
+ name: "account_deletion_requests";
1712
+ schema: string;
1713
+ columns: {
1714
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1715
+ name: "created_at";
1716
+ tableName: "account_deletion_requests";
1717
+ dataType: "date";
1718
+ columnType: "PgTimestamp";
1719
+ data: Date;
1720
+ driverParam: string;
1721
+ notNull: true;
1722
+ hasDefault: true;
1723
+ isPrimaryKey: false;
1724
+ isAutoincrement: false;
1725
+ hasRuntimeDefault: false;
1726
+ enumValues: undefined;
1727
+ baseColumn: never;
1728
+ identity: undefined;
1729
+ generated: undefined;
1730
+ }, {}, {}>;
1731
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
1732
+ name: "updated_at";
1733
+ tableName: "account_deletion_requests";
1734
+ dataType: "date";
1735
+ columnType: "PgTimestamp";
1736
+ data: Date;
1737
+ driverParam: string;
1738
+ notNull: true;
1739
+ hasDefault: true;
1740
+ isPrimaryKey: false;
1741
+ isAutoincrement: false;
1742
+ hasRuntimeDefault: false;
1743
+ enumValues: undefined;
1744
+ baseColumn: never;
1745
+ identity: undefined;
1746
+ generated: undefined;
1747
+ }, {}, {}>;
1748
+ id: drizzle_orm_pg_core.PgColumn<{
1749
+ name: "id";
1750
+ tableName: "account_deletion_requests";
1751
+ dataType: "number";
1752
+ columnType: "PgBigSerial53";
1753
+ data: number;
1754
+ driverParam: number;
1755
+ notNull: true;
1756
+ hasDefault: true;
1757
+ isPrimaryKey: true;
1758
+ isAutoincrement: false;
1759
+ hasRuntimeDefault: false;
1760
+ enumValues: undefined;
1761
+ baseColumn: never;
1762
+ identity: undefined;
1763
+ generated: undefined;
1764
+ }, {}, {}>;
1765
+ userId: drizzle_orm_pg_core.PgColumn<{
1766
+ name: `${string}_id`;
1767
+ tableName: "account_deletion_requests";
1768
+ dataType: "number";
1769
+ columnType: "PgBigInt53";
1770
+ data: number;
1771
+ driverParam: string | number;
1772
+ notNull: false;
1773
+ hasDefault: false;
1774
+ isPrimaryKey: false;
1775
+ isAutoincrement: false;
1776
+ hasRuntimeDefault: false;
1777
+ enumValues: undefined;
1778
+ baseColumn: never;
1779
+ identity: undefined;
1780
+ generated: undefined;
1781
+ }, {}, {}>;
1782
+ userPublicId: drizzle_orm_pg_core.PgColumn<{
1783
+ name: "user_public_id";
1784
+ tableName: "account_deletion_requests";
1785
+ dataType: "string";
1786
+ columnType: "PgText";
1787
+ data: string;
1788
+ driverParam: string;
1789
+ notNull: true;
1790
+ hasDefault: false;
1791
+ isPrimaryKey: false;
1792
+ isAutoincrement: false;
1793
+ hasRuntimeDefault: false;
1794
+ enumValues: [string, ...string[]];
1795
+ baseColumn: never;
1796
+ identity: undefined;
1797
+ generated: undefined;
1798
+ }, {}, {}>;
1799
+ requestedAt: drizzle_orm_pg_core.PgColumn<{
1800
+ name: string;
1801
+ tableName: "account_deletion_requests";
1802
+ dataType: "date";
1803
+ columnType: "PgTimestamp";
1804
+ data: Date;
1805
+ driverParam: string;
1806
+ notNull: true;
1807
+ hasDefault: true;
1808
+ isPrimaryKey: false;
1809
+ isAutoincrement: false;
1810
+ hasRuntimeDefault: false;
1811
+ enumValues: undefined;
1812
+ baseColumn: never;
1813
+ identity: undefined;
1814
+ generated: undefined;
1815
+ }, {}, {}>;
1816
+ purgeScheduledAt: drizzle_orm_pg_core.PgColumn<{
1817
+ name: string;
1818
+ tableName: "account_deletion_requests";
1819
+ dataType: "date";
1820
+ columnType: "PgTimestamp";
1821
+ data: Date;
1822
+ driverParam: string;
1823
+ notNull: true;
1824
+ hasDefault: false;
1825
+ isPrimaryKey: false;
1826
+ isAutoincrement: false;
1827
+ hasRuntimeDefault: false;
1828
+ enumValues: undefined;
1829
+ baseColumn: never;
1830
+ identity: undefined;
1831
+ generated: undefined;
1832
+ }, {}, {}>;
1833
+ status: drizzle_orm_pg_core.PgColumn<{
1834
+ name: string;
1835
+ tableName: "account_deletion_requests";
1836
+ dataType: "string";
1837
+ columnType: "PgText";
1838
+ data: "pending" | "cancelled" | "completed";
1839
+ driverParam: string;
1840
+ notNull: true;
1841
+ hasDefault: true;
1842
+ isPrimaryKey: false;
1843
+ isAutoincrement: false;
1844
+ hasRuntimeDefault: false;
1845
+ enumValues: ["pending", "cancelled", "completed"] & [string, ...string[]];
1846
+ baseColumn: never;
1847
+ identity: undefined;
1848
+ generated: undefined;
1849
+ }, {}, {}>;
1850
+ requestedBy: drizzle_orm_pg_core.PgColumn<{
1851
+ name: string;
1852
+ tableName: "account_deletion_requests";
1853
+ dataType: "string";
1854
+ columnType: "PgText";
1855
+ data: "admin" | "self";
1856
+ driverParam: string;
1857
+ notNull: true;
1858
+ hasDefault: true;
1859
+ isPrimaryKey: false;
1860
+ isAutoincrement: false;
1861
+ hasRuntimeDefault: false;
1862
+ enumValues: ["self", "admin"] & [string, ...string[]];
1863
+ baseColumn: never;
1864
+ identity: undefined;
1865
+ generated: undefined;
1866
+ }, {}, {}>;
1867
+ reason: drizzle_orm_pg_core.PgColumn<{
1868
+ name: "reason";
1869
+ tableName: "account_deletion_requests";
1870
+ dataType: "string";
1871
+ columnType: "PgText";
1872
+ data: string;
1873
+ driverParam: string;
1874
+ notNull: false;
1875
+ hasDefault: false;
1876
+ isPrimaryKey: false;
1877
+ isAutoincrement: false;
1878
+ hasRuntimeDefault: false;
1879
+ enumValues: [string, ...string[]];
1880
+ baseColumn: never;
1881
+ identity: undefined;
1882
+ generated: undefined;
1883
+ }, {}, {}>;
1884
+ cancelledAt: drizzle_orm_pg_core.PgColumn<{
1885
+ name: string;
1886
+ tableName: "account_deletion_requests";
1887
+ dataType: "date";
1888
+ columnType: "PgTimestamp";
1889
+ data: Date;
1890
+ driverParam: string;
1891
+ notNull: false;
1892
+ hasDefault: false;
1893
+ isPrimaryKey: false;
1894
+ isAutoincrement: false;
1895
+ hasRuntimeDefault: false;
1896
+ enumValues: undefined;
1897
+ baseColumn: never;
1898
+ identity: undefined;
1899
+ generated: undefined;
1900
+ }, {}, {}>;
1901
+ completedAt: drizzle_orm_pg_core.PgColumn<{
1902
+ name: string;
1903
+ tableName: "account_deletion_requests";
1904
+ dataType: "date";
1905
+ columnType: "PgTimestamp";
1906
+ data: Date;
1907
+ driverParam: string;
1908
+ notNull: false;
1909
+ hasDefault: false;
1910
+ isPrimaryKey: false;
1911
+ isAutoincrement: false;
1912
+ hasRuntimeDefault: false;
1913
+ enumValues: undefined;
1914
+ baseColumn: never;
1915
+ identity: undefined;
1916
+ generated: undefined;
1917
+ }, {}, {}>;
1918
+ purgeStrategy: drizzle_orm_pg_core.PgColumn<{
1919
+ name: string;
1920
+ tableName: "account_deletion_requests";
1921
+ dataType: "string";
1922
+ columnType: "PgText";
1923
+ data: "anonymize" | "hard-delete";
1924
+ driverParam: string;
1925
+ notNull: false;
1926
+ hasDefault: false;
1927
+ isPrimaryKey: false;
1928
+ isAutoincrement: false;
1929
+ hasRuntimeDefault: false;
1930
+ enumValues: ["anonymize", "hard-delete"] & [string, ...string[]];
1931
+ baseColumn: never;
1932
+ identity: undefined;
1933
+ generated: undefined;
1934
+ }, {}, {}>;
1935
+ };
1936
+ dialect: "pg";
1937
+ }>;
1938
+ type AccountDeletionRequest = typeof accountDeletionRequests.$inferSelect;
1939
+ type NewAccountDeletionRequest = typeof accountDeletionRequests.$inferInsert;
1940
+
1941
+ /**
1942
+ * @spfn/auth - Account Deletion Service
1943
+ *
1944
+ * Request / cancel (recovery) / purge for the account deletion lifecycle.
1945
+ *
1946
+ * Lifecycle:
1947
+ * active --request(reauth)--> pending_deletion --grace expires(cron)--> deleted (anonymize) | row removed (hard-delete)
1948
+ * ^ |
1949
+ * +-------cancel(reauth)---------+ immediate = grace period of 0, same pipeline
1950
+ */
1951
+
1952
+ interface PendingDeletionInfo {
1953
+ purgeScheduledAt: Date;
1954
+ }
1955
+ /**
1956
+ * Look up the pending deletion request's `purgeScheduledAt` for a `pending_deletion`
1957
+ * user, so login/OAuth/authenticate can surface it on `AccountPendingDeletionError`.
1958
+ * Returns null if there is no pending row (shouldn't normally happen for a user in
1959
+ * that status, but the caller falls back to an error without the date).
1960
+ *
1961
+ * Reads from the write primary, not a replica — this backs the same status gates
1962
+ * that decide whether to grant a session, so a replica-lag window must not let a
1963
+ * just-requested deletion go unnoticed.
1964
+ */
1965
+ declare function getPendingDeletionInfo(userId: number): Promise<PendingDeletionInfo | null>;
1966
+ interface RequestAccountDeletionParams {
1967
+ /** Who initiated this — 'self' requires re-auth via password/verificationToken; 'admin' does not. */
1968
+ requestedBy: AccountDeletionRequestedBy;
1969
+ password?: string;
1970
+ verificationToken?: string;
1971
+ reason?: string;
1972
+ /** Skip the grace period and purge inline. Self-service requires `deletion.allowSelfImmediate`. */
1973
+ immediate?: boolean;
1974
+ }
1975
+ interface RequestAccountDeletionResult {
1976
+ requestId: number;
1977
+ purgeScheduledAt: Date;
1978
+ }
1979
+ /**
1980
+ * Request account deletion (self-service re-auth gate, or trusted admin/DSR call).
1981
+ *
1982
+ * Sets status -> 'pending_deletion', records the request, revokes every active
1983
+ * session key, and (after commit) emits `auth.deletion.requested` and sends the
1984
+ * request-received notice. With a zero grace period (explicit `immediate: true`,
1985
+ * gated for self-service by `allowSelfImmediate`), purges inline instead of
1986
+ * waiting for the cron sweep.
1987
+ */
1988
+ declare function requestAccountDeletionService(userId: number, params: RequestAccountDeletionParams): Promise<RequestAccountDeletionResult>;
1989
+ interface CancelAccountDeletionParams {
1990
+ email?: string;
1991
+ phone?: string;
1992
+ password?: string;
1993
+ verificationToken?: string;
1994
+ }
1995
+ interface CancelAccountDeletionResult {
1996
+ userId: string;
1997
+ }
1998
+ /**
1999
+ * Cancel a pending deletion (recovery). All sessions were revoked at request
2000
+ * time, so this is credential-based (no Bearer token) rather than auth-context-based.
1597
2001
  *
1598
- * Apple은 Android·웹에 네이티브 SDK를 제공하지 않으므로, web 흐름(Sign in with Apple JS,
1599
- * Android Custom Tab)도 결국 id_token을 클라이언트가 받아 서버로 보낸다. 서버는 id_token을
1600
- * Apple JWKS로 검증만 한다(authorization code 교환·client secret 없음).
2002
+ * Credential verification always runs before the caller learns whether the
2003
+ * account exists / is pending deletion otherwise this endpoint could be used
2004
+ * to enumerate accounts or probe deletion status without a valid credential,
2005
+ * the same posture `loginService` takes for missing accounts.
1601
2006
  *
1602
- * 따라서 web 메서드(getAuthUrl/exchangeCodeForTokens/getUserInfo)는 지원하지 않는다.
2007
+ * Claims the `account_deletion_requests` row (conditional `WHERE status =
2008
+ * 'pending'`) before touching `users` — see the comment at that claim below for
2009
+ * why the `users` row must not move until the claim succeeds.
2010
+ */
2011
+ declare function cancelAccountDeletionService(params: CancelAccountDeletionParams): Promise<CancelAccountDeletionResult>;
2012
+ interface PurgeUserResult {
2013
+ outcome: 'purged' | 'skipped' | 'not-found';
2014
+ }
2015
+ /**
2016
+ * Purge a user's pending deletion request immediately, ignoring `purgeScheduledAt`.
2017
+ * Admin/GDPR-response entry point — the route (if any) belongs to the app.
1603
2018
  *
1604
- * 모듈을 import 하는 것만으로 apple provider가 registry에 자기 등록된다.
2019
+ * @throws DeletionNotRequestedError if the user has no pending request
1605
2020
  */
1606
-
1607
- declare const appleProvider: OAuthProvider;
2021
+ declare function purgeUserService(userId: number): Promise<PurgeUserResult>;
2022
+ interface SweepDuePurgesResult {
2023
+ processed: number;
2024
+ purged: number;
2025
+ skipped: number;
2026
+ }
2027
+ /**
2028
+ * Sweep every request whose grace period has elapsed and purge it. Called by the
2029
+ * `auth.deletion.purge` cron job. One user's failure (thrown error, not just an
2030
+ * `onBeforePurge` skip) is logged and does not stop the rest of the sweep — it
2031
+ * stays `pending` and is retried on the next tick.
2032
+ */
2033
+ declare function sweepDuePurges(now?: Date): Promise<SweepDuePurgesResult>;
1608
2034
 
1609
2035
  /**
1610
2036
  * @spfn/auth - Database Schema Definition
@@ -2473,14 +2899,14 @@ declare const verificationCodes: drizzle_orm_pg_core.PgTableWithColumns<{
2473
2899
  tableName: "verification_codes";
2474
2900
  dataType: "string";
2475
2901
  columnType: "PgText";
2476
- data: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
2902
+ data: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
2477
2903
  driverParam: string;
2478
2904
  notNull: true;
2479
2905
  hasDefault: false;
2480
2906
  isPrimaryKey: false;
2481
2907
  isAutoincrement: false;
2482
2908
  hasRuntimeDefault: false;
2483
- enumValues: ["registration", "login", "password_reset", "email_change", "phone_change"] & [string, ...string[]];
2909
+ enumValues: ["registration", "login", "password_reset", "email_change", "phone_change", "account_deletion"] & [string, ...string[]];
2484
2910
  baseColumn: never;
2485
2911
  identity: undefined;
2486
2912
  generated: undefined;
@@ -3106,6 +3532,33 @@ declare class UsersRepository extends BaseRepository {
3106
3532
  * Read replica 사용
3107
3533
  */
3108
3534
  findById(id: number): Promise<{
3535
+ deletedAt: Date | null;
3536
+ deletedBy: string | null;
3537
+ createdAt: Date;
3538
+ updatedAt: Date;
3539
+ id: number;
3540
+ publicId: string;
3541
+ email: string | null;
3542
+ phone: string | null;
3543
+ username: string | null;
3544
+ passwordHash: string | null;
3545
+ passwordChangeRequired: boolean;
3546
+ roleId: number;
3547
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3548
+ emailVerifiedAt: Date | null;
3549
+ phoneVerifiedAt: Date | null;
3550
+ lastLoginAt: Date | null;
3551
+ }>;
3552
+ /**
3553
+ * ID로 사용자 조회 — Write primary에서 직접 읽는다.
3554
+ *
3555
+ * 복제 지연 창에서 status 전이(예: 삭제 요청으로 pending_deletion 전환)를 놓치면
3556
+ * 안 되는 게이트(OAuth 세션 발급 등)가 사용한다. 일반 조회는 `findById`(replica)를
3557
+ * 계속 사용할 것.
3558
+ */
3559
+ findByIdOnPrimary(id: number): Promise<{
3560
+ deletedAt: Date | null;
3561
+ deletedBy: string | null;
3109
3562
  createdAt: Date;
3110
3563
  updatedAt: Date;
3111
3564
  id: number;
@@ -3116,7 +3569,7 @@ declare class UsersRepository extends BaseRepository {
3116
3569
  passwordHash: string | null;
3117
3570
  passwordChangeRequired: boolean;
3118
3571
  roleId: number;
3119
- status: "active" | "inactive" | "suspended";
3572
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3120
3573
  emailVerifiedAt: Date | null;
3121
3574
  phoneVerifiedAt: Date | null;
3122
3575
  lastLoginAt: Date | null;
@@ -3126,6 +3579,8 @@ declare class UsersRepository extends BaseRepository {
3126
3579
  * Read replica 사용
3127
3580
  */
3128
3581
  findByEmail(email: string): Promise<{
3582
+ deletedAt: Date | null;
3583
+ deletedBy: string | null;
3129
3584
  createdAt: Date;
3130
3585
  updatedAt: Date;
3131
3586
  id: number;
@@ -3136,7 +3591,7 @@ declare class UsersRepository extends BaseRepository {
3136
3591
  passwordHash: string | null;
3137
3592
  passwordChangeRequired: boolean;
3138
3593
  roleId: number;
3139
- status: "active" | "inactive" | "suspended";
3594
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3140
3595
  emailVerifiedAt: Date | null;
3141
3596
  phoneVerifiedAt: Date | null;
3142
3597
  lastLoginAt: Date | null;
@@ -3146,6 +3601,8 @@ declare class UsersRepository extends BaseRepository {
3146
3601
  * Read replica 사용
3147
3602
  */
3148
3603
  findByPhone(phone: string): Promise<{
3604
+ deletedAt: Date | null;
3605
+ deletedBy: string | null;
3149
3606
  createdAt: Date;
3150
3607
  updatedAt: Date;
3151
3608
  id: number;
@@ -3156,7 +3613,7 @@ declare class UsersRepository extends BaseRepository {
3156
3613
  passwordHash: string | null;
3157
3614
  passwordChangeRequired: boolean;
3158
3615
  roleId: number;
3159
- status: "active" | "inactive" | "suspended";
3616
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3160
3617
  emailVerifiedAt: Date | null;
3161
3618
  phoneVerifiedAt: Date | null;
3162
3619
  lastLoginAt: Date | null;
@@ -3166,6 +3623,8 @@ declare class UsersRepository extends BaseRepository {
3166
3623
  * Read replica 사용
3167
3624
  */
3168
3625
  findByUsername(username: string): Promise<{
3626
+ deletedAt: Date | null;
3627
+ deletedBy: string | null;
3169
3628
  createdAt: Date;
3170
3629
  updatedAt: Date;
3171
3630
  id: number;
@@ -3176,7 +3635,7 @@ declare class UsersRepository extends BaseRepository {
3176
3635
  passwordHash: string | null;
3177
3636
  passwordChangeRequired: boolean;
3178
3637
  roleId: number;
3179
- status: "active" | "inactive" | "suspended";
3638
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3180
3639
  emailVerifiedAt: Date | null;
3181
3640
  phoneVerifiedAt: Date | null;
3182
3641
  lastLoginAt: Date | null;
@@ -3186,6 +3645,8 @@ declare class UsersRepository extends BaseRepository {
3186
3645
  * Read replica 사용
3187
3646
  */
3188
3647
  findByPublicId(publicId: string): Promise<{
3648
+ deletedAt: Date | null;
3649
+ deletedBy: string | null;
3189
3650
  createdAt: Date;
3190
3651
  updatedAt: Date;
3191
3652
  id: number;
@@ -3196,7 +3657,7 @@ declare class UsersRepository extends BaseRepository {
3196
3657
  passwordHash: string | null;
3197
3658
  passwordChangeRequired: boolean;
3198
3659
  roleId: number;
3199
- status: "active" | "inactive" | "suspended";
3660
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3200
3661
  emailVerifiedAt: Date | null;
3201
3662
  phoneVerifiedAt: Date | null;
3202
3663
  lastLoginAt: Date | null;
@@ -3206,6 +3667,8 @@ declare class UsersRepository extends BaseRepository {
3206
3667
  * Read replica 사용
3207
3668
  */
3208
3669
  findByEmailOrPhone(email?: string, phone?: string): Promise<{
3670
+ deletedAt: Date | null;
3671
+ deletedBy: string | null;
3209
3672
  createdAt: Date;
3210
3673
  updatedAt: Date;
3211
3674
  id: number;
@@ -3216,7 +3679,7 @@ declare class UsersRepository extends BaseRepository {
3216
3679
  passwordHash: string | null;
3217
3680
  passwordChangeRequired: boolean;
3218
3681
  roleId: number;
3219
- status: "active" | "inactive" | "suspended";
3682
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3220
3683
  emailVerifiedAt: Date | null;
3221
3684
  phoneVerifiedAt: Date | null;
3222
3685
  lastLoginAt: Date | null;
@@ -3229,6 +3692,8 @@ declare class UsersRepository extends BaseRepository {
3229
3692
  */
3230
3693
  findByIdWithRole(id: number): Promise<{
3231
3694
  user: {
3695
+ deletedAt: Date | null;
3696
+ deletedBy: string | null;
3232
3697
  createdAt: Date;
3233
3698
  updatedAt: Date;
3234
3699
  id: number;
@@ -3239,7 +3704,7 @@ declare class UsersRepository extends BaseRepository {
3239
3704
  passwordHash: string | null;
3240
3705
  passwordChangeRequired: boolean;
3241
3706
  roleId: number;
3242
- status: "active" | "inactive" | "suspended";
3707
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3243
3708
  emailVerifiedAt: Date | null;
3244
3709
  phoneVerifiedAt: Date | null;
3245
3710
  lastLoginAt: Date | null;
@@ -3265,16 +3730,47 @@ declare class UsersRepository extends BaseRepository {
3265
3730
  roleId: number;
3266
3731
  createdAt: Date;
3267
3732
  updatedAt: Date;
3268
- status: "active" | "inactive" | "suspended";
3733
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3269
3734
  emailVerifiedAt: Date | null;
3270
3735
  phoneVerifiedAt: Date | null;
3271
3736
  lastLoginAt: Date | null;
3737
+ deletedAt: Date | null;
3738
+ deletedBy: string | null;
3272
3739
  }>;
3273
3740
  /**
3274
3741
  * 사용자 정보 업데이트
3275
3742
  * Write primary 사용
3276
3743
  */
3277
3744
  updateById(id: number, data: Partial<NewUser>): Promise<{
3745
+ deletedAt: Date | null;
3746
+ deletedBy: string | null;
3747
+ createdAt: Date;
3748
+ updatedAt: Date;
3749
+ id: number;
3750
+ publicId: string;
3751
+ email: string | null;
3752
+ phone: string | null;
3753
+ username: string | null;
3754
+ passwordHash: string | null;
3755
+ passwordChangeRequired: boolean;
3756
+ roleId: number;
3757
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3758
+ emailVerifiedAt: Date | null;
3759
+ phoneVerifiedAt: Date | null;
3760
+ lastLoginAt: Date | null;
3761
+ }>;
3762
+ /**
3763
+ * 계정 삭제 취소(복구) — `WHERE status = 'pending_deletion'` 조건부 UPDATE.
3764
+ *
3765
+ * cancelAccountDeletionService가 account_deletion_requests claim(조건부
3766
+ * markCancelled)에 성공한 **이후에만** 호출한다. 그 claim이 이미 purge와의
3767
+ * 경합을 해결하므로 이 조건은 방어적 이중 안전장치 — 0 row 매치(= 이미
3768
+ * status가 바뀐 상태) 시 null을 반환하며 예외를 던지지 않는다.
3769
+ * Write primary 사용
3770
+ */
3771
+ reactivateFromPendingDeletion(id: number): Promise<{
3772
+ deletedAt: Date | null;
3773
+ deletedBy: string | null;
3278
3774
  createdAt: Date;
3279
3775
  updatedAt: Date;
3280
3776
  id: number;
@@ -3285,7 +3781,7 @@ declare class UsersRepository extends BaseRepository {
3285
3781
  passwordHash: string | null;
3286
3782
  passwordChangeRequired: boolean;
3287
3783
  roleId: number;
3288
- status: "active" | "inactive" | "suspended";
3784
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3289
3785
  emailVerifiedAt: Date | null;
3290
3786
  phoneVerifiedAt: Date | null;
3291
3787
  lastLoginAt: Date | null;
@@ -3295,6 +3791,8 @@ declare class UsersRepository extends BaseRepository {
3295
3791
  * Write primary 사용
3296
3792
  */
3297
3793
  updatePassword(id: number, passwordHash: string, clearPasswordChangeRequired?: boolean): Promise<{
3794
+ deletedAt: Date | null;
3795
+ deletedBy: string | null;
3298
3796
  createdAt: Date;
3299
3797
  updatedAt: Date;
3300
3798
  id: number;
@@ -3305,7 +3803,7 @@ declare class UsersRepository extends BaseRepository {
3305
3803
  passwordHash: string | null;
3306
3804
  passwordChangeRequired: boolean;
3307
3805
  roleId: number;
3308
- status: "active" | "inactive" | "suspended";
3806
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3309
3807
  emailVerifiedAt: Date | null;
3310
3808
  phoneVerifiedAt: Date | null;
3311
3809
  lastLoginAt: Date | null;
@@ -3315,6 +3813,8 @@ declare class UsersRepository extends BaseRepository {
3315
3813
  * Write primary 사용
3316
3814
  */
3317
3815
  updateLastLogin(id: number): Promise<{
3816
+ deletedAt: Date | null;
3817
+ deletedBy: string | null;
3318
3818
  createdAt: Date;
3319
3819
  updatedAt: Date;
3320
3820
  id: number;
@@ -3325,7 +3825,7 @@ declare class UsersRepository extends BaseRepository {
3325
3825
  passwordHash: string | null;
3326
3826
  passwordChangeRequired: boolean;
3327
3827
  roleId: number;
3328
- status: "active" | "inactive" | "suspended";
3828
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3329
3829
  emailVerifiedAt: Date | null;
3330
3830
  phoneVerifiedAt: Date | null;
3331
3831
  lastLoginAt: Date | null;
@@ -3345,10 +3845,12 @@ declare class UsersRepository extends BaseRepository {
3345
3845
  roleId: number;
3346
3846
  createdAt: Date;
3347
3847
  updatedAt: Date;
3348
- status: "active" | "inactive" | "suspended";
3848
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
3349
3849
  emailVerifiedAt: Date | null;
3350
3850
  phoneVerifiedAt: Date | null;
3351
3851
  lastLoginAt: Date | null;
3852
+ deletedAt: Date | null;
3853
+ deletedBy: string | null;
3352
3854
  }>;
3353
3855
  /**
3354
3856
  * User의 Role과 Permissions 조회 (JOIN)
@@ -3553,6 +4055,14 @@ declare class KeysRepository extends BaseRepository {
3553
4055
  revokedAt: Date | null;
3554
4056
  revokedReason: string | null;
3555
4057
  }>;
4058
+ /**
4059
+ * 사용자의 모든 공개키 삭제 (계정 익명화 파기용)
4060
+ *
4061
+ * hard-delete는 FK cascade로 자동 처리되지만, anonymize 모드는 users row를
4062
+ * 남기므로 자식 row를 직접 지워야 한다.
4063
+ * Write primary 사용
4064
+ */
4065
+ deleteAllByUserId(userId: number): Promise<number>;
3556
4066
  /**
3557
4067
  * 마지막 사용 시간 업데이트
3558
4068
  * Write primary 사용
@@ -3630,7 +4140,7 @@ declare class VerificationCodesRepository extends BaseRepository {
3630
4140
  target: string;
3631
4141
  targetType: "email" | "phone";
3632
4142
  code: string;
3633
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
4143
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
3634
4144
  expiresAt: Date;
3635
4145
  usedAt: Date | null;
3636
4146
  attempts: number;
@@ -3646,7 +4156,7 @@ declare class VerificationCodesRepository extends BaseRepository {
3646
4156
  target: string;
3647
4157
  targetType: "email" | "phone";
3648
4158
  code: string;
3649
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
4159
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
3650
4160
  expiresAt: Date;
3651
4161
  usedAt: Date | null;
3652
4162
  attempts: number;
@@ -3658,7 +4168,7 @@ declare class VerificationCodesRepository extends BaseRepository {
3658
4168
  create(data: NewVerificationCode): Promise<{
3659
4169
  target: string;
3660
4170
  targetType: "email" | "phone";
3661
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
4171
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
3662
4172
  code: string;
3663
4173
  id: number;
3664
4174
  createdAt: Date;
@@ -3678,7 +4188,7 @@ declare class VerificationCodesRepository extends BaseRepository {
3678
4188
  target: string;
3679
4189
  targetType: "email" | "phone";
3680
4190
  code: string;
3681
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
4191
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
3682
4192
  expiresAt: Date;
3683
4193
  usedAt: Date | null;
3684
4194
  attempts: number;
@@ -3694,7 +4204,7 @@ declare class VerificationCodesRepository extends BaseRepository {
3694
4204
  target: string;
3695
4205
  targetType: "email" | "phone";
3696
4206
  code: string;
3697
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
4207
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change" | "account_deletion";
3698
4208
  expiresAt: Date;
3699
4209
  usedAt: Date | null;
3700
4210
  attempts: number;
@@ -3704,6 +4214,14 @@ declare class VerificationCodesRepository extends BaseRepository {
3704
4214
  * Write primary 사용
3705
4215
  */
3706
4216
  deleteExpired(): Promise<number>;
4217
+ /**
4218
+ * Target(email/phone)의 모든 인증 코드 삭제 (계정 파기용)
4219
+ *
4220
+ * verificationCodes는 userId FK가 없고 target 텍스트로만 연결되므로, 파기 시
4221
+ * 원래 email/phone을 이 메서드로 직접 정리해야 한다.
4222
+ * Write primary 사용
4223
+ */
4224
+ deleteByTarget(target: string): Promise<number>;
3707
4225
  /**
3708
4226
  * Target의 모든 이전 코드 무효화 (새 코드 발급 시)
3709
4227
  * Write primary 사용
@@ -4059,8 +4577,8 @@ declare class UserPermissionsRepository extends BaseRepository {
4059
4577
  createdAt: Date;
4060
4578
  updatedAt: Date;
4061
4579
  permissionId: number;
4062
- expiresAt: Date | null;
4063
4580
  reason: string | null;
4581
+ expiresAt: Date | null;
4064
4582
  granted: boolean;
4065
4583
  }>;
4066
4584
  /**
@@ -4085,8 +4603,8 @@ declare class UserPermissionsRepository extends BaseRepository {
4085
4603
  createdAt: Date;
4086
4604
  updatedAt: Date;
4087
4605
  permissionId: number;
4088
- expiresAt: Date | null;
4089
4606
  reason: string | null;
4607
+ expiresAt: Date | null;
4090
4608
  granted: boolean;
4091
4609
  }>;
4092
4610
  /**
@@ -4730,6 +5248,14 @@ declare class SocialAccountsRepository extends BaseRepository {
4730
5248
  refreshToken: string | null;
4731
5249
  tokenExpiresAt: Date | null;
4732
5250
  }>;
5251
+ /**
5252
+ * 사용자의 모든 소셜 계정 삭제 (계정 익명화 파기용)
5253
+ *
5254
+ * provider unique index(provider, providerUserId)를 해제해 같은 소셜 계정으로
5255
+ * 재가입할 수 있게 한다.
5256
+ * Write primary 사용
5257
+ */
5258
+ deleteAllByUserId(userId: number): Promise<number>;
4733
5259
  }
4734
5260
  declare const socialAccountsRepository: SocialAccountsRepository;
4735
5261
 
@@ -4752,6 +5278,168 @@ declare class AuthMetadataRepository extends BaseRepository {
4752
5278
  }
4753
5279
  declare const authMetadataRepository: AuthMetadataRepository;
4754
5280
 
5281
+ /**
5282
+ * Account Deletion Requests Repository
5283
+ *
5284
+ * 계정 탈퇴 요청/복구/파기 감사 이력 관리를 위한 Repository
5285
+ * BaseRepository를 상속받아 자동 트랜잭션 컨텍스트 지원 및 Read/Write 분리
5286
+ *
5287
+ * Row는 절대 삭제하지 않는다 — status 전이(pending -> cancelled|completed)로만 갱신.
5288
+ */
5289
+
5290
+ /**
5291
+ * Account Deletion Requests Repository 클래스
5292
+ */
5293
+ declare class AccountDeletionRequestsRepository extends BaseRepository {
5294
+ /**
5295
+ * ID로 요청 조회
5296
+ * Read replica 사용
5297
+ */
5298
+ findById(id: number): Promise<{
5299
+ createdAt: Date;
5300
+ updatedAt: Date;
5301
+ id: number;
5302
+ userId: number | null;
5303
+ userPublicId: string;
5304
+ requestedAt: Date;
5305
+ purgeScheduledAt: Date;
5306
+ status: "pending" | "cancelled" | "completed";
5307
+ requestedBy: "admin" | "self";
5308
+ reason: string | null;
5309
+ cancelledAt: Date | null;
5310
+ completedAt: Date | null;
5311
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5312
+ }>;
5313
+ /**
5314
+ * User ID로 pending 요청 조회 (유저당 최대 1건, partial unique index로 보장)
5315
+ * Read replica 사용
5316
+ */
5317
+ findPendingByUserId(userId: number): Promise<{
5318
+ createdAt: Date;
5319
+ updatedAt: Date;
5320
+ id: number;
5321
+ userId: number | null;
5322
+ userPublicId: string;
5323
+ requestedAt: Date;
5324
+ purgeScheduledAt: Date;
5325
+ status: "pending" | "cancelled" | "completed";
5326
+ requestedBy: "admin" | "self";
5327
+ reason: string | null;
5328
+ cancelledAt: Date | null;
5329
+ completedAt: Date | null;
5330
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5331
+ }>;
5332
+ /**
5333
+ * User ID로 pending 요청 조회 — Write primary에서 직접 읽는다.
5334
+ *
5335
+ * 로그인/OAuth/authenticate 게이트가 pending_deletion 유저의 purgeScheduledAt을
5336
+ * 표시하는 데 쓴다. 이 경로들은 복제 지연이 있으면 방금 커밋된 상태 전이를 놓칠 수
5337
+ * 있어(예: 삭제 요청 직후 OAuth 로그인 시도), replica가 아닌 primary에서 읽는다.
5338
+ */
5339
+ findPendingByUserIdOnPrimary(userId: number): Promise<{
5340
+ createdAt: Date;
5341
+ updatedAt: Date;
5342
+ id: number;
5343
+ userId: number | null;
5344
+ userPublicId: string;
5345
+ requestedAt: Date;
5346
+ purgeScheduledAt: Date;
5347
+ status: "pending" | "cancelled" | "completed";
5348
+ requestedBy: "admin" | "self";
5349
+ reason: string | null;
5350
+ cancelledAt: Date | null;
5351
+ completedAt: Date | null;
5352
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5353
+ }>;
5354
+ /**
5355
+ * 파기 스윕 대상 조회 — status='pending' AND purgeScheduledAt <= now
5356
+ * Read replica 사용
5357
+ */
5358
+ findDueForPurge(now: Date): Promise<{
5359
+ createdAt: Date;
5360
+ updatedAt: Date;
5361
+ id: number;
5362
+ userId: number | null;
5363
+ userPublicId: string;
5364
+ requestedAt: Date;
5365
+ purgeScheduledAt: Date;
5366
+ status: "pending" | "cancelled" | "completed";
5367
+ requestedBy: "admin" | "self";
5368
+ reason: string | null;
5369
+ cancelledAt: Date | null;
5370
+ completedAt: Date | null;
5371
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5372
+ }[]>;
5373
+ /**
5374
+ * 요청 생성
5375
+ * Write primary 사용
5376
+ */
5377
+ create(data: NewAccountDeletionRequest): Promise<{
5378
+ userId: number | null;
5379
+ id: number;
5380
+ createdAt: Date;
5381
+ updatedAt: Date;
5382
+ status: "pending" | "cancelled" | "completed";
5383
+ reason: string | null;
5384
+ purgeScheduledAt: Date;
5385
+ cancelledAt: Date | null;
5386
+ userPublicId: string;
5387
+ requestedAt: Date;
5388
+ requestedBy: "admin" | "self";
5389
+ completedAt: Date | null;
5390
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5391
+ }>;
5392
+ /**
5393
+ * 요청 취소 (복구)
5394
+ *
5395
+ * `WHERE status = 'pending'` 조건부 UPDATE — 이미 completed(파기 완료)된 row를
5396
+ * 뒤늦게 도착한 cancel이 덮어쓰지 않도록 한다. 0 row 매치(= 이미 completed거나
5397
+ * cancelled) 시 null을 반환하니 호출자가 그 결과를 확인해야 한다.
5398
+ * Write primary 사용
5399
+ */
5400
+ markCancelled(id: number): Promise<{
5401
+ createdAt: Date;
5402
+ updatedAt: Date;
5403
+ id: number;
5404
+ userId: number | null;
5405
+ userPublicId: string;
5406
+ requestedAt: Date;
5407
+ purgeScheduledAt: Date;
5408
+ status: "pending" | "cancelled" | "completed";
5409
+ requestedBy: "admin" | "self";
5410
+ reason: string | null;
5411
+ cancelledAt: Date | null;
5412
+ completedAt: Date | null;
5413
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5414
+ }>;
5415
+ /**
5416
+ * 요청 완료 처리 (파기 실행 직전에 호출하는 "claim" — 파기 실행 후가 아니다)
5417
+ *
5418
+ * `WHERE status = 'pending'` 조건부 UPDATE. Postgres UPDATE는 매치되는 row에 락을
5419
+ * 걸고 최신 커밋 상태로 WHERE를 재평가하므로, 이 호출 하나로 (1) 동시에 취소된
5420
+ * row를 안전하게 걸러내고 (2) row-level 락으로 동시 파기 시도를 직렬화한다.
5421
+ * 0 row 매치(= 이미 취소되었거나 다른 파기가 먼저 처리함) 시 null을 반환 — 호출자는
5422
+ * destructive DML을 실행하기 **전에** 반드시 이 결과를 확인해야 한다.
5423
+ * Write primary 사용
5424
+ */
5425
+ markCompleted(id: number, purgeStrategy: PurgeStrategy): Promise<{
5426
+ createdAt: Date;
5427
+ updatedAt: Date;
5428
+ id: number;
5429
+ userId: number | null;
5430
+ userPublicId: string;
5431
+ requestedAt: Date;
5432
+ purgeScheduledAt: Date;
5433
+ status: "pending" | "cancelled" | "completed";
5434
+ requestedBy: "admin" | "self";
5435
+ reason: string | null;
5436
+ cancelledAt: Date | null;
5437
+ completedAt: Date | null;
5438
+ purgeStrategy: "anonymize" | "hard-delete" | null;
5439
+ }>;
5440
+ }
5441
+ declare const accountDeletionRequestsRepository: AccountDeletionRequestsRepository;
5442
+
4755
5443
  /**
4756
5444
  * @spfn/auth - Password Helpers
4757
5445
  *
@@ -4815,6 +5503,7 @@ declare function hashPassword(password: string): Promise<string>;
4815
5503
  * ```
4816
5504
  */
4817
5505
  declare function verifyPassword(password: string, hash: string): Promise<boolean>;
5506
+ declare function getDummyPasswordHash(): Promise<string>;
4818
5507
  /**
4819
5508
  * Validate password strength
4820
5509
  *
@@ -5202,10 +5891,12 @@ declare function getUser(c: Context | {
5202
5891
  roleId: number;
5203
5892
  createdAt: Date;
5204
5893
  updatedAt: Date;
5205
- status: "active" | "inactive" | "suspended";
5894
+ status: "active" | "inactive" | "suspended" | "pending_deletion" | "deleted";
5206
5895
  emailVerifiedAt: Date | null;
5207
5896
  phoneVerifiedAt: Date | null;
5208
5897
  lastLoginAt: Date | null;
5898
+ deletedAt: Date | null;
5899
+ deletedBy: string | null;
5209
5900
  };
5210
5901
  /**
5211
5902
  * Get authenticated user ID from route context
@@ -5330,6 +6021,7 @@ declare function shouldRotateKey(createdAt: Date, rotationDays?: number): {
5330
6021
  *
5331
6022
  * Manages global auth configuration including session TTL
5332
6023
  */
6024
+
5333
6025
  /**
5334
6026
  * Cookie names used by SPFN Auth
5335
6027
  *
@@ -5346,6 +6038,18 @@ declare const COOKIE_NAMES: {
5346
6038
  /** OAuth CSRF nonce — double-submit against the (encrypted) state.nonce at callback */
5347
6039
  readonly OAUTH_CSRF: string;
5348
6040
  };
6041
+ /**
6042
+ * OAuth CSRF 쿠키를 PORT 접미사와 무관하게 전부 수집한다.
6043
+ *
6044
+ * 쿠키를 심는 쪽은 Next.js 프로세스, 읽는 쪽은 API 프로세스라 분리 배포에서는
6045
+ * 두 프로세스의 PORT가 달라 COOKIE_NAMES.OAUTH_CSRF 정확 일치 조회가 빗나간다.
6046
+ * nonce 자체가 랜덤값이고 암호화된 state의 nonce와 대조되므로, 접미사가 다른
6047
+ * spfn_oauth_csrf* 후보를 모두 대조 대상으로 넘겨도 안전하다.
6048
+ */
6049
+ declare function matchOAuthCsrfCookies(cookies: Record<string, string>): {
6050
+ name: string;
6051
+ value: string;
6052
+ }[];
5349
6053
  /**
5350
6054
  * Parse duration string to seconds
5351
6055
  *
@@ -5358,6 +6062,36 @@ declare const COOKIE_NAMES: {
5358
6062
  * parseDuration('3600') // 3600
5359
6063
  */
5360
6064
  declare function parseDuration(duration: string | number): number;
6065
+ /**
6066
+ * Registration channel passed to the beforeRegister hook
6067
+ *
6068
+ * - credentials: email/phone + password registration
6069
+ * - oauth: new-user signup through a social provider (web or native flow)
6070
+ * - invitation: invitation acceptance
6071
+ */
6072
+ type RegisterChannel = 'credentials' | 'oauth' | 'invitation';
6073
+ /**
6074
+ * Context passed to the beforeRegister hook
6075
+ *
6076
+ * Credentials (password, keys) are intentionally excluded — the hook is a
6077
+ * policy gate, not a credential handler.
6078
+ */
6079
+ interface BeforeRegisterContext {
6080
+ channel: RegisterChannel;
6081
+ /** Social provider — only set when channel is 'oauth' */
6082
+ provider?: SocialProvider;
6083
+ email?: string;
6084
+ /**
6085
+ * Whether the email is verified — only set when channel is 'oauth'.
6086
+ * OAuth providers may report an unverified (spoofable) email; the created
6087
+ * account stores it as null in that case, so email-based policies must
6088
+ * check this flag. credentials/invitation emails are already verified.
6089
+ */
6090
+ emailVerified?: boolean;
6091
+ phone?: string;
6092
+ /** App-supplied registration metadata (register params / OAuth start params / invitation) */
6093
+ metadata?: Record<string, unknown>;
6094
+ }
5361
6095
  /**
5362
6096
  * Auth configuration
5363
6097
  */
@@ -5372,6 +6106,35 @@ interface AuthConfig {
5372
6106
  * @default 7d (7 days)
5373
6107
  */
5374
6108
  sessionTtl?: string | number;
6109
+ /**
6110
+ * App-injected validator that runs before a new user row is created,
6111
+ * on every registration channel (credentials, oauth, invitation).
6112
+ *
6113
+ * Throw to reject the registration — RegistrationRejectedError (403) is
6114
+ * the recommended error; any HttpError subclass keeps its own status.
6115
+ * Runs after built-in checks (verification token, duplicate account),
6116
+ * so existing error precedence is unchanged. Not called for admin
6117
+ * seeding (initializeAuth) or when linking a social account to an
6118
+ * existing user.
6119
+ *
6120
+ * Runs inside the registration DB transaction on every channel — keep it
6121
+ * fast. A slow call (e.g. an external policy API) holds a pooled DB
6122
+ * connection open for its full duration on every signup.
6123
+ *
6124
+ * @example
6125
+ * ```typescript
6126
+ * configureAuth({
6127
+ * beforeRegister: async ({ channel, metadata }) =>
6128
+ * {
6129
+ * if (channel === 'credentials' && !isOldEnough(metadata?.birthDate))
6130
+ * {
6131
+ * throw new RegistrationRejectedError({ message: 'Age requirement not met' });
6132
+ * }
6133
+ * },
6134
+ * });
6135
+ * ```
6136
+ */
6137
+ beforeRegister?: (context: BeforeRegisterContext) => void | Promise<void>;
5375
6138
  }
5376
6139
  /**
5377
6140
  * Configure global auth settings
@@ -5390,6 +6153,14 @@ declare function configureAuth(config: AuthConfig): void;
5390
6153
  * Get current auth configuration
5391
6154
  */
5392
6155
  declare function getAuthConfig(): AuthConfig;
6156
+ /**
6157
+ * Run the app-injected beforeRegister hook if configured — throws to reject.
6158
+ *
6159
+ * Single entry point for every registration channel so a new channel cannot
6160
+ * forget the configured-check. Callers invoke this right before creating the
6161
+ * user row.
6162
+ */
6163
+ declare function runBeforeRegister(context: BeforeRegisterContext): Promise<void>;
5393
6164
  /**
5394
6165
  * Get session TTL in seconds
5395
6166
  *
@@ -5442,6 +6213,66 @@ declare function initOneTimeTokenManager(config?: {
5442
6213
  */
5443
6214
  declare function getOneTimeTokenManager(): SSETokenManager;
5444
6215
 
6216
+ /**
6217
+ * Account Deletion Configuration
6218
+ *
6219
+ * Singleton config for the account deletion/recovery lifecycle, mirroring the
6220
+ * one-time-token manager pattern (`lib/one-time-token.ts`): a mutable module-level
6221
+ * value, set once during `createAuthLifecycle()` and read at request/handler time.
6222
+ *
6223
+ * `purgeCron` is the one field this module does NOT make effective on its own —
6224
+ * `job('auth.deletion.purge').cron(expression)` bakes the cron string in at
6225
+ * *module-import* time, which always runs before `createAuthLifecycle()` executes
6226
+ * (ESM evaluates imports before the importing module's own statements). So a
6227
+ * statically-exported job router can never see a `purgeCron` set here. Apps that
6228
+ * need a non-default cron must build the router themselves, after configuring
6229
+ * deletion, with `createAuthDeletionJobRouter({ purgeCron })` — see
6230
+ * `server/jobs/deletion-purge.ts` and the README.
6231
+ */
6232
+
6233
+ interface AccountDeletionPurgeUser {
6234
+ id: number;
6235
+ publicId: string;
6236
+ email: string | null;
6237
+ phone: string | null;
6238
+ }
6239
+ interface AuthDeletionConfig {
6240
+ /** Days between a deletion request and the purge becoming eligible. 0 = immediate. */
6241
+ gracePeriodDays: number;
6242
+ /** How the purge job destroys the account once the grace period elapses. */
6243
+ purgeStrategy: PurgeStrategy;
6244
+ /** Whether a self-service caller may request `immediate: true` (skip the grace period). */
6245
+ allowSelfImmediate: boolean;
6246
+ /**
6247
+ * Cron schedule for the purge sweep. NOT read automatically by the static
6248
+ * `authJobRouter` export — see the module doc comment above.
6249
+ */
6250
+ purgeCron: string;
6251
+ /** Whether to email users (when they have one) at request/recovery/purge time. */
6252
+ sendNotifications: boolean;
6253
+ /**
6254
+ * Synchronous hook invoked immediately before a user is purged. Throw to skip
6255
+ * that user for this sweep (they stay `pending`, retried on the next tick).
6256
+ */
6257
+ onBeforePurge?: (user: AccountDeletionPurgeUser) => Promise<void>;
6258
+ }
6259
+ declare const DEFAULT_DELETION_GRACE_PERIOD_DAYS = 30;
6260
+ declare const DEFAULT_DELETION_PURGE_STRATEGY: PurgeStrategy;
6261
+ declare const DEFAULT_DELETION_ALLOW_SELF_IMMEDIATE = false;
6262
+ declare const DEFAULT_DELETION_PURGE_CRON = "0 4 * * *";
6263
+ declare const DEFAULT_DELETION_SEND_NOTIFICATIONS = true;
6264
+ /**
6265
+ * Set the resolved deletion config. Called synchronously from `createAuthLifecycle()`
6266
+ * (not from `afterInfrastructure`) so it takes effect before any route/job handler
6267
+ * that reads `getDeletionConfig()` can run.
6268
+ */
6269
+ declare function configureDeletion(options?: Partial<AuthDeletionConfig>): void;
6270
+ /**
6271
+ * Read the current deletion config. Safe to call any time — defaults apply even
6272
+ * if `createAuthLifecycle()` was never given a `deletion` block.
6273
+ */
6274
+ declare function getDeletionConfig(): AuthDeletionConfig;
6275
+
5445
6276
  /**
5446
6277
  * @spfn/auth - Centralized Logger
5447
6278
  *
@@ -5581,9 +6412,131 @@ interface AuthLifecycleOptions extends AuthInitOptions {
5581
6412
  */
5582
6413
  store?: SSETokenStore;
5583
6414
  };
6415
+ /**
6416
+ * Account deletion/recovery lifecycle configuration
6417
+ *
6418
+ * Controls the grace-period request → recover → purge flow exposed by
6419
+ * `POST /_auth/deletion/request` and `POST /_auth/deletion/cancel`.
6420
+ * Registering the purge job itself is a separate step — see `authJobRouter`
6421
+ * (`@spfn/auth/server`) and the README, since job registration happens after
6422
+ * this lifecycle hook runs and can't be triggered from here.
6423
+ *
6424
+ * @example
6425
+ * ```typescript
6426
+ * createAuthLifecycle({
6427
+ * deletion: {
6428
+ * gracePeriodDays: 14,
6429
+ * purgeStrategy: 'anonymize',
6430
+ * allowSelfImmediate: false,
6431
+ * sendNotifications: true,
6432
+ * onBeforePurge: async (user) => {
6433
+ * await appDataCleanup(user.id); // throw to skip this user this sweep
6434
+ * },
6435
+ * },
6436
+ * })
6437
+ * ```
6438
+ */
6439
+ deletion?: {
6440
+ /**
6441
+ * Days between a deletion request and the purge becoming eligible.
6442
+ * 0 = immediate (still goes through the same request/purge pipeline).
6443
+ * @default 30
6444
+ */
6445
+ gracePeriodDays?: number;
6446
+ /**
6447
+ * How the purge job destroys the account once the grace period elapses.
6448
+ * - 'anonymize': scrub PII, keep the row (status -> 'deleted') — recommended default
6449
+ * - 'hard-delete': physically remove the `users` row (cascades to child rows)
6450
+ * @default 'anonymize'
6451
+ */
6452
+ purgeStrategy?: PurgeStrategy;
6453
+ /**
6454
+ * Whether a self-service caller may pass `immediate: true` on
6455
+ * `POST /_auth/deletion/request` to skip the grace period entirely.
6456
+ * @default false
6457
+ */
6458
+ allowSelfImmediate?: boolean;
6459
+ /**
6460
+ * Cron schedule for the purge sweep. NOTE: this does not reach the static
6461
+ * `authJobRouter` export automatically — job schedules are fixed at
6462
+ * module-import time, before this lifecycle hook runs. For a non-default
6463
+ * cron, build the router yourself after this call with
6464
+ * `createAuthDeletionJobRouter({ purgeCron })` (see `@spfn/auth/server`).
6465
+ * @default '0 4 * * *'
6466
+ */
6467
+ purgeCron?: string;
6468
+ /**
6469
+ * Whether to email users (when they have one on file) at request,
6470
+ * recovery, and final-purge time.
6471
+ * @default true
6472
+ */
6473
+ sendNotifications?: boolean;
6474
+ /**
6475
+ * Invoked immediately before a user is purged (both the immediate inline
6476
+ * path and the cron sweep). Throw to skip that user for this run — they
6477
+ * stay `pending` and are retried on the next sweep.
6478
+ */
6479
+ onBeforePurge?: (user: AccountDeletionPurgeUser) => Promise<void>;
6480
+ };
5584
6481
  }
5585
6482
  declare function createAuthLifecycle(options?: AuthLifecycleOptions): AuthLifecycleConfig;
5586
6483
 
6484
+ /**
6485
+ * @spfn/auth - Account Deletion Purge Job
6486
+ *
6487
+ * The package's first background job (`@spfn/core/job`). Sweeps
6488
+ * `account_deletion_requests` for rows whose grace period elapsed and purges
6489
+ * each one (see `sweepDuePurges` in `account-deletion.service.ts`).
6490
+ *
6491
+ * Registration is NOT automatic. `createAuthLifecycle()`'s `afterInfrastructure`
6492
+ * hook runs *before* `initBoss`/`registerJobs` in `@spfn/core`'s server startup
6493
+ * (`initializeInfrastructure()`), so the lifecycle has no opportunity to register
6494
+ * jobs itself. Apps must register explicitly:
6495
+ *
6496
+ * ```typescript
6497
+ * import { authJobRouter } from '@spfn/auth/server';
6498
+ *
6499
+ * export default defineServerConfig()
6500
+ * .lifecycle(createAuthLifecycle())
6501
+ * .jobs(authJobRouter)
6502
+ * .routes(appRouter)
6503
+ * .build();
6504
+ * ```
6505
+ *
6506
+ * Cron schedule caveat: `job(...).cron(expression)` bakes the cron string in at
6507
+ * *module-import* time, which always happens before `createAuthLifecycle()` runs
6508
+ * in the app's own module (ESM evaluates imports before the importing module's
6509
+ * statements). So the static `authJobRouter` export below always uses the default
6510
+ * cron — `deletion.purgeCron` passed to `createAuthLifecycle()` cannot reach it.
6511
+ * To use a custom cron, build the router yourself, after calling
6512
+ * `createAuthLifecycle()`, with `createAuthDeletionJobRouter({ purgeCron })` and
6513
+ * register that instead of the static export.
6514
+ */
6515
+ /**
6516
+ * Build the `auth.deletion.purge` job with a given cron schedule.
6517
+ *
6518
+ * @param cronExpression - Defaults to `deletion-config`'s default (daily at 04:00).
6519
+ */
6520
+ declare function createAuthDeletionPurgeJob(cronExpression?: string): _spfn_core_job.JobDef<void, void>;
6521
+ /**
6522
+ * Build a job router containing only the deletion purge job, with a given cron.
6523
+ * Use this (after calling `createAuthLifecycle({ deletion: { purgeCron } })`) when
6524
+ * you need a non-default schedule — see the module doc comment above for why the
6525
+ * static `authJobRouter` export can't pick up `deletion.purgeCron` automatically.
6526
+ */
6527
+ declare function createAuthDeletionJobRouter(options?: {
6528
+ purgeCron?: string;
6529
+ }): _spfn_core_job.JobRouter<{
6530
+ deletionPurge: _spfn_core_job.JobDef<void, void>;
6531
+ }>;
6532
+ /**
6533
+ * Default job router — the default cron (`0 4 * * *`). Register with
6534
+ * `.jobs(authJobRouter)` in `server.config.ts`.
6535
+ */
6536
+ declare const authJobRouter: _spfn_core_job.JobRouter<{
6537
+ deletionPurge: _spfn_core_job.JobDef<void, void>;
6538
+ }>;
6539
+
5587
6540
  /**
5588
6541
  * @spfn/auth - Auth Events
5589
6542
  *
@@ -5695,6 +6648,48 @@ declare const invitationAcceptedEvent: _spfn_core_event.EventDef<{
5695
6648
  invitedBy: string;
5696
6649
  invitationId: string;
5697
6650
  }>;
6651
+ /**
6652
+ * auth.deletion.requested - 계정 탈퇴 요청 이벤트
6653
+ *
6654
+ * 발행 시점:
6655
+ * - requestAccountDeletionService() 성공 시 (self/admin 공통)
6656
+ *
6657
+ * @example
6658
+ * ```typescript
6659
+ * authDeletionRequestedEvent.subscribe(async (payload) => {
6660
+ * await analytics.trackChurnRisk(payload.userId);
6661
+ * });
6662
+ * ```
6663
+ */
6664
+ declare const authDeletionRequestedEvent: _spfn_core_event.EventDef<{
6665
+ userId: string;
6666
+ purgeScheduledAt: string;
6667
+ userPublicId: string;
6668
+ requestedBy: "admin" | "self";
6669
+ }>;
6670
+ /**
6671
+ * auth.deletion.cancelled - 계정 탈퇴 복구 이벤트
6672
+ *
6673
+ * 발행 시점:
6674
+ * - cancelAccountDeletionService() 성공 시 (유예 기간 내 복구)
6675
+ */
6676
+ declare const authDeletionCancelledEvent: _spfn_core_event.EventDef<{
6677
+ userId: string;
6678
+ userPublicId: string;
6679
+ }>;
6680
+ /**
6681
+ * auth.deletion.completed - 계정 파기 완료 이벤트
6682
+ *
6683
+ * 발행 시점:
6684
+ * - purge job(또는 즉시 파기 경로)이 유저를 파기한 직후
6685
+ *
6686
+ * PII를 담지 않는다 — userId(내부 순번)/email/phone 없이 userPublicId만 실어
6687
+ * 파기 완료 이후에도 구독자가 식별 정보를 다시 축적하지 않도록 한다.
6688
+ */
6689
+ declare const authDeletionCompletedEvent: _spfn_core_event.EventDef<{
6690
+ userPublicId: string;
6691
+ purgeStrategy: "anonymize" | "hard-delete";
6692
+ }>;
5698
6693
  /**
5699
6694
  * Auth event payload types
5700
6695
  */
@@ -5702,5 +6697,8 @@ type AuthLoginPayload = typeof authLoginEvent._payload;
5702
6697
  type AuthRegisterPayload = typeof authRegisterEvent._payload;
5703
6698
  type InvitationCreatedPayload = typeof invitationCreatedEvent._payload;
5704
6699
  type InvitationAcceptedPayload = typeof invitationAcceptedEvent._payload;
6700
+ type AuthDeletionRequestedPayload = typeof authDeletionRequestedEvent._payload;
6701
+ type AuthDeletionCancelledPayload = typeof authDeletionCancelledEvent._payload;
6702
+ type AuthDeletionCompletedPayload = typeof authDeletionCompletedEvent._payload;
5705
6703
 
5706
- export { type AuthConfig, AuthContext, type AuthLifecycleConfig, type AuthLifecycleOptions, type AuthLoginPayload, type AuthMetadataEntity, AuthMetadataRepository, AuthProviderSchema, type AuthRegisterPayload, COOKIE_NAMES, type CreateOAuthStateParams, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, type InvitationAcceptedPayload, type InvitationCreatedPayload, InvitationStatus, InvitationsRepository, KeyAlgorithmType, type KeyPair, KeysRepository, type NewAuthMetadataEntity, type NewInvitation, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, OAuthProvider, type OAuthState, type Permission, type PermissionEntity, PermissionsRepository, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionPayload, SocialAccountsRepository, SocialProvider, type TokenPayload, type UpdateProfileParams, type User, type UserPermission, UserPermissionsRepository, type UserProfile, UserProfilesRepository, type UserPublicKey, type UserSocialAccount, UsersRepository, type VerificationCode, VerificationCodesRepository, VerificationPurpose, type VerifyIdTokenParams, acceptInvitation, addPermissionToRole, appleProvider, assertCanAssignRole, authLogger, authLoginEvent, authMetadata, authMetadataRepository, authRegisterEvent, authSchema, cancelInvitation, checkUsernameAvailableService, configureAuth, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, deleteInvitation, deleteRole, exchangeCodeForTokens, expireOldInvitations, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, generateOAuthNonce, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getKeySize, getLocale, getOneTimeTokenManager, getOptionalAuth, getRole, getRoleByName, getRolePermissions, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, googleProvider, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initOneTimeTokenManager, initializeAuth, invitationAcceptedEvent, invitationCreatedEvent, invitationsRepository, isGoogleOAuthEnabled, keysRepository, listInvitations, oneTimeTokenAuth, parseDuration, permissions, permissionsRepository, refreshAccessToken, removePermissionFromRole, requireAnyPermission, requirePermissions, requireRole, resendInvitation, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, setRolePermissions, shouldRotateKey, socialAccountsRepository, updateLastLoginService, updateLocaleService, updateRole, updateUserProfileService, updateUserService, updateUsernameService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyIdToken, verifyKeyFingerprint, verifyOAuthState, verifyPassword, verifyToken };
6704
+ export { type AccountDeletionPurgeUser, type AccountDeletionRequest, AccountDeletionRequestedBy, AccountDeletionRequestsRepository, type AuthConfig, AuthContext, type AuthDeletionCancelledPayload, type AuthDeletionCompletedPayload, type AuthDeletionConfig, type AuthDeletionRequestedPayload, type AuthLifecycleConfig, type AuthLifecycleOptions, type AuthLoginPayload, type AuthMetadataEntity, AuthMetadataRepository, AuthProviderSchema, type AuthRegisterPayload, type BeforeRegisterContext, COOKIE_NAMES, type CancelAccountDeletionParams, type CancelAccountDeletionResult, type CreateOAuthStateParams, DEFAULT_DELETION_ALLOW_SELF_IMMEDIATE, DEFAULT_DELETION_GRACE_PERIOD_DAYS, DEFAULT_DELETION_PURGE_CRON, DEFAULT_DELETION_PURGE_STRATEGY, DEFAULT_DELETION_SEND_NOTIFICATIONS, type DecryptedOAuthToken, EnvironmentKeyringTokenCipher, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, type InvitationAcceptedPayload, type InvitationCreatedPayload, InvitationStatus, InvitationsRepository, KeyAlgorithmType, type KeyPair, KeysRepository, type NewAccountDeletionRequest, type NewAuthMetadataEntity, type NewInvitation, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, OAuthProvider, type OAuthState, type OAuthTokenCipher, type OAuthTokenContext, type OAuthTokenType, type PendingDeletionInfo, type Permission, type PermissionEntity, PermissionsRepository, PurgeStrategy, type PurgeUserResult, type RegisterChannel, type RequestAccountDeletionParams, type RequestAccountDeletionResult, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionPayload, SocialAccountsRepository, SocialProvider, type SweepDuePurgesResult, type TokenPayload, type UpdateProfileParams, type User, type UserPermission, UserPermissionsRepository, type UserProfile, UserProfilesRepository, type UserPublicKey, type UserSocialAccount, UsersRepository, type VerificationCode, VerificationCodesRepository, VerificationPurpose, type VerifyIdTokenParams, acceptInvitation, accountDeletionRequests, accountDeletionRequestsRepository, addPermissionToRole, appleProvider, assertCanAssignRole, authDeletionCancelledEvent, authDeletionCompletedEvent, authDeletionRequestedEvent, authJobRouter, authLogger, authLoginEvent, authMetadata, authMetadataRepository, authRegisterEvent, authSchema, cancelAccountDeletionService, cancelInvitation, checkUsernameAvailableService, configureAuth, configureDeletion, configureOAuthTokenCipher, createAuthDeletionJobRouter, createAuthDeletionPurgeJob, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, decryptToken, deleteInvitation, deleteRole, encryptToken, exchangeCodeForTokens, expireOldInvitations, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, generateOAuthNonce, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getDeletionConfig, getDummyPasswordHash, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getKeySize, getLocale, getOneTimeTokenManager, getOptionalAuth, getPendingDeletionInfo, getRole, getRoleByName, getRolePermissions, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, googleProvider, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initOneTimeTokenManager, initializeAuth, invitationAcceptedEvent, invitationCreatedEvent, invitationsRepository, isEncrypted, isGoogleOAuthEnabled, kakaoProvider, keysRepository, listInvitations, matchOAuthCsrfCookies, naverProvider, oneTimeTokenAuth, parseDuration, permissions, permissionsRepository, purgeUserService, refreshAccessToken, removePermissionFromRole, requestAccountDeletionService, requireAnyPermission, requirePermissions, requireRole, resendInvitation, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, runBeforeRegister, setRolePermissions, shouldRotateKey, socialAccountsRepository, sweepDuePurges, updateLastLoginService, updateLocaleService, updateRole, updateUserProfileService, updateUserService, updateUsernameService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyIdToken, verifyKeyFingerprint, verifyOAuthState, verifyPassword, verifyToken };