1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const sdk_1 = require("@cto.ai/sdk");
|
5 | const command_1 = tslib_1.__importStar(require("@oclif/command"));
|
6 | exports.flags = command_1.flags;
|
7 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
8 | const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
|
9 | const services_1 = require("./services");
|
10 | const utils_1 = require("./utils");
|
11 | const env_1 = require("./constants/env");
|
12 | const CustomErrors_1 = require("./errors/CustomErrors");
|
13 | const debug = debug_1.default('ops:BaseCommand');
|
14 | class 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 |
|
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 |
|
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 |
|
99 | const sessionState = this.state.config
|
100 | ? this.state.config.tokens
|
101 | ? this.state.config.tokens.sessionState
|
102 | : null
|
103 | : null;
|
104 |
|
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 | }
|
174 | exports.default = CTOCommand;
|