@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
JavaScript
"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;