@trayio/cdk-cli 5.5.0 → 5.6.0

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 (52) hide show
  1. package/OAUTH2_TOKEN.md +290 -0
  2. package/README.md +38 -1
  3. package/dist/commands/connector/oauth2-token.d.ts +45 -0
  4. package/dist/commands/connector/oauth2-token.d.ts.map +1 -0
  5. package/dist/commands/connector/oauth2-token.js +243 -0
  6. package/dist/commands/connector/oauth2-token.unit.test.d.ts +2 -0
  7. package/dist/commands/connector/oauth2-token.unit.test.d.ts.map +1 -0
  8. package/dist/commands/connector/oauth2-token.unit.test.js +1244 -0
  9. package/dist/lib/oauth2-token/flows/authorization-code.d.ts +4 -0
  10. package/dist/lib/oauth2-token/flows/authorization-code.d.ts.map +1 -0
  11. package/dist/lib/oauth2-token/flows/authorization-code.js +218 -0
  12. package/dist/lib/oauth2-token/flows/client-credentials.d.ts +4 -0
  13. package/dist/lib/oauth2-token/flows/client-credentials.d.ts.map +1 -0
  14. package/dist/lib/oauth2-token/flows/client-credentials.js +55 -0
  15. package/dist/lib/oauth2-token/flows/device-code.d.ts +4 -0
  16. package/dist/lib/oauth2-token/flows/device-code.d.ts.map +1 -0
  17. package/dist/lib/oauth2-token/flows/device-code.js +143 -0
  18. package/dist/lib/oauth2-token/flows/index.d.ts +8 -0
  19. package/dist/lib/oauth2-token/flows/index.d.ts.map +1 -0
  20. package/dist/lib/oauth2-token/flows/index.js +14 -0
  21. package/dist/lib/oauth2-token/flows/refresh-token.d.ts +4 -0
  22. package/dist/lib/oauth2-token/flows/refresh-token.d.ts.map +1 -0
  23. package/dist/lib/oauth2-token/flows/refresh-token.js +60 -0
  24. package/dist/lib/oauth2-token/token-writer.d.ts +7 -0
  25. package/dist/lib/oauth2-token/token-writer.d.ts.map +1 -0
  26. package/dist/lib/oauth2-token/token-writer.js +83 -0
  27. package/dist/lib/oauth2-token/types.d.ts +34 -0
  28. package/dist/lib/oauth2-token/types.d.ts.map +1 -0
  29. package/dist/lib/oauth2-token/types.js +5 -0
  30. package/dist/lib/oauth2-token/utils/browser.d.ts +2 -0
  31. package/dist/lib/oauth2-token/utils/browser.d.ts.map +1 -0
  32. package/dist/lib/oauth2-token/utils/browser.js +22 -0
  33. package/dist/lib/oauth2-token/utils/crypto.d.ts +6 -0
  34. package/dist/lib/oauth2-token/utils/crypto.d.ts.map +1 -0
  35. package/dist/lib/oauth2-token/utils/crypto.js +47 -0
  36. package/dist/lib/oauth2-token/utils/env.d.ts +7 -0
  37. package/dist/lib/oauth2-token/utils/env.d.ts.map +1 -0
  38. package/dist/lib/oauth2-token/utils/env.js +85 -0
  39. package/dist/lib/oauth2-token/utils/file.d.ts +4 -0
  40. package/dist/lib/oauth2-token/utils/file.d.ts.map +1 -0
  41. package/dist/lib/oauth2-token/utils/file.js +40 -0
  42. package/dist/lib/oauth2-token/utils/index.d.ts +10 -0
  43. package/dist/lib/oauth2-token/utils/index.d.ts.map +1 -0
  44. package/dist/lib/oauth2-token/utils/index.js +25 -0
  45. package/dist/lib/oauth2-token/utils/json.d.ts +9 -0
  46. package/dist/lib/oauth2-token/utils/json.d.ts.map +1 -0
  47. package/dist/lib/oauth2-token/utils/json.js +52 -0
  48. package/dist/lib/oauth2-token/utils/url.d.ts +6 -0
  49. package/dist/lib/oauth2-token/utils/url.d.ts.map +1 -0
  50. package/dist/lib/oauth2-token/utils/url.js +22 -0
  51. package/oclif.manifest.json +150 -1
  52. package/package.json +11 -9
