UNPKG

7.53 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const sdk_1 = require("@cto.ai/sdk");
5const command_1 = tslib_1.__importStar(require("@oclif/command"));
6exports.flags = command_1.flags;
7const debug_1 = tslib_1.__importDefault(require("debug"));
8const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
9const services_1 = require("./services");
10const utils_1 = require("./utils");
11const env_1 = require("./constants/env");
12const CustomErrors_1 = require("./errors/CustomErrors");
13const debug = debug_1.default('ops:BaseCommand');
14class CTOCommand extends command_1.default {
15 constructor(argv, config, services = services_1.defaultServicesList) {
16 super(argv, config);
17 this.ux = sdk_1.ux;
18 this.isTokenValid = (tokens) => {
19 const { refreshToken } = tokens;
20 const { exp: refreshTokenExp } = jsonwebtoken_1.default.decode(refreshToken);
21 const clockTimestamp = Math.floor(Date.now() / 1000);
22 /*
23 * Note: when the token is an offline token, refreshTokenExp will be equal to 0. We are not issuing offline tokens at the moment, but if we do, we need to add the extra condition that refreshTokenExp !== 0
24 */
25 return clockTimestamp < refreshTokenExp;
26 };
27 this.checkAndRefreshAccessToken = async (tokens) => {
28 debug('checking for valid access token');
29 try {
30 const { refreshToken } = tokens;
31 if (!this.isTokenValid(tokens))
32 throw new CustomErrors_1.TokenExpiredError();
33 /**
34 * The following code updates the access token every time a command is run
35 */
36 const oldConfig = await this.readConfig();
37 const newTokens = await this.services.keycloakService.refreshAccessToken(oldConfig, refreshToken);
38 this.accessToken = newTokens.accessToken;
39 await this.writeConfig(oldConfig, { tokens: newTokens });
40 const config = await this.readConfig();
41 this.state.config = config;
42 return config;
43 }
44 catch (error) {
45 debug('%O', error);
46 await this.clearConfig();
47 throw new CustomErrors_1.TokenExpiredError();
48 }
49 };
50 this.fetchUserInfo = async ({ tokens }) => {
51 if (!tokens) {
52 this.ux.spinner.stop(`failed`);
53 this.log('missing parameter');
54 process.exit();
55 }
56 const { accessToken, idToken } = tokens;
57 if (!accessToken || !idToken) {
58 this.ux.spinner.stop(`❗️\n`);
59 this.log(`🤔 Sorry, we couldn’t find an account with that email or password.\nForgot your password? Run ${this.ux.colors.bold('ops account:reset')}.\n`);
60 process.exit();
61 }
62 const { sub, preferred_username, email } = jsonwebtoken_1.default.decode(idToken);
63 const me = {
64 id: sub,
65 username: preferred_username,
66 email,
67 };
68 const { data: teams } = await this.services.api
69 .find('/private/teams', {
70 query: {
71 userId: sub,
72 },
73 headers: { Authorization: accessToken },
74 })
75 .catch(err => {
76 debug('%O', err);
77 throw new CustomErrors_1.APIError(err);
78 });
79 if (!teams) {
80 throw new CustomErrors_1.APIError('According to the API, this user does not belong to any teams.');
81 }
82 const meResponse = {
83 me,
84 teams,
85 };
86 return { meResponse, tokens };
87 };
88 this.writeConfig = async (oldConfigObj = {}, newConfigObj) => {
89 return utils_1.writeConfig(oldConfigObj, newConfigObj, this.config.configDir);
90 };
91 this.readConfig = async () => {
92 return utils_1.readConfig(this.config.configDir);
93 };
94 this.clearConfig = async () => {
95 return utils_1.clearConfig(this.config.configDir);
96 };
97 this.invalidateKeycloakSession = async () => {
98 // Obtains the session state if exists
99 const sessionState = this.state.config
100 ? this.state.config.tokens
101 ? this.state.config.tokens.sessionState
102 : null
103 : null;
104 // If session state exists, invalidate it
105 if (sessionState) {
106 const { accessToken, refreshToken } = this.state.config.tokens;
107 this.services.keycloakService
108 .InvalidateSession(accessToken, refreshToken)
109 .catch(err => {
110 console.log('err :>> ', err);
111 });
112 }
113 };
114 this.initConfig = async (tokens) => {
115 await this.clearConfig();
116 const signinFlowPipeline = utils_1.asyncPipe(this.fetchUserInfo, utils_1.formatConfigObject, this.writeConfig, this.readConfig);
117 const config = await signinFlowPipeline({ tokens });
118 return config;
119 };
120 this.services = Object.assign(services_1.defaultServicesList, services);
121 }
122 async init() {
123 try {
124 debug('initiating base command');
125 const config = await this.readConfig();
126 const { user, tokens, team } = config;
127 if (tokens) {
128 this.accessToken = tokens.accessToken;
129 }
130 this.user = user;
131 this.team = team;
132 this.state = { config };
133 }
134 catch (err) {
135 this.config.runHook('error', { err, accessToken: this.accessToken });
136 }
137 }
138 async isLoggedIn() {
139 debug('checking if user is logged in');
140 const config = await this.readConfig();
141 const { tokens } = config;
142 if (!this.user ||
143 !this.team ||
144 !this.accessToken ||
145 !tokens ||
146 !tokens.accessToken ||
147 !tokens.refreshToken ||
148 !tokens.idToken) {
149 this.log('');
150 this.log('✋ Sorry you need to be logged in to do that.');
151 this.log(`🎳 You can sign up with ${this.ux.colors.green('$')} ${this.ux.colors.callOutCyan('ops account:signup')}`);
152 this.log('');
153 this.log('❔ Please reach out to us with questions anytime!');
154 this.log(`⌚️ We are typically available ${this.ux.colors.white('Monday-Friday 9am-5pm PT')}.`);
155 this.log(`📬 You can always reach us by ${this.ux.url('email', `mailto:${env_1.INTERCOM_EMAIL}`)} ${this.ux.colors.dim(`(${env_1.INTERCOM_EMAIL})`)}.\n`);
156 this.log("🖖 We'll get back to you as soon as we possibly can.");
157 this.log('');
158 process.exit();
159 }
160 return this.checkAndRefreshAccessToken(tokens);
161 }
162 async validateUniqueField(query, accessToken) {
163 const response = await this.services.api
164 .find('/private/validate', {
165 query,
166 headers: { Authorization: accessToken },
167 })
168 .catch(err => {
169 throw new CustomErrors_1.APIError(err);
170 });
171 return response.data;
172 }
173}
174exports.default = CTOCommand;