ai-auth
Version:
Complete Auth-Agent SDK - Agent authentication for AI developers + OAuth client integration for website developers
335 lines • 11.6 kB
JavaScript
"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