UNPKG

logto-auth-node-sdk

Version:

A comprehensive Logto authentication client library with circuit breaker pattern support

195 lines (194 loc) 9.19 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogtoClient = void 0; const axios_1 = __importDefault(require("axios")); const circuit_breaker_1 = require("./circuit-breaker"); class LogtoClient { constructor(config) { this.config = config; this.accessToken = null; this.tokenExpiresAt = 0; this.axiosInstance = axios_1.default.create({ timeout: config.timeout || 10000, }); this.circuitBreaker = new circuit_breaker_1.CircuitBreaker(config.circuitBreakerOptions); // Initialize circuits this.initCircuits(); } initCircuits() { const circuits = [ 'logto-auth', 'logto-organizations', 'logto-users', 'logto-fetch-user', 'logto-organization-users', 'logto-user-password-verify', 'logto-user-suspension', 'logto-user-custom-data' ]; circuits.forEach(circuit => { this.circuitBreaker.initCircuit(circuit, this.config.circuitBreakerOptions); }); } async getAccessToken() { // Check if we have a valid token const now = Date.now(); if (this.accessToken && this.tokenExpiresAt > now + 60000) { return this.accessToken; } return this.circuitBreaker.execute('logto-auth', async () => { const response = await this.executeWithBackoff(() => this.axiosInstance.post(`${this.config.authEndpoint}/oidc/token`, { grant_type: 'client_credentials', client_id: this.config.appId, client_secret: this.config.appSecret, scope: 'all', resource: this.config.resourceEndpoint, }, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }), 3, 300); this.accessToken = response.access_token; this.tokenExpiresAt = now + response.expires_in * 1000; return this.accessToken; }); } async getHeaders() { const accessToken = await this.getAccessToken(); return { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }; } async executeWithBackoff(requestFn, maxRetries = 3, baseDelayMs = 300) { let retries = 0; while (true) { try { const response = await requestFn(); return response.data; } catch (error) { const statusCode = error?.response?.status; const shouldRetry = (statusCode >= 500 || statusCode === 429) && retries < maxRetries; if (!shouldRetry) { throw new Error(error?.response?.data?.message || 'Logto API request failed'); } const delay = baseDelayMs * Math.pow(2, retries) + Math.random() * 100; await new Promise(resolve => setTimeout(resolve, delay)); retries++; } } } // Organization Methods async createOrganization(organizationData) { return this.circuitBreaker.execute('logto-organizations', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.post(`${this.config.authEndpoint}/api/organizations`, organizationData, { headers }), 3, 300); }); } async getOrganization(organizationId) { return this.circuitBreaker.execute('logto-organizations', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.get(`${this.config.authEndpoint}/api/organizations/${organizationId}`, { headers }), 3, 300); }); } async updateOrganization(organizationId, organizationData) { return this.circuitBreaker.execute('logto-organizations', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.patch(`${this.config.authEndpoint}/api/organizations/${organizationId}`, organizationData, { headers }), 3, 300); }); } async deleteOrganization(organizationId) { return this.circuitBreaker.execute('logto-organizations', async () => { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.delete(`${this.config.authEndpoint}/api/organizations/${organizationId}`, { headers }), 3, 300); }); } // Organization User Management Methods async addUsersToOrganization(organizationId, userData) { return this.circuitBreaker.execute('logto-organization-users', async () => { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.post(`${this.config.authEndpoint}/api/organizations/${organizationId}/users`, userData, { headers }), 3, 300); }); } async removeUserFromOrganization(organizationId, userId) { return this.circuitBreaker.execute('logto-organization-users', async () => { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.delete(`${this.config.authEndpoint}/api/organizations/${organizationId}/users/${userId}`, { headers }), 3, 300); }); } // User Methods async createUser(userData) { return this.circuitBreaker.execute('logto-users', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.post(`${this.config.authEndpoint}/api/users`, userData, { headers }), 3, 300); }); } async getUser(userId) { return this.circuitBreaker.execute('logto-users', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.get(`${this.config.authEndpoint}/api/users/${userId}`, { headers }), 3, 300); }); } async updateUser(userId, userData) { return this.circuitBreaker.execute('logto-users', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.patch(`${this.config.authEndpoint}/api/users/${userId}`, userData, { headers }), 3, 300); }); } async deleteUser(userId) { return this.circuitBreaker.execute('logto-users', async () => { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.delete(`${this.config.authEndpoint}/api/users/${userId}`, { headers }), 3, 300); }); } // User Password Methods async updateUserPassword(userId, passwordData) { return this.circuitBreaker.execute('logto-users', async () => { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.patch(`${this.config.authEndpoint}/api/users/${userId}/password`, passwordData, { headers }), 3, 300); }); } async verifyUserPassword(userId, passwordData) { return this.circuitBreaker.execute('logto-user-password-verify', async () => { try { const headers = await this.getHeaders(); await this.executeWithBackoff(() => this.axiosInstance.post(`${this.config.authEndpoint}/api/users/${userId}/password/verify`, passwordData, { headers }), 3, 300); return true; } catch (error) { if (error?.response?.status === 422 || error?.response?.status === 400) { return false; } throw error; } }); } // User Status and Data Methods async updateUserSuspensionStatus(userId, suspensionData) { return this.circuitBreaker.execute('logto-user-suspension', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.patch(`${this.config.authEndpoint}/api/users/${userId}/is-suspended`, suspensionData, { headers }), 3, 300); }); } async updateUserCustomData(userId, customData) { return this.circuitBreaker.execute('logto-user-custom-data', async () => { const headers = await this.getHeaders(); return this.executeWithBackoff(() => this.axiosInstance.patch(`${this.config.authEndpoint}/api/users/${userId}/custom-data`, customData, { headers }), 3, 300); }); } // Circuit Breaker Management getCircuitBreakerStatus(circuitName) { return this.circuitBreaker.getCircuitInfo(circuitName); } getAllCircuitBreakers() { return this.circuitBreaker.getAllCircuits(); } resetCircuitBreaker(circuitName) { this.circuitBreaker.resetCircuit(circuitName); } } exports.LogtoClient = LogtoClient;