@amplitude/ampli
Version:
Amplitude CLI
203 lines (202 loc) • 7.67 kB
JavaScript
"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();