@redocly/cli
Version:
[@Redocly](https://redocly.com) CLI is your all-in-one API documentation utility. It builds, manages, improves, and quality-checks your API descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make
112 lines • 4.32 kB
JavaScript
import { homedir } from 'node:os';
import path from 'node:path';
import { mkdirSync, existsSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import crypto from 'node:crypto';
import { Buffer } from 'node:buffer';
import { logger } from '@redocly/openapi-core';
import { RedoclyOAuthDeviceFlow } from './device-flow.js';
import { isValidReuniteUrl } from '../reunite/api/domains.js';
const CREDENTIALS_SALT = '4618dbc9-8aed-4e27-aaf0-225f4603e5a4';
const CRYPTO_ALGORITHM = 'aes-256-cbc';
export class RedoclyOAuthClient {
constructor() {
this.getAccessToken = async (reuniteUrl) => {
const deviceFlow = new RedoclyOAuthDeviceFlow(reuniteUrl);
const credentials = await this.readCredentials();
if (!credentials ||
!isValidReuniteUrl(reuniteUrl) ||
(credentials.residency && credentials.residency !== reuniteUrl)) {
return null;
}
const isValid = await deviceFlow.verifyToken(credentials.access_token);
if (isValid) {
return credentials.access_token;
}
try {
const newCredentials = await deviceFlow.refreshToken(credentials.refresh_token);
await this.saveCredentials(newCredentials);
return newCredentials.access_token;
}
catch {
return null;
}
};
const homeDirPath = homedir();
this.credentialsFolderPath = path.join(homeDirPath, '.redocly');
this.credentialsFileName = 'credentials';
this.credentialsFilePath = path.join(this.credentialsFolderPath, this.credentialsFileName);
this.key = crypto
.createHash('sha256')
.update(`${this.credentialsFolderPath}${CREDENTIALS_SALT}`)
.digest();
this.iv = crypto.createHash('md5').update(this.credentialsFolderPath).digest();
mkdirSync(this.credentialsFolderPath, { recursive: true });
}
async login(baseUrl) {
const deviceFlow = new RedoclyOAuthDeviceFlow(baseUrl);
const credentials = await deviceFlow.run();
if (!credentials) {
throw new Error('Failed to login. No credentials received.');
}
this.saveCredentials(credentials);
}
async logout() {
try {
this.removeCredentials();
}
catch (err) {
// do nothing
}
}
async isAuthorized(reuniteUrl, apiKey) {
if (apiKey) {
const deviceFlow = new RedoclyOAuthDeviceFlow(reuniteUrl);
return deviceFlow.verifyApiKey(apiKey);
}
const accessToken = await this.getAccessToken(reuniteUrl);
return Boolean(accessToken);
}
async saveCredentials(credentials) {
try {
const encryptedCredentials = this.encryptCredentials(credentials);
writeFileSync(this.credentialsFilePath, encryptedCredentials, 'utf8');
}
catch (error) {
logger.error(`Failed to save credentials: ${error.message}`);
}
}
async readCredentials() {
if (!existsSync(this.credentialsFilePath)) {
return null;
}
try {
const encryptedCredentials = readFileSync(this.credentialsFilePath, 'utf8');
return this.decryptCredentials(encryptedCredentials);
}
catch {
return null;
}
}
async removeCredentials() {
if (existsSync(this.credentialsFilePath)) {
rmSync(this.credentialsFilePath);
}
}
encryptCredentials(credentials) {
const cipher = crypto.createCipheriv(CRYPTO_ALGORITHM, this.key, this.iv);
const encrypted = Buffer.concat([
cipher.update(JSON.stringify(credentials), 'utf8'),
cipher.final(),
]);
return encrypted.toString('hex');
}
decryptCredentials(encryptedCredentials) {
const decipher = crypto.createDecipheriv(CRYPTO_ALGORITHM, this.key, this.iv);
const decrypted = Buffer.concat([
decipher.update(Buffer.from(encryptedCredentials, 'hex')),
decipher.final(),
]);
return JSON.parse(decrypted.toString('utf8'));
}
}
//# sourceMappingURL=oauth-client.js.map