@redocly/cli
Version:
[@Redocly](https://redocly.com) CLI is your all-in-one OpenAPI utility. It builds, manages, improves, and quality-checks your OpenAPI descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make API g
138 lines • 5.28 kB
JavaScript
import { blue, green } from 'colorette';
import * as childProcess from 'node:child_process';
import { logger } from '@redocly/openapi-core';
import { ReuniteApiClient } from '../reunite/api/api-client.js';
import { DEFAULT_FETCH_TIMEOUT } from '../utils/constants.js';
export class RedoclyOAuthDeviceFlow {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.clientName = 'redocly-cli';
this.apiClient = new ReuniteApiClient('login');
}
async run() {
const code = await this.getDeviceCode();
logger.output('Attempting to automatically open the SSO authorization page in your default browser.\n');
logger.output('If the browser does not open or you wish to use a different device to authorize this request, open the following URL:\n\n');
logger.output(blue(code.verificationUri));
logger.output(`\n\n`);
logger.output(`Then enter the code:\n\n`);
logger.output(blue(code.userCode));
logger.output(`\n\n`);
this.openBrowser(code.verificationUriComplete);
const accessToken = await this.pollingAccessToken(code.deviceCode, code.interval, code.expiresIn);
logger.output(green('✅ Logged in\n\n'));
return this.withResidency(accessToken);
}
openBrowser(url) {
try {
const cmd = process.platform === 'win32'
? `start ${url}`
: process.platform === 'darwin'
? `open ${url}`
: `xdg-open ${url}`;
childProcess.execSync(cmd);
}
catch {
// silently fail if browser cannot be opened
}
}
async verifyToken(accessToken) {
try {
const response = await this.sendRequest('/session', 'GET', undefined, {
Cookie: `accessToken=${accessToken};`,
});
return !!response.user;
}
catch {
return false;
}
}
async verifyApiKey(apiKey) {
try {
const response = await this.sendRequest('/api-keys-verify', 'POST', {
apiKey,
});
return !!response.success;
}
catch {
return false;
}
}
async refreshToken(refreshToken) {
const response = await this.sendRequest(`/device-rotate-token`, 'POST', {
grant_type: 'refresh_token',
client_name: this.clientName,
refresh_token: refreshToken,
});
if (!response.access_token) {
throw new Error('Failed to refresh token');
}
return this.withResidency({
access_token: response.access_token,
refresh_token: response.refresh_token,
expires_in: response.expires_in,
});
}
async pollingAccessToken(deviceCode, interval, expiresIn) {
return new Promise((resolve, reject) => {
const intervalId = setInterval(async () => {
const response = await this.getAccessToken(deviceCode);
if (response.access_token) {
clearInterval(intervalId);
clearTimeout(timeoutId);
resolve(response);
}
if (response.error && response.error !== 'authorization_pending') {
clearInterval(intervalId);
clearTimeout(timeoutId);
reject(response.error_description);
}
}, interval * 1000);
const timeoutId = setTimeout(async () => {
clearInterval(intervalId);
clearTimeout(timeoutId);
reject('Authorization has expired. Please try again.');
}, expiresIn * 1000);
});
}
async getAccessToken(deviceCode) {
return await this.sendRequest('/device-token', 'POST', {
client_name: this.clientName,
device_code: deviceCode,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
});
}
async getDeviceCode() {
const { device_code: deviceCode, user_code: userCode, verification_uri: verificationUri, verification_uri_complete: verificationUriComplete, interval = 10, expires_in: expiresIn = 300, } = await this.sendRequest('/device-authorize', 'POST', {
client_name: this.clientName,
});
return {
deviceCode,
userCode,
verificationUri,
verificationUriComplete,
interval,
expiresIn,
};
}
async sendRequest(path, method = 'GET', body = undefined, headers = {}) {
const url = `${this.baseUrl}/api${path}`;
const response = await this.apiClient.request(url, {
body: body ? JSON.stringify(body) : body,
method,
headers: { 'Content-Type': 'application/json', ...headers },
timeout: DEFAULT_FETCH_TIMEOUT,
});
if (response.status === 204) {
return { success: true };
}
return await response.json();
}
withResidency(credentials) {
return {
...credentials,
residency: this.baseUrl,
};
}
}
//# sourceMappingURL=device-flow.js.map