logto-auth-node-sdk
Version:
A comprehensive Logto authentication client library with circuit breaker pattern support
195 lines (194 loc) • 9.19 kB
JavaScript
"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;