@spfn/auth 0.2.0-beta.26 → 0.2.0-beta.28

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
@@ -113,6 +113,23 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
113
113
  identity: undefined;
114
114
  generated: undefined;
115
115
  }, {}, {}>;
116
+ username: drizzle_orm_pg_core.PgColumn<{
117
+ name: "username";
118
+ tableName: "users";
119
+ dataType: "string";
120
+ columnType: "PgText";
121
+ data: string;
122
+ driverParam: string;
123
+ notNull: false;
124
+ hasDefault: false;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: [string, ...string[]];
129
+ baseColumn: never;
130
+ identity: undefined;
131
+ generated: undefined;
132
+ }, {}, {}>;
116
133
  passwordHash: drizzle_orm_pg_core.PgColumn<{
117
134
  name: "password_hash";
118
135
  tableName: "users";
@@ -253,6 +270,7 @@ declare function getUserByIdService(userId: number): Promise<{
253
270
  id: number;
254
271
  email: string | null;
255
272
  phone: string | null;
273
+ username: string | null;
256
274
  passwordHash: string | null;
257
275
  passwordChangeRequired: boolean;
258
276
  roleId: number;
@@ -270,6 +288,7 @@ declare function getUserByEmailService(email: string): Promise<{
270
288
  id: number;
271
289
  email: string | null;
272
290
  phone: string | null;
291
+ username: string | null;
273
292
  passwordHash: string | null;
274
293
  passwordChangeRequired: boolean;
275
294
  roleId: number;
@@ -287,6 +306,7 @@ declare function getUserByPhoneService(phone: string): Promise<{
287
306
  id: number;
288
307
  email: string | null;
289
308
  phone: string | null;
309
+ username: string | null;
290
310
  passwordHash: string | null;
291
311
  passwordChangeRequired: boolean;
292
312
  roleId: number;
@@ -303,6 +323,34 @@ declare function updateLastLoginService(userId: number): Promise<void>;
303
323
  * Update user data
304
324
  */
305
325
  declare function updateUserService(userId: number, updates: Partial<NewUser>): Promise<void>;
326
+ /**
327
+ * Check if username is available
328
+ *
329
+ * @returns true if the username is available
330
+ */
331
+ declare function checkUsernameAvailableService(username: string): Promise<boolean>;
332
+ /**
333
+ * Update username with duplicate check
334
+ *
335
+ * @param userId - User ID (string, number, or bigint)
336
+ * @param username - New username or null to clear
337
+ * @throws UsernameAlreadyTakenError if username is already in use by another user
338
+ */
339
+ declare function updateUsernameService(userId: string | number | bigint, username: string | null): Promise<{
340
+ createdAt: Date;
341
+ updatedAt: Date;
342
+ id: number;
343
+ email: string | null;
344
+ phone: string | null;
345
+ username: string | null;
346
+ passwordHash: string | null;
347
+ passwordChangeRequired: boolean;
348
+ roleId: number;
349
+ status: "active" | "inactive" | "suspended";
350
+ emailVerifiedAt: Date | null;
351
+ phoneVerifiedAt: Date | null;
352
+ lastLoginAt: Date | null;
353
+ }>;
306
354
 
307
355
  /**
308
356
  * @spfn/auth - RBAC Initialization Service
@@ -2765,6 +2813,7 @@ declare class UsersRepository extends BaseRepository {
2765
2813
  id: number;
2766
2814
  email: string | null;
2767
2815
  phone: string | null;
2816
+ username: string | null;
2768
2817
  passwordHash: string | null;
2769
2818
  passwordChangeRequired: boolean;
2770
2819
  roleId: number;
@@ -2783,6 +2832,7 @@ declare class UsersRepository extends BaseRepository {
2783
2832
  id: number;
2784
2833
  email: string | null;
2785
2834
  phone: string | null;
2835
+ username: string | null;
2786
2836
  passwordHash: string | null;
2787
2837
  passwordChangeRequired: boolean;
2788
2838
  roleId: number;
@@ -2801,6 +2851,26 @@ declare class UsersRepository extends BaseRepository {
2801
2851
  id: number;
2802
2852
  email: string | null;
2803
2853
  phone: string | null;
2854
+ username: string | null;
2855
+ passwordHash: string | null;
2856
+ passwordChangeRequired: boolean;
2857
+ roleId: number;
2858
+ status: "active" | "inactive" | "suspended";
2859
+ emailVerifiedAt: Date | null;
2860
+ phoneVerifiedAt: Date | null;
2861
+ lastLoginAt: Date | null;
2862
+ }>;
2863
+ /**
2864
+ * 사용자명으로 사용자 조회
2865
+ * Read replica 사용
2866
+ */
2867
+ findByUsername(username: string): Promise<{
2868
+ createdAt: Date;
2869
+ updatedAt: Date;
2870
+ id: number;
2871
+ email: string | null;
2872
+ phone: string | null;
2873
+ username: string | null;
2804
2874
  passwordHash: string | null;
2805
2875
  passwordChangeRequired: boolean;
2806
2876
  roleId: number;
@@ -2819,6 +2889,7 @@ declare class UsersRepository extends BaseRepository {
2819
2889
  id: number;
2820
2890
  email: string | null;
2821
2891
  phone: string | null;
2892
+ username: string | null;
2822
2893
  passwordHash: string | null;
2823
2894
  passwordChangeRequired: boolean;
2824
2895
  roleId: number;
@@ -2840,6 +2911,7 @@ declare class UsersRepository extends BaseRepository {
2840
2911
  id: number;
2841
2912
  email: string | null;
2842
2913
  phone: string | null;
2914
+ username: string | null;
2843
2915
  passwordHash: string | null;
2844
2916
  passwordChangeRequired: boolean;
2845
2917
  roleId: number;
@@ -2862,6 +2934,7 @@ declare class UsersRepository extends BaseRepository {
2862
2934
  email: string | null;
2863
2935
  phone: string | null;
2864
2936
  id: number;
2937
+ username: string | null;
2865
2938
  passwordHash: string | null;
2866
2939
  passwordChangeRequired: boolean;
2867
2940
  roleId: number;
@@ -2882,6 +2955,7 @@ declare class UsersRepository extends BaseRepository {
2882
2955
  id: number;
2883
2956
  email: string | null;
2884
2957
  phone: string | null;
2958
+ username: string | null;
2885
2959
  passwordHash: string | null;
2886
2960
  passwordChangeRequired: boolean;
2887
2961
  roleId: number;
@@ -2900,6 +2974,7 @@ declare class UsersRepository extends BaseRepository {
2900
2974
  id: number;
2901
2975
  email: string | null;
2902
2976
  phone: string | null;
2977
+ username: string | null;
2903
2978
  passwordHash: string | null;
2904
2979
  passwordChangeRequired: boolean;
2905
2980
  roleId: number;
@@ -2918,6 +2993,7 @@ declare class UsersRepository extends BaseRepository {
2918
2993
  id: number;
2919
2994
  email: string | null;
2920
2995
  phone: string | null;
2996
+ username: string | null;
2921
2997
  passwordHash: string | null;
2922
2998
  passwordChangeRequired: boolean;
2923
2999
  roleId: number;
@@ -2934,6 +3010,7 @@ declare class UsersRepository extends BaseRepository {
2934
3010
  email: string | null;
2935
3011
  phone: string | null;
2936
3012
  id: number;
3013
+ username: string | null;
2937
3014
  passwordHash: string | null;
2938
3015
  passwordChangeRequired: boolean;
2939
3016
  roleId: number;
@@ -2975,6 +3052,7 @@ declare class UsersRepository extends BaseRepository {
2975
3052
  fetchMinimalUserData(userId: number): Promise<{
2976
3053
  userId: number;
2977
3054
  email: string | null;
3055
+ username: string | null;
2978
3056
  isEmailVerified: boolean;
2979
3057
  isPhoneVerified: boolean;
2980
3058
  }>;
@@ -2988,6 +3066,7 @@ declare class UsersRepository extends BaseRepository {
2988
3066
  fetchFullUserData(userId: number): Promise<{
2989
3067
  userId: number;
2990
3068
  email: string | null;
3069
+ username: string | null;
2991
3070
  isEmailVerified: boolean;
2992
3071
  isPhoneVerified: boolean;
2993
3072
  lastLoginAt: Date | null;
@@ -4710,6 +4789,7 @@ declare function getUser(c: Context | {
4710
4789
  email: string | null;
4711
4790
  phone: string | null;
4712
4791
  id: number;
4792
+ username: string | null;
4713
4793
  passwordHash: string | null;
4714
4794
  passwordChangeRequired: boolean;
4715
4795
  roleId: number;
@@ -5204,4 +5284,4 @@ declare const authRegisterEvent: _spfn_core_event.EventDef<{
5204
5284
  type AuthLoginPayload = typeof authLoginEvent._payload;
5205
5285
  type AuthRegisterPayload = typeof authRegisterEvent._payload;
5206
5286
 
5207
- export { type AuthConfig, AuthContext, type AuthLoginPayload, AuthProviderSchema, type AuthRegisterPayload, COOKIE_NAMES, type CreateOAuthStateParams, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, InvitationStatus, InvitationsRepository, KeyAlgorithmType, type KeyPair, KeysRepository, type NewInvitation, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, type OAuthState, type Permission, type PermissionEntity, PermissionsRepository, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionData, 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, acceptInvitation, addPermissionToRole, authLogger, authLoginEvent, authRegisterEvent, authSchema, cancelInvitation, configureAuth, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, deleteInvitation, deleteRole, exchangeCodeForTokens, expireOldInvitations, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getKeySize, getOptionalAuth, getRole, getRoleByName, getRolePermissions, getSessionInfo, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initializeAuth, invitationsRepository, isGoogleOAuthEnabled, keysRepository, listInvitations, parseDuration, permissions, permissionsRepository, refreshAccessToken, removePermissionFromRole, requireAnyPermission, requirePermissions, requireRole, resendInvitation, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, sealSession, setRolePermissions, shouldRefreshSession, shouldRotateKey, socialAccountsRepository, unsealSession, updateLastLoginService, updateRole, updateUserProfileService, updateUserService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyKeyFingerprint, verifyOAuthState, verifyPassword, verifyToken };
5287
+ export { type AuthConfig, AuthContext, type AuthLoginPayload, AuthProviderSchema, type AuthRegisterPayload, COOKIE_NAMES, type CreateOAuthStateParams, type GoogleTokenResponse, type GoogleUserInfo, type Invitation, InvitationStatus, InvitationsRepository, KeyAlgorithmType, type KeyPair, KeysRepository, type NewInvitation, type NewPermission, type NewPermissionEntity, type NewRole, type NewRoleEntity, type NewRolePermission, type NewUser, type NewUserPermission, type NewUserProfile, type NewUserPublicKey, type NewUserSocialAccount, type NewVerificationCode, type OAuthState, type Permission, type PermissionEntity, PermissionsRepository, type Role, type RoleEntity, type RoleGuardOptions, type RolePermission, RolePermissionsRepository, RolesRepository, type SessionData, 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, acceptInvitation, addPermissionToRole, authLogger, authLoginEvent, authRegisterEvent, authSchema, cancelInvitation, checkUsernameAvailableService, configureAuth, createAuthLifecycle, createInvitation, createOAuthState, createRole, decodeToken, deleteInvitation, deleteRole, exchangeCodeForTokens, expireOldInvitations, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, generateToken, getAllRoles, getAuth, getAuthConfig, getAuthSessionService, getGoogleAuthUrl, getGoogleOAuthConfig, getGoogleUserInfo, getInvitationByToken, getInvitationWithDetails, getKeyId, getKeySize, getOptionalAuth, getRole, getRoleByName, getRolePermissions, getSessionInfo, getSessionTtl, getUser, getUserByEmailService, getUserByIdService, getUserByPhoneService, getUserId, getUserPermissions, getUserProfileService, getUserRole, hasAllPermissions, hasAnyPermission, hasAnyRole, hasPermission, hasRole, hashPassword, initializeAuth, invitationsRepository, isGoogleOAuthEnabled, keysRepository, listInvitations, parseDuration, permissions, permissionsRepository, refreshAccessToken, removePermissionFromRole, requireAnyPermission, requirePermissions, requireRole, resendInvitation, roleGuard, rolePermissions, rolePermissionsRepository, roles, rolesRepository, sealSession, setRolePermissions, shouldRefreshSession, shouldRotateKey, socialAccountsRepository, unsealSession, updateLastLoginService, updateRole, updateUserProfileService, updateUserService, updateUsernameService, userInvitations, userPermissions, userPermissionsRepository, userProfiles, userProfilesRepository, userPublicKeys, userSocialAccounts, users, usersRepository, validateInvitation, validatePasswordStrength, verificationCodes, verificationCodesRepository, verifyClientToken, verifyKeyFingerprint, verifyOAuthState, verifyPassword, verifyToken };
package/dist/server.js CHANGED
@@ -4586,6 +4586,9 @@ var init_users = __esm({
4586
4586
  // Format: +[country code][number] (e.g., +821012345678)
4587
4587
  // Used for: SMS login, 2FA, notifications
4588
4588
  phone: text2("phone").unique(),
4589
+ // Username (unique, optional)
4590
+ // Used for: display, mention, public profile URL
4591
+ username: text2("username").unique(),
4589
4592
  // Authentication
4590
4593
  // Bcrypt password hash ($2b$10$[salt][hash], 60 chars)
4591
4594
  // Nullable to support OAuth-only accounts
@@ -4625,6 +4628,7 @@ var init_users = __esm({
4625
4628
  // Indexes for query optimization
4626
4629
  index2("users_email_idx").on(table.email),
4627
4630
  index2("users_phone_idx").on(table.phone),
4631
+ index2("users_username_idx").on(table.username),
4628
4632
  index2("users_status_idx").on(table.status),
4629
4633
  index2("users_role_id_idx").on(table.roleId)
4630
4634
  ]
@@ -5343,6 +5347,14 @@ var init_users_repository = __esm({
5343
5347
  const result = await this.readDb.select().from(users).where(eq(users.phone, phone)).limit(1);
5344
5348
  return result[0] ?? null;
5345
5349
  }
5350
+ /**
5351
+ * 사용자명으로 사용자 조회
5352
+ * Read replica 사용
5353
+ */
5354
+ async findByUsername(username) {
5355
+ const result = await this.readDb.select().from(users).where(eq(users.username, username)).limit(1);
5356
+ return result[0] ?? null;
5357
+ }
5346
5358
  /**
5347
5359
  * 이메일 또는 전화번호로 사용자 조회
5348
5360
  * Read replica 사용
@@ -5484,6 +5496,7 @@ var init_users_repository = __esm({
5484
5496
  const user = await this.readDb.select({
5485
5497
  id: users.id,
5486
5498
  email: users.email,
5499
+ username: users.username,
5487
5500
  emailVerifiedAt: users.emailVerifiedAt,
5488
5501
  phoneVerifiedAt: users.phoneVerifiedAt
5489
5502
  }).from(users).where(eq(users.id, userId)).limit(1).then((rows) => rows[0] ?? null);
@@ -5493,6 +5506,7 @@ var init_users_repository = __esm({
5493
5506
  return {
5494
5507
  userId: user.id,
5495
5508
  email: user.email,
5509
+ username: user.username,
5496
5510
  isEmailVerified: !!user.emailVerifiedAt,
5497
5511
  isPhoneVerified: !!user.phoneVerifiedAt
5498
5512
  };
@@ -5508,6 +5522,7 @@ var init_users_repository = __esm({
5508
5522
  const user = await this.readDb.select({
5509
5523
  id: users.id,
5510
5524
  email: users.email,
5525
+ username: users.username,
5511
5526
  emailVerifiedAt: users.emailVerifiedAt,
5512
5527
  phoneVerifiedAt: users.phoneVerifiedAt,
5513
5528
  lastLoginAt: users.lastLoginAt,
@@ -5520,6 +5535,7 @@ var init_users_repository = __esm({
5520
5535
  return {
5521
5536
  userId: user.id,
5522
5537
  email: user.email,
5538
+ username: user.username,
5523
5539
  isEmailVerified: !!user.emailVerifiedAt,
5524
5540
  isPhoneVerified: !!user.phoneVerifiedAt,
5525
5541
  lastLoginAt: user.lastLoginAt,
@@ -7031,6 +7047,7 @@ async function revokeKeyService(params) {
7031
7047
 
7032
7048
  // src/server/services/user.service.ts
7033
7049
  init_repositories();
7050
+ import { UsernameAlreadyTakenError } from "@spfn/auth/errors";
7034
7051
  async function getUserByIdService(userId) {
7035
7052
  return await usersRepository.findById(userId);
7036
7053
  }
@@ -7046,6 +7063,20 @@ async function updateLastLoginService(userId) {
7046
7063
  async function updateUserService(userId, updates) {
7047
7064
  await usersRepository.updateById(userId, updates);
7048
7065
  }
7066
+ async function checkUsernameAvailableService(username) {
7067
+ const existing = await usersRepository.findByUsername(username);
7068
+ return !existing;
7069
+ }
7070
+ async function updateUsernameService(userId, username) {
7071
+ const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
7072
+ if (username !== null) {
7073
+ const existing = await usersRepository.findByUsername(username);
7074
+ if (existing && existing.id !== userIdNum) {
7075
+ throw new UsernameAlreadyTakenError({ username });
7076
+ }
7077
+ }
7078
+ return await usersRepository.updateById(userIdNum, { username });
7079
+ }
7049
7080
 
7050
7081
  // src/server/events/index.ts
7051
7082
  init_esm();
@@ -8719,9 +8750,31 @@ var updateUserProfile = route3.patch("/_auth/users/profile").input({
8719
8750
  const { body } = await c.data();
8720
8751
  return await updateUserProfileService(userId, body);
8721
8752
  });
8753
+ var checkUsername = route3.get("/_auth/users/username/check").input({
8754
+ query: Type.Object({
8755
+ username: Type.String({ minLength: 1 })
8756
+ })
8757
+ }).handler(async (c) => {
8758
+ const { query } = await c.data();
8759
+ return { available: await checkUsernameAvailableService(query.username) };
8760
+ });
8761
+ var updateUsername = route3.patch("/_auth/users/username").input({
8762
+ body: Type.Object({
8763
+ username: Type.Union([
8764
+ Type.String({ minLength: 1 }),
8765
+ Type.Null()
8766
+ ], { description: "New username or null to clear" })
8767
+ })
8768
+ }).handler(async (c) => {
8769
+ const { userId } = getAuth(c);
8770
+ const { body } = await c.data();
8771
+ return await updateUsernameService(userId, body.username);
8772
+ });
8722
8773
  var userRouter = defineRouter3({
8723
8774
  getUserProfile,
8724
- updateUserProfile
8775
+ updateUserProfile,
8776
+ checkUsername,
8777
+ updateUsername
8725
8778
  });
8726
8779
 
8727
8780
  // src/server/routes/oauth/index.ts
@@ -9307,6 +9360,7 @@ export {
9307
9360
  cancelInvitation,
9308
9361
  changePasswordService,
9309
9362
  checkAccountExistsService,
9363
+ checkUsernameAvailableService,
9310
9364
  configureAuth,
9311
9365
  createAuthLifecycle,
9312
9366
  createInvitation,
@@ -9395,6 +9449,7 @@ export {
9395
9449
  updateRole,
9396
9450
  updateUserProfileService,
9397
9451
  updateUserService,
9452
+ updateUsernameService,
9398
9453
  userInvitations,
9399
9454
  userPermissions,
9400
9455
  userPermissionsRepository,