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

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/README.md CHANGED
@@ -602,6 +602,7 @@ import {
602
602
  ```typescript
603
603
  import {
604
604
  authenticate,
605
+ optionalAuth,
605
606
  requirePermissions,
606
607
  requireAnyPermission,
607
608
  requireRole,
@@ -624,6 +625,18 @@ app.bind(
624
625
  // User has either content:read OR admin:access
625
626
  }
626
627
  );
628
+
629
+ // Usage - optional auth (public route with optional user context)
630
+ // Auto-skips global 'auth' middleware — no .skip(['auth']) needed
631
+ export const getProducts = route.get('/products')
632
+ .use([optionalAuth])
633
+ .handler(async (c) => {
634
+ const auth = getOptionalAuth(c); // AuthContext | undefined
635
+ if (auth) {
636
+ return getPersonalizedProducts(auth.userId);
637
+ }
638
+ return getPublicProducts();
639
+ });
627
640
  ```
628
641
 
629
642
  **Helpers:**
@@ -631,6 +644,7 @@ app.bind(
631
644
  import {
632
645
  // Context
633
646
  getAuth,
647
+ getOptionalAuth,
634
648
  getUser,
635
649
  getUserId,
636
650
  getKeyId,
@@ -2412,6 +2426,6 @@ MIT License - See LICENSE file for details.
2412
2426
 
2413
2427
  ---
2414
2428
 
2415
- **Last Updated:** 2026-01-29
2416
- **Document Version:** 2.5.0 (Technical Documentation)
2429
+ **Last Updated:** 2026-02-23
2430
+ **Document Version:** 2.6.0 (Technical Documentation)
2417
2431
  **Package Version:** 0.2.0-beta.15
@@ -799,6 +799,7 @@ interface AuthContext {
799
799
  user: User;
800
800
  userId: string;
801
801
  keyId: string;
802
+ role: string | null;
802
803
  }
803
804
  declare module 'hono' {
804
805
  interface ContextVariableMap {
@@ -836,5 +837,33 @@ declare module 'hono' {
836
837
  * ```
837
838
  */
838
839
  declare const authenticate: _spfn_core_route.NamedMiddleware<"auth">;
840
+ /**
841
+ * Optional authentication middleware
842
+ *
843
+ * Same as `authenticate` but does NOT reject unauthenticated requests.
844
+ * - No token → continues without auth context
845
+ * - Invalid token → continues without auth context
846
+ * - Valid token → sets auth context normally
847
+ *
848
+ * Auto-skips the global 'auth' middleware when used at route level.
849
+ *
850
+ * @example
851
+ * ```typescript
852
+ * // No need for .skip(['auth']) — handled automatically
853
+ * export const getProducts = route.get('/products')
854
+ * .use([optionalAuth])
855
+ * .handler(async (c) => {
856
+ * const auth = getOptionalAuth(c); // AuthContext | undefined
857
+ *
858
+ * if (auth)
859
+ * {
860
+ * return getPersonalizedProducts(auth.userId);
861
+ * }
862
+ *
863
+ * return getPublicProducts();
864
+ * });
865
+ * ```
866
+ */
867
+ declare const optionalAuth: _spfn_core_route.NamedMiddleware<"optionalAuth">;
839
868
 
840
- export { getEnabledOAuthProviders as $, type AuthSession as A, type ChangePasswordParams as B, type CheckAccountExistsResult as C, sendVerificationCodeService as D, verifyCodeService as E, type SendVerificationCodeParams as F, type VerifyCodeParams as G, type VerifyCodeResult as H, INVITATION_STATUSES as I, registerPublicKeyService as J, KEY_ALGORITHM as K, type LoginResult as L, rotateKeyService as M, revokeKeyService as N, type OAuthStartResult as O, type PermissionConfig as P, type RegisterPublicKeyParams as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RotateKeyParams as T, type UserProfile as U, type VerificationTargetType as V, type RevokeKeyParams as W, oauthStartService as X, oauthCallbackService as Y, buildOAuthErrorUrl as Z, isOAuthProviderEnabled as _, type RegisterResult as a, getGoogleAccessToken as a0, type OAuthStartParams as a1, type OAuthCallbackParams as a2, type OAuthCallbackResult as a3, authenticate as a4, EmailSchema as a5, PhoneSchema as a6, PasswordSchema as a7, TargetTypeSchema as a8, VerificationPurposeSchema as a9, type RotateKeyResult as b, type ProfileInfo as c, USER_STATUSES as d, SOCIAL_PROVIDERS as e, type VerificationPurpose as f, VERIFICATION_TARGET_TYPES as g, VERIFICATION_PURPOSES as h, PERMISSION_CATEGORIES as i, type PermissionCategory as j, type AuthInitOptions as k, type KeyAlgorithmType as l, mainAuthRouter as m, type InvitationStatus as n, type UserStatus as o, type SocialProvider as p, type AuthContext as q, checkAccountExistsService as r, registerService as s, loginService as t, logoutService as u, changePasswordService as v, type CheckAccountExistsParams as w, type RegisterParams as x, type LoginParams as y, type LogoutParams as z };
869
+ export { getEnabledOAuthProviders as $, type AuthSession as A, type ChangePasswordParams as B, type CheckAccountExistsResult as C, sendVerificationCodeService as D, verifyCodeService as E, type SendVerificationCodeParams as F, type VerifyCodeParams as G, type VerifyCodeResult as H, INVITATION_STATUSES as I, registerPublicKeyService as J, KEY_ALGORITHM as K, type LoginResult as L, rotateKeyService as M, revokeKeyService as N, type OAuthStartResult as O, type PermissionConfig as P, type RegisterPublicKeyParams as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RotateKeyParams as T, type UserProfile as U, type VerificationTargetType as V, type RevokeKeyParams as W, oauthStartService as X, oauthCallbackService as Y, buildOAuthErrorUrl as Z, isOAuthProviderEnabled as _, type RegisterResult as a, getGoogleAccessToken as a0, type OAuthStartParams as a1, type OAuthCallbackParams as a2, type OAuthCallbackResult as a3, authenticate as a4, optionalAuth as a5, EmailSchema as a6, PhoneSchema as a7, PasswordSchema as a8, TargetTypeSchema as a9, VerificationPurposeSchema as aa, type RotateKeyResult as b, type ProfileInfo as c, USER_STATUSES as d, SOCIAL_PROVIDERS as e, type VerificationPurpose as f, VERIFICATION_TARGET_TYPES as g, VERIFICATION_PURPOSES as h, PERMISSION_CATEGORIES as i, type PermissionCategory as j, type AuthInitOptions as k, type KeyAlgorithmType as l, mainAuthRouter as m, type InvitationStatus as n, type UserStatus as o, type SocialProvider as p, type AuthContext as q, checkAccountExistsService as r, registerService as s, loginService as t, logoutService as u, changePasswordService as v, type CheckAccountExistsParams as w, type RegisterParams as x, type LoginParams as y, type LogoutParams as z };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _spfn_core_nextjs from '@spfn/core/nextjs';
2
- import { R as RoleConfig, P as PermissionConfig, C as CheckAccountExistsResult, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, O as OAuthStartResult, U as UserProfile, c as ProfileInfo, m as mainAuthRouter } from './authenticate-BmzJ6hTF.js';
3
- export { k as AuthInitOptions, A as AuthSession, I as INVITATION_STATUSES, n as InvitationStatus, K as KEY_ALGORITHM, l as KeyAlgorithmType, i as PERMISSION_CATEGORIES, j as PermissionCategory, e as SOCIAL_PROVIDERS, p as SocialProvider, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, f as VerificationPurpose, V as VerificationTargetType } from './authenticate-BmzJ6hTF.js';
2
+ import { R as RoleConfig, P as PermissionConfig, C as CheckAccountExistsResult, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, O as OAuthStartResult, U as UserProfile, c as ProfileInfo, m as mainAuthRouter } from './authenticate-kCg_KD-V.js';
3
+ export { k as AuthInitOptions, A as AuthSession, I as INVITATION_STATUSES, n as InvitationStatus, K as KEY_ALGORITHM, l as KeyAlgorithmType, i as PERMISSION_CATEGORIES, j as PermissionCategory, e as SOCIAL_PROVIDERS, p as SocialProvider, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, f as VerificationPurpose, V as VerificationTargetType } from './authenticate-kCg_KD-V.js';
4
4
  import * as _spfn_core_route from '@spfn/core/route';
5
5
  import { HttpMethod } from '@spfn/core/route';
6
6
  import * as _sinclair_typebox from '@sinclair/typebox';
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { k as AuthInitOptions, l as KeyAlgorithmType, n as InvitationStatus, f as VerificationPurpose, j as PermissionCategory, p as SocialProvider, q as AuthContext } from './authenticate-BmzJ6hTF.js';
2
- export { B as ChangePasswordParams, w as CheckAccountExistsParams, C as CheckAccountExistsResult, a5 as EmailSchema, I as INVITATION_STATUSES, K as KEY_ALGORITHM, y as LoginParams, L as LoginResult, z as LogoutParams, a2 as OAuthCallbackParams, a3 as OAuthCallbackResult, a1 as OAuthStartParams, O as OAuthStartResult, a7 as PasswordSchema, a6 as PhoneSchema, x as RegisterParams, Q as RegisterPublicKeyParams, a as RegisterResult, W as RevokeKeyParams, T as RotateKeyParams, b as RotateKeyResult, e as SOCIAL_PROVIDERS, F as SendVerificationCodeParams, S as SendVerificationCodeResult, a8 as TargetTypeSchema, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, a9 as VerificationPurposeSchema, V as VerificationTargetType, G as VerifyCodeParams, H as VerifyCodeResult, m as authRouter, a4 as authenticate, Z as buildOAuthErrorUrl, v as changePasswordService, r as checkAccountExistsService, $ as getEnabledOAuthProviders, a0 as getGoogleAccessToken, _ as isOAuthProviderEnabled, t as loginService, u as logoutService, Y as oauthCallbackService, X as oauthStartService, J as registerPublicKeyService, s as registerService, N as revokeKeyService, M as rotateKeyService, D as sendVerificationCodeService, E as verifyCodeService } from './authenticate-BmzJ6hTF.js';
1
+ import { k as AuthInitOptions, l as KeyAlgorithmType, n as InvitationStatus, f as VerificationPurpose, j as PermissionCategory, p as SocialProvider, q as AuthContext } from './authenticate-kCg_KD-V.js';
2
+ export { B as ChangePasswordParams, w as CheckAccountExistsParams, C as CheckAccountExistsResult, a6 as EmailSchema, I as INVITATION_STATUSES, K as KEY_ALGORITHM, y as LoginParams, L as LoginResult, z as LogoutParams, a2 as OAuthCallbackParams, a3 as OAuthCallbackResult, a1 as OAuthStartParams, O as OAuthStartResult, a8 as PasswordSchema, a7 as PhoneSchema, x as RegisterParams, Q as RegisterPublicKeyParams, a as RegisterResult, W as RevokeKeyParams, T as RotateKeyParams, b as RotateKeyResult, e as SOCIAL_PROVIDERS, F as SendVerificationCodeParams, S as SendVerificationCodeResult, a9 as TargetTypeSchema, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, aa as VerificationPurposeSchema, V as VerificationTargetType, G as VerifyCodeParams, H as VerifyCodeResult, m as authRouter, a4 as authenticate, Z as buildOAuthErrorUrl, v as changePasswordService, r as checkAccountExistsService, $ as getEnabledOAuthProviders, a0 as getGoogleAccessToken, _ as isOAuthProviderEnabled, t as loginService, u as logoutService, Y as oauthCallbackService, X as oauthStartService, a5 as optionalAuth, J as registerPublicKeyService, s as registerService, N as revokeKeyService, M as rotateKeyService, D as sendVerificationCodeService, E as verifyCodeService } from './authenticate-kCg_KD-V.js';
3
3
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
4
4
  import { UserProfile as UserProfile$1, ProfileInfo } from '@spfn/auth';
5
5
  import { BaseRepository } from '@spfn/core/db';
@@ -2827,6 +2827,33 @@ declare class UsersRepository extends BaseRepository {
2827
2827
  phoneVerifiedAt: Date | null;
2828
2828
  lastLoginAt: Date | null;
2829
2829
  } | null>;
2830
+ /**
2831
+ * ID로 사용자 + Role 조회 (leftJoin)
2832
+ * Read replica 사용
2833
+ *
2834
+ * roleId가 null인 유저는 role: null 반환
2835
+ */
2836
+ findByIdWithRole(id: number): Promise<{
2837
+ user: {
2838
+ createdAt: Date;
2839
+ updatedAt: Date;
2840
+ id: number;
2841
+ email: string | null;
2842
+ phone: string | null;
2843
+ passwordHash: string | null;
2844
+ passwordChangeRequired: boolean;
2845
+ roleId: number;
2846
+ status: "active" | "inactive" | "suspended";
2847
+ emailVerifiedAt: Date | null;
2848
+ phoneVerifiedAt: Date | null;
2849
+ lastLoginAt: Date | null;
2850
+ };
2851
+ role: {
2852
+ name: string;
2853
+ displayName: string;
2854
+ priority: number;
2855
+ } | null;
2856
+ } | null>;
2830
2857
  /**
2831
2858
  * 사용자 생성
2832
2859
  * Write primary 사용
@@ -4640,6 +4667,32 @@ declare const roleGuard: _spfn_core_route.NamedMiddlewareFactory<"roleGuard", [o
4640
4667
  declare function getAuth(c: Context | {
4641
4668
  raw: Context;
4642
4669
  }): AuthContext;
4670
+ /**
4671
+ * Get optional auth context from route context
4672
+ *
4673
+ * Returns AuthContext if authenticated, undefined otherwise.
4674
+ * Use with `optionalAuth` middleware for routes that serve both
4675
+ * authenticated and unauthenticated users.
4676
+ *
4677
+ * @example
4678
+ * ```typescript
4679
+ * export const getProducts = route.get('/products')
4680
+ * .use([optionalAuth])
4681
+ * .handler(async (c) => {
4682
+ * const auth = getOptionalAuth(c);
4683
+ *
4684
+ * if (auth)
4685
+ * {
4686
+ * return getPersonalizedProducts(auth.userId);
4687
+ * }
4688
+ *
4689
+ * return getPublicProducts();
4690
+ * });
4691
+ * ```
4692
+ */
4693
+ declare function getOptionalAuth(c: Context | {
4694
+ raw: Context;
4695
+ }): AuthContext | undefined;
4643
4696
  /**
4644
4697
  * Get authenticated user from route context
4645
4698
  *
@@ -4681,6 +4734,22 @@ declare function getUser(c: Context | {
4681
4734
  declare function getUserId(c: Context | {
4682
4735
  raw: Context;
4683
4736
  }): string;
4737
+ /**
4738
+ * Get authenticated user's role from route context
4739
+ *
4740
+ * @returns Role name or null if user has no role
4741
+ *
4742
+ * @example
4743
+ * ```typescript
4744
+ * app.bind(adminContract, [authenticate], async (c) => {
4745
+ * const role = getRole(c);
4746
+ * // 'admin' | 'superadmin' | null
4747
+ * });
4748
+ * ```
4749
+ */
4750
+ declare function getRole(c: Context | {
4751
+ raw: Context;
4752
+ }): string | null;
4684
4753
  /**
4685
4754
  * Get current key ID from route context
4686
4755
  *
@@ -5135,4 +5204,4 @@ declare const authRegisterEvent: _spfn_core_event.EventDef<{
5135
5204
  type AuthLoginPayload = typeof authLoginEvent._payload;
5136
5205
  type AuthRegisterPayload = typeof authRegisterEvent._payload;
5137
5206
 
5138
- 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, 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 };
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 };
package/dist/server.js CHANGED
@@ -5355,6 +5355,28 @@ var init_users_repository = __esm({
5355
5355
  }
5356
5356
  return null;
5357
5357
  }
5358
+ /**
5359
+ * ID로 사용자 + Role 조회 (leftJoin)
5360
+ * Read replica 사용
5361
+ *
5362
+ * roleId가 null인 유저는 role: null 반환
5363
+ */
5364
+ async findByIdWithRole(id11) {
5365
+ const result = await this.readDb.select({
5366
+ user: users,
5367
+ roleName: roles.name,
5368
+ roleDisplayName: roles.displayName,
5369
+ rolePriority: roles.priority
5370
+ }).from(users).leftJoin(roles, eq(users.roleId, roles.id)).where(eq(users.id, id11)).limit(1);
5371
+ const row = result[0];
5372
+ if (!row) {
5373
+ return null;
5374
+ }
5375
+ return {
5376
+ user: row.user,
5377
+ role: row.roleName ? { name: row.roleName, displayName: row.roleDisplayName, priority: row.rolePriority } : null
5378
+ };
5379
+ }
5358
5380
  /**
5359
5381
  * 사용자 생성
5360
5382
  * Write primary 사용
@@ -6760,12 +6782,21 @@ function getAuth(c) {
6760
6782
  }
6761
6783
  return c.get("auth");
6762
6784
  }
6785
+ function getOptionalAuth(c) {
6786
+ if ("raw" in c && c.raw) {
6787
+ return c.raw.get("auth");
6788
+ }
6789
+ return c.get("auth");
6790
+ }
6763
6791
  function getUser(c) {
6764
6792
  return getAuth(c).user;
6765
6793
  }
6766
6794
  function getUserId(c) {
6767
6795
  return getAuth(c).userId;
6768
6796
  }
6797
+ function getRole(c) {
6798
+ return getAuth(c).role;
6799
+ }
6769
6800
  function getKeyId(c) {
6770
6801
  return getAuth(c).keyId;
6771
6802
  }
@@ -8244,10 +8275,11 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
8244
8275
  }
8245
8276
  throw new UnauthorizedError({ message: "Authentication failed" });
8246
8277
  }
8247
- const user = await usersRepository2.findById(keyRecord.userId);
8248
- if (!user) {
8278
+ const result = await usersRepository2.findByIdWithRole(keyRecord.userId);
8279
+ if (!result) {
8249
8280
  throw new UnauthorizedError({ message: "User not found" });
8250
8281
  }
8282
+ const { user, role } = result;
8251
8283
  if (user.status !== "active") {
8252
8284
  throw new AccountDisabledError2({ status: user.status });
8253
8285
  }
@@ -8255,7 +8287,8 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
8255
8287
  c.set("auth", {
8256
8288
  user,
8257
8289
  userId: String(user.id),
8258
- keyId
8290
+ keyId,
8291
+ role: role?.name ?? null
8259
8292
  });
8260
8293
  const method = c.req.method;
8261
8294
  const path = c.req.path;
@@ -8270,6 +8303,51 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
8270
8303
  });
8271
8304
  await next();
8272
8305
  });
8306
+ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
8307
+ const authHeader = c.req.header("Authorization");
8308
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
8309
+ await next();
8310
+ return;
8311
+ }
8312
+ const token = authHeader.substring(7);
8313
+ try {
8314
+ const decoded = decodeToken2(token);
8315
+ if (!decoded || !decoded.keyId) {
8316
+ await next();
8317
+ return;
8318
+ }
8319
+ const keyId = decoded.keyId;
8320
+ const keyRecord = await keysRepository2.findActiveByKeyId(keyId);
8321
+ if (!keyRecord) {
8322
+ await next();
8323
+ return;
8324
+ }
8325
+ if (keyRecord.expiresAt && /* @__PURE__ */ new Date() > keyRecord.expiresAt) {
8326
+ await next();
8327
+ return;
8328
+ }
8329
+ verifyClientToken2(
8330
+ token,
8331
+ keyRecord.publicKey,
8332
+ keyRecord.algorithm
8333
+ );
8334
+ const result = await usersRepository2.findByIdWithRole(keyRecord.userId);
8335
+ if (!result || result.user.status !== "active") {
8336
+ await next();
8337
+ return;
8338
+ }
8339
+ const { user, role } = result;
8340
+ keysRepository2.updateLastUsedById(keyRecord.id).catch((err) => authLogger2.middleware.error("Failed to update lastUsedAt", err));
8341
+ c.set("auth", {
8342
+ user,
8343
+ userId: String(user.id),
8344
+ keyId,
8345
+ role: role?.name ?? null
8346
+ });
8347
+ } catch {
8348
+ }
8349
+ await next();
8350
+ }, { skips: ["auth"] });
8273
8351
 
8274
8352
  // src/server/middleware/require-permission.ts
8275
8353
  import { defineMiddleware as defineMiddleware2 } from "@spfn/core/route";
@@ -8335,7 +8413,7 @@ var requireAnyPermission = defineMiddleware2(
8335
8413
 
8336
8414
  // src/server/middleware/require-role.ts
8337
8415
  import { defineMiddleware as defineMiddleware3 } from "@spfn/core/route";
8338
- import { getAuth as getAuth3, hasAnyRole as hasAnyRole2, authLogger as authLogger4 } from "@spfn/auth/server";
8416
+ import { getAuth as getAuth3, authLogger as authLogger4 } from "@spfn/auth/server";
8339
8417
  import { ForbiddenError as ForbiddenError2 } from "@spfn/core/errors";
8340
8418
  import { InsufficientRoleError } from "@spfn/auth/errors";
8341
8419
  var requireRole = defineMiddleware3(
@@ -8349,11 +8427,11 @@ var requireRole = defineMiddleware3(
8349
8427
  });
8350
8428
  throw new ForbiddenError2({ message: "Authentication required" });
8351
8429
  }
8352
- const { userId } = auth;
8353
- const allowed = await hasAnyRole2(userId, roleNames);
8354
- if (!allowed) {
8430
+ const { userId, role: userRole } = auth;
8431
+ if (!userRole || !roleNames.includes(userRole)) {
8355
8432
  authLogger4.middleware.warn("Role check failed", {
8356
8433
  userId,
8434
+ userRole,
8357
8435
  requiredRoles: roleNames,
8358
8436
  path: c.req.path
8359
8437
  });
@@ -8361,6 +8439,7 @@ var requireRole = defineMiddleware3(
8361
8439
  }
8362
8440
  authLogger4.middleware.debug("Role check passed", {
8363
8441
  userId,
8442
+ userRole,
8364
8443
  roles: roleNames
8365
8444
  });
8366
8445
  await next();
@@ -8369,7 +8448,7 @@ var requireRole = defineMiddleware3(
8369
8448
 
8370
8449
  // src/server/middleware/role-guard.ts
8371
8450
  import { defineMiddleware as defineMiddleware4 } from "@spfn/core/route";
8372
- import { getAuth as getAuth4, getUserRole as getUserRole2, authLogger as authLogger5 } from "@spfn/auth/server";
8451
+ import { getAuth as getAuth4, authLogger as authLogger5 } from "@spfn/auth/server";
8373
8452
  import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
8374
8453
  import { InsufficientRoleError as InsufficientRoleError2 } from "@spfn/auth/errors";
8375
8454
  var roleGuard = defineMiddleware4(
@@ -8386,8 +8465,7 @@ var roleGuard = defineMiddleware4(
8386
8465
  });
8387
8466
  throw new ForbiddenError3({ message: "Authentication required" });
8388
8467
  }
8389
- const { userId } = auth;
8390
- const userRole = await getUserRole2(userId);
8468
+ const { userId, role: userRole } = auth;
8391
8469
  if (deny && deny.length > 0) {
8392
8470
  if (userRole && deny.includes(userRole)) {
8393
8471
  authLogger5.middleware.warn("Role guard denied", {
@@ -9257,6 +9335,8 @@ export {
9257
9335
  getInvitationWithDetails,
9258
9336
  getKeyId,
9259
9337
  getKeySize,
9338
+ getOptionalAuth,
9339
+ getRole,
9260
9340
  getRoleByName,
9261
9341
  getRolePermissions,
9262
9342
  getSessionInfo,
@@ -9285,6 +9365,7 @@ export {
9285
9365
  logoutService,
9286
9366
  oauthCallbackService,
9287
9367
  oauthStartService,
9368
+ optionalAuth,
9288
9369
  parseDuration,
9289
9370
  permissions,
9290
9371
  permissionsRepository,