@contentstack/cli-utilities
Version:
Utilities for contentstack projects
400 lines (399 loc) • 17.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const cli_ux_1 = tslib_1.__importDefault(require("./cli-ux"));
const config_handler_1 = tslib_1.__importDefault(require("./config-handler"));
const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
const open_1 = tslib_1.__importDefault(require("open"));
const http_1 = tslib_1.__importDefault(require("http"));
const url_1 = tslib_1.__importDefault(require("url"));
const logger_1 = require("./logger");
const contentstack_management_sdk_1 = tslib_1.__importDefault(require("./contentstack-management-sdk"));
const helpers_1 = require("./helpers");
dotenv_1.default.config();
/**
* @class
* Auth handler
*/
class AuthHandler {
set host(contentStackHost) {
this._host = contentStackHost;
// Update cmaHost when host is set
this.cmaHost = this.getCmaHost();
}
constructor() {
this.isRefreshingToken = false; // Flag to track if a refresh operation is in progress
this.checkExpiryAndRefresh = (force = false) => this.compareOAuthExpiry(force);
this.OAuthAppId = process.env.OAUTH_APP_ID || '6400aa06db64de001a31c8a9';
this.OAuthClientId = process.env.OAUTH_CLIENT_ID || 'Ie0FEfTzlfAHL4xM';
this.OAuthRedirectURL = process.env.OAUTH_APP_REDIRECT_URL || 'http://localhost:8184';
this.OAuthScope = [];
this.OAuthResponseType = 'code';
this.authTokenKeyName = 'authtoken';
this.authEmailKeyName = 'email';
this.oauthAccessTokenKeyName = 'oauthAccessToken';
this.oauthDateTimeKeyName = 'oauthDateTime';
this.oauthUserUidKeyName = 'userUid';
this.oauthOrgUidKeyName = 'oauthOrgUid';
this.oauthRefreshTokenKeyName = 'oauthRefreshToken';
this.authorisationTypeKeyName = 'authorisationType';
this.authorisationTypeOAUTHValue = 'OAUTH';
this.authorisationTypeAUTHValue = 'BASIC';
this.allAuthConfigItems = {
refreshToken: [
this.authTokenKeyName,
this.oauthAccessTokenKeyName,
this.oauthDateTimeKeyName,
this.oauthRefreshTokenKeyName,
],
default: [
this.authTokenKeyName,
this.authEmailKeyName,
this.oauthAccessTokenKeyName,
this.oauthDateTimeKeyName,
this.oauthUserUidKeyName,
this.oauthOrgUidKeyName,
this.oauthRefreshTokenKeyName,
this.authorisationTypeKeyName,
],
};
this.cmaHost = this.getCmaHost();
}
getCmaHost() {
var _a;
if (this._host) {
return this._host;
}
const cma = (_a = config_handler_1.default.get('region')) === null || _a === void 0 ? void 0 : _a.cma;
if (cma && cma.startsWith('http')) {
try {
const u = new URL(cma);
if (u.host)
return u.host;
}
catch (error) {
// If URL parsing fails, return the original cma value
}
}
return cma;
}
initLog() {
this.logger = new logger_1.LoggerService(process.cwd(), 'cli-log');
}
async setOAuthBaseURL() {
if (config_handler_1.default.get('region')['uiHost']) {
this.OAuthBaseURL = config_handler_1.default.get('region')['uiHost'] || '';
}
else {
throw new Error('Invalid ui-host URL while authenticating. Please set your region correctly using the command - csdx config:set:region');
}
}
async initSDK() {
// Ensure we have a valid host for the SDK initialization
const host = this._host || this.getCmaHost();
this.managementAPIClient = await (0, contentstack_management_sdk_1.default)({ host });
this.oauthHandler = this.managementAPIClient.oauth({
appId: this.OAuthAppId,
clientId: this.OAuthClientId,
redirectUri: this.OAuthRedirectURL,
scope: this.OAuthScope,
responseType: this.OAuthResponseType,
});
this.restoreOAuthConfig();
}
/*
*
* Login into Contentstack
* @returns {Promise} Promise object returns {} on success
*/
async oauth() {
try {
this.initLog();
await this.initSDK();
await this.createHTTPServer();
await this.openOAuthURL();
}
catch (error) {
this.logger.error('OAuth login failed', error.message);
throw error;
}
}
async createHTTPServer() {
try {
const server = http_1.default.createServer(async (req, res) => {
const queryObject = url_1.default.parse(req.url, true).query;
if (!queryObject.code) {
cli_ux_1.default.error('Error occoured while login with OAuth, please login with command - csdx auth:login --oauth');
return sendErrorResponse(res);
}
cli_ux_1.default.print('Auth code successfully fetched.');
try {
await this.getAccessToken(queryObject.code);
await this.setOAuthBaseURL();
cli_ux_1.default.print('Access token fetched using auth code successfully.');
cli_ux_1.default.print(`You can review the access permissions on the page - ${this.OAuthBaseURL}/#!/marketplace/authorized-apps`);
sendSuccessResponse(res);
stopServer();
}
catch (error) {
cli_ux_1.default.error('Error occoured while login with OAuth, please login with command - csdx auth:login --oauth');
cli_ux_1.default.error(error);
sendErrorResponse(res);
stopServer();
}
});
const sendSuccessResponse = (res) => {
const successHtml = `
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
p { color: #475161; margin-bottom: 20px; }
p button { background-color: #6c5ce7; color: #fff; border: 1px solid transparent; border-radius: 4px; font-weight: 600; line-height: 100%; text-align: center; min-height: 2rem; padding: 0.3125rem 1rem; }
</style>
<h1 style="color: #6c5ce7">Successfully authorized!</h1>
<p style="color: #475161; font-size: 16px; font-weight: 600">You can close this window now.</p>
<p>
You can review the access permissions on the
<a style="color: #6c5ce7; text-decoration: none" href="${this.OAuthBaseURL}/#!/marketplace/authorized-apps" target="_blank">Authorized Apps page</a>.
</p>`;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(successHtml);
};
const sendErrorResponse = (res) => {
const errorHtml = `
<h1>Sorry!</h1><h2>Something went wrong, please login with command.</h2>`;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(errorHtml);
};
const stopServer = () => {
server.close();
process.exit();
};
server.listen(8184, () => {
cli_ux_1.default.print('Waiting for the authorization server to respond...');
return { true: true };
});
// Listen for errors
server.on('error', (err) => {
cli_ux_1.default.error('Server encountered an error:', (0, helpers_1.formatError)(err));
});
}
catch (error) {
cli_ux_1.default.error(error);
throw error;
}
}
async openOAuthURL() {
try {
const url = await this.oauthHandler.authorize();
cli_ux_1.default.print('This will automatically start the browser and open the below URL, if it does not, you can copy and paste the below URL in the browser without terminating this command.', { color: 'yellow' });
cli_ux_1.default.print(url, { color: 'green' });
await (0, open_1.default)(url);
}
catch (error) {
throw error;
}
}
async getAccessToken(code) {
try {
const data = await this.oauthHandler.exchangeCodeForToken(code);
const userData = await this.getUserDetails(data);
if (userData['access_token'] && userData['refresh_token']) {
await this.setConfigData('oauth', userData);
}
else {
throw new Error('Invalid request');
}
}
catch (error) {
cli_ux_1.default.error('An error occurred while fetching the access token, run the command - csdx auth:login --oauth');
cli_ux_1.default.error(error);
throw error;
}
}
async setConfigData(type, userData = {}) {
try {
this.unsetConfigData(type);
switch (type) {
case 'oauth':
case 'refreshToken':
if (userData.access_token && userData.refresh_token) {
this.setOAuthConfigData(userData, type);
return userData;
}
else {
throw new Error('Invalid request');
}
case 'basicAuth':
if (userData.authtoken && userData.email) {
this.setBasicAuthConfigData(userData);
return userData;
}
else {
throw new Error('Invalid request');
}
case 'logout':
return userData;
default:
throw new Error('Invalid request');
}
}
catch (error) {
throw error;
}
}
setOAuthConfigData(userData, type) {
config_handler_1.default.set(this.oauthAccessTokenKeyName, userData.access_token);
config_handler_1.default.set(this.oauthRefreshTokenKeyName, userData.refresh_token);
config_handler_1.default.set(this.oauthDateTimeKeyName, new Date());
if (type === 'oauth') {
config_handler_1.default.set(this.authEmailKeyName, userData.email);
config_handler_1.default.set(this.oauthUserUidKeyName, userData.user_uid);
config_handler_1.default.set(this.oauthOrgUidKeyName, userData.organization_uid);
config_handler_1.default.set(this.authorisationTypeKeyName, this.authorisationTypeOAUTHValue);
}
}
setBasicAuthConfigData(userData) {
config_handler_1.default.set(this.authTokenKeyName, userData.authtoken);
config_handler_1.default.set(this.authEmailKeyName, userData.email);
config_handler_1.default.set(this.authorisationTypeKeyName, this.authorisationTypeAUTHValue);
}
unsetConfigData(type = 'default') {
const removeItems = type === 'refreshToken' ? this.allAuthConfigItems.refreshToken : this.allAuthConfigItems.default;
removeItems.forEach((element) => config_handler_1.default.delete(element));
}
async refreshToken() {
try {
if (!this.oauthHandler) {
await this.initSDK(); // Initialize oauthHandler if not already initialized
}
const configOauthRefreshToken = config_handler_1.default.get(this.oauthRefreshTokenKeyName);
const configAuthorisationType = config_handler_1.default.get(this.authorisationTypeKeyName);
if (configAuthorisationType !== this.authorisationTypeOAUTHValue || !configOauthRefreshToken) {
cli_ux_1.default.error('Invalid refresh token, run the command- csdx auth:login --oauth');
throw new Error('Invalid refresh token');
}
const data = await this.oauthHandler.refreshAccessToken(configOauthRefreshToken);
if (data['access_token'] && data['refresh_token']) {
await this.setConfigData('refreshToken', data);
return data; // Returning the data from the refresh token operation
}
else {
throw new Error('Invalid request');
}
}
catch (error) {
cli_ux_1.default.error('An error occurred while refreshing the token');
cli_ux_1.default.error(error);
throw error; // Throwing the error to be handled by the caller
}
}
async getUserDetails(data) {
if (data.access_token) {
try {
const user = await this.managementAPIClient.getUser();
data.email = (user === null || user === void 0 ? void 0 : user.email) || '';
return data;
}
catch (error) {
cli_ux_1.default.error('Error fetching user details.');
cli_ux_1.default.error(error);
throw error;
}
}
else {
cli_ux_1.default.error('Invalid/Empty access token.');
throw new Error('Invalid/Empty access token');
}
}
async oauthLogout() {
try {
if (!this.oauthHandler) {
await this.initSDK();
}
const response = await this.oauthHandler.logout();
return response || {};
}
catch (error) {
cli_ux_1.default.error('An error occurred while logging out');
cli_ux_1.default.error(error);
throw error;
}
}
isAuthenticated() {
const authorizationType = config_handler_1.default.get(this.authorisationTypeKeyName);
return (authorizationType === this.authorisationTypeOAUTHValue || authorizationType === this.authorisationTypeAUTHValue);
}
async getAuthorisationType() {
return config_handler_1.default.get(this.authorisationTypeKeyName) ? config_handler_1.default.get(this.authorisationTypeKeyName) : false;
}
async isAuthorisationTypeBasic() {
return config_handler_1.default.get(this.authorisationTypeKeyName) === this.authorisationTypeAUTHValue ? true : false;
}
async isAuthorisationTypeOAuth() {
return config_handler_1.default.get(this.authorisationTypeKeyName) === this.authorisationTypeOAUTHValue ? true : false;
}
async compareOAuthExpiry(force = false) {
// Avoid recursive refresh operations
if (this.isRefreshingToken) {
cli_ux_1.default.print('Refresh operation already in progress');
return Promise.resolve();
}
const oauthDateTime = config_handler_1.default.get(this.oauthDateTimeKeyName);
const authorisationType = config_handler_1.default.get(this.authorisationTypeKeyName);
if (oauthDateTime && authorisationType === this.authorisationTypeOAUTHValue) {
const now = new Date();
const oauthDate = new Date(oauthDateTime);
const oauthValidUpto = new Date();
oauthValidUpto.setTime(oauthDate.getTime() + 59 * 60 * 1000);
if (force) {
cli_ux_1.default.print('Force refreshing the token');
return this.refreshToken();
}
else {
if (oauthValidUpto > now) {
return Promise.resolve();
}
else {
cli_ux_1.default.print('Token expired, refreshing the token');
// Set the flag before refreshing the token
this.isRefreshingToken = true;
try {
await this.refreshToken();
}
catch (error) {
cli_ux_1.default.error('Error refreshing token');
throw error;
}
finally {
// Reset the flag after refresh operation is completed
this.isRefreshingToken = false;
}
return Promise.resolve();
}
}
}
else {
cli_ux_1.default.print('No OAuth set');
this.unsetConfigData();
}
}
restoreOAuthConfig() {
const oauthAccessToken = config_handler_1.default.get(this.oauthAccessTokenKeyName);
const oauthRefreshToken = config_handler_1.default.get(this.oauthRefreshTokenKeyName);
const oauthDateTime = config_handler_1.default.get(this.oauthDateTimeKeyName);
const oauthUserUid = config_handler_1.default.get(this.oauthUserUidKeyName);
const oauthOrgUid = config_handler_1.default.get(this.oauthOrgUidKeyName);
if (oauthAccessToken && !this.oauthHandler.getAccessToken())
this.oauthHandler.setAccessToken(oauthAccessToken);
if (oauthRefreshToken && !this.oauthHandler.getRefreshToken())
this.oauthHandler.setRefreshToken(oauthRefreshToken);
if (oauthUserUid && !this.oauthHandler.getUserUID())
this.oauthHandler.setUserUID(oauthUserUid);
if (oauthOrgUid && !this.oauthHandler.getOrganizationUID())
this.oauthHandler.setOrganizationUID(oauthOrgUid);
if (oauthDateTime && !this.oauthHandler.getTokenExpiryTime()) {
this.oauthHandler.setTokenExpiryTime(oauthDateTime);
}
}
}
exports.default = new AuthHandler();