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

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.
@@ -488,12 +488,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
488
488
  oldKeyId: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
489
489
  }>;
490
490
  }, LoginResult>;
491
- logout: _spfn_core_route.RouteDef<{
492
- body: _sinclair_typebox.TObject<{}>;
493
- }, {}, void>;
494
- rotateKey: _spfn_core_route.RouteDef<{
495
- body: _sinclair_typebox.TObject<{}>;
496
- }, {
491
+ logout: _spfn_core_route.RouteDef<{}, {}, void>;
492
+ rotateKey: _spfn_core_route.RouteDef<{}, {
497
493
  body: _sinclair_typebox.TObject<{
498
494
  publicKey: _sinclair_typebox.TString;
499
495
  keyId: _sinclair_typebox.TString;
@@ -518,7 +514,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
518
514
  id: number;
519
515
  name: string;
520
516
  displayName: string;
521
- category: "auth" | "custom" | "user" | "rbac" | "system" | undefined;
517
+ category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
522
518
  }[];
523
519
  userId: number;
524
520
  email: string | null;
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, U as UserProfile, a as ProfileInfo, C as CheckAccountExistsResult, S as SendVerificationCodeResult, b as RegisterResult, L as LoginResult, c as RotateKeyResult, m as mainAuthRouter } from './dto-Bb2qFUO6.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 './dto-Bb2qFUO6.js';
2
+ import { R as RoleConfig, P as PermissionConfig, U as UserProfile, a as ProfileInfo, C as CheckAccountExistsResult, S as SendVerificationCodeResult, b as RegisterResult, L as LoginResult, c as RotateKeyResult, m as mainAuthRouter } from './dto-lZmWuObc.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 './dto-lZmWuObc.js';
4
4
  import * as _spfn_core_route from '@spfn/core/route';
5
5
  import * as _sinclair_typebox from '@sinclair/typebox';
6
6
  import '@spfn/auth/server';
@@ -248,12 +248,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
248
248
  oldKeyId: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
249
249
  }>;
250
250
  }, LoginResult>;
