@hyperdrive.bot/cli 1.0.12 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +1495 -474
  2. package/dist/commands/deploy.d.ts +18 -0
  3. package/dist/commands/deploy.js +239 -0
  4. package/dist/commands/deployment/create.js +10 -2
  5. package/dist/commands/domain/{switch.d.ts → set-production.d.ts} +1 -1
  6. package/dist/commands/domain/set-production.js +27 -0
  7. package/dist/commands/git/list-open-prs.d.ts +12 -0
  8. package/dist/commands/git/list-open-prs.js +87 -0
  9. package/dist/commands/hook/add.d.ts +22 -0
  10. package/dist/commands/hook/add.js +299 -0
  11. package/dist/commands/hook/list.d.ts +11 -0
  12. package/dist/commands/hook/list.js +111 -0
  13. package/dist/commands/hook/logs.d.ts +13 -0
  14. package/dist/commands/hook/logs.js +124 -0
  15. package/dist/commands/hook/remove.d.ts +12 -0
  16. package/dist/commands/hook/remove.js +115 -0
  17. package/dist/commands/hook/toggle.d.ts +12 -0
  18. package/dist/commands/hook/toggle.js +125 -0
  19. package/dist/commands/init.d.ts +1 -1
  20. package/dist/commands/init.js +49 -9
  21. package/dist/commands/module/bindings.d.ts +14 -0
  22. package/dist/commands/module/bindings.js +125 -0
  23. package/dist/commands/module/create.d.ts +3 -0
  24. package/dist/commands/module/create.js +156 -78
  25. package/dist/commands/module/list.d.ts +1 -0
  26. package/dist/commands/module/list.js +22 -1
  27. package/dist/commands/module/sync.d.ts +29 -0
  28. package/dist/commands/module/sync.js +409 -0
  29. package/dist/commands/module/unlink.d.ts +11 -0
  30. package/dist/commands/module/unlink.js +77 -0
  31. package/dist/commands/module/update.d.ts +10 -0
  32. package/dist/commands/module/update.js +168 -5
  33. package/dist/commands/network/discover.d.ts +12 -0
  34. package/dist/commands/network/discover.js +210 -0
  35. package/dist/commands/network/get.d.ts +13 -0
  36. package/dist/commands/network/get.js +90 -0
  37. package/dist/commands/{auth/logout.d.ts → network/list.d.ts} +2 -9
  38. package/dist/commands/network/list.js +71 -0
  39. package/dist/commands/network/register.d.ts +16 -0
  40. package/dist/commands/network/register.js +144 -0
  41. package/dist/commands/parameter/sync.d.ts +13 -0
  42. package/dist/commands/parameter/sync.js +69 -1
  43. package/dist/commands/project/sync.d.ts +5 -11
  44. package/dist/commands/project/sync.js +12 -381
  45. package/dist/commands/seed.d.ts +93 -0
  46. package/dist/commands/seed.js +324 -0
  47. package/dist/commands/service/backup.d.ts +17 -0
  48. package/dist/commands/service/backup.js +156 -0
  49. package/dist/commands/service/backups.d.ts +14 -0
  50. package/dist/commands/service/backups.js +110 -0
  51. package/dist/commands/service/bind.d.ts +16 -0
  52. package/dist/commands/service/bind.js +106 -0
  53. package/dist/commands/service/bindings.d.ts +13 -0
  54. package/dist/commands/service/bindings.js +78 -0
  55. package/dist/commands/service/clone.d.ts +19 -0
  56. package/dist/commands/service/clone.js +153 -0
  57. package/dist/commands/service/create.d.ts +16 -0
  58. package/dist/commands/service/create.js +212 -0
  59. package/dist/commands/service/get.d.ts +13 -0
  60. package/dist/commands/service/get.js +97 -0
  61. package/dist/commands/service/list.d.ts +12 -0
  62. package/dist/commands/service/list.js +86 -0
  63. package/dist/commands/service/register.d.ts +21 -0
  64. package/dist/commands/service/register.js +215 -0
  65. package/dist/commands/service/restore.d.ts +19 -0
  66. package/dist/commands/service/restore.js +158 -0
  67. package/dist/commands/service/seed.d.ts +17 -0
  68. package/dist/commands/service/seed.js +173 -0
  69. package/dist/commands/service/templates.d.ts +10 -0
  70. package/dist/commands/service/templates.js +66 -0
  71. package/dist/commands/service/unbind.d.ts +15 -0
  72. package/dist/commands/service/unbind.js +74 -0
  73. package/dist/commands/stage/create.d.ts +23 -0
  74. package/dist/commands/stage/create.js +145 -6
  75. package/dist/commands/stage/delete.d.ts +11 -0
  76. package/dist/commands/stage/delete.js +85 -0
  77. package/dist/commands/stage/deploy.d.ts +34 -0
  78. package/dist/commands/stage/deploy.js +294 -0
  79. package/dist/commands/stage/ensure-branches.d.ts +23 -0
  80. package/dist/commands/stage/ensure-branches.js +101 -0
  81. package/dist/commands/stage/list.js +4 -0
  82. package/dist/commands/stage/status.d.ts +14 -0
  83. package/dist/commands/stage/status.js +100 -0
  84. package/dist/commands/{jira → tracker}/connect.js +32 -23
  85. package/dist/commands/tracker/hook/add.d.ts +25 -0
  86. package/dist/commands/tracker/hook/add.js +284 -0
  87. package/dist/commands/{jira → tracker}/hook/list.js +20 -11
  88. package/dist/commands/{jira/hook/add.d.ts → tracker/hook/logs.d.ts} +2 -3
  89. package/dist/commands/tracker/hook/logs.js +126 -0
  90. package/dist/commands/{jira → tracker}/hook/remove.js +9 -8
  91. package/dist/commands/{jira → tracker}/hook/toggle.js +14 -12
  92. package/dist/commands/tracker/project/init.d.ts +17 -0
  93. package/dist/commands/tracker/project/init.js +178 -0
  94. package/dist/commands/tracker/project/link-module.d.ts +17 -0
  95. package/dist/commands/tracker/project/link-module.js +287 -0
  96. package/dist/commands/tracker/project/list-modules.d.ts +11 -0
  97. package/dist/commands/tracker/project/list-modules.js +117 -0
  98. package/dist/commands/tracker/project/list.d.ts +10 -0
  99. package/dist/commands/tracker/project/list.js +90 -0
  100. package/dist/commands/tracker/project/status.d.ts +13 -0
  101. package/dist/commands/tracker/project/status.js +168 -0
  102. package/dist/commands/tracker/project/unlink-module.d.ts +13 -0
  103. package/dist/commands/tracker/project/unlink-module.js +251 -0
  104. package/dist/commands/{jira → tracker}/status.js +3 -3
  105. package/dist/lib/ensure-branches.d.ts +53 -0
  106. package/dist/lib/ensure-branches.js +149 -0
  107. package/dist/lib/git-providers/github.d.ts +16 -0
  108. package/dist/lib/git-providers/github.js +157 -0
  109. package/dist/lib/git-providers/gitlab.d.ts +16 -0
  110. package/dist/lib/git-providers/gitlab.js +148 -0
  111. package/dist/lib/git-providers/index.d.ts +67 -0
  112. package/dist/lib/git-providers/index.js +39 -0
  113. package/dist/lib/lambda-warmer.d.ts +106 -0
  114. package/dist/lib/lambda-warmer.js +189 -0
  115. package/dist/services/hyperdrive-sigv4.d.ts +360 -5
  116. package/dist/services/hyperdrive-sigv4.js +192 -24
  117. package/dist/utils/hook-flow.d.ts +60 -3
  118. package/dist/utils/hook-flow.js +437 -2
  119. package/dist/utils/hook-normalize.d.ts +6 -0
  120. package/dist/utils/hook-normalize.js +33 -0
  121. package/dist/utils/lifecycle-poller.d.ts +32 -0
  122. package/dist/utils/lifecycle-poller.js +72 -0
  123. package/dist/utils/retry.d.ts +43 -0
  124. package/dist/utils/retry.js +88 -0
  125. package/dist/utils/summary-display.js +1 -1
  126. package/dist/utils/tracker-project-flow.d.ts +84 -0
  127. package/dist/utils/tracker-project-flow.js +564 -0
  128. package/package.json +35 -7
  129. package/dist/commands/auth/login.d.ts +0 -16
  130. package/dist/commands/auth/login.js +0 -179
  131. package/dist/commands/auth/logout.js +0 -116
  132. package/dist/commands/auth/refresh.d.ts +0 -6
  133. package/dist/commands/auth/refresh.js +0 -66
  134. package/dist/commands/auth/status.d.ts +0 -6
  135. package/dist/commands/auth/status.js +0 -63
  136. package/dist/commands/config/get.d.ts +0 -9
  137. package/dist/commands/config/get.js +0 -37
  138. package/dist/commands/config/set.d.ts +0 -10
  139. package/dist/commands/config/set.js +0 -48
  140. package/dist/commands/config/show.d.ts +0 -6
  141. package/dist/commands/config/show.js +0 -10
  142. package/dist/commands/domain/current.d.ts +0 -6
  143. package/dist/commands/domain/current.js +0 -18
  144. package/dist/commands/domain/list.d.ts +0 -6
  145. package/dist/commands/domain/list.js +0 -42
  146. package/dist/commands/domain/switch.js +0 -40
  147. package/dist/commands/jira/hook/add.js +0 -147
  148. package/dist/services/tenant-service.d.ts +0 -127
  149. package/dist/services/tenant-service.js +0 -396
  150. package/dist/utils/auth-flow.d.ts +0 -147
  151. package/dist/utils/auth-flow.js +0 -479
  152. package/oclif.manifest.json +0 -3519
  153. /package/dist/commands/{jira → tracker}/connect.d.ts +0 -0
  154. /package/dist/commands/{jira → tracker}/hook/list.d.ts +0 -0
  155. /package/dist/commands/{jira → tracker}/hook/remove.d.ts +0 -0
  156. /package/dist/commands/{jira → tracker}/hook/toggle.d.ts +0 -0
  157. /package/dist/commands/{jira → tracker}/status.d.ts +0 -0
