UNPKG

@amplitude/ampli

Version:

Amplitude CLI

203 lines (202 loc) 7.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const OAuth2 = require("client-oauth2"); const randomString = require("randomstring"); const pkce = require("pkce-challenge"); const node_fetch_1 = require("node-fetch"); const settings_1 = require("../settings"); const errors_1 = require("../errors"); const types_1 = require("../types"); const constants_1 = require("../constants"); const proxy_1 = require("../util/http/proxy"); const SCOPES = ['openid', 'offline']; class Auth { constructor() { this.oauth2Token = undefined; } getOAuth2(zone) { if (this.oauth2 != null) { return this.oauth2; } this.fetchAgent = proxy_1.createHttpProxyAgent(); const { clientId, accessTokenUri, authorizationUri } = Auth.getOAuth2Options(zone); this.oauth2 = new OAuth2({ clientId, scopes: SCOPES, accessTokenUri, authorizationUri, }, this.request.bind(this)); return this.oauth2; } get isActiveAuthMethod() { return !!this.oauth2Token || settings_1.getSettings().hasOAuthToken(); } writeTokensToSettings(userId) { if (!this.oauth2Token) { throw new errors_1.UserFixableError(errors_1.USER_ERROR_MESSAGES.notLoggedInYet); } settings_1.getSettings().setOAuthToken(userId, this.oauth2Token); } async getAccessToken() { var _a; let userId; const { userId: settingsUserId, token } = settings_1.getSettings().getOAuthToken(this.authUserId); const zone = (_a = settingsUserId === null || settingsUserId === void 0 ? void 0 : settingsUserId.zone) !== null && _a !== void 0 ? _a : types_1.DEFAULT_ZONE; if (!this.oauth2Token) { const oauth2 = this.getOAuth2(zone); this.oauth2Token = oauth2.createToken(token); userId = settingsUserId; } if (this.oauth2Token.expired()) { this.oauth2Token = await this.refreshToken(this.oauth2Token, zone); if (userId) { this.writeTokensToSettings(userId); } } return this.oauth2Token.data.id_token; } getAuthorizationUri(port, zone) { var _a, _b; this.authorizationState = randomString.generate(); this.pkceChallenge = pkce(); const oauth2 = this.getOAuth2(zone); return oauth2.code.getUri({ redirectUri: `http://localhost:${port}/callback`, state: this.authorizationState, query: { code_challenge: (_b = (_a = this.pkceChallenge) === null || _a === void 0 ? void 0 : _a.code_challenge) !== null && _b !== void 0 ? _b : '', code_challenge_method: 'S256', }, }); } async waitForAuthorizationToComplete() { if (this.oauth2Token) { return; } if (this.oauth2Token === null) { throw new errors_1.UserFixableError(errors_1.USER_ERROR_MESSAGES.unauthorized()); } await new Promise((resolve, reject) => { this.authorizationSucceeded = resolve; this.authorizationFailed = reject; }); } async processAuthorizationCode(code, state, port) { var _a, _b; if (state !== this.authorizationState) { this.oauth2Token = null; if (this.authorizationFailed) { const errorMessage = 'Authorization failed (state does not match).'; this.authorizationFailed(errorMessage); throw new Error(errorMessage); } return; } const tokenConfig = { redirectUri: `http://localhost:${port}/callback`, body: { code_verifier: (_b = (_a = this.pkceChallenge) === null || _a === void 0 ? void 0 : _a.code_verifier) !== null && _b !== void 0 ? _b : '', }, }; const { oauth2 } = this; if (oauth2 == null) { if (this.authorizationFailed) { const errorMessage = 'Authorization failed (oauth2 is null).'; this.authorizationFailed(errorMessage); throw new Error(errorMessage); } return; } try { this.oauth2Token = await oauth2.code.getToken(`/callback?code=${code}`, tokenConfig); if (this.authorizationSucceeded) { this.authorizationSucceeded(); } } catch (error) { this.oauth2Token = null; if (this.authorizationFailed) { const errorMessage = `Access Token Error ${error.message}`; this.authorizationFailed(errorMessage); throw new Error(errorMessage); } } } authenticate(apiToken, userId, withOrg) { if (apiToken) { this.apiToken = apiToken; } else if (userId) { this.authUserId = userId; } else if (this.isActiveAuthMethod) { } this.withOrg = withOrg; } async getHttpHeaders() { const headers = {}; if (this.apiToken) { headers.authorization = `Bearer ${this.apiToken}`; } else if (this.authUserId || this.isActiveAuthMethod) { headers.authorization = `${await this.getAccessToken()}`; } else { throw new errors_1.UserFixableError(errors_1.USER_ERROR_MESSAGES.notLoggedInYet); } if (this.withOrg) { headers['x-org'] = this.withOrg; } return headers; } async refreshToken(token, zone) { const oauth2 = this.getOAuth2(zone); const { clientId, accessTokenUri } = Auth.getOAuth2Options(zone); try { const data = await this.oauth2._request({ url: accessTokenUri, method: 'POST', body: { client_id: clientId, refresh_token: token.refreshToken, grant_type: 'refresh_token', }, query: {}, headers: { Accept: 'application/json, application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded', }, }); return oauth2.createToken(data); } catch (_a) { throw new errors_1.UserFixableError(errors_1.USER_ERROR_MESSAGES.expiredAuthentication); } } static getOAuth2Options(zone) { const { oAuthHost, oAuthClientId } = constants_1.ZONE_SETTINGS[zone]; return { clientId: process.env.OAUTH_CLIENT_ID || oAuthClientId || 'cli-client-pkce', accessTokenUri: new URL('/oauth2/token', process.env.OAUTH_HOST || oAuthHost || 'https://auth.amplitude.com').href, authorizationUri: new URL('/oauth2/auth', process.env.OAUTH_HOST || oAuthHost || 'https://auth.amplitude.com').href, }; } async request(method, url, body, headers) { const fetchHeaders = Object.entries(headers).reduce((acc, [key, value]) => { acc[key] = Array.isArray(value) ? value[0] : value; return acc; }, {}); const response = await node_fetch_1.default(url, { method, body, headers: fetchHeaders, agent: this.fetchAgent, }); const responseBody = await response.text(); return { status: response.status, body: responseBody, }; } } exports.default = new Auth();