houser-js-utils
Version:
A comprehensive collection of TypeScript utility functions for common development tasks including array manipulation, string processing, date handling, random number generation, validation, and much more.
1 lines • 12 kB
Source Map (JSON)
{"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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"}