UNPKG

@contentstack/cli-utilities

Version:

Utilities for contentstack projects

400 lines (399 loc) 17.8 kB
"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();