@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/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/{dto-CLYtuAom.d.ts → dto-Bb2qFUO6.d.ts} +23 -4
- package/dist/index.d.ts +24 -6
- package/dist/nextjs/server.js +0 -1
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +136 -5
- package/dist/server.js +166 -4
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
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
|
|
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
|
|
7699
|
+
return null;
|
|
7681
7700
|
}
|
|
7682
7701
|
const role = await rolesRepository.findById(user.roleId);
|
|
7683
|
-
return role?.name
|
|
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,
|