251
- logout: _spfn_core_route.RouteDef<{
252
- body: _sinclair_typebox.TObject<{}>;
253
- }, {}, void>;
254
- rotateKey: _spfn_core_route.RouteDef<{
255
- body: _sinclair_typebox.TObject<{}>;
256
- }, {
251
+ logout: _spfn_core_route.RouteDef<{}, {}, void>;
252
+ rotateKey: _spfn_core_route.RouteDef<{}, {
257
253
  body: _sinclair_typebox.TObject<{
258
254
  publicKey: _sinclair_typebox.TString;
259
255
  keyId: _sinclair_typebox.TString;
@@ -278,7 +274,7 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
278
274
  id: number;
279
275
  name: string;
280
276
  displayName: string;
281
- category: "auth" | "custom" | "user" | "rbac" | "system" | undefined;
277
+ category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
282
278
  }[];
283
279
  userId: number;
284
280
  email: string | null;
@@ -213,7 +213,7 @@ var generalAuthInterceptor = {
213
213
  const err = error;
214
214
  authLogger2.interceptor.general.error("Failed to refresh session", err);
215
215
  }
216
- } else if (ctx.path === "/_auth/logout" && ctx.response.status === 200) {
216
+ } else if (ctx.path === "/_auth/logout" && ctx.response.ok) {
217
217
  ctx.setCookies.push({
218
218
  name: COOKIE_NAMES2.SESSION,
219
219
  value: "",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/nextjs/api.ts","../../src/nextjs/interceptors/login-register.ts","../../src/nextjs/interceptors/general-auth.ts","../../src/nextjs/interceptors/key-rotation.ts","../../src/nextjs/interceptors/index.ts"],"sourcesContent":["/**\n * @spfn/auth/adapters/nextjs/api\n *\n * Next.js Adapter for SPFN Auth\n *\n * Provides automatic interceptor registration for seamless auth flow:\n * - Session management (HttpOnly cookies)\n * - JWT generation and signing\n * - Public key encryption\n *\n * @requires next >= 13.0.0\n *\n * @example\n * ```typescript\n * // Just import to auto-register interceptors\n * import '@spfn/auth/nextjs/api';\n * ```\n */\n\n// Re-export interceptors for advanced usage\nimport { registerInterceptors } from \"@spfn/core/nextjs/server\";\nimport { authInterceptors } from './interceptors';\n\n// Auto-register interceptors on import\nregisterInterceptors('auth', authInterceptors);","/**\n * Login/Register Interceptor\n *\n * Automatically handles key generation and session management\n * for login and register endpoints\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Login and Register Interceptor\n *\n * Request: Generates key pair and adds publicKey to request body\n * Response: Saves privateKey to HttpOnly cookie\n */\nexport const loginRegisterInterceptor: InterceptorRule =\n {\n pathPattern: /^\\/_auth\\/(login|register)$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n // Get old session if exists (for key rotation on login)\n const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);\n\n // Extract remember option from request body (if provided)\n const remember = ctx.body?.remember;\n\n // Generate new key pair\n const keyPair = generateKeyPair('ES256');\n\n // Add publicKey data to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = keyPair.publicKey;\n ctx.body.keyId = keyPair.keyId;\n ctx.body.fingerprint = keyPair.fingerprint;\n ctx.body.algorithm = keyPair.algorithm;\n ctx.body.keySize = Buffer.from(keyPair.publicKey, 'base64').length;\n\n // Add oldKeyId for login (key rotation)\n if (ctx.path === '/_auth/login' && oldKeyId)\n {\n ctx.body.oldKeyId = oldKeyId;\n }\n\n // Remove remember from body (not part of contract)\n delete ctx.body.remember;\n\n // Store privateKey and remember in metadata for response interceptor\n ctx.metadata.privateKey = keyPair.privateKey;\n ctx.metadata.keyId = keyPair.keyId;\n ctx.metadata.algorithm = keyPair.algorithm;\n ctx.metadata.remember = remember;\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only process successful responses\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n // Handle both wrapped ({ data: { userId } }) and direct ({ userId }) responses\n const userData = ctx.response.body?.data || ctx.response.body;\n if (!userData?.userId)\n {\n authLogger.interceptor.login.error('No userId in response');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL (priority: runtime > global > env > default)\n const ttl = getSessionTtl(ctx.metadata.remember);\n\n // Encrypt session data\n const sessionData =\n {\n userId: userData.userId,\n privateKey: ctx.metadata.privateKey,\n keyId: ctx.metadata.keyId,\n algorithm: ctx.metadata.algorithm,\n };\n\n const sealed = await sealSession(sessionData, ttl);\n\n // Set HttpOnly session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Set keyId cookie (for oldKeyId lookup)\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.login.error('Failed to save session', err);\n }\n\n await next();\n },\n };","/**\n * General Authentication Interceptor\n *\n * Handles authentication for all API requests except login/register\n * - Session validation and renewal\n * - JWT generation and signing\n * - Expired session cleanup\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { unsealSession, sealSession, shouldRefreshSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Check if path requires authentication\n */\nfunction requiresAuth(path: string): boolean\n{\n // Paths that don't require auth\n const publicPaths = [\n /^\\/_auth\\/login$/,\n /^\\/_auth\\/register$/,\n /^\\/_auth\\/codes$/, // Send verification code\n /^\\/_auth\\/codes\\/verify$/, // Verify code\n /^\\/_auth\\/exists$/, // Check account exists\n ];\n\n return !publicPaths.some((pattern) => pattern.test(path));\n}\n\n/**\n * General Authentication Interceptor\n *\n * Applies to all paths except login/register/codes\n * - Validates session\n * - Generates JWT token\n * - Refreshes session if needed\n * - Clears expired sessions\n */\nexport const generalAuthInterceptor: InterceptorRule =\n{\n pathPattern: '*', // Match all paths, filter by requiresAuth()\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n\n request: async (ctx, next) =>\n {\n // Skip if path doesn't require auth\n if (!requiresAuth(ctx.path))\n {\n authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);\n await next();\n return;\n }\n\n // Log available cookies\n const cookieNames = Array.from(ctx.cookies.keys());\n authLogger.interceptor.general.debug('Available cookies:', {\n cookieNames,\n totalCount: cookieNames.length,\n lookingFor: COOKIE_NAMES.SESSION,\n });\n\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n authLogger.interceptor.general.debug('Request', {\n method: ctx.method,\n path: ctx.path,\n hasSession: !!sessionCookie,\n sessionCookieValue: sessionCookie ? '***EXISTS***' : 'NOT_FOUND',\n });\n\n // No session cookie\n if (!sessionCookie)\n {\n authLogger.interceptor.general.debug('No session cookie, proceeding without auth');\n // Let request proceed - server will return 401\n await next();\n return;\n }\n\n try\n {\n // Decrypt and validate session\n const session = await unsealSession(sessionCookie);\n\n authLogger.interceptor.general.debug('Session valid', {\n userId: session.userId,\n keyId: session.keyId,\n });\n\n // Check if session should be refreshed (within 24h of expiry)\n const needsRefresh = await shouldRefreshSession(sessionCookie, 24);\n\n if (needsRefresh)\n {\n authLogger.interceptor.general.debug('Session needs refresh (within 24h of expiry)');\n // Mark for session renewal in response interceptor\n ctx.metadata.refreshSession = true;\n ctx.metadata.sessionData = session;\n }\n\n // Generate JWT token\n const token = generateClientToken(\n {\n userId: session.userId,\n keyId: session.keyId,\n timestamp: Date.now(),\n },\n session.privateKey,\n session.algorithm,\n { expiresIn: '15m' }\n );\n\n authLogger.interceptor.general.debug('Generated JWT token (expires in 15m)');\n\n // Add authentication headers\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = session.keyId;\n\n // Store session info in metadata\n ctx.metadata.userId = session.userId;\n ctx.metadata.sessionValid = true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Session expired or invalid\n if (err.message.includes('expired') || err.message.includes('invalid'))\n {\n authLogger.interceptor.general.warn('Session expired or invalid', { message: err.message });\n authLogger.interceptor.general.debug('Marking session for cleanup');\n\n // Mark for cleanup in response interceptor\n ctx.metadata.clearSession = true;\n ctx.metadata.sessionValid = false;\n }\n else\n {\n authLogger.interceptor.general.error('Failed to process session', err);\n }\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Clear expired/invalid session\n if (ctx.metadata.clearSession)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n // Refresh session if needed and request was successful\n else if (ctx.metadata.refreshSession && ctx.response.status === 200)\n {\n try\n {\n const sessionData = ctx.metadata.sessionData;\n const ttl = getSessionTtl();\n\n // Re-encrypt session with new TTL\n const sealed = await sealSession(sessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: sessionData.keyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n authLogger.interceptor.general.info('Session refreshed', { userId: sessionData.userId });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.general.error('Failed to refresh session', err);\n }\n }\n // Handle logout (clear session)\n else if (ctx.path === '/_auth/logout' && ctx.response.status === 200)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n\n await next();\n },\n};","/**\n * Key Rotation Interceptor\n *\n * Handles key rotation with new key generation and session update\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, unsealSession, sealSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\n\n/**\n * Key Rotation Interceptor\n *\n * Request: Generates new key pair and adds to body, authenticates with current key\n * Response: Updates session with new privateKey\n */\nexport const keyRotationInterceptor: InterceptorRule =\n{\n pathPattern: '/_auth/keys/rotate',\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n await next();\n return;\n }\n\n try\n {\n // Get current session\n const currentSession = await unsealSession(sessionCookie);\n\n // Generate new key pair\n const newKeyPair = generateKeyPair('ES256');\n\n // Add new publicKey to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = newKeyPair.publicKey;\n ctx.body.keyId = newKeyPair.keyId;\n ctx.body.fingerprint = newKeyPair.fingerprint;\n ctx.body.algorithm = newKeyPair.algorithm;\n ctx.body.keySize = Buffer.from(newKeyPair.publicKey, 'base64').length;\n\n console.log('New key generated:', newKeyPair);\n console.log('publicKey:', newKeyPair.publicKey);\n console.log('keyId:', newKeyPair.keyId);\n console.log('fingerprint:', newKeyPair.fingerprint);\n\n // Authenticate with CURRENT key\n const token = generateClientToken(\n {\n userId: currentSession.userId,\n keyId: currentSession.keyId,\n action: 'rotate_key',\n timestamp: Date.now(),\n },\n currentSession.privateKey,\n currentSession.algorithm,\n {expiresIn: '15m'}\n );\n\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = currentSession.keyId;\n\n // Store new key and userId in metadata\n ctx.metadata.newPrivateKey = newKeyPair.privateKey;\n ctx.metadata.newKeyId = newKeyPair.keyId;\n ctx.metadata.newAlgorithm = newKeyPair.algorithm;\n ctx.metadata.userId = currentSession.userId;\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to prepare key rotation', err);\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only update session on successful rotation\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId)\n {\n authLogger.interceptor.keyRotation.error('Missing key rotation metadata');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL\n const ttl = getSessionTtl();\n\n // Create new session with rotated key\n const newSessionData =\n {\n userId: ctx.metadata.userId,\n privateKey: ctx.metadata.newPrivateKey,\n keyId: ctx.metadata.newKeyId,\n algorithm: ctx.metadata.newAlgorithm,\n };\n\n const sealed = await sealSession(newSessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.newKeyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to update session after rotation', err);\n }\n\n await next();\n },\n};","/**\n * Auth Interceptors for Next.js Proxy\n *\n * Automatically registers interceptors for authentication flow\n *\n * Order matters - more specific interceptors first:\n * 1. loginRegisterInterceptor - Most specific (login/register only)\n * 2. keyRotationInterceptor - Specific (key rotation only)\n * 3. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\n\n/**\n * All auth interceptors\n *\n * Execution order:\n * 1. loginRegisterInterceptor - Handles login/register (key generation + session save)\n * 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)\n * 3. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\n\n// Deprecated: use generalAuthInterceptor instead\nexport { generalAuthInterceptor as authenticationInterceptor };"],"mappings":"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","names":["sealSession","getSessionTtl","COOKIE_NAMES","authLogger","env","generateKeyPair","unsealSession","sealSession","generateClientToken","getSessionTtl","COOKIE_NAMES","authLogger"]}
1
+ {"version":3,"sources":["../../src/nextjs/api.ts","../../src/nextjs/interceptors/login-register.ts","../../src/nextjs/interceptors/general-auth.ts","../../src/nextjs/interceptors/key-rotation.ts","../../src/nextjs/interceptors/index.ts"],"sourcesContent":["/**\n * @spfn/auth/adapters/nextjs/api\n *\n * Next.js Adapter for SPFN Auth\n *\n * Provides automatic interceptor registration for seamless auth flow:\n * - Session management (HttpOnly cookies)\n * - JWT generation and signing\n * - Public key encryption\n *\n * @requires next >= 13.0.0\n *\n * @example\n * ```typescript\n * // Just import to auto-register interceptors\n * import '@spfn/auth/nextjs/api';\n * ```\n */\n\n// Re-export interceptors for advanced usage\nimport { registerInterceptors } from \"@spfn/core/nextjs/server\";\nimport { authInterceptors } from './interceptors';\n\n// Auto-register interceptors on import\nregisterInterceptors('auth', authInterceptors);","/**\n * Login/Register Interceptor\n *\n * Automatically handles key generation and session management\n * for login and register endpoints\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Login and Register Interceptor\n *\n * Request: Generates key pair and adds publicKey to request body\n * Response: Saves privateKey to HttpOnly cookie\n */\nexport const loginRegisterInterceptor: InterceptorRule =\n {\n pathPattern: /^\\/_auth\\/(login|register)$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n // Get old session if exists (for key rotation on login)\n const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);\n\n // Extract remember option from request body (if provided)\n const remember = ctx.body?.remember;\n\n // Generate new key pair\n const keyPair = generateKeyPair('ES256');\n\n // Add publicKey data to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = keyPair.publicKey;\n ctx.body.keyId = keyPair.keyId;\n ctx.body.fingerprint = keyPair.fingerprint;\n ctx.body.algorithm = keyPair.algorithm;\n ctx.body.keySize = Buffer.from(keyPair.publicKey, 'base64').length;\n\n // Add oldKeyId for login (key rotation)\n if (ctx.path === '/_auth/login' && oldKeyId)\n {\n ctx.body.oldKeyId = oldKeyId;\n }\n\n // Remove remember from body (not part of contract)\n delete ctx.body.remember;\n\n // Store privateKey and remember in metadata for response interceptor\n ctx.metadata.privateKey = keyPair.privateKey;\n ctx.metadata.keyId = keyPair.keyId;\n ctx.metadata.algorithm = keyPair.algorithm;\n ctx.metadata.remember = remember;\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only process successful responses\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n // Handle both wrapped ({ data: { userId } }) and direct ({ userId }) responses\n const userData = ctx.response.body?.data || ctx.response.body;\n if (!userData?.userId)\n {\n authLogger.interceptor.login.error('No userId in response');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL (priority: runtime > global > env > default)\n const ttl = getSessionTtl(ctx.metadata.remember);\n\n // Encrypt session data\n const sessionData =\n {\n userId: userData.userId,\n privateKey: ctx.metadata.privateKey,\n keyId: ctx.metadata.keyId,\n algorithm: ctx.metadata.algorithm,\n };\n\n const sealed = await sealSession(sessionData, ttl);\n\n // Set HttpOnly session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Set keyId cookie (for oldKeyId lookup)\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.login.error('Failed to save session', err);\n }\n\n await next();\n },\n };","/**\n * General Authentication Interceptor\n *\n * Handles authentication for all API requests except login/register\n * - Session validation and renewal\n * - JWT generation and signing\n * - Expired session cleanup\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { unsealSession, sealSession, shouldRefreshSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Check if path requires authentication\n */\nfunction requiresAuth(path: string): boolean\n{\n // Paths that don't require auth\n const publicPaths = [\n /^\\/_auth\\/login$/,\n /^\\/_auth\\/register$/,\n /^\\/_auth\\/codes$/, // Send verification code\n /^\\/_auth\\/codes\\/verify$/, // Verify code\n /^\\/_auth\\/exists$/, // Check account exists\n ];\n\n return !publicPaths.some((pattern) => pattern.test(path));\n}\n\n/**\n * General Authentication Interceptor\n *\n * Applies to all paths except login/register/codes\n * - Validates session\n * - Generates JWT token\n * - Refreshes session if needed\n * - Clears expired sessions\n */\nexport const generalAuthInterceptor: InterceptorRule =\n{\n pathPattern: '*', // Match all paths, filter by requiresAuth()\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n\n request: async (ctx, next) =>\n {\n // Skip if path doesn't require auth\n if (!requiresAuth(ctx.path))\n {\n authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);\n await next();\n return;\n }\n\n // Log available cookies\n const cookieNames = Array.from(ctx.cookies.keys());\n authLogger.interceptor.general.debug('Available cookies:', {\n cookieNames,\n totalCount: cookieNames.length,\n lookingFor: COOKIE_NAMES.SESSION,\n });\n\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n authLogger.interceptor.general.debug('Request', {\n method: ctx.method,\n path: ctx.path,\n hasSession: !!sessionCookie,\n sessionCookieValue: sessionCookie ? '***EXISTS***' : 'NOT_FOUND',\n });\n\n // No session cookie\n if (!sessionCookie)\n {\n authLogger.interceptor.general.debug('No session cookie, proceeding without auth');\n // Let request proceed - server will return 401\n await next();\n return;\n }\n\n try\n {\n // Decrypt and validate session\n const session = await unsealSession(sessionCookie);\n\n authLogger.interceptor.general.debug('Session valid', {\n userId: session.userId,\n keyId: session.keyId,\n });\n\n // Check if session should be refreshed (within 24h of expiry)\n const needsRefresh = await shouldRefreshSession(sessionCookie, 24);\n\n if (needsRefresh)\n {\n authLogger.interceptor.general.debug('Session needs refresh (within 24h of expiry)');\n // Mark for session renewal in response interceptor\n ctx.metadata.refreshSession = true;\n ctx.metadata.sessionData = session;\n }\n\n // Generate JWT token\n const token = generateClientToken(\n {\n userId: session.userId,\n keyId: session.keyId,\n timestamp: Date.now(),\n },\n session.privateKey,\n session.algorithm,\n { expiresIn: '15m' }\n );\n\n authLogger.interceptor.general.debug('Generated JWT token (expires in 15m)');\n\n // Add authentication headers\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = session.keyId;\n\n // Store session info in metadata\n ctx.metadata.userId = session.userId;\n ctx.metadata.sessionValid = true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Session expired or invalid\n if (err.message.includes('expired') || err.message.includes('invalid'))\n {\n authLogger.interceptor.general.warn('Session expired or invalid', { message: err.message });\n authLogger.interceptor.general.debug('Marking session for cleanup');\n\n // Mark for cleanup in response interceptor\n ctx.metadata.clearSession = true;\n ctx.metadata.sessionValid = false;\n }\n else\n {\n authLogger.interceptor.general.error('Failed to process session', err);\n }\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Clear expired/invalid session\n if (ctx.metadata.clearSession)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n // Refresh session if needed and request was successful\n else if (ctx.metadata.refreshSession && ctx.response.status === 200)\n {\n try\n {\n const sessionData = ctx.metadata.sessionData;\n const ttl = getSessionTtl();\n\n // Re-encrypt session with new TTL\n const sealed = await sealSession(sessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: sessionData.keyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n authLogger.interceptor.general.info('Session refreshed', { userId: sessionData.userId });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.general.error('Failed to refresh session', err);\n }\n }\n // Handle logout (clear session)\n else if (ctx.path === '/_auth/logout' && ctx.response.ok)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n\n await next();\n },\n};","/**\n * Key Rotation Interceptor\n *\n * Handles key rotation with new key generation and session update\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, unsealSession, sealSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\n\n/**\n * Key Rotation Interceptor\n *\n * Request: Generates new key pair and adds to body, authenticates with current key\n * Response: Updates session with new privateKey\n */\nexport const keyRotationInterceptor: InterceptorRule =\n{\n pathPattern: '/_auth/keys/rotate',\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n await next();\n return;\n }\n\n try\n {\n // Get current session\n const currentSession = await unsealSession(sessionCookie);\n\n // Generate new key pair\n const newKeyPair = generateKeyPair('ES256');\n\n // Add new publicKey to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = newKeyPair.publicKey;\n ctx.body.keyId = newKeyPair.keyId;\n ctx.body.fingerprint = newKeyPair.fingerprint;\n ctx.body.algorithm = newKeyPair.algorithm;\n ctx.body.keySize = Buffer.from(newKeyPair.publicKey, 'base64').length;\n\n console.log('New key generated:', newKeyPair);\n console.log('publicKey:', newKeyPair.publicKey);\n console.log('keyId:', newKeyPair.keyId);\n console.log('fingerprint:', newKeyPair.fingerprint);\n\n // Authenticate with CURRENT key\n const token = generateClientToken(\n {\n userId: currentSession.userId,\n keyId: currentSession.keyId,\n action: 'rotate_key',\n timestamp: Date.now(),\n },\n currentSession.privateKey,\n currentSession.algorithm,\n {expiresIn: '15m'}\n );\n\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = currentSession.keyId;\n\n // Store new key and userId in metadata\n ctx.metadata.newPrivateKey = newKeyPair.privateKey;\n ctx.metadata.newKeyId = newKeyPair.keyId;\n ctx.metadata.newAlgorithm = newKeyPair.algorithm;\n ctx.metadata.userId = currentSession.userId;\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to prepare key rotation', err);\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only update session on successful rotation\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId)\n {\n authLogger.interceptor.keyRotation.error('Missing key rotation metadata');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL\n const ttl = getSessionTtl();\n\n // Create new session with rotated key\n const newSessionData =\n {\n userId: ctx.metadata.userId,\n privateKey: ctx.metadata.newPrivateKey,\n keyId: ctx.metadata.newKeyId,\n algorithm: ctx.metadata.newAlgorithm,\n };\n\n const sealed = await sealSession(newSessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.newKeyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to update session after rotation', err);\n }\n\n await next();\n },\n};","/**\n * Auth Interceptors for Next.js Proxy\n *\n * Automatically registers interceptors for authentication flow\n *\n * Order matters - more specific interceptors first:\n * 1. loginRegisterInterceptor - Most specific (login/register only)\n * 2. keyRotationInterceptor - Specific (key rotation only)\n * 3. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\n\n/**\n * All auth interceptors\n *\n * Execution order:\n * 1. loginRegisterInterceptor - Handles login/register (key generation + session save)\n * 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)\n * 3. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\n\n// Deprecated: use generalAuthInterceptor instead\nexport { generalAuthInterceptor as authenticationInterceptor };"],"mappings":"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","names":["sealSession","getSessionTtl","COOKIE_NAMES","authLogger","env","generateKeyPair","unsealSession","sealSession","generateClientToken","getSessionTtl","COOKIE_NAMES","authLogger"]}
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, q as AuthContext } from './dto-Bb2qFUO6.js';
2
- export { B as ChangePasswordParams, w as CheckAccountExistsParams, C as CheckAccountExistsResult, X as EmailSchema, I as INVITATION_STATUSES, K as KEY_ALGORITHM, y as LoginParams, L as LoginResult, z as LogoutParams, Z as PasswordSchema, Y as PhoneSchema, x as RegisterParams, O as RegisterPublicKeyParams, b as RegisterResult, T as RevokeKeyParams, Q as RotateKeyParams, c as RotateKeyResult, e as SOCIAL_PROVIDERS, F as SendVerificationCodeParams, S as SendVerificationCodeResult, p as SocialProvider, _ as TargetTypeSchema, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, $ as VerificationPurposeSchema, V as VerificationTargetType, G as VerifyCodeParams, H as VerifyCodeResult, m as authRouter, W as authenticate, v as changePasswordService, r as checkAccountExistsService, t as loginService, u as logoutService, J as registerPublicKeyService, s as registerService, N as revokeKeyService, M as rotateKeyService, D as sendVerificationCodeService, E as verifyCodeService } from './dto-Bb2qFUO6.js';
1
+ import { k as AuthInitOptions, l as KeyAlgorithmType, n as InvitationStatus, f as VerificationPurpose, j as PermissionCategory, q as AuthContext } from './dto-lZmWuObc.js';
2
+ export { B as ChangePasswordParams, w as CheckAccountExistsParams, C as CheckAccountExistsResult, X as EmailSchema, I as INVITATION_STATUSES, K as KEY_ALGORITHM, y as LoginParams, L as LoginResult, z as LogoutParams, Z as PasswordSchema, Y as PhoneSchema, x as RegisterParams, O as RegisterPublicKeyParams, b as RegisterResult, T as RevokeKeyParams, Q as RotateKeyParams, c as RotateKeyResult, e as SOCIAL_PROVIDERS, F as SendVerificationCodeParams, S as SendVerificationCodeResult, p as SocialProvider, _ as TargetTypeSchema, d as USER_STATUSES, o as UserStatus, h as VERIFICATION_PURPOSES, g as VERIFICATION_TARGET_TYPES, $ as VerificationPurposeSchema, V as VerificationTargetType, G as VerifyCodeParams, H as VerifyCodeResult, m as authRouter, W as authenticate, v as changePasswordService, r as checkAccountExistsService, t as loginService, u as logoutService, J as registerPublicKeyService, s as registerService, N as revokeKeyService, M as rotateKeyService, D as sendVerificationCodeService, E as verifyCodeService } from './dto-lZmWuObc.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';
@@ -1255,7 +1255,7 @@ declare function getAuthSessionService(userId: string | number | bigint): Promis
1255
1255
  id: number;
1256
1256
  name: string;
1257
1257
  displayName: string;
1258
- category: "auth" | "custom" | "user" | "rbac" | "system" | undefined;
1258
+ category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
1259
1259
  }[];
1260
1260
  userId: number;
1261
1261
  email: string | null;
@@ -2747,7 +2747,7 @@ declare const permissions: drizzle_orm_pg_core.PgTableWithColumns<{
2747
2747
  tableName: "permissions";
2748
2748
  dataType: "string";
2749
2749
  columnType: "PgText";
2750
- data: "auth" | "custom" | "user" | "rbac" | "system";
2750
+ data: "custom" | "user" | "auth" | "rbac" | "system";
2751
2751
  driverParam: string;
2752
2752
  notNull: false;
2753
2753
  hasDefault: false;
@@ -3196,13 +3196,13 @@ declare class UsersRepository extends BaseRepository {
3196
3196
  create(data: NewUser): Promise<{
3197
3197
  email: string | null;
3198
3198
  phone: string | null;
3199
+ status: "active" | "inactive" | "suspended";
3199
3200
  id: number;
3201
+ createdAt: Date;
3202
+ updatedAt: Date;
3200
3203
  passwordHash: string | null;
3201
3204
  passwordChangeRequired: boolean;
3202
3205
  roleId: number;
3203
- createdAt: Date;
3204
- updatedAt: Date;
3205
- status: "active" | "inactive" | "suspended";
3206
3206
  emailVerifiedAt: Date | null;
3207
3207
  phoneVerifiedAt: Date | null;
3208
3208
  lastLoginAt: Date | null;
@@ -3268,13 +3268,13 @@ declare class UsersRepository extends BaseRepository {
3268
3268
  deleteById(id: number): Promise<{
3269
3269
  email: string | null;
3270
3270
  phone: string | null;
3271
+ status: "active" | "inactive" | "suspended";
3271
3272
  id: number;
3273
+ createdAt: Date;
3274
+ updatedAt: Date;
3272
3275
  passwordHash: string | null;
3273
3276
  passwordChangeRequired: boolean;
3274
3277
  roleId: number;
3275
- createdAt: Date;
3276
- updatedAt: Date;
3277
- status: "active" | "inactive" | "suspended";
3278
3278
  emailVerifiedAt: Date | null;
3279
3279
  phoneVerifiedAt: Date | null;
3280
3280
  lastLoginAt: Date | null;
@@ -3297,7 +3297,7 @@ declare class UsersRepository extends BaseRepository {
3297
3297
  id: number;
3298
3298
  name: string;
3299
3299
  displayName: string;
3300
- category: "auth" | "custom" | "user" | "rbac" | "system" | undefined;
3300
+ category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
3301
3301
  }[];
3302
3302
  }>;
3303
3303
  /**
@@ -3407,16 +3407,16 @@ declare class KeysRepository extends BaseRepository {
3407
3407
  * Write primary 사용
3408
3408
  */
3409
3409
  create(data: NewUserPublicKey): Promise<{
3410
- publicKey: string;
3411
- keyId: string;
3412
- fingerprint: string;
3413
- algorithm: "ES256" | "RS256";
3414
3410
  userId: number;
3411
+ keyId: string;
3415
3412
  id: number;
3416
3413
  isActive: boolean;
3417
3414
  createdAt: Date;
3418
- expiresAt: Date | null;
3415
+ publicKey: string;
3416
+ algorithm: "ES256" | "RS256";
3417
+ fingerprint: string;
3419
3418
  lastUsedAt: Date | null;
3419
+ expiresAt: Date | null;
3420
3420
  revokedAt: Date | null;
3421
3421
  revokedReason: string | null;
3422
3422
  }>;
@@ -3443,16 +3443,16 @@ declare class KeysRepository extends BaseRepository {
3443
3443
  * Write primary 사용
3444
3444
  */
3445
3445
  deleteByKeyIdAndUserId(keyId: string, userId: number): Promise<{
3446
- publicKey: string;
3447
- keyId: string;
3448
- fingerprint: string;
3449
- algorithm: "ES256" | "RS256";
3450
3446
  userId: number;
3447
+ keyId: string;
3451
3448
  id: number;
3452
3449
  isActive: boolean;
3453
3450
  createdAt: Date;
3454
- expiresAt: Date | null;
3451
+ publicKey: string;
3452
+ algorithm: "ES256" | "RS256";
3453
+ fingerprint: string;
3455
3454
  lastUsedAt: Date | null;
3455
+ expiresAt: Date | null;
3456
3456
  revokedAt: Date | null;
3457
3457
  revokedReason: string | null;
3458
3458
  }>;
@@ -3567,14 +3567,14 @@ declare class VerificationCodesRepository extends BaseRepository {
3567
3567
  * Write primary 사용
3568
3568
  */
3569
3569
  create(data: NewVerificationCode): Promise<{
3570
- target: string;
3571
- targetType: "email" | "phone";
3572
- purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
3573
- code: string;
3574
3570
  id: number;
3575
3571
  createdAt: Date;
3576
3572
  updatedAt: Date;
3577
3573
  expiresAt: Date;
3574
+ target: string;
3575
+ targetType: "email" | "phone";
3576
+ code: string;
3577
+ purpose: "registration" | "login" | "password_reset" | "email_change" | "phone_change";
3578
3578
  usedAt: Date | null;
3579
3579
  attempts: number;
3580
3580
  }>;
@@ -3763,7 +3763,7 @@ declare class PermissionsRepository extends BaseRepository {
3763
3763
  name: string;
3764
3764
  displayName: string;
3765
3765
  description: string | null;
3766
- category: "auth" | "custom" | "user" | "rbac" | "system" | null;
3766
+ category: "custom" | "user" | "auth" | "rbac" | "system" | null;
3767
3767
  isBuiltin: boolean;
3768
3768
  isSystem: boolean;
3769
3769
  isActive: boolean;
@@ -3779,7 +3779,7 @@ declare class PermissionsRepository extends BaseRepository {
3779
3779
  name: string;
3780
3780
  displayName: string;
3781
3781
  description: string | null;
3782
- category: "auth" | "custom" | "user" | "rbac" | "system" | null;
3782
+ category: "custom" | "user" | "auth" | "rbac" | "system" | null;
3783
3783
  isBuiltin: boolean;
3784
3784
  isSystem: boolean;
3785
3785
  isActive: boolean;
@@ -3819,7 +3819,7 @@ declare class PermissionsRepository extends BaseRepository {
3819
3819
  name: string;
3820
3820
  displayName: string;
3821
3821
  description: string | null;
3822
- category: "auth" | "custom" | "user" | "rbac" | "system" | null;
3822
+ category: "custom" | "user" | "auth" | "rbac" | "system" | null;
3823
3823
  isBuiltin: boolean;
3824
3824
  isSystem: boolean;
3825
3825
  isActive: boolean;
@@ -3838,8 +3838,8 @@ declare class PermissionsRepository extends BaseRepository {
3838
3838
  isActive: boolean;
3839
3839
  createdAt: Date;
3840
3840
  updatedAt: Date;
3841
- category: "auth" | "custom" | "user" | "rbac" | "system" | null;
3842
3841
  metadata: Record<string, any> | null;
3842
+ category: "custom" | "user" | "auth" | "rbac" | "system" | null;
3843
3843
  }>;
3844
3844
  }
3845
3845
  declare const permissionsRepository: PermissionsRepository;
@@ -3884,9 +3884,9 @@ declare class RolePermissionsRepository extends BaseRepository {
3884
3884
  */
3885
3885
  createMany(data: NewRolePermission[]): Promise<{
3886
3886
  id: number;
3887
- roleId: number;
3888
3887
  createdAt: Date;
3889
3888
  updatedAt: Date;
3889
+ roleId: number;
3890
3890
  permissionId: number;
3891
3891
  }[]>;
3892
3892
  /**
@@ -3902,9 +3902,9 @@ declare class RolePermissionsRepository extends BaseRepository {
3902
3902
  */
3903
3903
  setPermissionsForRole(roleId: number, permissionIds: number[]): Promise<{
3904
3904
  id: number;
3905
- roleId: number;
3906
3905
  createdAt: Date;
3907
3906
  updatedAt: Date;
3907
+ roleId: number;
3908
3908
  permissionId: number;
3909
3909
  }[]>;
3910
3910
  }
@@ -3969,10 +3969,10 @@ declare class UserPermissionsRepository extends BaseRepository {
3969
3969
  id: number;
3970
3970
  createdAt: Date;
3971
3971
  updatedAt: Date;
3972
- permissionId: number;
3973
3972
  expiresAt: Date | null;
3974
- reason: string | null;
3973
+ permissionId: number;
3975
3974
  granted: boolean;
3975
+ reason: string | null;
3976
3976
  }>;
3977
3977
  /**
3978
3978
  * 사용자 권한 오버라이드 업데이트
@@ -3995,10 +3995,10 @@ declare class UserPermissionsRepository extends BaseRepository {
3995
3995
  id: number;
3996
3996
  createdAt: Date;
3997
3997
  updatedAt: Date;
3998
- permissionId: number;
3999
3998
  expiresAt: Date | null;
4000
- reason: string | null;
3999
+ permissionId: number;
4001
4000
  granted: boolean;
4001
+ reason: string | null;
4002
4002
  }>;
4003
4003
  /**
4004
4004
  * 사용자의 모든 권한 오버라이드 삭제
@@ -4077,7 +4077,6 @@ declare class UserProfilesRepository extends BaseRepository {
4077
4077
  displayName: string;
4078
4078
  createdAt: Date;
4079
4079
  updatedAt: Date;
4080
- metadata: Record<string, any> | null;
4081
4080
  firstName: string | null;
4082
4081
  lastName: string | null;
4083
4082
  avatarUrl: string | null;
@@ -4090,6 +4089,7 @@ declare class UserProfilesRepository extends BaseRepository {
4090
4089
  location: string | null;
4091
4090
  company: string | null;
4092
4091
  jobTitle: string | null;
4092
+ metadata: Record<string, any> | null;
4093
4093
  }>;
4094
4094
  /**
4095
4095
  * 프로필 업데이트 (by ID)
@@ -4146,7 +4146,6 @@ declare class UserProfilesRepository extends BaseRepository {
4146
4146
  displayName: string;
4147
4147
  createdAt: Date;
4148
4148
  updatedAt: Date;
4149
- metadata: Record<string, any> | null;
4150
4149
  firstName: string | null;
4151
4150
  lastName: string | null;
4152
4151
  avatarUrl: string | null;
@@ -4159,6 +4158,7 @@ declare class UserProfilesRepository extends BaseRepository {
4159
4158
  location: string | null;
4160
4159
  company: string | null;
4161
4160
  jobTitle: string | null;
4161
+ metadata: Record<string, any> | null;
4162
4162
  }>;
4163
4163
  /**
4164
4164
  * 프로필 삭제 (by User ID)
@@ -4169,7 +4169,6 @@ declare class UserProfilesRepository extends BaseRepository {
4169
4169
  displayName: string;
4170
4170
  createdAt: Date;
4171
4171
  updatedAt: Date;
4172
- metadata: Record<string, any> | null;
4173
4172
  firstName: string | null;
4174
4173
  lastName: string | null;
4175
4174
  avatarUrl: string | null;
@@ -4182,6 +4181,7 @@ declare class UserProfilesRepository extends BaseRepository {
4182
4181
  location: string | null;
4183
4182
  company: string | null;
4184
4183
  jobTitle: string | null;
4184
+ metadata: Record<string, any> | null;
4185
4185
  }>;
4186
4186
  /**
4187
4187
  * 프로필 Upsert (by User ID)
@@ -4195,7 +4195,6 @@ declare class UserProfilesRepository extends BaseRepository {
4195
4195
  displayName: string;
4196
4196
  createdAt: Date;
4197
4197
  updatedAt: Date;
4198
- metadata: Record<string, any> | null;
4199
4198
  firstName: string | null;
4200
4199
  lastName: string | null;
4201
4200
  avatarUrl: string | null;
@@ -4208,6 +4207,7 @@ declare class UserProfilesRepository extends BaseRepository {
4208
4207
  location: string | null;
4209
4208
  company: string | null;
4210
4209
  jobTitle: string | null;
4210
+ metadata: Record<string, any> | null;
4211
4211
  }>;
4212
4212
  /**
4213
4213
  * User ID로 프로필 데이터 조회 (formatted)
@@ -4335,15 +4335,15 @@ declare class InvitationsRepository extends BaseRepository {
4335
4335
  */
4336
4336
  create(data: NewInvitation): Promise<{
4337
4337
  email: string;
4338
+ status: "pending" | "accepted" | "expired" | "cancelled";
4338
4339
  id: number;
4339
- roleId: number;
4340
4340
  createdAt: Date;
4341
4341
  updatedAt: Date;
4342
- status: "pending" | "accepted" | "expired" | "cancelled";
4342
+ roleId: number;
4343
4343
  metadata: Record<string, any> | null;
4344
+ expiresAt: Date;
4344
4345
  token: string;
4345
4346
  invitedBy: number;
4346
- expiresAt: Date;
4347
4347
  acceptedAt: Date | null;
4348
4348
  cancelledAt: Date | null;
4349
4349
  }>;
@@ -4369,15 +4369,15 @@ declare class InvitationsRepository extends BaseRepository {
4369
4369
  */
4370
4370
  deleteById(id: number): Promise<{
4371
4371
  email: string;
4372
+ status: "pending" | "accepted" | "expired" | "cancelled";
4372
4373
  id: number;
4373
- roleId: number;
4374
4374
  createdAt: Date;
4375
4375
  updatedAt: Date;
4376
- status: "pending" | "accepted" | "expired" | "cancelled";
4376
+ roleId: number;
4377
4377
  metadata: Record<string, any> | null;
4378
+ expiresAt: Date;
4378
4379
  token: string;
4379
4380
  invitedBy: number;
4380
- expiresAt: Date;
4381
4381
  acceptedAt: Date | null;
4382
4382
  cancelledAt: Date | null;
4383
4383
  }>;
@@ -4888,13 +4888,13 @@ declare function getUser(c: Context | {
4888
4888
  }): {
4889
4889
  email: string | null;
4890
4890
  phone: string | null;
4891
+ status: "active" | "inactive" | "suspended";
4891
4892
  id: number;
4893
+ createdAt: Date;
4894
+ updatedAt: Date;
4892
4895
  passwordHash: string | null;
4893
4896
  passwordChangeRequired: boolean;
4894
4897
  roleId: number;
4895
- createdAt: Date;
4896
- updatedAt: Date;
4897
- status: "active" | "inactive" | "suspended";
4898
4898
  emailVerifiedAt: Date | null;
4899
4899
  phoneVerifiedAt: Date | null;
4900
4900
  lastLoginAt: Date | null;
package/dist/server.js CHANGED
@@ -8062,9 +8062,7 @@ var login = route.post("/_auth/login").input({
8062
8062
  const { body } = await c.data();
8063
8063
  return await loginService(body);
8064
8064
  });
8065
- var logout = route.post("/_auth/logout").input({
8066
- body: Type.Object({})
8067
- }).handler(async (c) => {
8065
+ var logout = route.post("/_auth/logout").handler(async (c) => {
8068
8066
  const auth = getAuth(c);
8069
8067
  if (!auth) {
8070
8068
  return c.noContent();
@@ -8073,9 +8071,7 @@ var logout = route.post("/_auth/logout").input({
8073
8071
  await logoutService({ userId: Number(userId), keyId });
8074
8072
  return c.noContent();
8075
8073
  });
8076
- var rotateKey = route.post("/_auth/keys/rotate").input({
8077
- body: Type.Object({})
8078
- }).interceptor({
8074
+ var rotateKey = route.post("/_auth/keys/rotate").interceptor({
8079
8075
  body: Type.Object({
8080
8076
  publicKey: Type.String({ description: "New public key" }),
8081
8077
  keyId: Type.String({ description: "New key identifier" }),