UNPKG

9.22 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 axios_1 = tslib_1.__importDefault(require("axios"));
10const utils_1 = require("./utils");
11const env_1 = require("./constants/env");
12const Feathers_1 = require("./services/Feathers");
13const Analytics_1 = require("./services/Analytics");
14const CustomErrors_1 = require("./errors/CustomErrors");
15const Publish_1 = require("./services/Publish");
16const BuildSteps_1 = require("./services/BuildSteps");
17const Image_1 = require("./services/Image");
18const Workflow_1 = require("./services/Workflow");
19const Op_1 = require("./services/Op");
20const Keycloak_1 = require("./services/Keycloak");
21const RegistryAuth_1 = require("./services/RegistryAuth");
22const debug = debug_1.default('ops:BaseCommand');
23class CTOCommand extends command_1.default {
24 constructor(argv, config, services = {
25 api: new Feathers_1.FeathersClient(),
26 publishService: new Publish_1.Publish(),
27 buildStepService: new BuildSteps_1.BuildSteps(),
28 imageService: new Image_1.ImageService(),
29 analytics: new Analytics_1.AnalyticsService(env_1.OPS_SEGMENT_KEY),
30 workflowService: new Workflow_1.WorkflowService(),
31 opService: new Op_1.OpService(),
32 keycloakService: new Keycloak_1.KeycloakService(),
33 registryAuthService: new RegistryAuth_1.RegistryAuthService(),
34 }) {
35 super(argv, config);
36 this.ux = sdk_1.ux;
37 this.isTokenValid = (tokens) => {
38 const { refreshToken } = tokens;
39 const { exp: refreshTokenExp } = jsonwebtoken_1.default.decode(refreshToken);
40 const clockTimestamp = Math.floor(Date.now() / 1000);
41 /*
42 * 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
43 */
44 return clockTimestamp < refreshTokenExp;
45 };
46 this.checkAndRefreshAccessToken = async (tokens) => {
47 debug('checking for valid access token');
48 try {
49 const { refreshToken } = tokens;
50 if (!this.isTokenValid(tokens))
51 throw new CustomErrors_1.TokenExpiredError();
52 /**
53 * The following code updates the access token every time a command is run
54 */
55 const oldConfig = await this.readConfig();
56 const newTokens = await this.services.keycloakService.refreshAccessToken(oldConfig, refreshToken);
57 this.accessToken = newTokens.accessToken;
58 await this.writeConfig(oldConfig, { tokens: newTokens });
59 const config = await this.readConfig();
60 this.state.config = config;
61 return config;
62 }
63 catch (error) {
64 debug('%O', error);
65 await this.clearConfig();
66 throw new CustomErrors_1.TokenExpiredError();
67 }
68 };
69 this.fetchUserInfo = async ({ tokens }) => {
70 if (!tokens) {
71 this.ux.spinner.stop(`failed`);
72 this.log('missing parameter');
73 process.exit();
74 }
75 const { accessToken, idToken } = tokens;
76 if (!accessToken || !idToken) {
77 this.ux.spinner.stop(`❗️\n`);
78 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`);
79 process.exit();
80 }
81 const { sub, preferred_username, email } = jsonwebtoken_1.default.decode(idToken);
82 const me = {
83 id: sub,
84 username: preferred_username,
85 email,
86 };
87 const { data: teams } = await this.services.api
88 .find('teams', {
89 query: {
90 userId: sub,
91 },
92 headers: { Authorization: accessToken },
93 })
94 .catch(err => {
95 debug('%O', err);
96 throw new CustomErrors_1.APIError(err);
97 });
98 if (!teams) {
99 throw new CustomErrors_1.APIError('According to the API, this user does not belong to any teams.');
100 }
101 const meResponse = {
102 me,
103 teams,
104 };
105 return { meResponse, tokens };
106 };
107 this.writeConfig = async (oldConfigObj = {}, newConfigObj) => {
108 return utils_1.writeConfig(oldConfigObj, newConfigObj, this.config.configDir);
109 };
110 this.readConfig = async () => {
111 return utils_1.readConfig(this.config.configDir);
112 };
113 this.clearConfig = async () => {
114 return utils_1.clearConfig(this.config.configDir);
115 };
116 this.invalidateKeycloakSession = async () => {
117 // Obtains the session state if exists
118 const sessionState = this.state.config
119 ? this.state.config.tokens
120 ? this.state.config.tokens.sessionState
121 : null
122 : null;
123 // If session state exists, invalidate it
124 if (sessionState)
125 await axios_1.default.get(this.services.keycloakService.buildInvalidateSessionUrl(), {
126 headers: this.services.keycloakService.buildInvalidateSessionHeaders(sessionState, this.accessToken),
127 });
128 };
129 this.initConfig = async (tokens) => {
130 await this.clearConfig();
131 const signinFlowPipeline = utils_1.asyncPipe(this.fetchUserInfo, utils_1.formatConfigObject, this.writeConfig, this.readConfig);
132 const config = await signinFlowPipeline({ tokens });
133 return config;
134 };
135 this.services = services;
136 this.services.api = services.api || new Feathers_1.FeathersClient();
137 this.services.publishService = services.publishService || new Publish_1.Publish();
138 this.services.buildStepService =
139 services.buildStepService || new BuildSteps_1.BuildSteps();
140 this.services.imageService = services.imageService || new Image_1.ImageService();
141 this.services.analytics =
142 services.analytics || new Analytics_1.AnalyticsService(env_1.OPS_SEGMENT_KEY);
143 this.services.workflowService =
144 services.workflowService || new Workflow_1.WorkflowService();
145 this.services.opService = services.opService || new Op_1.OpService();
146 this.services.keycloakService =
147 services.keycloakService || new Keycloak_1.KeycloakService();
148 this.services.registryAuthService =
149 services.registryAuthService || new RegistryAuth_1.RegistryAuthService();
150 }
151 async init() {
152 try {
153 debug('initiating base command');
154 const config = await this.readConfig();
155 const { user, tokens, team } = config;
156 if (tokens) {
157 this.accessToken = tokens.accessToken;
158 }
159 this.user = user;
160 this.team = team;
161 this.state = { config };
162 }
163 catch (err) {
164 this.config.runHook('error', { err, accessToken: this.accessToken });
165 }
166 }
167 async isLoggedIn() {
168 debug('checking if user is logged in');
169 const config = await this.readConfig();
170 const { tokens } = config;
171 if (!this.user ||
172 !this.team ||
173 !this.accessToken ||
174 !tokens ||
175 !tokens.accessToken ||
176 !tokens.refreshToken ||
177 !tokens.idToken) {
178 this.log('');
179 this.log('✋ Sorry you need to be logged in to do that.');
180 this.log(`🎳 You can sign up with ${this.ux.colors.green('$')} ${this.ux.colors.callOutCyan('ops account:signup')}`);
181 this.log('');
182 this.log('❔ Please reach out to us with questions anytime!');
183 this.log(`⌚️ We are typically available ${this.ux.colors.white('Monday-Friday 9am-5pm PT')}.`);
184 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`);
185 this.log("🖖 We'll get back to you as soon as we possibly can.");
186 this.log('');
187 process.exit();
188 }
189 return this.checkAndRefreshAccessToken(tokens);
190 }
191 async validateUniqueField(query) {
192 const response = await this.services.api
193 .find('validate', {
194 query,
195 })
196 .catch(err => {
197 throw new CustomErrors_1.APIError(err);
198 });
199 return response.data;
200 }
201}
202exports.default = CTOCommand;