@@ -1,479 +0,0 @@
1
- import { CognitoIdentityClient, GetCredentialsForIdentityCommand, GetIdCommand } from '@aws-sdk/client-cognito-identity';
2
- import { CognitoIdentityProviderClient, InitiateAuthCommand, } from '@aws-sdk/client-cognito-identity-provider';
3
- import axios from 'axios';
4
- import crypto from 'crypto';
5
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
6
- import { createServer } from 'http';
7
- import open from 'open';
8
- import { homedir } from 'os';
9
- import { join } from 'path';
10
- import { parse } from 'url';
11
- import { TenantService } from '../services/tenant-service.js';
12
- /**
13
- * Generate PKCE code verifier (random base64url string)
14
- */
15
- export function generateCodeVerifier() {
16
- return crypto.randomBytes(32).toString('base64url');
17
- }
18
- /**
19
- * Generate PKCE code challenge (SHA256 hash of verifier)
20
- */
21
- export function generateCodeChallenge(verifier) {
22
- return crypto.createHash('sha256').update(verifier).digest('base64url');
23
- }
24
- /**
25
- * Build Cognito authorization URL with PKCE parameters
26
- */
27
- export function buildAuthUrl(tenantConfig, codeChallenge, port) {
28
- // CLI only needs these scopes to authenticate and get AWS credentials via Identity Pool
29
- // - openid: Required for OIDC authentication
30
- // - email: User's email address (used by Identity Pool)
31
- // - profile: User's profile information
32
- const requiredScopes = 'openid email profile';
33
- const params = new URLSearchParams({
34
- client_id: tenantConfig.cognitoClientId,
35
- code_challenge: codeChallenge,
36
- code_challenge_method: 'S256',
37
- redirect_uri: `http://localhost:${port}/callback`,
38
- response_type: 'code',
39
- scope: requiredScopes,
40
- });
41
- return `https://${tenantConfig.cognitoDomain}/oauth2/authorize?${params}`;
42
- }
43
- /**
44
- * Start local HTTP server to receive OAuth callback
45
- * Returns a promise that resolves with the authorization code
46
- */
47
- export function startCallbackServer(port, timeout) {
48
- return new Promise((resolve, reject) => {
49
- let timeoutId = null;
50
- const server = createServer((req, res) => {
51
- const { pathname, query } = parse(req.url || '', true);
52
- if (pathname === '/callback') {
53
- if (timeoutId) {
54
- clearTimeout(timeoutId);
55
- }
56
- if (query.code) {
57
- // Success response
58
- res.writeHead(200, { 'Content-Type': 'text/html' });
59
- res.end(`
60
- <!DOCTYPE html>
61
- <html>
62
- <head>
63
- <title>Hyperdrive - Authentication Successful</title>
64
- <style>
65
- body {
66
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
67
- display: flex;
68
- align-items: center;
69
- justify-content: center;
70
- height: 100vh;
71
- margin: 0;
72
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
73
- }
74
- .container {
75
- text-align: center;
76
- background: white;
77
- padding: 3rem;
78
- border-radius: 1rem;
79
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
80
- max-width: 400px;
81
- }
82
- .checkmark {
83
- width: 80px;
84
- height: 80px;
85
- border-radius: 50%;
86
- display: block;
87
- stroke-width: 4;
88
- stroke: #10B981;
89
- stroke-miterlimit: 10;
90
- margin: 0 auto 1.5rem;
91
- animation: fill 0.4s ease-in-out 0.4s forwards, scale 0.3s ease-in-out 0.9s both;
92
- }
93
- .checkmark-circle {
94
- stroke-dasharray: 166;
95
- stroke-dashoffset: 166;
96
- stroke-width: 4;
97
- stroke-miterlimit: 10;
98
- stroke: #10B981;
99
- fill: none;
100
- animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
101
- }
102
- .checkmark-check {
103
- transform-origin: 50% 50%;
104
- stroke-dasharray: 48;
105
- stroke-dashoffset: 48;
106
- animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
107
- }
108
- @keyframes stroke {
109
- 100% { stroke-dashoffset: 0; }
110
- }
111
- @keyframes scale {
112
- 0%, 100% { transform: none; }
113
- 50% { transform: scale3d(1.1, 1.1, 1); }
114
- }
115
- h1 { color: #1F2937; margin: 0 0 0.5rem; }
116
- p { color: #6B7280; margin: 0 0 1.5rem; }
117
- .close-btn {
118
- background: #667eea;
119
- color: white;
120
- border: none;
121
- padding: 0.75rem 2rem;
122
- border-radius: 0.5rem;
123
- font-size: 1rem;
124
- cursor: pointer;
125
- transition: background 0.2s;
126
- }
127
- .close-btn:hover { background: #5a67d8; }
128
- </style>
129
- </head>
130
- <body>
131
- <div class="container">
132
- <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
133
- <circle class="checkmark-circle" cx="26" cy="26" r="25" fill="none"/>
134
- <path class="checkmark-check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
135
- </svg>
136
- <h1>Authentication Successful!</h1>
137
- <p>You can close this window and return to the CLI.</p>
138
- <button class="close-btn" onclick="window.close()">Close Window</button>
139
- </div>
140
- <script>setTimeout(() => window.close(), 3000);</script>
141
- </body>
142
- </html>
143
- `);
144
- server.close();
145
- resolve(query.code);
146
- }
147
- else if (query.error) {
148
- // Error response
149
- res.writeHead(400, { 'Content-Type': 'text/html' });
150
- res.end(`
151
- <h1>Authentication Failed</h1>
152
- <p>Error: ${query.error}</p>
153
- <p>Description: ${query.error_description || 'Unknown error'}</p>
154
- `);
155
- server.close();
156
- reject(new Error(`Authentication failed: ${query.error}`));
157
- }
158
- }
159
- else {
160
- res.writeHead(404);
161
- res.end('Not Found');
162
- }
163
- });
164
- server.listen(port, 'localhost');
165
- server.on('error', (err) => {
166
- if (timeoutId) {
167
- clearTimeout(timeoutId);
168
- }
169
- reject(new Error(`Failed to start callback server: ${err.message}`));
170
- });
171
- // Set timeout for authentication
172
- timeoutId = setTimeout(() => {
173
- server.close();
174
- reject(new Error('Authentication timed out. Please try again.'));
175
- }, timeout);
176
- });
177
- }
178
- /**
179
- * Exchange authorization code for Cognito tokens
180
- */
181
- export async function exchangeCodeForTokens(tenantConfig, code, codeVerifier, port) {
182
- try {
183
- const response = await axios.post(`https://${tenantConfig.cognitoDomain}/oauth2/token`, new URLSearchParams({
184
- client_id: tenantConfig.cognitoClientId,
185
- code,
186
- code_verifier: codeVerifier,
187
- grant_type: 'authorization_code',
188
- redirect_uri: `http://localhost:${port}/callback`,
189
- }), {
190
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
191
- });
192
- return response.data;
193
- }
194
- catch (error) {
195
- if (axios.isAxiosError(error)) {
196
- throw new Error(`Token exchange failed: ${error.response?.data?.error_description || error.message}`);
197
- }
198
- throw error;
199
- }
200
- }
201
- /**
202
- * Get AWS credentials from Cognito Identity Pool
203
- */
204
- export async function getAWSCredentials(tenantConfig, idToken) {
205
- const client = new CognitoIdentityClient({ region: tenantConfig.region });
206
- try {
207
- // Step 1: Get Identity ID
208
- const getIdResponse = await client.send(new GetIdCommand({
209
- IdentityPoolId: tenantConfig.cognitoIdentityPoolId,
210
- Logins: {
211
- [`cognito-idp.${tenantConfig.region}.amazonaws.com/${tenantConfig.cognitoUserPoolId}`]: idToken,
212
- },
213
- }));
214
- if (!getIdResponse.IdentityId) {
215
- throw new Error('Failed to get Identity ID from Cognito');
216
- }
217
- // Step 2: Get temporary AWS credentials
218
- const getCredentialsResponse = await client.send(new GetCredentialsForIdentityCommand({
219
- IdentityId: getIdResponse.IdentityId,
220
- Logins: {
221
- [`cognito-idp.${tenantConfig.region}.amazonaws.com/${tenantConfig.cognitoUserPoolId}`]: idToken,
222
- },
223
- }));
224
- if (!getCredentialsResponse.Credentials) {
225
- throw new Error('Failed to get AWS credentials from Cognito');
226
- }
227
- const { AccessKeyId, Expiration, SecretKey, SessionToken } = getCredentialsResponse.Credentials;
228
- if (!AccessKeyId || !SecretKey || !SessionToken || !Expiration) {
229
- throw new Error('Incomplete AWS credentials received from Cognito');
230
- }
231
- return {
232
- accessKeyId: AccessKeyId,
233
- expiration: Expiration,
234
- secretAccessKey: SecretKey,
235
- sessionToken: SessionToken,
236
- };
237
- }
238
- catch (error) {
239
- if (error instanceof Error) {
240
- throw new Error(`Failed to obtain AWS credentials: ${error.message}`);
241
- }
242
- throw error;
243
- }
244
- }
245
- /**
246
- * Save credentials to domain-specific path (always required)
247
- */
248
- export function saveCredentials(credentials, domain) {
249
- // Prevent saving test/mock credentials to production path
250
- const testPatterns = ['test-client-id', 'us-east-1_TEST', 'test-identity', 'test.auth.amazoncognito'];
251
- const credString = JSON.stringify(credentials);
252
- for (const pattern of testPatterns) {
253
- if (credString.includes(pattern)) {
254
- throw new Error(`Refusing to save credentials containing test value: "${pattern}". This looks like test data.`);
255
- }
256
- }
257
- if (!domain) {
258
- throw new Error('Domain is required to save credentials');
259
- }
260
- const credDir = join(homedir(), '.hyperdrive');
261
- const credPath = join(credDir, `credentials.${domain}`);
262
- // Create directory if it doesn't exist
263
- if (!existsSync(credDir)) {
264
- mkdirSync(credDir, { recursive: true });
265
- }
266
- // Write credentials with secure file permissions
267
- writeFileSync(credPath, JSON.stringify(credentials, null, 2), { mode: 0o600 });
268
- }
269
- /**
270
- * Get the path to the credentials file for a domain
271
- */
272
- export function getCredentialsPath(domain) {
273
- if (!domain) {
274
- throw new Error('Domain is required to get credentials path');
275
- }
276
- return join(homedir(), '.hyperdrive', `credentials.${domain}`);
277
- }
278
- /**
279
- * Execute the complete OAuth PKCE authentication flow
280
- *
281
- * This function orchestrates the entire authentication flow:
282
- * 1. Bootstrap tenant to get Cognito config
283
- * 2. Generate PKCE codes
284
- * 3. Start callback server
285
- * 4. Open browser for authentication
286
- * 5. Wait for callback with timeout
287
- * 6. Exchange code for tokens
288
- * 7. Get AWS credentials from Identity Pool
289
- * 8. Save credentials to file
290
- *
291
- * @param options - Configuration options for the auth flow
292
- * @returns AuthResult indicating success or failure
293
- */
294
- export async function executeAuthFlow(options) {
295
- const { callbackPort = 8765, logger, tenantDomain, timeout = 300000 } = options;
296
- const tenantService = new TenantService(tenantDomain);
297
- try {
298
- // Step 1: Bootstrap tenant to get Cognito config
299
- const tenantConfig = await tenantService.fetchTenantConfig(tenantDomain);
300
- // Step 2: Generate PKCE parameters
301
- const codeVerifier = generateCodeVerifier();
302
- const codeChallenge = generateCodeChallenge(codeVerifier);
303
- // Step 3: Start local callback server and get promise for auth code
304
- const authCodePromise = startCallbackServer(callbackPort, timeout);
305
- // Step 4: Open browser for authentication
306
- const authUrl = buildAuthUrl(tenantConfig, codeChallenge, callbackPort);
307
- // Display URL to user in case browser opens in wrong profile
308
- if (logger) {
309
- logger(`Opening browser for authentication...`);
310
- logger(`If browser doesn't open or opens in wrong profile, copy this URL:`);
311
- logger(` ${authUrl}`);
312
- logger('');
313
- }
314
- await open(authUrl);
315
- // Step 5: Wait for callback with auth code
316
- const code = await authCodePromise;
317
- // Step 6: Exchange code for tokens
318
- const tokens = await exchangeCodeForTokens(tenantConfig, code, codeVerifier, callbackPort);
319
- // Step 7: Get AWS credentials from Cognito Identity Pool
320
- const awsCredentials = await getAWSCredentials(tenantConfig, tokens.id_token);
321
- // Step 8: Save credentials
322
- saveCredentials({
323
- ...tokens,
324
- additionalApiUrls: tenantConfig.additionalApiUrls,
325
- apiUrl: tenantConfig.apiUrl,
326
- awsCredentials,
327
- cognitoConfig: {
328
- clientId: tenantConfig.cognitoClientId,
329
- domain: tenantConfig.cognitoDomain,
330
- identityPoolId: tenantConfig.cognitoIdentityPoolId,
331
- userPoolId: tenantConfig.cognitoUserPoolId,
332
- },
333
- obtainedAt: new Date().toISOString(),
334
- region: tenantConfig.region,
335
- tenantDomain: tenantConfig.tenantDomain,
336
- tenantId: tenantConfig.tenantId,
337
- }, tenantConfig.tenantDomain);
338
- return { success: true };
339
- }
340
- catch (error) {
341
- const errorMessage = error instanceof Error ? error.message : String(error);
342
- return { error: errorMessage, success: false };
343
- }
344
- }
345
- /**
346
- * Execute CI authentication flow using USER_PASSWORD_AUTH
347
- *
348
- * This is for non-interactive CI/CD environments where browser-based
349
- * OAuth is not possible. Uses Cognito's USER_PASSWORD_AUTH flow.
350
- *
351
- * @param options - CI authentication options
352
- * @returns AuthResult indicating success or failure
353
- */
354
- export async function executeCIAuthFlow(options) {
355
- const { logger, password, tenantDomain, username } = options;
356
- const tenantService = new TenantService(tenantDomain);
357
- try {
358
- // Step 1: Bootstrap tenant to get Cognito config
359
- if (logger)
360
- logger('Fetching tenant configuration...');
361
- const tenantConfig = await tenantService.fetchTenantConfig(tenantDomain);
362
- // Step 2: Authenticate with Cognito using USER_PASSWORD_AUTH
363
- if (logger)
364
- logger('Authenticating with Cognito...');
365
- const cognitoClient = new CognitoIdentityProviderClient({ region: tenantConfig.region });
366
- let authResult;
367
- try {
368
- const initiateAuthResponse = await cognitoClient.send(new InitiateAuthCommand({
369
- AuthFlow: 'USER_PASSWORD_AUTH',
370
- AuthParameters: {
371
- PASSWORD: password,
372
- USERNAME: username,
373
- },
374
- ClientId: tenantConfig.cognitoClientId,
375
- }));
376
- // Handle NEW_PASSWORD_REQUIRED challenge (first login with temp password)
377
- if (initiateAuthResponse.ChallengeName === 'NEW_PASSWORD_REQUIRED') {
378
- throw new Error('This CI account requires a password change on first login.\n' +
379
- 'Please log in interactively once with: hd auth login --domain ' + tenantDomain + '\n' +
380
- 'Or create a new CI account with: hd ci account create');
381
- }
382
- authResult = initiateAuthResponse.AuthenticationResult;
383
- }
384
- catch (error) {
385
- const err = error;
386
- if (err.name === 'NotAuthorizedException') {
387
- throw new Error('Invalid token. Check your HD_TOKEN environment variable.');
388
- }
389
- if (err.name === 'UserNotFoundException') {
390
- throw new Error('CI token not found or revoked. Create a new one with: hd ci account create');
391
- }
392
- throw error;
393
- }
394
- if (!authResult || !authResult.IdToken) {
395
- throw new Error('Authentication failed: No tokens received from Cognito');
396
- }
397
- if (logger)
398
- logger('Authentication successful!');
399
- // Step 3: Get AWS credentials from Cognito Identity Pool
400
- if (logger)
401
- logger('Obtaining AWS credentials...');
402
- const awsCredentials = await getAWSCredentials(tenantConfig, authResult.IdToken);
403
- // Step 4: Save credentials
404
- if (logger)
405
- logger('Saving credentials...');
406
- saveCredentials({
407
- access_token: authResult.AccessToken || '',
408
- additionalApiUrls: tenantConfig.additionalApiUrls,
409
- apiUrl: tenantConfig.apiUrl,
410
- awsCredentials,
411
- cognitoConfig: {
412
- clientId: tenantConfig.cognitoClientId,
413
- domain: tenantConfig.cognitoDomain,
414
- identityPoolId: tenantConfig.cognitoIdentityPoolId,
415
- userPoolId: tenantConfig.cognitoUserPoolId,
416
- },
417
- expires_in: authResult.ExpiresIn || 3600,
418
- id_token: authResult.IdToken,
419
- obtainedAt: new Date().toISOString(),
420
- refresh_token: authResult.RefreshToken || '',
421
- region: tenantConfig.region,
422
- tenantDomain: tenantConfig.tenantDomain,
423
- tenantId: tenantConfig.tenantId,
424
- token_type: authResult.TokenType || 'Bearer',
425
- }, tenantConfig.tenantDomain);
426
- return { success: true };
427
- }
428
- catch (error) {
429
- const errorMessage = error instanceof Error ? error.message : String(error);
430
- return { error: errorMessage, success: false };
431
- }
432
- }
433
- /**
434
- * Check if running in a CI environment
435
- */
436
- export function isCI() {
437
- return !!(process.env.CI ||
438
- process.env.GITHUB_ACTIONS ||
439
- process.env.GITLAB_CI ||
440
- process.env.JENKINS_URL ||
441
- process.env.CIRCLECI ||
442
- process.env.BUILDKITE ||
443
- process.env.TRAVIS ||
444
- process.env.CODEBUILD_BUILD_ID);
445
- }
446
- /**
447
- * Decode a CI token into username and password
448
- * Token format: hd_sk_{base64url(username:password)}
449
- */
450
- export function decodeToken(token) {
451
- if (!token.startsWith('hd_sk_')) {
452
- return null;
453
- }
454
- try {
455
- const encoded = token.slice(6); // Remove 'hd_sk_' prefix
456
- const decoded = Buffer.from(encoded, 'base64url').toString('utf-8');
457
- const colonIndex = decoded.indexOf(':');
458
- if (colonIndex === -1) {
459
- return null;
460
- }
461
- return {
462
- password: decoded.slice(colonIndex + 1),
463
- username: decoded.slice(0, colonIndex),
464
- };
465
- }
466
- catch {
467
- return null;
468
- }
469
- }
470
- /**
471
- * Get CI credentials from HD_TOKEN environment variable
472
- */
473
- export function getCICredentials() {
474
- const token = process.env.HD_TOKEN;
475
- if (!token) {
476
- return null;
477
- }
478
- return decodeToken(token);
479
- }