@spfn/auth 0.2.0-beta.3 → 0.2.0-beta.5

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.js CHANGED
@@ -6132,6 +6132,23 @@ var init_user_profiles_repository = __esm({
6132
6132
  const result = await this.db.delete(userProfiles).where(eq8(userProfiles.userId, userId)).returning();
6133
6133
  return result[0] ?? null;
6134
6134
  }
6135
+ /**
6136
+ * 프로필 Upsert (by User ID)
6137
+ *
6138
+ * 프로필이 없으면 생성, 있으면 업데이트
6139
+ * 새로 생성 시 displayName은 필수 (없으면 'User'로 설정)
6140
+ */
6141
+ async upsertByUserId(userId, data) {
6142
+ const existing = await this.findByUserId(userId);
6143
+ if (existing) {
6144
+ return await this.updateByUserId(userId, data);
6145
+ }
6146
+ return await this.create({
6147
+ userId,
6148
+ displayName: data.displayName || "User",
6149
+ ...data
6150
+ });
6151
+ }
6135
6152
  /**
6136
6153
  * User ID로 프로필 데이터 조회 (formatted)
6137
6154
  *
@@ -6151,6 +6168,7 @@ var init_user_profiles_repository = __esm({
6151
6168
  location: userProfiles.location,
6152
6169
  company: userProfiles.company,
6153
6170
  jobTitle: userProfiles.jobTitle,
6171
+ metadata: userProfiles.metadata,
6154
6172
  createdAt: userProfiles.createdAt,
6155
6173
  updatedAt: userProfiles.updatedAt
6156
6174
  }).from(userProfiles).where(eq8(userProfiles.userId, userId)).limit(1).then((rows) => rows[0] ?? null);
@@ -6170,6 +6188,7 @@ var init_user_profiles_repository = __esm({
6170
6188
  location: profile.location,
6171
6189
  company: profile.company,
6172
6190
  jobTitle: profile.jobTitle,
6191
+ metadata: profile.metadata,
6173
6192
  createdAt: profile.createdAt,
6174
6193
  updatedAt: profile.updatedAt
6175
6194
  };
@@ -7673,14 +7692,18 @@ async function hasAllPermissions(userId, permissionNames) {
7673
7692
  const perms = await getUserPermissions(userId);
7674
7693
  return permissionNames.every((p) => perms.includes(p));
7675
7694
  }
7676
- async function hasRole(userId, roleName) {
7695
+ async function getUserRole(userId) {
7677
7696
  const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
7678
7697
  const user = await usersRepository.findById(userIdNum);
7679
7698
  if (!user || !user.roleId) {
7680
- return false;
7699
+ return null;
7681
7700
  }
7682
7701
  const role = await rolesRepository.findById(user.roleId);
7683
- return role?.name === roleName;
7702
+ return role?.name || null;
7703
+ }
7704
+ async function hasRole(userId, roleName) {
7705
+ const role = await getUserRole(userId);
7706
+ return role === roleName;
7684
7707
  }
7685
7708
  async function hasAnyRole(userId, roleNames) {
7686
7709
  for (const roleName of roleNames) {
@@ -7887,6 +7910,65 @@ async function getUserProfileService(userId) {
7887
7910
  profile
7888
7911
  };
7889
7912
  }
7913
+ function emptyToNull(value) {
7914
+ if (value === "") {
7915
+ return null;
7916
+ }
7917
+ return value;
7918
+ }
7919
+ async function updateUserProfileService(userId, params) {
7920
+ const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
7921
+ const updateData = {};
7922
+ if (params.displayName !== void 0) {
7923
+ updateData.displayName = emptyToNull(params.displayName) || "User";
7924
+ }
7925
+ if (params.firstName !== void 0) {
7926
+ updateData.firstName = emptyToNull(params.firstName);
7927
+ }
7928
+ if (params.lastName !== void 0) {
7929
+ updateData.lastName = emptyToNull(params.lastName);
7930
+ }
7931
+ if (params.avatarUrl !== void 0) {
7932
+ updateData.avatarUrl = emptyToNull(params.avatarUrl);
7933
+ }
7934
+ if (params.bio !== void 0) {
7935
+ updateData.bio = emptyToNull(params.bio);
7936
+ }
7937
+ if (params.locale !== void 0) {
7938
+ updateData.locale = emptyToNull(params.locale) || "en";
7939
+ }
7940
+ if (params.timezone !== void 0) {
7941
+ updateData.timezone = emptyToNull(params.timezone) || "UTC";
7942
+ }
7943
+ if (params.dateOfBirth !== void 0) {
7944
+ updateData.dateOfBirth = emptyToNull(params.dateOfBirth);
7945
+ }
7946
+ if (params.gender !== void 0) {
7947
+ updateData.gender = emptyToNull(params.gender);
7948
+ }
7949
+ if (params.website !== void 0) {
7950
+ updateData.website = emptyToNull(params.website);
7951
+ }
7952
+ if (params.location !== void 0) {
7953
+ updateData.location = emptyToNull(params.location);
7954
+ }
7955
+ if (params.company !== void 0) {
7956
+ updateData.company = emptyToNull(params.company);
7957
+ }
7958
+ if (params.jobTitle !== void 0) {
7959
+ updateData.jobTitle = emptyToNull(params.jobTitle);
7960
+ }
7961
+ if (params.metadata !== void 0) {
7962
+ updateData.metadata = params.metadata;
7963
+ }
7964
+ const existing = await userProfilesRepository.findByUserId(userIdNum);
7965
+ if (!existing && !updateData.displayName) {
7966
+ updateData.displayName = "User";
7967
+ }
7968
+ await userProfilesRepository.upsertByUserId(userIdNum, updateData);
7969
+ const profile = await userProfilesRepository.fetchProfileData(userIdNum);
7970
+ return profile;
7971
+ }
7890
7972
 
7891
7973
  // src/server/routes/auth/index.ts
7892
7974
  init_esm();
@@ -8223,6 +8305,59 @@ var requireRole = defineMiddleware3(
8223
8305
  }
8224
8306
  );
8225
8307
 
8308
+ // src/server/middleware/role-guard.ts
8309
+ import { defineMiddleware as defineMiddleware4 } from "@spfn/core/route";
8310
+ import { getAuth as getAuth4, getUserRole as getUserRole2, authLogger as authLogger5 } from "@spfn/auth/server";
8311
+ import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
8312
+ import { InsufficientRoleError as InsufficientRoleError2 } from "@spfn/auth/errors";
8313
+ var roleGuard = defineMiddleware4(
8314
+ "roleGuard",
8315
+ (options) => async (c, next) => {
8316
+ const { allow, deny } = options;
8317
+ if (!allow && !deny) {
8318
+ throw new Error("roleGuard requires at least one of: allow, deny");
8319
+ }
8320
+ const auth = getAuth4(c);
8321
+ if (!auth) {
8322
+ authLogger5.middleware.warn("Role guard failed: not authenticated", {
8323
+ path: c.req.path
8324
+ });
8325
+ throw new ForbiddenError3({ message: "Authentication required" });
8326
+ }
8327
+ const { userId } = auth;
8328
+ const userRole = await getUserRole2(userId);
8329
+ if (deny && deny.length > 0) {
8330
+ if (userRole && deny.includes(userRole)) {
8331
+ authLogger5.middleware.warn("Role guard denied", {
8332
+ userId,
8333
+ userRole,
8334
+ deniedRoles: deny,
8335
+ path: c.req.path
8336
+ });
8337
+ throw new InsufficientRoleError2({ requiredRoles: allow || [] });
8338
+ }
8339
+ }
8340
+ if (allow && allow.length > 0) {
8341
+ if (!userRole || !allow.includes(userRole)) {
8342
+ authLogger5.middleware.warn("Role guard failed: role not allowed", {
8343
+ userId,
8344
+ userRole,
8345
+ allowedRoles: allow,
8346
+ path: c.req.path
8347
+ });
8348
+ throw new InsufficientRoleError2({ requiredRoles: allow });
8349
+ }
8350
+ }
8351
+ authLogger5.middleware.debug("Role guard passed", {
8352
+ userId,
8353
+ userRole,
8354
+ allow,
8355
+ deny
8356
+ });
8357
+ await next();
8358
+ }
8359
+ );
8360
+
8226
8361
  // src/server/routes/invitations/index.ts
8227
8362
  init_types();
8228
8363
  init_esm();
@@ -8416,13 +8551,37 @@ var invitationRouter = defineRouter2({
8416
8551
  });
8417
8552
 
8418
8553
  // src/server/routes/users/index.ts
8554
+ init_esm();
8419
8555
  import { defineRouter as defineRouter3, route as route3 } from "@spfn/core/route";
8420
8556
  var getUserProfile = route3.get("/_auth/users/profile").handler(async (c) => {
8421
8557
  const { userId } = getAuth(c);
8422
8558
  return await getUserProfileService(userId);
8423
8559
  });
8560
+ var updateUserProfile = route3.patch("/_auth/users/profile").input({
8561
+ body: Type.Object({
8562
+ displayName: Type.Optional(Type.String({ description: "Display name shown in UI" })),
8563
+ firstName: Type.Optional(Type.String({ description: "First name" })),
8564
+ lastName: Type.Optional(Type.String({ description: "Last name" })),
8565
+ avatarUrl: Type.Optional(Type.String({ description: "Avatar/profile picture URL" })),
8566
+ bio: Type.Optional(Type.String({ description: "Short bio/description" })),
8567
+ locale: Type.Optional(Type.String({ description: "Locale/language preference (e.g., en, ko)" })),
8568
+ timezone: Type.Optional(Type.String({ description: "Timezone (e.g., Asia/Seoul)" })),
8569
+ dateOfBirth: Type.Optional(Type.String({ description: "Date of birth (YYYY-MM-DD)" })),
8570
+ gender: Type.Optional(Type.String({ description: "Gender" })),
8571
+ website: Type.Optional(Type.String({ description: "Personal or professional website" })),
8572
+ location: Type.Optional(Type.String({ description: "Location (city, country, etc.)" })),
8573
+ company: Type.Optional(Type.String({ description: "Company name" })),
8574
+ jobTitle: Type.Optional(Type.String({ description: "Job title" })),
8575
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "Additional metadata" }))
8576
+ })
8577
+ }).handler(async (c) => {
8578
+ const { userId } = getAuth(c);
8579
+ const { body } = await c.data();
8580
+ return await updateUserProfileService(userId, body);
8581
+ });
8424
8582
  var userRouter = defineRouter3({
8425
- getUserProfile
8583
+ getUserProfile,
8584
+ updateUserProfile
8426
8585
  });
8427
8586
 
8428
8587
  // src/server/routes/index.ts
@@ -8805,6 +8964,7 @@ export {
8805
8964
  getUserId,
8806
8965
  getUserPermissions,
8807
8966
  getUserProfileService,
8967
+ getUserRole,
8808
8968
  getVerificationCodeTemplate,
8809
8969
  getWelcomeTemplate,
8810
8970
  hasAllPermissions,
@@ -8833,6 +8993,7 @@ export {
8833
8993
  requireRole,
8834
8994
  resendInvitation,
8835
8995
  revokeKeyService,
8996
+ roleGuard,
8836
8997
  rolePermissions,
8837
8998
  rolePermissionsRepository,
8838
8999
  roles,
@@ -8848,6 +9009,7 @@ export {
8848
9009
  unsealSession,
8849
9010
  updateLastLoginService,
8850
9011
  updateRole,
9012
+ updateUserProfileService,
8851
9013
  updateUserService,
8852
9014
  userInvitations,
8853
9015
  userPermissions,