UNPKG

ai-auth

Version:

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

462 lines 17 kB
"use strict"; /** * Auth-Agent SDK for AI agents using OAuth 2.1 with PKCE * Server-side TypeScript SDK */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentSDK = void 0; const token_manager_1 = require("./token-manager"); const utils_1 = require("./utils"); class AgentSDK { agentId; agentSecret; modelName; serverUrl = 'https://api.auth-agent.com'; timeout; customFetch; tokenManager; codeVerifier = null; state = null; constructor(config) { this.agentId = config.agentId; this.agentSecret = config.agentSecret; this.modelName = config.modelName; this.timeout = config.timeout || 10000; this.customFetch = config.customFetch || fetch.bind(globalThis); this.tokenManager = new token_manager_1.TokenManager(); } // ============================================================================ // OAuth 2.1 / OIDC Authentication Flow // ============================================================================ /** * Generate OAuth 2.1 authorization URL with PKCE */ async getAuthorizationUrl(options) { const { redirectUri, scope = 'openid profile email agent', state } = options; // Generate PKCE parameters const pkce = await (0, utils_1.generatePKCEAsync)(); this.codeVerifier = pkce.codeVerifier; // Generate or use provided state this.state = state || (0, utils_1.generateState)(); // Build authorization URL const params = new URLSearchParams({ response_type: 'code', client_id: this.agentId, redirect_uri: redirectUri, scope: scope, state: this.state, code_challenge: pkce.codeChallenge, code_challenge_method: 'S256', }); const url = `${this.serverUrl}/oauth2/authorize?${params.toString()}`; return { url, codeVerifier: this.codeVerifier, state: this.state }; } /** * Exchange authorization code for access token (OAuth 2.1 with PKCE) */ async exchangeCode(code, state, redirectUri) { // Validate state if (state !== this.state) { throw this.createError('state_mismatch', 'State parameter mismatch - possible CSRF attack'); } if (!this.codeVerifier) { throw this.createError('pkce_missing', 'No code_verifier found - call getAuthorizationUrl() first'); } // Exchange code for tokens const response = await this.customFetch(`${this.serverUrl}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: redirectUri, client_id: this.agentId, code_verifier: this.codeVerifier, }).toString(), signal: AbortSignal.timeout(this.timeout), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'token_exchange_failed', error.error_description, response.status); } const tokenData = await response.json(); this.tokenManager.setTokens(tokenData); // Clear PKCE state this.codeVerifier = null; this.state = null; return tokenData; } /** * Refresh access token using refresh token */ async refreshAccessToken() { if (!this.tokenManager.refreshToken) { throw this.createError('no_refresh_token', 'No refresh token available'); } const response = await this.customFetch(`${this.serverUrl}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.tokenManager.refreshToken, client_id: this.agentId, }).toString(), signal: AbortSignal.timeout(this.timeout), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'refresh_failed', error.error_description, response.status); } const tokenData = await response.json(); this.tokenManager.setTokens(tokenData); return tokenData; } /** * Headless authentication for automated agents (Resource Owner Password flow) * Note: Less secure than Authorization Code flow. Use only for trusted agents. */ async authenticateHeadless(username, password, scope = 'openid profile email agent') { const response = await this.customFetch(`${this.serverUrl}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'password', username: username, password: password, client_id: this.agentId, scope: scope, }).toString(), signal: AbortSignal.timeout(this.timeout), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'authentication_failed', error.error_description, response.status); } const tokenData = await response.json(); this.tokenManager.setTokens(tokenData); return tokenData; } // ============================================================================ // Authenticated API Requests // ============================================================================ /** * Make authenticated request using OAuth access token */ async makeRequest(endpoint, options = {}) { const { method = 'POST', params, body, expectJson = true, autoRefresh = true, } = options; const url = new URL(`${this.serverUrl}/${endpoint}`); if (params) { Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value)); } // Get access token (try refresh if expired) let accessToken; try { accessToken = this.tokenManager.getAccessToken(); } catch (error) { if (autoRefresh && this.tokenManager.refreshToken) { await this.refreshAccessToken(); accessToken = this.tokenManager.getAccessToken(); } else { throw error; } } // Build headers with Bearer token const headers = { 'Authorization': `${this.tokenManager.tokenType} ${accessToken}`, }; if (body) { headers['Content-Type'] = 'application/json'; } // Make request const response = await this.customFetch(url.toString(), { method, headers, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(this.timeout), }); // Handle response if (!response.ok) { if (response.status === 401) { this.tokenManager.clear(); throw this.createError('token_expired', 'Access token invalid or expired', 401); } else if (response.status === 403) { throw this.createError('forbidden', 'Insufficient permissions', 403); } const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'request_failed', error.error_description || error.message, response.status); } if (expectJson) { return response.json(); } else { return { ok: response.ok, status: response.status, text: await response.text(), }; } } // ============================================================================ // Agent Authentication // ============================================================================ /** * Authenticate agent and create auth session * Note: Agents must be registered at https://console.auth-agent.com */ async authenticateAgent(request) { const response = await this.customFetch(`${this.serverUrl}/api/agent-auth`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ agent_id: this.agentId, agent_secret: this.agentSecret, model_name: this.modelName, ...request, }), signal: AbortSignal.timeout(this.timeout), }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw this.createError(error.error || 'agent_auth_failed', error.error_description, response.status); } return response.json(); } /** * Get agent profile information */ async getAgentProfile() { return this.makeRequest('api/agents/profile', { method: 'GET', }); } /** * Update agent profile */ async updateAgentProfile(updates) { return this.makeRequest('api/agents/profile', { method: 'PUT', body: updates, }); } /** * Get user information from OIDC userinfo endpoint */ async getUserInfo() { return this.makeRequest('oauth2/userinfo', { method: 'GET', }); } // ============================================================================ // Event Sending & Logging // ============================================================================ /** * Send event data to Auth-Agent */ async sendEvent(eventData) { return this.makeRequest('api/events', { method: 'POST', body: eventData, }); } /** * Send a verified click event */ async sendVerifiedClick(selector, site, evidence = 'oauth_authenticated') { return this.sendEvent({ event: 'click', selector, site, evidence, }); } /** * Send a post-run event with result */ async sendPostRun(selector, site, result) { return this.sendEvent({ event: 'post_run', selector, site, result: result.substring(0, 2000), // Truncate to 2000 chars }); } // ============================================================================ // AI Verification / Challenge Flow // ============================================================================ /** * Request a verification challenge */ async requestChallenge() { return this.makeRequest('api/verify_challenge', { method: 'POST', body: {}, }); } /** * Confirm verification with challenge response */ async confirmVerification(request) { return this.makeRequest('api/verify_confirm', { method: 'POST', body: request, }); } /** * Get current verification status */ async getVerifyStatus() { return this.makeRequest('api/verify_status', { method: 'GET', }); } /** * Poll for challenge and authenticate using verify_ctx_id * For challenge-based authentication flow */ async pollAndAuthenticate(verifyCtxId, options = {}) { const { timeout = 30000, interval = 2000 } = options; const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { // Get the challenge const response = await this.customFetch(`${this.serverUrl}/api/verify_challenge?verify_ctx_id=${verifyCtxId}`, { method: 'GET', signal: AbortSignal.timeout(this.timeout), }); if (response.status === 200) { const challengeData = await response.json(); const challenge = challengeData.challenge; if (challenge) { // Confirm verification const confirmData = { agent_id: this.agentId, verify_ctx_id: verifyCtxId, challenge: challenge, }; // Add agent_secret if provided if (this.agentSecret) { confirmData.agent_secret = this.agentSecret; } const confirmResponse = await this.customFetch(`${this.serverUrl}/api/verify_confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(confirmData), signal: AbortSignal.timeout(this.timeout), }); if (confirmResponse.status === 200) { return true; } else { console.error('Verification confirm failed:', confirmResponse.status); return false; } } } else if (response.status === 404) { // No challenge available yet, keep waiting await (0, utils_1.sleep)(interval); continue; } else { console.error('Challenge request failed:', response.status); return false; } } catch (error) { console.error('Error during polling:', error); await (0, utils_1.sleep)(interval); } } console.error('Authentication timeout'); return false; } // ============================================================================ // Token & Session Management // ============================================================================ /** * Revoke access token or refresh token */ async revokeToken(token) { const tokenToRevoke = token || this.tokenManager.accessToken; const result = await this.makeRequest('api/revoke', { method: 'POST', body: { token: tokenToRevoke }, autoRefresh: false, }); if (!token) { // If revoking current token, clear all tokens this.tokenManager.clear(); } return result; } /** * Logout agent (revoke tokens and clear session) */ async logout() { if (this.tokenManager.accessToken) { try { await this.revokeToken(); } catch (error) { // Ignore errors, clear tokens anyway } } this.tokenManager.clear(); } /** * Introspect token to check validity and claims */ async introspectToken(token) { const tokenToCheck = token || this.tokenManager.accessToken; return this.makeRequest('api/introspect', { method: 'POST', body: { token: tokenToCheck }, }); } // ============================================================================ // Token Access & Status // ============================================================================ /** * Check if user is authenticated */ isAuthenticated() { return !this.tokenManager.isExpired(); } /** * Get current access token */ getAccessToken() { return this.tokenManager.accessToken; } /** * Get current refresh token */ getRefreshToken() { return this.tokenManager.refreshToken; } // ============================================================================ // Error Handling // ============================================================================ createError(code, description, statusCode) { const error = new Error(description || code); error.code = code; error.description = description; error.statusCode = statusCode; return error; } } exports.AgentSDK = AgentSDK; //# sourceMappingURL=agent-sdk.js.map