UNPKG

@narangcia-oss/cryptic-auth-client-plain-ts

Version:

A TypeScript client for interacting with a cryptic-auth host web server, crafted by Narangcia OSS.

296 lines (295 loc) 12.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthClient = void 0; const axios_1 = __importStar(require("axios")); const tokens_1 = require("../utils/tokens"); /** * Core authentication client for handling all auth flows * Handles login, signup, OAuth, token refresh, and validation */ class AuthClient { constructor(config) { this.accessToken = null; this.refreshToken = null; this.isRefreshing = false; this.failedQueue = []; console.log("[AuthClient] Initializing with config:", Object.assign(Object.assign({}, config), { baseURL: config.baseURL })); if (!config.baseURL) { throw new Error("Base URL is required for AuthClient initialization."); } this.config = Object.assign({ enableAutoRefresh: true, tokenStorage: "memory" }, config); this.api = axios_1.default.create({ baseURL: this.config.baseURL, headers: { "Content-Type": "application/json", }, }); this.setupInterceptors(); } setupInterceptors() { // Request interceptor: attach access token this.api.interceptors.request.use((config) => { if (this.accessToken && config.headers) { console.log("[AuthClient] Attaching access token to request."); config.headers.Authorization = `Bearer ${this.accessToken}`; } return config; }, (error) => { console.error("[AuthClient] Request error:", error); return Promise.reject(error); }); // Response interceptor: handle automatic token refresh this.api.interceptors.response.use((response) => { console.log("[AuthClient] Response received:", response.status, response.config.url); return response; }, async (error) => { var _a; const originalRequest = error.config; if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401 && originalRequest && !originalRequest._isRetry && this.config.enableAutoRefresh) { console.warn("[AuthClient] 401 detected, attempting token refresh."); if (this.isRefreshing) { console.log("[AuthClient] Token refresh already in progress, queueing request."); return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject, config: originalRequest, }); }); } this.isRefreshing = true; originalRequest._isRetry = true; try { if (!this.refreshToken) { console.error("[AuthClient] No refresh token available, clearing tokens."); this.clearTokens(); this.processQueue(null); return Promise.reject(new Error("Refresh token missing. Please re-authenticate.")); } console.log("[AuthClient] Attempting to refresh token..."); const response = await this.refreshTokenFlow(this.refreshToken); const newAccessToken = response.access_token; const newRefreshToken = response.refresh_token; console.log("[AuthClient] Token refresh successful. New access token set."); this.setTokens(newAccessToken, newRefreshToken); this.processQueue(newAccessToken); if (originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; } console.log("[AuthClient] Retrying original request after token refresh."); return this.api(originalRequest); } catch (refreshError) { console.error("[AuthClient] Token refresh failed:", refreshError); this.clearTokens(); this.processQueue(null, refreshError); return Promise.reject(refreshError); } finally { console.log("[AuthClient] Token refresh process finished."); this.isRefreshing = false; } } console.error("[AuthClient] Response error:", error); return Promise.reject(error); }); } processQueue(accessToken, error = null) { console.log(`[AuthClient] Processing failed request queue. Queue length: ${this.failedQueue.length}`); while (this.failedQueue.length) { const { resolve, reject, config } = this.failedQueue.shift(); if (accessToken) { if (config.headers) { config.headers.Authorization = `Bearer ${accessToken}`; } console.log("[AuthClient] Retrying queued request with new access token."); resolve(this.api(config)); } else { console.error("[AuthClient] Rejecting queued request due to missing access token."); reject(error || new axios_1.AxiosError("Authentication required", "401", config)); } } } setTokens(accessToken, refreshToken) { console.log("[AuthClient] Setting tokens."); this.accessToken = accessToken; this.refreshToken = refreshToken || null; } clearTokens() { console.log("[AuthClient] Clearing all tokens."); this.accessToken = null; this.refreshToken = null; } getAccessToken() { return this.accessToken; } getRefreshToken() { return this.refreshToken; } isAuthenticated() { return this.accessToken !== null; } async login(credentials) { console.log("[AuthClient] login called with username:", credentials.username); try { const response = await this.api.post("/login", credentials); console.log("[AuthClient] Login successful for:", credentials.username); this.setTokens(response.data.access_token, response.data.refresh_token); return response.data; } catch (error) { console.error("[AuthClient] Login failed for:", credentials.username, error); throw error; } } async signup(credentials) { console.log("[AuthClient] signup called with username:", credentials.username); try { const response = await this.api.post("/signup", credentials); console.log("[AuthClient] Signup successful for:", credentials.username); return response.data; } catch (error) { console.error("[AuthClient] Signup failed for:", credentials.username, error); throw error; } } async refreshTokenFlow(refreshToken) { console.log("[AuthClient] refreshTokenFlow called."); try { const response = await this.api.post("/token/refresh", { refresh_token: refreshToken, }); console.log("[AuthClient] Token refresh API call successful."); return response.data; } catch (error) { console.error("[AuthClient] Token refresh API call failed:", error); throw error; } } async validateToken(token) { console.log("[AuthClient] validateToken called."); try { const response = await this.api.post("/token/validate", { token }); console.log("[AuthClient] Token validation response:", response.data); return response.data; } catch (error) { console.error("[AuthClient] Token validation failed:", error); throw error; } } async healthCheck() { console.log("[AuthClient] healthCheck called."); try { const response = await this.api.get("/health"); console.log("[AuthClient] Health check response:", response.data); return response.data; } catch (error) { console.error("[AuthClient] Health check failed:", error); throw error; } } async generateOAuthAuthUrl(provider, state, scopes) { console.log(`[AuthClient] generateOAuthAuthUrl called for provider: ${provider}`); try { const response = await this.api.get(`/oauth/${provider}/auth`, { params: { state, scopes: scopes.join(" "), }, }); if (response.data && response.data.auth_url) { console.log(`[AuthClient] OAuth auth URL generated: ${response.data.auth_url}`); return response.data.auth_url; } console.error("[AuthClient] Invalid response from OAuth auth endpoint."); throw new Error("Invalid response from OAuth auth endpoint"); } catch (error) { console.error(`[AuthClient] Failed to generate OAuth URL for ${provider}:`, error); throw error; } } async oauthLoginCallback(provider, params) { console.log(`[AuthClient] oauthLoginCallback called for provider: ${provider}`); try { const response = await this.api.post(`/oauth/login`, { provider, code: params.code, state: params.state, }); console.log(`[AuthClient] OAuth login successful for provider: ${provider}`); this.setTokens(response.data.access_token, response.data.refresh_token); return response.data; } catch (error) { console.error(`[AuthClient] OAuth login failed for provider: ${provider}`, error); throw error; } } async oauthSignupCallback(provider, params) { console.log(`[AuthClient] oauthSignupCallback called for provider: ${provider}`); try { const response = await this.api.post(`/oauth/signup`, { provider, code: params.code, state: params.state, }); console.log(`[AuthClient] OAuth signup successful for provider: ${provider}`); this.setTokens(response.data.access_token, response.data.refresh_token); return response.data; } catch (error) { console.error(`[AuthClient] OAuth signup failed for provider: ${provider}`, error); throw error; } } getAxiosInstance() { console.log("[AuthClient] getAxiosInstance called."); return this.api; } } exports.AuthClient = AuthClient; // Static utility methods AuthClient.extractTokens = tokens_1.extractTokens; AuthClient.isTokenExpired = tokens_1.isTokenExpired;