{"version":3,"file":"JwtUtils.mjs","sources":["../src/JwtUtils.ts"],"sourcesContent":["/**\n * @module JwtUtils\n * @description A comprehensive collection of utility functions for working with JSON Web Tokens (JWT).\n * Provides methods for decoding, parsing, validating, and managing JWT tokens including expiration checks,\n * claim extraction, and token lifecycle management.\n * @example\n * ```typescript\n * import { JwtUtils } from 'houser-js-utils';\n *\n * // Decode a JWT token\n * const payload = JwtUtils.decodeToken(token);\n *\n * // Check if token is expired\n * const isExpired = JwtUtils.isTokenExpired(token);\n *\n * // Get specific claim\n * const userId = JwtUtils.getPayloadClaim(token, 'sub');\n * ```\n */\n\n/**\n * Interface representing the structure of a JWT payload with standard claims.\n */\nexport interface JwtPayload {\n  /** Subject (user ID) */\n  sub?: string;\n  /** Issuer */\n  iss?: string;\n  /** Audience */\n  aud?: string;\n  /** Expiration time (Unix timestamp in seconds) */\n  exp?: number;\n  /** Not before time (Unix timestamp in seconds) */\n  nbf?: number;\n  /** Issued at time (Unix timestamp in seconds) */\n  iat?: number;\n  /** JWT ID */\n  jti?: string;\n  /** Additional custom claims */\n  [key: string]: any;\n}\n\nexport const JwtUtils = {\n  /**\n   * Decodes and parses a JWT token into its payload object.\n   * @param token - The JWT token string to decode\n   * @returns The decoded JWT payload containing all claims\n   * @throws Error if token is invalid, malformed, or cannot be parsed\n   * @example\n   * ```typescript\n   * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';\n   * const payload = JwtUtils.decodeToken(token);\n   * console.log(payload.sub); // '1234567890'\n   * console.log(payload.exp); // 1234567890\n   * ```\n   */\n  decodeToken(token: string): JwtPayload {\n    try {\n      const base64Url = token.split(\".\")[1];\n      if (!base64Url) {\n        throw new Error(\"Invalid JWT token format\");\n      }\n\n      const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n      const jsonPayload = decodeURIComponent(\n        atob(base64)\n          .split(\"\")\n          .map((c) => {\n            const hex = c.charCodeAt(0).toString(16);\n            return \"%\" + (\"00\" + hex).slice(-2);\n          })\n          .join(\"\")\n      );\n\n      return JSON.parse(jsonPayload);\n    } catch (error) {\n      throw new Error(\n        `Failed to decode JWT token: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n\n  /**\n   * Extracts a specific claim value from a JWT token's payload.\n   * @param token - The JWT token string to parse\n   * @param claim - The name of the claim to retrieve\n   * @returns The claim value or undefined if the claim is not found\n   * @throws Error if token is invalid or cannot be parsed\n   * @example\n   * ```typescript\n   * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';\n   * const userId = JwtUtils.getPayloadClaim(token, 'sub'); // \"1234567890\"\n   * const role = JwtUtils.getPayloadClaim(token, 'role'); // \"admin\"\n   * const email = JwtUtils.getPayloadClaim(token, 'email'); // \"user@example.com\"\n   * ```\n   */\n  getPayloadClaim(token: string, claim: string): any {\n    try {\n      const payload = this.decodeToken(token);\n      return payload[claim];\n    } catch (error) {\n      throw new Error(\n        `Failed to get claim: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n\n  /**\n   * Calculates the time remaining until a JWT token expires.\n   * @param token - The JWT token to check for expiration\n   * @returns The time remaining in seconds, or null if no expiration time is set\n   * @throws Error if token is invalid or cannot be parsed\n   * @example\n   * ```typescript\n   * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';\n   * const timeRemaining = JwtUtils.getTokenTimeRemaining(token);\n   * if (timeRemaining !== null) {\n   *   console.log(`Token expires in ${timeRemaining} seconds`);\n   * } else {\n   *   console.log('Token has no expiration time');\n   * }\n   * ```\n   */\n  getTokenTimeRemaining(token: string): number | null {\n    try {\n      const payload = this.decodeToken(token);\n      if (!payload.exp) return null;\n\n      const currentTime = Math.floor(Date.now() / 1000);\n      return Math.max(0, payload.exp - currentTime);\n    } catch (error) {\n      throw new Error(\n        `Failed to get token time remaining: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n\n  /**\n   * Validates if a string follows the correct JWT token format (three base64url-encoded parts).\n   * @param token - The string to validate as a JWT token\n   * @returns True if the string follows JWT format, false otherwise\n   * @example\n   * ```typescript\n   * const validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature';\n   * const invalidToken = 'not.a.jwt.token';\n   *\n   * JwtUtils.isJwt(validToken); // true\n   * JwtUtils.isJwt(invalidToken); // false\n   * JwtUtils.isJwt('invalid'); // false\n   * ```\n   */\n  isJwt(token: string): boolean {\n    // JWT format: header.payload.signature\n    // Each part must be valid base64url encoded\n    const parts = token.split(\".\");\n    if (parts.length !== 3) return false;\n\n    // Each part must be non-empty and contain only base64url characters\n    const base64UrlPattern = /^[A-Za-z0-9-_]+$/;\n    return parts.every(\n      (part) => part.length > 0 && base64UrlPattern.test(part)\n    );\n  },\n\n  /**\n   * Checks if the JWT token stored in localStorage will expire within the next hour.\n   * @returns True if token will expire within an hour, false if not expiring soon, null if no token exists\n   * @example\n   * ```typescript\n   * const result = JwtUtils.isTokenExpiringSoon();\n   * if (result === null) {\n   *   console.log('No token found in localStorage');\n   * } else if (result) {\n   *   console.log('Token expires soon - consider refreshing');\n   * } else {\n   *   console.log('Token is still valid for more than an hour');\n   * }\n   * ```\n   */\n  isTokenExpiringSoon(): boolean | null {\n    const token = localStorage.getItem(\"token\");\n\n    if (!token) {\n      return null;\n    }\n\n    try {\n      const payload = this.decodeToken(token);\n      if (!payload.exp) return false;\n\n      const secondsDiff =\n        (new Date(payload.exp * 1000).getTime() - Date.now()) / 1000;\n      return secondsDiff > 0 && secondsDiff < 3600; // 3600 seconds = 1 hour\n    } catch (error) {\n      console.error(\"Error checking JWT expiration:\", error);\n      return false;\n    }\n  },\n\n  /**\n   * Determines if a JWT token has expired based on its expiration time claim.\n   * @param token - The JWT token string to check\n   * @returns True if the token is expired, false if still valid or has no expiration\n   * @throws Error if token is invalid or cannot be parsed\n   * @example\n   * ```typescript\n   * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';\n   *\n   * if (JwtUtils.isTokenExpired(token)) {\n   *   console.log('Token has expired - please authenticate again');\n   * } else {\n   *   console.log('Token is still valid');\n   * }\n   * ```\n   */\n  isTokenExpired(token: string): boolean {\n    try {\n      const payload = this.decodeToken(token);\n      if (!payload.exp) return false;\n\n      const currentTime = Math.floor(Date.now() / 1000);\n      return payload.exp < currentTime;\n    } catch (error) {\n      throw new Error(\n        `Failed to check token expiration: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n\n  /**\n   * Performs comprehensive validation of a JWT token including expiration and required claims.\n   * @param token - The JWT token string to validate\n   * @param requiredClaims - Array of claim names that must be present in the token\n   * @returns True if token is valid, not expired, and contains all required claims\n   * @throws Error if token is invalid or cannot be parsed\n   * @example\n   * ```typescript\n   * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';\n   *\n   * // Basic validation (just check expiration)\n   * const isValid = JwtUtils.isValidToken(token);\n   *\n   * // Validation with required claims\n   * const isValidWithClaims = JwtUtils.isValidToken(token, ['sub', 'role', 'email']);\n   *\n   * if (isValidWithClaims) {\n   *   console.log('Token is valid and has all required claims');\n   * }\n   * ```\n   */\n  isValidToken(token: string, requiredClaims: string[] = []): boolean {\n    try {\n      const payload = this.decodeToken(token);\n\n      // Check expiration\n      if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {\n        return false;\n      }\n\n      // Check required claims\n      return requiredClaims.every((claim) => payload[claim] !== undefined);\n    } catch (error) {\n      throw new Error(\n        `Failed to validate token: ${\n          error instanceof Error ? error.message : \"Unknown error\"\n        }`\n      );\n    }\n  },\n};\n"],"names":[],"mappings":"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"}