@@ -0,0 +1,4 @@
1
+ import { AxiosHttpClient } from '@trayio/axios/http/AxiosHttpClient';
2
+ import { OAuth2Config, TokenResponse } from '../types';
3
+ export declare const executeAuthorizationCodeFlow: (config: OAuth2Config, httpClient: AxiosHttpClient, shouldOpenBrowser: boolean) => Promise<TokenResponse>;
4
+ //# sourceMappingURL=authorization-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../../../src/lib/oauth2-token/flows/authorization-code.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAOrE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKvD,eAAO,MAAM,4BAA4B,WAChC,YAAY,cACR,eAAe,qBACR,OAAO,KACxB,QAAQ,aAAa,CAiEvB,CAAC"}
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.executeAuthorizationCodeFlow = void 0;
30
+ /**
31
+ * OAuth2 Authorization Code Flow with PKCE
32
+ */
33
+ const core_1 = require("@oclif/core");
34
+ const chalk_1 = __importDefault(require("chalk"));
35
+ const http = __importStar(require("http"));
36
+ const inquirer = __importStar(require("inquirer"));
37
+ const Http_1 = require("@trayio/commons/http/Http");
38
+ const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
39
+ const crypto_1 = require("../utils/crypto");
40
+ const browser_1 = require("../utils/browser");
41
+ const url_1 = require("../utils/url");
42
+ const executeAuthorizationCodeFlow = async (config, httpClient, shouldOpenBrowser) => {
43
+ if (!config.tokenUrl || !config.clientId || !config.authorizeUrl) {
44
+ throw new Error('Missing required parameters: tokenUrl, clientId, and authorizeUrl are required for authorization_code flow');
45
+ }
46
+ // Validate URLs are absolute
47
+ try {
48
+ const _tokenUrl = new URL(config.tokenUrl);
49
+ }
50
+ catch {
51
+ throw new Error(`Invalid tokenUrl: "${config.tokenUrl}" - must be an absolute URL (e.g., https://example.com/oauth/token)`);
52
+ }
53
+ try {
54
+ const _authorizeUrl = new URL(config.authorizeUrl);
55
+ }
56
+ catch {
57
+ throw new Error(`Invalid authorizeUrl: "${config.authorizeUrl}" - must be an absolute URL (e.g., https://example.com/oauth/authorize)`);
58
+ }
59
+ const redirectUri = config.redirectUri || 'http://127.0.0.1:3400/callback';
60
+ // Generate PKCE if not disabled
61
+ const pkce = config.disablePkce ? null : (0, crypto_1.generatePkce)();
62
+ const state = (0, crypto_1.generateState)();
63
+ const authUrl = new URL(config.authorizeUrl);
64
+ authUrl.searchParams.set('response_type', 'code');
65
+ authUrl.searchParams.set('client_id', config.clientId);
66
+ authUrl.searchParams.set('redirect_uri', redirectUri);
67
+ if (config.scope)
68
+ authUrl.searchParams.set('scope', config.scope);
69
+ if (config.audience)
70
+ authUrl.searchParams.set('audience', config.audience);
71
+ // Add PKCE parameters if enabled
72
+ if (pkce) {
73
+ authUrl.searchParams.set('code_challenge', pkce.challenge);
74
+ authUrl.searchParams.set('code_challenge_method', 'S256');
75
+ }
76
+ authUrl.searchParams.set('state', state);
77
+ let code = '';
78
+ if (shouldOpenBrowser) {
79
+ // eslint-disable-next-line no-console
80
+ console.log(chalk_1.default.gray('Opening browser for authorization...'));
81
+ (0, browser_1.openBrowser)(authUrl.toString());
82
+ }
83
+ if ((0, url_1.isLocalhostRedirect)(redirectUri)) {
84
+ code = await startLocalCallbackServer(redirectUri, state);
85
+ }
86
+ else {
87
+ code = await promptForRedirectUrl(state);
88
+ }
89
+ core_1.ux.action.start('Exchanging code for tokens');
90
+ return exchangeCodeForTokens(config, code, redirectUri, pkce?.verifier, httpClient);
91
+ };
92
+ exports.executeAuthorizationCodeFlow = executeAuthorizationCodeFlow;
93
+ /**
94
+ * Start a local HTTP server to capture the OAuth callback
95
+ */
96
+ const startLocalCallbackServer = async (redirectUri, state) => new Promise((resolve, reject) => {
97
+ const redirect = new URL(redirectUri);
98
+ const server = http.createServer((req, res) => {
99
+ try {
100
+ if (!req.url)
101
+ return;
102
+ const received = new URL(req.url, redirect.origin);
103
+ if (received.pathname !== redirect.pathname)
104
+ return;
105
+ const rcvdState = received.searchParams.get('state') || '';
106
+ const codeParam = received.searchParams.get('code') || '';
107
+ if (!codeParam)
108
+ throw new Error('Missing code in redirect');
109
+ if (rcvdState !== state)
110
+ throw new Error('State mismatch');
111
+ res.writeHead(200, { 'Content-Type': 'text/html' });
112
+ res.end('<html><body>Authorization complete. You can close this tab.</body></html>');
113
+ server.close(() => resolve(codeParam));
114
+ }
115
+ catch (err) {
116
+ server.close(() => reject(err));
117
+ }
118
+ });
119
+ server.listen(Number(redirect.port) || 3400, redirect.hostname, () => {
120
+ // server ready
121
+ });
122
+ });
123
+ /**
124
+ * Prompt user to manually paste the redirect URL (for non-localhost redirects)
125
+ */
126
+ const promptForRedirectUrl = async (state) => {
127
+ // eslint-disable-next-line no-console
128
+ console.log(chalk_1.default.yellow('Redirect URI is not localhost. Complete the flow and paste the resulting redirect URL.'));
129
+ const { pastedUrl } = await inquirer.prompt([
130
+ {
131
+ name: 'pastedUrl',
132
+ message: 'Paste the full redirect URL:',
133
+ type: 'input',
134
+ },
135
+ ]);
136
+ const received = new URL(String(pastedUrl));
137
+ const rcvdState = received.searchParams.get('state') || '';
138
+ const code = received.searchParams.get('code') || '';
139
+ if (!code)
140
+ throw new Error('Missing code in URL');
141
+ if (state && rcvdState && rcvdState !== state) {
142
+ throw new Error('State mismatch');
143
+ }
144
+ return code;
145
+ };
146
+ /**
147
+ * Exchange authorization code for access token
148
+ */
149
+ const exchangeCodeForTokens = async (config, code, redirectUri, verifier, httpClient) => {
150
+ const formParams = new URLSearchParams();
151
+ formParams.set('grant_type', 'authorization_code');
152
+ formParams.set('code', code);
153
+ formParams.set('redirect_uri', redirectUri);
154
+ // Only include PKCE verifier if it was generated
155
+ if (verifier) {
156
+ formParams.set('code_verifier', verifier);
157
+ }
158
+ // Build headers
159
+ const headers = {
160
+ 'Content-Type': Http_1.HttpContentType.FormUrlEncoded,
161
+ Accept: Http_1.HttpContentType.Json,
162
+ };
163
+ // Determine client authentication method (default to 'body' for backwards compatibility)
164
+ const authMethod = config.clientAuthMethod || 'body';
165
+ // Add client credentials based on authentication method
166
+ if (config.clientSecret) {
167
+ switch (authMethod) {
168
+ case 'basic':
169
+ // HTTP Basic Authentication - send credentials ONLY in Authorization header
170
+ // Do NOT include in body per OAuth2 spec for confidential clients
171
+ const credentials = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
172
+ headers.Authorization = `Basic ${credentials}`;
173
+ break;
174
+ case 'both':
175
+ // Send credentials in both header and body (some providers accept either)
176
+ const credentialsBoth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
177
+ headers.Authorization = `Basic ${credentialsBoth}`;
178
+ formParams.set('client_id', config.clientId);
179
+ formParams.set('client_secret', config.clientSecret);
180
+ break;
181
+ case 'body':
182
+ default:
183
+ // Send credentials in request body (default, backwards compatible)
184
+ formParams.set('client_id', config.clientId);
185
+ formParams.set('client_secret', config.clientSecret);
186
+ break;
187
+ }
188
+ }
189
+ else {
190
+ // No client secret - public client
191
+ formParams.set('client_id', config.clientId);
192
+ }
193
+ const request = Http_1.HttpRequest.create({
194
+ headers,
195
+ pathParams: {},
196
+ queryString: {},
197
+ body: Buffer.from(formParams.toString()),
198
+ });
199
+ const responseE = await httpClient.execute(Http_1.HttpMethod.Post, config.tokenUrl, request)();
200
+ if (responseE._tag === 'Left') {
201
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
202
+ throw responseE.left;
203
+ }
204
+ const bodyBufferE = await BufferExtensions_1.BufferExtensions.readableToArrayBuffer(responseE.right.body)();
205
+ if (bodyBufferE._tag === 'Left') {
206
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
207
+ throw bodyBufferE.left;
208
+ }
209
+ const tokenResponse = JSON.parse(Buffer.from(bodyBufferE.right).toString('utf-8'));
210
+ if (!tokenResponse.access_token) {
211
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
212
+ // eslint-disable-next-line no-console
213
+ console.error(chalk_1.default.red('\nToken response from server:'), JSON.stringify(tokenResponse, null, 2));
214
+ throw new Error(`No access_token in token response. Server returned: ${JSON.stringify(tokenResponse)}`);
215
+ }
216
+ core_1.ux.action.stop(chalk_1.default.green('done'));
217
+ return tokenResponse;
218
+ };
@@ -0,0 +1,4 @@
1
+ import { AxiosHttpClient } from '@trayio/axios/http/AxiosHttpClient';
2
+ import { OAuth2Config, TokenResponse } from '../types';
3
+ export declare const executeClientCredentialsFlow: (config: OAuth2Config, http: AxiosHttpClient) => Promise<TokenResponse>;
4
+ //# sourceMappingURL=client-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../../../src/lib/oauth2-token/flows/client-credentials.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAOrE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEvD,eAAO,MAAM,4BAA4B,WAChC,YAAY,QACd,eAAe,KACnB,QAAQ,aAAa,CAqDvB,CAAC"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.executeClientCredentialsFlow = void 0;
7
+ /**
8
+ * OAuth2 Client Credentials Flow
9
+ */
10
+ const core_1 = require("@oclif/core");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const Http_1 = require("@trayio/commons/http/Http");
13
+ const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
14
+ const executeClientCredentialsFlow = async (config, http) => {
15
+ if (!config.tokenUrl || !config.clientId || !config.clientSecret) {
16
+ throw new Error('Missing required parameters: tokenUrl, clientId, and clientSecret are required for client_credentials flow');
17
+ }
18
+ core_1.ux.action.start('Requesting OAuth2 token (client_credentials)');
19
+ const formParams = new URLSearchParams();
20
+ formParams.set('grant_type', 'client_credentials');
21
+ formParams.set('client_id', config.clientId);
22
+ formParams.set('client_secret', config.clientSecret);
23
+ if (config.scope)
24
+ formParams.set('scope', config.scope);
25
+ if (config.audience)
26
+ formParams.set('audience', config.audience);
27
+ const request = Http_1.HttpRequest.create({
28
+ headers: {
29
+ 'Content-Type': Http_1.HttpContentType.FormUrlEncoded,
30
+ Accept: Http_1.HttpContentType.Json,
31
+ },
32
+ pathParams: {},
33
+ queryString: {},
34
+ body: Buffer.from(formParams.toString()),
35
+ });
36
+ const responseE = await http.execute(Http_1.HttpMethod.Post, config.tokenUrl, request)();
37
+ if (responseE._tag === 'Left') {
38
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
39
+ throw responseE.left;
40
+ }
41
+ const bodyBufferE = await BufferExtensions_1.BufferExtensions.readableToArrayBuffer(responseE.right.body)();
42
+ if (bodyBufferE._tag === 'Left') {
43
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
44
+ throw bodyBufferE.left;
45
+ }
46
+ const jsonText = Buffer.from(bodyBufferE.right).toString('utf-8');
47
+ const tokenResponse = JSON.parse(jsonText);
48
+ if (!tokenResponse.access_token) {
49
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
50
+ throw new Error('No access_token in token response');
51
+ }
52
+ core_1.ux.action.stop(chalk_1.default.green('done'));
53
+ return tokenResponse;
54
+ };
55
+ exports.executeClientCredentialsFlow = executeClientCredentialsFlow;
@@ -0,0 +1,4 @@
1
+ import { AxiosHttpClient } from '@trayio/axios/http/AxiosHttpClient';
2
+ import { OAuth2Config, TokenResponse } from '../types';
3
+ export declare const executeDeviceCodeFlow: (config: OAuth2Config, httpClient: AxiosHttpClient, shouldOpenBrowser: boolean) => Promise<TokenResponse>;
4
+ //# sourceMappingURL=device-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../../../src/lib/oauth2-token/flows/device-code.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAOrE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAa,MAAM,UAAU,CAAC;AAIlE,eAAO,MAAM,qBAAqB,WACzB,YAAY,cACR,eAAe,qBACR,OAAO,KACxB,QAAQ,aAAa,CAevB,CAAC"}
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.executeDeviceCodeFlow = void 0;
7
+ /**
8
+ * OAuth2 Device Code Flow
9
+ */
10
+ const core_1 = require("@oclif/core");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const Http_1 = require("@trayio/commons/http/Http");
13
+ const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
14
+ const browser_1 = require("../utils/browser");
15
+ const url_1 = require("../utils/url");
16
+ const executeDeviceCodeFlow = async (config, httpClient, shouldOpenBrowser) => {
17
+ if (!config.tokenUrl || !config.clientId || !config.deviceCodeUrl) {
18
+ throw new Error('Missing required parameters: tokenUrl, clientId, and deviceCodeUrl are required for device_code flow');
19
+ }
20
+ // Step 1: Initialize device authorization
21
+ const deviceInit = await initializeDeviceAuthorization(config, httpClient);
22
+ // Step 2: Display user code and verification URL
23
+ displayDeviceAuthInstructions(deviceInit, shouldOpenBrowser);
24
+ // Step 3: Poll for token
25
+ return pollForDeviceToken(config, deviceInit, httpClient);
26
+ };
27
+ exports.executeDeviceCodeFlow = executeDeviceCodeFlow;
28
+ /**
29
+ * Initialize device authorization and get device code
30
+ */
31
+ const initializeDeviceAuthorization = async (config, httpClient) => {
32
+ core_1.ux.action.start('Starting device authorization');
33
+ const initParams = new URLSearchParams();
34
+ initParams.set('client_id', config.clientId);
35
+ if (config.clientSecret)
36
+ initParams.set('client_secret', config.clientSecret);
37
+ if (config.scope)
38
+ initParams.set('scope', config.scope);
39
+ if (config.audience)
40
+ initParams.set('audience', config.audience);
41
+ const request = Http_1.HttpRequest.create({
42
+ headers: {
43
+ 'Content-Type': Http_1.HttpContentType.FormUrlEncoded,
44
+ Accept: Http_1.HttpContentType.Json,
45
+ },
46
+ pathParams: {},
47
+ queryString: {},
48
+ body: Buffer.from(initParams.toString()),
49
+ });
50
+ const responseE = await httpClient.execute(Http_1.HttpMethod.Post, config.deviceCodeUrl, request)();
51
+ if (responseE._tag === 'Left') {
52
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
53
+ throw responseE.left;
54
+ }
55
+ const bodyBufferE = await BufferExtensions_1.BufferExtensions.readableToArrayBuffer(responseE.right.body)();
56
+ if (bodyBufferE._tag === 'Left') {
57
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
58
+ throw bodyBufferE.left;
59
+ }
60
+ const deviceInit = JSON.parse(Buffer.from(bodyBufferE.right).toString('utf-8'));
61
+ core_1.ux.action.stop(chalk_1.default.green('done'));
62
+ return deviceInit;
63
+ };
64
+ /**
65
+ * Display user code and verification URL instructions
66
+ */
67
+ const displayDeviceAuthInstructions = (deviceInit, shouldOpenBrowser) => {
68
+ const verificationUri = deviceInit.verification_uri || deviceInit.verification_uri_complete;
69
+ const verificationUriComplete = deviceInit.verification_uri_complete;
70
+ // eslint-disable-next-line no-console
71
+ console.log(chalk_1.default.gray(`User code: ${deviceInit.user_code}`));
72
+ // eslint-disable-next-line no-console
73
+ console.log(chalk_1.default.gray(`Visit: ${verificationUri}`));
74
+ if (shouldOpenBrowser && verificationUriComplete) {
75
+ (0, browser_1.openBrowser)(verificationUriComplete);
76
+ }
77
+ };
78
+ /**
79
+ * Poll token endpoint until authorization is complete
80
+ */
81
+ const pollForDeviceToken = async (config, deviceInit, httpClient) => {
82
+ const expiresIn = Number(deviceInit.expires_in) || 900;
83
+ let intervalMs = (Number(deviceInit.interval) || 5) * 1000;
84
+ const deadline = Date.now() + expiresIn * 1000;
85
+ core_1.ux.action.start('Waiting for user authorization');
86
+ /* eslint-disable no-await-in-loop */
87
+ while (Date.now() < deadline) {
88
+ const pollParams = new URLSearchParams();
89
+ pollParams.set('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
90
+ pollParams.set('device_code', deviceInit.device_code);
91
+ pollParams.set('client_id', config.clientId);
92
+ if (config.clientSecret) {
93
+ pollParams.set('client_secret', config.clientSecret);
94
+ }
95
+ const request = Http_1.HttpRequest.create({
96
+ headers: {
97
+ 'Content-Type': Http_1.HttpContentType.FormUrlEncoded,
98
+ Accept: Http_1.HttpContentType.Json,
99
+ },
100
+ pathParams: {},
101
+ queryString: {},
102
+ body: Buffer.from(pollParams.toString()),
103
+ });
104
+ const responseE = await httpClient.execute(Http_1.HttpMethod.Post, config.tokenUrl, request)();
105
+ if (responseE._tag === 'Left') {
106
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
107
+ throw responseE.left;
108
+ }
109
+ const bodyBufferE = await BufferExtensions_1.BufferExtensions.readableToArrayBuffer(responseE.right.body)();
110
+ if (bodyBufferE._tag === 'Left') {
111
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
112
+ throw bodyBufferE.left;
113
+ }
114
+ const pollResponse = JSON.parse(Buffer.from(bodyBufferE.right).toString('utf-8'));
115
+ if (pollResponse.access_token) {
116
+ core_1.ux.action.stop(chalk_1.default.green('done'));
117
+ return pollResponse;
118
+ }
119
+ const err = String(pollResponse.error || 'authorization_pending');
120
+ if (err === 'authorization_pending') {
121
+ await (0, url_1.sleep)(intervalMs);
122
+ }
123
+ else if (err === 'slow_down') {
124
+ intervalMs += 5000;
125
+ await (0, url_1.sleep)(intervalMs);
126
+ }
127
+ else if (err === 'access_denied') {
128
+ core_1.ux.action.stop(chalk_1.default.red('denied'));
129
+ throw new Error('User denied the request');
130
+ }
131
+ else if (err === 'expired_token' || err === 'expired') {
132
+ core_1.ux.action.stop(chalk_1.default.red('expired'));
133
+ throw new Error('The device code has expired');
134
+ }
135
+ else {
136
+ // Unknown error; wait a bit and try again
137
+ await (0, url_1.sleep)(intervalMs);
138
+ }
139
+ }
140
+ /* eslint-enable no-await-in-loop */
141
+ core_1.ux.action.stop(chalk_1.default.red('timeout'));
142
+ throw new Error('Timed out waiting for device authorization');
143
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * OAuth2 Flow handlers
3
+ */
4
+ export { executeClientCredentialsFlow } from './client-credentials';
5
+ export { executeAuthorizationCodeFlow } from './authorization-code';
6
+ export { executeDeviceCodeFlow } from './device-code';
7
+ export { executeRefreshTokenFlow } from './refresh-token';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/oauth2-token/flows/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeRefreshTokenFlow = exports.executeDeviceCodeFlow = exports.executeAuthorizationCodeFlow = exports.executeClientCredentialsFlow = void 0;
4
+ /**
5
+ * OAuth2 Flow handlers
6
+ */
7
+ var client_credentials_1 = require("./client-credentials");
8
+ Object.defineProperty(exports, "executeClientCredentialsFlow", { enumerable: true, get: function () { return client_credentials_1.executeClientCredentialsFlow; } });
9
+ var authorization_code_1 = require("./authorization-code");
10
+ Object.defineProperty(exports, "executeAuthorizationCodeFlow", { enumerable: true, get: function () { return authorization_code_1.executeAuthorizationCodeFlow; } });
11
+ var device_code_1 = require("./device-code");
12
+ Object.defineProperty(exports, "executeDeviceCodeFlow", { enumerable: true, get: function () { return device_code_1.executeDeviceCodeFlow; } });
13
+ var refresh_token_1 = require("./refresh-token");
14
+ Object.defineProperty(exports, "executeRefreshTokenFlow", { enumerable: true, get: function () { return refresh_token_1.executeRefreshTokenFlow; } });
@@ -0,0 +1,4 @@
1
+ import { AxiosHttpClient } from '@trayio/axios/http/AxiosHttpClient';
2
+ import { OAuth2Config, TokenResponse } from '../types';
3
+ export declare const executeRefreshTokenFlow: (config: OAuth2Config, refreshToken: string, http: AxiosHttpClient) => Promise<TokenResponse>;
4
+ //# sourceMappingURL=refresh-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../../../src/lib/oauth2-token/flows/refresh-token.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAOrE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEvD,eAAO,MAAM,uBAAuB,WAC3B,YAAY,gBACN,MAAM,QACd,eAAe,KACnB,QAAQ,aAAa,CA4DvB,CAAC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.executeRefreshTokenFlow = void 0;
7
+ /**
8
+ * OAuth2 Refresh Token Flow
9
+ */
10
+ const core_1 = require("@oclif/core");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const Http_1 = require("@trayio/commons/http/Http");
13
+ const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
14
+ const executeRefreshTokenFlow = async (config, refreshToken, http) => {
15
+ if (!refreshToken) {
16
+ throw new Error('No refresh token found in context file. Run without --refresh flag to obtain initial tokens.');
17
+ }
18
+ if (!config.tokenUrl || !config.clientId) {
19
+ throw new Error('Unable to locate tokenUrl or clientId. Provide overrides with flags or include them in your test.ctx.json.');
20
+ }
21
+ core_1.ux.action.start('Refreshing OAuth2 token');
22
+ const formParams = new URLSearchParams();
23
+ formParams.set('grant_type', 'refresh_token');
24
+ formParams.set('refresh_token', refreshToken);
25
+ formParams.set('client_id', config.clientId);
26
+ if (config.clientSecret)
27
+ formParams.set('client_secret', config.clientSecret);
28
+ if (config.scope)
29
+ formParams.set('scope', config.scope);
30
+ if (config.audience)
31
+ formParams.set('audience', config.audience);
32
+ const request = Http_1.HttpRequest.create({
33
+ headers: {
34
+ 'Content-Type': Http_1.HttpContentType.FormUrlEncoded,
35
+ Accept: Http_1.HttpContentType.Json,
36
+ },
37
+ pathParams: {},
38
+ queryString: {},
39
+ body: Buffer.from(formParams.toString()),
40
+ });
41
+ const responseE = await http.execute(Http_1.HttpMethod.Post, config.tokenUrl, request)();
42
+ if (responseE._tag === 'Left') {
43
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
44
+ throw responseE.left;
45
+ }
46
+ const bodyBufferE = await BufferExtensions_1.BufferExtensions.readableToArrayBuffer(responseE.right.body)();
47
+ if (bodyBufferE._tag === 'Left') {
48
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
49
+ throw bodyBufferE.left;
50
+ }
51
+ const jsonText = Buffer.from(bodyBufferE.right).toString('utf-8');
52
+ const tokenResponse = JSON.parse(jsonText);
53
+ if (!tokenResponse.access_token) {
54
+ core_1.ux.action.stop(chalk_1.default.red('failed'));
55
+ throw new Error('No access_token in refresh response');
56
+ }
57
+ core_1.ux.action.stop(chalk_1.default.green('done'));
58
+ return tokenResponse;
59
+ };
60
+ exports.executeRefreshTokenFlow = executeRefreshTokenFlow;
@@ -0,0 +1,7 @@
1
+ import { AnyObject, TokenResponse } from './types';
2
+ /**
3
+ * Write OAuth2 tokens to context file
4
+ * Attempts to find the appropriate location (near client credentials) or falls back to auth.user, then root
5
+ */
6
+ export declare const writeTokens: (ctxPath: string, ctx: AnyObject, tokenResponse: TokenResponse) => Promise<void>;
7
+ //# sourceMappingURL=token-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-writer.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth2-token/token-writer.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAInD;;;GAGG;AACH,eAAO,MAAM,WAAW,YACd,MAAM,OACV,SAAS,iBACC,aAAa,KAC1B,QAAQ,IAAI,CAmDd,CAAC"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.writeTokens = void 0;
30
+ /**
31
+ * Token writer - responsible for writing OAuth2 tokens back to context file
32
+ */
33
+ const chalk_1 = __importDefault(require("chalk"));
34
+ const O = __importStar(require("fp-ts/Option"));
35
+ const function_1 = require("fp-ts/function");
36
+ const json_1 = require("./utils/json");
37
+ const file_1 = require("./utils/file");
38
+ /**
39
+ * Write OAuth2 tokens to context file
40
+ * Attempts to find the appropriate location (near client credentials) or falls back to auth.user, then root
41
+ */
42
+ const writeTokens = async (ctxPath, ctx, tokenResponse) => {
43
+ // Decide where to write access_token: prefer the object that contains client credentials
44
+ const credsContainerOpt = (0, function_1.pipe)((0, json_1.findFirstByKeys)(ctx, ['client_secret', 'clientSecret']), O.alt(() => (0, json_1.findFirstByKeys)(ctx, ['client_id', 'clientId'])), O.alt(() => (0, json_1.findFirstByKeys)(ctx, ['token_url', 'tokenUrl', 'tokenEndpoint'])));
45
+ // Determine the container to write tokens to
46
+ let container;
47
+ if (O.isSome(credsContainerOpt)) {
48
+ // Found client credentials, use that parent object
49
+ container = credsContainerOpt.value.parent;
50
+ }
51
+ else if (ctx.auth &&
52
+ typeof ctx.auth === 'object' &&
53
+ ctx.auth.user &&
54
+ typeof ctx.auth.user === 'object') {
55
+ // Look for auth.user object as fallback
56
+ container = ctx.auth.user;
57
+ }
58
+ else if (ctx.user && typeof ctx.user === 'object') {
59
+ // Also check for user at root level
60
+ container = ctx.user;
61
+ }
62
+ else {
63
+ // Final fallback: write to root
64
+ container = ctx;
65
+ }
66
+ // Write the tokens to the determined container
67
+ container.access_token = tokenResponse.access_token;
68
+ if (tokenResponse.refresh_token) {
69
+ container.refresh_token = tokenResponse.refresh_token;
70
+ }
71
+ if (typeof tokenResponse.expires_in === 'number') {
72
+ container.expires_in = tokenResponse.expires_in;
73
+ container.expires_at =
74
+ Math.floor(Date.now() / 1000) + tokenResponse.expires_in;
75
+ }
76
+ if (tokenResponse.token_type) {
77
+ container.token_type = tokenResponse.token_type;
78
+ }
79
+ await (0, file_1.writeJsonFile)(ctxPath, ctx);
80
+ // eslint-disable-next-line no-console
81
+ console.log(chalk_1.default.bold(chalk_1.default.green('Access token written to test context file')));
82
+ };
83
+ exports.writeTokens = writeTokens;