UNPKG

ai-auth

Version:

Complete Auth-Agent SDK - Agent authentication for AI developers + OAuth client integration for website developers

335 lines 11.6 kB
"use strict"; /** * Core api.Auth-Agent authentication client * Framework-agnostic, can be used in any JavaScript/TypeScript project */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthAgentClient = void 0; const utils_1 = require("./utils"); class AuthAgentClient { config; refreshTimer; memoryStorage = null; constructor(config) { this.config = { authServerUrl: 'https://api.auth-agent.com', scope: 'openid profile email', storage: 'localStorage', autoRefresh: true, customFetch: fetch.bind(globalThis), ...config, }; // Start auto-refresh if enabled and user is authenticated if (this.config.autoRefresh && this.isAuthenticated()) { this.scheduleTokenRefresh(); } } /** * Generate login URL with PKCE */ async getLoginUrl(options) { const { codeVerifier, codeChallenge } = await (0, utils_1.generatePKCEAsync)(); // Store code verifier for later use this.storePKCEVerifier(codeVerifier); const params = new URLSearchParams({ client_id: this.config.clientId, redirect_uri: options?.redirectUri || this.config.redirectUri, response_type: 'code', scope: options?.scope || this.config.scope, code_challenge: codeChallenge, code_challenge_method: 'S256', }); if (options?.state) { params.set('state', options.state); this.storeState(options.state); } const url = `${this.config.authServerUrl}/oauth2/authorize?${params.toString()}`; return { url, codeVerifier }; } /** * Redirect to login page */ async redirectToLogin(options) { const { url } = await this.getLoginUrl(options); window.location.href = url; } /** * Handle OAuth callback (exchange code for tokens) */ async handleCallback(callbackUrl) { try { const url = new URL(callbackUrl || window.location.href); const code = url.searchParams.get('code'); const state = url.searchParams.get('state'); const error = url.searchParams.get('error'); if (error) { const errorDescription = url.searchParams.get('error_description'); throw this.createError(error, errorDescription || undefined); } if (!code) { throw this.createError('invalid_callback', 'No authorization code found'); } // Verify state if it was stored const storedState = this.getStoredState(); if (storedState && state !== storedState) { throw this.createError('state_mismatch', 'State parameter mismatch - possible CSRF attack'); } // Get stored PKCE verifier const codeVerifier = this.getPKCEVerifier(); if (!codeVerifier) { throw this.createError('pkce_missing', 'PKCE code verifier not found'); } // Exchange code for tokens const tokens = await this.exchangeCodeForTokens(code, codeVerifier); // Store tokens this.storeTokens(tokens); // Get user info const user = await this.getUserInfo(); // Clean up temporary storage this.clearPKCEVerifier(); this.clearState(); // Schedule auto-refresh if (this.config.autoRefresh) { this.scheduleTokenRefresh(); } return { success: true, user }; } catch (error) { return { success: false, error: error, }; } } /** * Exchange authorization code for tokens */ async exchangeCodeForTokens(code, codeVerifier, redirectUri) { const body = new URLSearchParams({ grant_type: 'authorization_code', code, client_id: this.config.clientId, redirect_uri: redirectUri || this.config.redirectUri, code_verifier: codeVerifier, }); if (this.config.clientSecret) { body.set('client_secret', this.config.clientSecret); } const response = await this.config.customFetch(`${this.config.authServerUrl}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'token_exchange_failed', error.error_description, response.status); } return response.json(); } /** * Get user information */ async getUserInfo(accessToken) { const token = accessToken || this.getAccessToken(); if (!token) { throw this.createError('no_token', 'No access token available'); } const response = await this.config.customFetch(`${this.config.authServerUrl}/oauth2/userinfo`, { headers: { Authorization: `Bearer ${token}`, }, }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'userinfo_failed', error.error_description, response.status); } return response.json(); } /** * Refresh access token */ async refreshToken() { const refreshToken = this.getRefreshToken(); if (!refreshToken) { throw this.createError('no_refresh_token', 'No refresh token available'); } const body = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: this.config.clientId, }); if (this.config.clientSecret) { body.set('client_secret', this.config.clientSecret); } try { const response = await this.config.customFetch(`${this.config.authServerUrl}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'refresh_failed', error.error_description, response.status); } const tokens = await response.json(); this.storeTokens(tokens); // Call callback if provided if (this.config.onTokenRefresh) { this.config.onTokenRefresh(tokens); } // Reschedule refresh if (this.config.autoRefresh) { this.scheduleTokenRefresh(); } return tokens; } catch (error) { // Call error callback if provided if (this.config.onTokenRefreshError) { this.config.onTokenRefreshError(error); } throw error; } } /** * Logout user */ logout() { this.clearTokens(); if (this.refreshTimer) { clearTimeout(this.refreshTimer); } if (this.config.onLogout) { this.config.onLogout(); } } /** * Check if user is authenticated */ isAuthenticated() { const token = this.getAccessToken(); if (!token) return false; const tokenData = this.getStoredTokenData(); if (!tokenData) return false; // Check if token is expired return tokenData.expires_at > Date.now(); } /** * Get current access token */ getAccessToken() { const tokenData = this.getStoredTokenData(); return tokenData?.access_token || null; } /** * Get current refresh token */ getRefreshToken() { const tokenData = this.getStoredTokenData(); return tokenData?.refresh_token || null; } /** * Schedule automatic token refresh */ scheduleTokenRefresh() { if (this.refreshTimer) { clearTimeout(this.refreshTimer); } const tokenData = this.getStoredTokenData(); if (!tokenData) return; // Refresh 5 minutes before expiration const timeUntilRefresh = tokenData.expires_at - Date.now() - 5 * 60 * 1000; if (timeUntilRefresh > 0) { this.refreshTimer = setTimeout(() => { this.refreshToken().catch((error) => { console.error('Auto-refresh failed:', error); }); }, timeUntilRefresh); } } /** * Storage methods */ storeTokens(tokens) { const expiresAt = Date.now() + tokens.expires_in * 1000; const data = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, expires_at: expiresAt, scope: tokens.scope, }; if (this.config.storage === 'localStorage') { localStorage.setItem('auth_agent_tokens', JSON.stringify(data)); } else if (this.config.storage === 'sessionStorage') { sessionStorage.setItem('auth_agent_tokens', JSON.stringify(data)); } else if (this.config.storage === 'memory') { this.memoryStorage = data; } } getStoredTokenData() { try { if (this.config.storage === 'localStorage') { const data = localStorage.getItem('auth_agent_tokens'); return data ? JSON.parse(data) : null; } else if (this.config.storage === 'sessionStorage') { const data = sessionStorage.getItem('auth_agent_tokens'); return data ? JSON.parse(data) : null; } else if (this.config.storage === 'memory') { return this.memoryStorage; } } catch { return null; } return null; } clearTokens() { if (this.config.storage === 'localStorage') { localStorage.removeItem('auth_agent_tokens'); } else if (this.config.storage === 'sessionStorage') { sessionStorage.removeItem('auth_agent_tokens'); } else if (this.config.storage === 'memory') { this.memoryStorage = null; } } storePKCEVerifier(verifier) { sessionStorage.setItem('auth_agent_pkce_verifier', verifier); } getPKCEVerifier() { return sessionStorage.getItem('auth_agent_pkce_verifier'); } clearPKCEVerifier() { sessionStorage.removeItem('auth_agent_pkce_verifier'); } storeState(state) { sessionStorage.setItem('auth_agent_state', state); } getStoredState() { return sessionStorage.getItem('auth_agent_state'); } clearState() { sessionStorage.removeItem('auth_agent_state'); } createError(code, description, statusCode) { const error = new Error(description || code); error.code = code; error.description = description; error.statusCode = statusCode; return error; } } exports.AuthAgentClient = AuthAgentClient; //# sourceMappingURL=client.js.map