{"version":3,"file":"auth-guard.mjs","sources":["../../../../src/lib/routing/guards/auth-guard.ts"],"sourcesContent":["/**\n * @file Authentication Guard\n * @description Route guard for authentication checks. Protects routes that require\n * users to be logged in and handles redirect to login page.\n *\n * @module @/lib/routing/guards/auth-guard\n *\n * This module provides:\n * - Authentication state checking\n * - Login redirect handling\n * - Return URL preservation\n * - Session validation\n * - Token refresh integration\n *\n * @example\n * ```typescript\n * import { createAuthGuard, AuthGuard } from '@/lib/routing/guards/auth-guard';\n *\n * const authGuard = createAuthGuard({\n *   loginPath: '/login',\n *   isAuthenticated: () => Boolean(getCurrentUser()),\n *   returnUrlParam: 'returnTo',\n * });\n * ```\n */\n\nimport {\n  BaseRouteGuard,\n  GuardResult,\n  type GuardContext,\n  type GuardResultObject,\n} from './route-guard';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Authentication guard configuration\n */\nexport interface AuthGuardConfig {\n  /** Guard name */\n  readonly name?: string;\n  /** Path to redirect unauthenticated users */\n  readonly loginPath?: string;\n  /** Query parameter name for return URL */\n  readonly returnUrlParam?: string;\n  /** Check if user is authenticated */\n  readonly isAuthenticated?: (context: GuardContext) => boolean | Promise<boolean>;\n  /** Custom authentication check with full context */\n  readonly checkAuth?: (context: GuardContext) => GuardResultObject | Promise<GuardResultObject>;\n  /** Public paths that don't require authentication */\n  readonly publicPaths?: readonly string[];\n  /** Path patterns that require authentication */\n  readonly protectedPaths?: readonly string[];\n  /** Whether to preserve query params in return URL */\n  readonly preserveQuery?: boolean;\n  /** Custom redirect builder */\n  readonly buildRedirect?: (context: GuardContext) => string;\n  /** Session validation function */\n  readonly validateSession?: (context: GuardContext) => boolean | Promise<boolean>;\n  /** Token refresh function */\n  readonly refreshToken?: () => Promise<boolean>;\n  /** Minimum session time before refresh (ms) */\n  readonly refreshThreshold?: number;\n  /** Guard priority */\n  readonly priority?: number;\n  /** Guard timeout */\n  readonly timeout?: number;\n  /** Feature flag */\n  readonly featureFlag?: string;\n}\n\n/**\n * Authentication state\n */\nexport interface AuthState {\n  /** Whether user is authenticated */\n  readonly isAuthenticated: boolean;\n  /** Session expiry timestamp */\n  readonly expiresAt?: number;\n  /** Whether session needs refresh */\n  readonly needsRefresh: boolean;\n  /** Authentication error (if any) */\n  readonly error?: string;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * Default authentication guard configuration\n */\nexport const DEFAULT_AUTH_CONFIG: AuthGuardConfig = {\n  name: 'auth',\n  loginPath: '/login',\n  returnUrlParam: 'returnTo',\n  publicPaths: ['/login', '/register', '/forgot-password', '/reset-password'],\n  preserveQuery: true,\n  priority: 10,\n  timeout: 10000,\n  refreshThreshold: 5 * 60 * 1000, // 5 minutes\n};\n\n// =============================================================================\n// AuthGuard Class\n// =============================================================================\n\n/**\n * Authentication route guard\n *\n * @example\n * ```typescript\n * const guard = new AuthGuard({\n *   loginPath: '/auth/login',\n *   isAuthenticated: (ctx) => ctx.user?.isAuthenticated ?? false,\n * });\n *\n * // Add to route\n * const result = await guard.execute('canActivate', context);\n * ```\n */\nexport class AuthGuard extends BaseRouteGuard {\n  private readonly authConfig: AuthGuardConfig;\n\n  constructor(config: AuthGuardConfig = {}) {\n    const mergedConfig = { ...DEFAULT_AUTH_CONFIG, ...config };\n\n    super({\n      name: mergedConfig.name ?? 'auth',\n      description: 'Authentication guard - checks if user is logged in',\n      priority: mergedConfig.priority,\n      timeout: mergedConfig.timeout,\n      exclude: mergedConfig.publicPaths,\n      routes: mergedConfig.protectedPaths,\n      featureFlag: mergedConfig.featureFlag,\n      canActivate: async (context) => this.checkAuthentication(context),\n      canLoad: async (context) => this.checkAuthentication(context),\n    });\n\n    this.authConfig = mergedConfig;\n  }\n\n  /**\n   * Get authentication state for a context\n   */\n  async getAuthState(context: GuardContext): Promise<AuthState> {\n    const isAuthenticated = await this.checkAuthStatus(context);\n\n    let expiresAt: number | undefined;\n    let needsRefresh = false;\n\n    if (isAuthenticated && context.user?.metadata?.['expiresAt'] != null) {\n      expiresAt = context.user.metadata['expiresAt'] as number;\n      const threshold = this.authConfig.refreshThreshold ?? 0;\n      needsRefresh = (expiresAt - Date.now()) < threshold;\n    }\n\n    return {\n      isAuthenticated,\n      expiresAt,\n      needsRefresh,\n    };\n  }\n\n  /**\n   * Check if a specific path requires authentication\n   */\n  requiresAuth(path: string): boolean {\n    return !this.isPublicPath(path);\n  }\n\n  /**\n   * Get public paths configuration\n   */\n  getPublicPaths(): readonly string[] {\n    return this.authConfig.publicPaths ?? [];\n  }\n\n  /**\n   * Get login path\n   */\n  getLoginPath(): string {\n    return this.authConfig.loginPath ?? '/login';\n  }\n\n  /**\n   * Check authentication status\n   */\n  private async checkAuthentication(context: GuardContext): Promise<GuardResultObject> {\n    // Use custom check if provided\n    if (this.authConfig.checkAuth !== undefined && this.authConfig.checkAuth !== null) {\n      return this.authConfig.checkAuth(context);\n    }\n\n    // Check if path is public\n    if (this.isPublicPath(context.path)) {\n      return GuardResult.allow();\n    }\n\n    // Check authentication status\n    const isAuthenticated = await this.checkAuthStatus(context);\n\n    if (!isAuthenticated) {\n      return this.buildLoginRedirect(context);\n    }\n\n    // Check session validity\n    if (this.authConfig.validateSession !== undefined && this.authConfig.validateSession !== null) {\n      const isValid = await this.authConfig.validateSession(context);\n      if (isValid !== true) {\n        return this.buildLoginRedirect(context, 'Session expired');\n      }\n    }\n\n    // Check if token needs refresh\n    await this.checkTokenRefresh(context);\n\n    return GuardResult.allow();\n  }\n\n  /**\n   * Check if user is authenticated\n   */\n  private async checkAuthStatus(context: GuardContext): Promise<boolean> {\n    // Use custom isAuthenticated function if provided\n    if (this.authConfig.isAuthenticated !== undefined && this.authConfig.isAuthenticated !== null) {\n      return this.authConfig.isAuthenticated(context);\n    }\n\n    // Default: check context.user\n    return context.user?.isAuthenticated ?? false;\n  }\n\n  /**\n   * Check if path is public\n   */\n  private isPublicPath(path: string): boolean {\n    const publicPaths = this.authConfig.publicPaths ?? [];\n\n    for (const publicPath of publicPaths) {\n      // Exact match\n      if (path === publicPath) {\n        return true;\n      }\n\n      // Glob match\n      if (publicPath.includes('*')) {\n        const regexStr = publicPath\n          .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n          .replace(/\\*\\*/g, '.*')\n          .replace(/\\*/g, '[^/]*');\n\n        const regex = new RegExp(`^${regexStr}$`);\n        if (regex.test(path)) {\n          return true;\n        }\n      }\n    }\n\n    return false;\n  }\n\n  /**\n   * Build redirect to login page\n   */\n  private buildLoginRedirect(context: GuardContext, reason?: string): GuardResultObject {\n    // Use custom redirect builder if provided\n    if (this.authConfig.buildRedirect !== undefined && this.authConfig.buildRedirect !== null) {\n      const redirectPath = this.authConfig.buildRedirect(context);\n      return GuardResult.redirect(redirectPath);\n    }\n\n    // Build default redirect with return URL\n    let loginPath = this.authConfig.loginPath ?? '/login';\n    const returnUrlParam = this.authConfig.returnUrlParam ?? 'returnTo';\n\n    // Build return URL\n    let returnUrl = context.path;\n    if ((this.authConfig.preserveQuery ?? false) && Object.keys(context.query).length > 0) {\n      const queryString = new URLSearchParams(context.query).toString();\n      returnUrl = `${context.path}?${queryString}`;\n    }\n\n    // Append return URL to login path\n    loginPath = `${loginPath}?${returnUrlParam}=${encodeURIComponent(returnUrl)}`;\n\n    return GuardResult.redirect(loginPath, {\n      replace: true,\n      state: { reason },\n    });\n  }\n\n  /**\n   * Check if token needs refresh\n   */\n  private async checkTokenRefresh(context: GuardContext): Promise<void> {\n    if (!this.authConfig.refreshToken || (this.authConfig.refreshThreshold == null)) {\n      return;\n    }\n\n    // Check if session is about to expire\n    const {user} = context;\n    if (user?.metadata?.['expiresAt'] == null) {\n      return;\n    }\n\n    const expiresAt = user.metadata['expiresAt'] as number;\n    const timeUntilExpiry = expiresAt - Date.now();\n\n    if (timeUntilExpiry < this.authConfig.refreshThreshold) {\n      try {\n        await this.authConfig.refreshToken();\n      } catch (error) {\n        console.warn('Token refresh failed:', error);\n      }\n    }\n  }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\n/**\n * Create an authentication guard\n *\n * @param config - Guard configuration\n * @returns AuthGuard instance\n */\nexport function createAuthGuard(config: AuthGuardConfig = {}): AuthGuard {\n  return new AuthGuard(config);\n}\n\n/**\n * Create a simple auth guard from an authentication check function\n *\n * @param isAuthenticated - Function to check authentication\n * @param options - Additional options\n * @returns AuthGuard instance\n */\nexport function createSimpleAuthGuard(\n  isAuthenticated: (context: GuardContext) => boolean | Promise<boolean>,\n  options: Partial<AuthGuardConfig> = {}\n): AuthGuard {\n  return new AuthGuard({\n    ...options,\n    isAuthenticated,\n  });\n}\n\n/**\n * Create an auth guard with token-based authentication\n *\n * @param options - Configuration options\n * @returns AuthGuard instance\n */\nexport function createTokenAuthGuard(options: {\n  getToken: () => string | null;\n  validateToken?: (token: string) => boolean | Promise<boolean>;\n  refreshToken?: () => Promise<string | null>;\n  loginPath?: string;\n}): AuthGuard {\n  return new AuthGuard({\n    loginPath: options.loginPath ?? '/login',\n    isAuthenticated: async () => {\n      const token = options.getToken();\n      if (token == null) return false;\n\n      if (options.validateToken) {\n        return options.validateToken(token);\n      }\n\n      return true;\n    },\n    refreshToken: options.refreshToken\n      ? async () => {\n          const newToken = await options.refreshToken?.();\n          return newToken !== null;\n        }\n      : undefined,\n  });\n}\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Build a return URL for login redirect\n *\n * @param currentPath - Current path\n * @param query - Current query params\n * @param preserveQuery - Whether to preserve query params\n * @returns Encoded return URL\n */\nexport function buildReturnUrl(\n  currentPath: string,\n  query?: Record<string, string>,\n  preserveQuery = true\n): string {\n  let returnUrl = currentPath;\n\n  if (preserveQuery && query && Object.keys(query).length > 0) {\n    const queryString = new URLSearchParams(query).toString();\n    returnUrl = `${currentPath}?${queryString}`;\n  }\n\n  return encodeURIComponent(returnUrl);\n}\n\n/**\n * Parse return URL from query parameters\n *\n * @param query - Query parameters\n * @param paramName - Parameter name for return URL\n * @returns Decoded return URL or undefined\n */\nexport function parseReturnUrl(\n  query: Record<string, string>,\n  paramName = 'returnTo'\n): string | undefined {\n  const returnUrl = query[paramName];\n  if (returnUrl == null) return undefined;\n\n  try {\n    return decodeURIComponent(returnUrl);\n  } catch {\n    return undefined;\n  }\n}\n\n/**\n * Check if a path requires authentication based on patterns\n *\n * @param path - Path to check\n * @param publicPaths - Public path patterns\n * @returns True if path requires authentication\n */\nexport function pathRequiresAuth(\n  path: string,\n  publicPaths: readonly string[]\n): boolean {\n  for (const publicPath of publicPaths) {\n    if (path === publicPath) return false;\n\n    if (publicPath.includes('*')) {\n      const regexStr = publicPath\n        .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n        .replace(/\\*\\*/g, '.*')\n        .replace(/\\*/g, '[^/]*');\n\n      const regex = new RegExp(`^${regexStr}$`);\n      if (regex.test(path)) return false;\n    }\n  }\n\n  return true;\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Type guard for AuthGuard\n */\nexport function isAuthGuard(value: unknown): value is AuthGuard {\n  return value instanceof AuthGuard;\n}\n\n/**\n * Type guard for AuthState\n */\nexport function isAuthState(value: unknown): value is AuthState {\n  return (\n    typeof value === 'object' &&\n    value !== null &&\n    'isAuthenticated' in value &&\n    'needsRefresh' in value\n  );\n}\n"],"names":["DEFAULT_AUTH_CONFIG","AuthGuard","BaseRouteGuard","config","mergedConfig","context","isAuthenticated","expiresAt","needsRefresh","threshold","path","GuardResult","publicPaths","publicPath","regexStr","reason","redirectPath","loginPath","returnUrlParam","returnUrl","queryString","user","error","parseReturnUrl","query","paramName","pathRequiresAuth","isAuthGuard","value","isAuthState"],"mappings":"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"}