@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,290 @@
1
+ # OAuth2 Token Command
2
+
3
+ Generate or refresh OAuth2 tokens for connector testing and development.
4
+
5
+ ## Overview
6
+
7
+ The `oauth2-token` command automates OAuth2 token acquisition for testing connectors locally. It supports multiple OAuth2 grant types and can read configuration from your test context file, environment variables, or command-line flags.
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ # Auto-detect grant type from test.ctx.json
13
+ tray-cdk connector oauth2-token
14
+
15
+ # Use specific grant type
16
+ tray-cdk connector oauth2-token --grantType client_credentials
17
+
18
+ # Refresh an existing token
19
+ tray-cdk connector oauth2-token --refresh
20
+ ```
21
+
22
+ ## Supported Grant Types
23
+
24
+ - **Client Credentials**: Machine-to-machine authentication
25
+ - **Authorization Code** (with PKCE): User authentication with browser redirect
26
+ - **Device Code**: Authentication for devices with limited input capabilities
27
+ - **Refresh Token**: Renew access tokens using a refresh token
28
+
29
+ ## Configuration
30
+
31
+ The command uses a three-tier configuration system with the following precedence:
32
+
33
+ 1. **Command-line flags** (highest priority)
34
+ 2. **Environment variables** (from `.env` file)
35
+ 3. **Context file** (`src/test.ctx.json` by default)
36
+
37
+ ### Command-Line Flags
38
+
39
+ ```bash
40
+ --ctxPath, -c Path to context JSON file (default: src/test.ctx.json)
41
+ --envPath, -e Path to .env file (default: .env)
42
+ --grantType, -g OAuth2 grant type (auto|client_credentials|authorization_code|device_code)
43
+ --refresh, -r Use refresh token to get new access token
44
+ --tokenUrl Override token URL
45
+ --clientId Override client ID
46
+ --clientSecret Override client secret
47
+ --authorizeUrl Override authorization URL
48
+ --deviceCodeUrl Override device authorization URL
49
+ --redirectUri Override redirect URI for auth code flow
50
+ --scope Override scope (space-separated)
51
+ --audience Override audience
52
+ --openBrowser Open browser for interactive flows (default: true)
53
+ --dryRun, -n Print token JSON without writing to file
54
+ ```
55
+
56
+ ### Environment Variables
57
+
58
+ Create a `.env` file in your project root:
59
+
60
+ ```bash
61
+ # OAuth2 Configuration
62
+ CLIENT_ID=your_client_id_here
63
+ CLIENT_SECRET=your_client_secret_here
64
+ TOKEN_URL=https://oauth.provider.com/token
65
+ AUTHORIZE_URL=https://oauth.provider.com/authorize
66
+ DEVICE_CODE_URL=https://oauth.provider.com/device_authorization
67
+ REDIRECT_URI=http://127.0.0.1:3400/callback
68
+ SCOPE="read write admin"
69
+ AUDIENCE=https://api.provider.com
70
+ REFRESH_TOKEN=your_refresh_token_here
71
+ ```
72
+
73
+ ### Context File
74
+
75
+ The command writes tokens to `src/test.ctx.json` by default. This file is where your OAuth2 tokens are automatically stored after successful authentication.
76
+
77
+ **Recommended approach:** Keep secrets in `.env` and let the command write tokens to `test.ctx.json`:
78
+
79
+ **Initial state** (should use the correct structure for your connector auth):
80
+
81
+ ```json
82
+ {
83
+ "auth": {
84
+ "user": {},
85
+ "app": {}
86
+ }
87
+ }
88
+ ```
89
+
90
+ **After running the command**, tokens are automatically written:
91
+
92
+ ```json
93
+ {
94
+
95
+ "auth": {
96
+ "user": {
97
+ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
98
+ "refresh_token": "v1.MRkaGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
99
+ "expires_in": 3600,
100
+ "expires_at": 1699564800
101
+ "token_type": "Bearer"
102
+ },
103
+ "app": {}
104
+ }
105
+ }
106
+ ```
107
+
108
+ **Note:** While you *can* put `client_id`, `client_secret`, and other OAuth config in `test.ctx.json`, it's better practice to keep secrets in your `.env` file (which should be gitignored) and let the command read from there.
109
+
110
+ ## Usage Examples
111
+
112
+ ### Client Credentials Flow
113
+
114
+ Best for: Machine-to-machine authentication, server-to-server APIs
115
+
116
+ ```bash
117
+ # Using context file
118
+ tray-cdk connector oauth2-token --grantType client_credentials
119
+
120
+ # Using environment variables
121
+ tray-cdk connector oauth2-token --grantType client_credentials --envPath .env.production
122
+
123
+ # Using command-line flags
124
+ tray-cdk connector oauth2-token \
125
+ --grantType client_credentials \
126
+ --tokenUrl https://oauth.example.com/token \
127
+ --clientId my-client-id \
128
+ --clientSecret my-client-secret \
129
+ --scope "read write"
130
+ ```
131
+
132
+ **Required configuration:**
133
+ - `token_url` / `tokenUrl`
134
+ - `client_id` / `clientId`
135
+ - `client_secret` / `clientSecret`
136
+
137
+ **Optional configuration:**
138
+ - `scope` / `scopes`
139
+ - `audience`
140
+
141
+ ### Authorization Code Flow (with PKCE)
142
+
143
+ Best for: User authentication, web applications, mobile apps
144
+
145
+ ```bash
146
+ # Auto-opens browser for authentication
147
+ tray-cdk connector oauth2-token --grantType authorization_code
148
+
149
+ # Custom redirect URI
150
+ tray-cdk connector oauth2-token \
151
+ --grantType authorization_code \
152
+ --redirectUri http://localhost:8080/callback
153
+
154
+ # Non-localhost redirect (manual URL paste)
155
+ tray-cdk connector oauth2-token \
156
+ --grantType authorization_code \
157
+ --redirectUri https://oauth.pstmn.io/v1/callback \
158
+ --openBrowser false
159
+ ```
160
+
161
+ #### HTTPS Redirect URI Requirements
162
+
163
+ Some OAuth providers (like Workday, Salesforce) require HTTPS redirect URIs and won't accept `http://localhost`. Since the CLI's local callback server only supports HTTP, you need to use a **static HTTPS redirect URI** with manual URL pasting.
164
+
165
+ **Recommended: Use Postman's Public OAuth Callback**
166
+
167
+ Postman provides a free, public OAuth callback service at a static URL that you can pre-register in your OAuth provider:
168
+
169
+ ```bash
170
+ # In your .env file
171
+ REDIRECT_URI=https://oauth.pstmn.io/v1/callback
172
+
173
+ # Run the command
174
+ tray-cdk connector oauth2-token --grantType authorization_code
175
+ ```
176
+
177
+ **How it works:**
178
+ 1. **One-time setup:** Register `https://oauth.pstmn.io/v1/callback` as an allowed redirect URI in your OAuth provider (e.g., Workday)
179
+ 2. Run the command - it will open your browser for authorization
180
+ 3. After authorizing, Workday redirects to Postman's callback page
181
+ 4. Copy the **full URL** from your browser's address bar (it contains the authorization code)
182
+ 5. Paste it into the terminal when prompted
183
+ 6. The command extracts the authorization code and exchanges it for tokens
184
+
185
+ **Required configuration:**
186
+ - `token_url` / `tokenUrl`
187
+ - `authorize_url` / `authorizeUrl` / `authorization_endpoint`
188
+ - `client_id` / `clientId`
189
+
190
+ **Optional configuration:**
191
+ - `client_secret` / `clientSecret` (if required by provider)
192
+ - `redirect_uri` / `redirectUri` (default: `http://127.0.0.1:3400/callback`)
193
+ - `scope` / `scopes`
194
+ - `audience`
195
+
196
+ ### Device Code Flow
197
+
198
+ Best for: Devices with limited input (smart TVs, IoT devices, CLI tools)
199
+
200
+ ```bash
201
+ # Opens browser with user code
202
+ tray-cdk connector oauth2-token --grantType device_code
203
+
204
+ # Without opening browser
205
+ tray-cdk connector oauth2-token \
206
+ --grantType device_code \
207
+ --openBrowser false
208
+ ```
209
+
210
+ **Required configuration:**
211
+ - `token_url` / `tokenUrl`
212
+ - `device_code_url` / `device_authorization_endpoint` / `deviceAuthorizationUrl`
213
+ - `client_id` / `clientId`
214
+
215
+ **Optional configuration:**
216
+ - `client_secret` / `clientSecret`
217
+ - `scope` / `scopes`
218
+ - `audience`
219
+
220
+ **Flow behavior:**
221
+ 1. Requests device code from authorization server
222
+ 2. Displays user code and verification URL
223
+ 3. Optionally opens browser to verification URL
224
+ 4. Polls token endpoint until user completes authorization
225
+ 5. Writes tokens to context file
226
+
227
+ ### Refresh Token Flow
228
+
229
+ Best for: Renewing expired access tokens
230
+
231
+ ```bash
232
+ # Refresh using token from context file
233
+ tray-cdk connector oauth2-token --refresh
234
+
235
+ # Refresh using token from environment
236
+ REFRESH_TOKEN=your_token tray-cdk connector oauth2-token --refresh
237
+
238
+ # Dry run to see new token without writing
239
+ tray-cdk connector oauth2-token --refresh --dryRun
240
+ ```
241
+
242
+ **Required configuration:**
243
+ - `token_url` / `tokenUrl`
244
+ - `client_id` / `clientId`
245
+ - `refresh_token` / `refreshToken` (from context, env, or previous token response)
246
+
247
+ **Optional configuration:**
248
+ - `client_secret` / `clientSecret`
249
+ - `scope` / `scopes`
250
+ - `audience`
251
+
252
+ ### Auto Grant Type Detection
253
+
254
+ When using `--grantType auto` (default), the command automatically selects the appropriate flow:
255
+
256
+ ```bash
257
+ # Auto-detects based on available configuration
258
+ tray-cdk connector oauth2-token
259
+ ```
260
+
261
+ **Detection priority:**
262
+ 1. If `authorize_url` is present → **Authorization Code Flow**
263
+ 2. If `device_code_url` is present → **Device Code Flow**
264
+ 3. If `grant_type` is specified in context → Use that type
265
+ 4. Otherwise → **Client Credentials Flow**
266
+
267
+ ## Common Scenarios
268
+
269
+ ### Testing with Multiple Environments
270
+
271
+ ```bash
272
+ # Development
273
+ tray-cdk connector oauth2-token --envPath .env.dev
274
+
275
+ # Staging
276
+ tray-cdk connector oauth2-token --envPath .env.staging
277
+
278
+ # Production
279
+ tray-cdk connector oauth2-token --envPath .env.production
280
+ ```
281
+
282
+ ### Inspecting Token Without Writing
283
+
284
+ ```bash
285
+ # Dry run to see token response
286
+ tray-cdk connector oauth2-token --dryRun
287
+
288
+ # Pipe to jq for formatted output
289
+ tray-cdk connector oauth2-token --dryRun | jq
290
+ ```
package/README.md CHANGED
@@ -19,7 +19,7 @@ $ npm install -g @trayio/cdk-cli
19
19
  $ tray-cdk COMMAND
20
20
  running command...
21
21
  $ tray-cdk (--version|-v)
22
- @trayio/cdk-cli/5.5.0 linux-x64 node-v18.20.8
22
+ @trayio/cdk-cli/5.6.0 linux-x64 node-v18.20.8
23
23
  $ tray-cdk --help [COMMAND]
24
24
  USAGE
25
25
  $ tray-cdk COMMAND
@@ -36,6 +36,7 @@ USAGE
36
36
  * [`tray-cdk connector build`](#tray-cdk-connector-build)
37
37
  * [`tray-cdk connector import [OPENAPISPEC] [CONNECTORNAME]`](#tray-cdk-connector-import-openapispec-connectorname)
38
38
  * [`tray-cdk connector init [CONNECTORNAME]`](#tray-cdk-connector-init-connectorname)
39
+ * [`tray-cdk connector oauth2-token`](#tray-cdk-connector-oauth2-token)
39
40
  * [`tray-cdk connector test [OPERATIONNAME]`](#tray-cdk-connector-test-operationname)
40
41
  * [`tray-cdk deployment create`](#tray-cdk-deployment-create)
41
42
  * [`tray-cdk deployment get [CONNECTORNAME] [CONNECTORVERSION] [UUID]`](#tray-cdk-deployment-get-connectorname-connectorversion-uuid)
@@ -161,6 +162,42 @@ DESCRIPTION
161
162
  Initialize a connector project
162
163
  ```
163
164
 
165
+ ## `tray-cdk connector oauth2-token`
166
+
167
+ Generate or refresh an OAuth2 token from .env and write it to src/test.ctx.json.
168
+
169
+ ```
170
+ USAGE
171
+ $ tray-cdk connector oauth2-token [-c <value>] [-e <value>] [-g auto|client_credentials|authorization_code|device_code]
172
+ [-r] [--authorizeUrl <value>] [--redirectUri <value>] [--deviceCodeUrl <value>] [--openBrowser] [--tokenUrl <value>]
173
+ [--clientId <value>] [--clientSecret <value>] [--scope <value>] [--audience <value>] [--clientAuthMethod
174
+ basic|body|both] [--disablePkce] [-n]
175
+
176
+ FLAGS
177
+ -c, --ctxPath=<value> [default: src/test.ctx.json] Path to context JSON file
178
+ -e, --envPath=<value> [default: .env] Path to .env file for sensitive credentials
179
+ -g, --grantType=<option> [default: auto] OAuth2 grant type to use (auto infers from context)
180
+ <options: auto|client_credentials|authorization_code|device_code>
181
+ -n, --dryRun Do not write to file, just print the token JSON
182
+ -r, --refresh Use refresh token to get new access token
183
+ --audience=<value> Override audience
184
+ --authorizeUrl=<value> Override authorization URL
185
+ --clientAuthMethod=<option> Client authentication method: basic (header), body (form), or both (default: body for
186
+ backwards compatibility)
187
+ <options: basic|body|both>
188
+ --clientId=<value> Override client ID
189
+ --clientSecret=<value> Override client secret
190
+ --deviceCodeUrl=<value> Override device authorization URL
191
+ --disablePkce Disable PKCE (Proof Key for Code Exchange) for providers that do not support it
192
+ --openBrowser Open browser for interactive flows
193
+ --redirectUri=<value> Override redirect URI for auth code
194
+ --scope=<value> Override scope (space-separated)
195
+ --tokenUrl=<value> Override token URL
196
+
197
+ DESCRIPTION
198
+ Generate or refresh an OAuth2 token from .env and write it to src/test.ctx.json.
199
+ ```
200
+
164
201
  ## `tray-cdk connector test [OPERATIONNAME]`
165
202
 
166
203
  Build and test connector project or an operation
@@ -0,0 +1,45 @@
1
+ /**
2
+ * OAuth2 Token Command - Generate or refresh OAuth2 tokens
3
+ */
4
+ import { Command } from '@oclif/core';
5
+ export default class OAuth2Token extends Command {
6
+ static description: string;
7
+ static flags: {
8
+ readonly ctxPath: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ readonly envPath: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ readonly grantType: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ readonly refresh: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
+ readonly authorizeUrl: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ readonly redirectUri: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
+ readonly deviceCodeUrl: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ readonly openBrowser: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ readonly tokenUrl: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ readonly clientId: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ readonly clientSecret: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ readonly scope: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ readonly audience: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ readonly clientAuthMethod: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ readonly disablePkce: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
+ readonly dryRun: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
+ };
25
+ private http;
26
+ run(): Promise<void>;
27
+ /**
28
+ * Extract OAuth2 configuration from context, environment, and flags
29
+ * Precedence: flags > env > context file
30
+ */
31
+ private extractOAuth2Config;
32
+ /**
33
+ * Handle refresh token flow
34
+ */
35
+ private handleRefreshTokenFlow;
36
+ /**
37
+ * Determine which grant type to use
38
+ */
39
+ private determineGrantType;
40
+ /**
41
+ * Execute the appropriate grant type flow
42
+ */
43
+ private executeGrantTypeFlow;
44
+ }
45
+ //# sourceMappingURL=oauth2-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2-token.d.ts","sourceRoot":"","sources":["../../../src/commands/connector/oauth2-token.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAa,MAAM,aAAa,CAAC;AAsBjD,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC/C,MAAM,CAAC,WAAW,SACkE;IAEpF,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;;;MA0DD;IAEX,OAAO,CAAC,IAAI,CAAyB;IAE/B,GAAG;IA2CT;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA6H3B;;OAEG;YACW,sBAAsB;IA6BpC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;OAEG;YACW,oBAAoB;CA2ClC"}
@@ -0,0 +1,243 @@
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
+ /**
30
+ * OAuth2 Token Command - Generate or refresh OAuth2 tokens
31
+ */
32
+ const core_1 = require("@oclif/core");
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 path = __importStar(require("path"));
37
+ const AxiosHttpClient_1 = require("@trayio/axios/http/AxiosHttpClient");
38
+ const utils_1 = require("../../lib/oauth2-token/utils");
39
+ const flows_1 = require("../../lib/oauth2-token/flows");
40
+ const token_writer_1 = require("../../lib/oauth2-token/token-writer");
41
+ class OAuth2Token extends core_1.Command {
42
+ static description = 'Generate or refresh an OAuth2 token from .env and write it to src/test.ctx.json.';
43
+ static flags = {
44
+ ctxPath: core_1.Flags.string({
45
+ char: 'c',
46
+ default: path.join('src', 'test.ctx.json'),
47
+ description: 'Path to context JSON file',
48
+ }),
49
+ envPath: core_1.Flags.string({
50
+ char: 'e',
51
+ default: '.env',
52
+ description: 'Path to .env file for sensitive credentials',
53
+ }),
54
+ grantType: core_1.Flags.string({
55
+ char: 'g',
56
+ options: [
57
+ 'auto',
58
+ 'client_credentials',
59
+ 'authorization_code',
60
+ 'device_code',
61
+ ],
62
+ default: 'auto',
63
+ description: 'OAuth2 grant type to use (auto infers from context)',
64
+ }),
65
+ refresh: core_1.Flags.boolean({
66
+ char: 'r',
67
+ default: false,
68
+ description: 'Use refresh token to get new access token',
69
+ }),
70
+ authorizeUrl: core_1.Flags.string({ description: 'Override authorization URL' }),
71
+ redirectUri: core_1.Flags.string({
72
+ description: 'Override redirect URI for auth code',
73
+ }),
74
+ deviceCodeUrl: core_1.Flags.string({
75
+ description: 'Override device authorization URL',
76
+ }),
77
+ openBrowser: core_1.Flags.boolean({
78
+ default: true,
79
+ description: 'Open browser for interactive flows',
80
+ }),
81
+ tokenUrl: core_1.Flags.string({ description: 'Override token URL' }),
82
+ clientId: core_1.Flags.string({ description: 'Override client ID' }),
83
+ clientSecret: core_1.Flags.string({ description: 'Override client secret' }),
84
+ scope: core_1.Flags.string({ description: 'Override scope (space-separated)' }),
85
+ audience: core_1.Flags.string({ description: 'Override audience' }),
86
+ clientAuthMethod: core_1.Flags.string({
87
+ options: ['basic', 'body', 'both'],
88
+ description: 'Client authentication method: basic (header), body (form), or both (default: body for backwards compatibility)',
89
+ }),
90
+ disablePkce: core_1.Flags.boolean({
91
+ default: false,
92
+ description: 'Disable PKCE (Proof Key for Code Exchange) for providers that do not support it',
93
+ }),
94
+ dryRun: core_1.Flags.boolean({
95
+ char: 'n',
96
+ default: false,
97
+ description: 'Do not write to file, just print the token JSON',
98
+ }),
99
+ };
100
+ http = new AxiosHttpClient_1.AxiosHttpClient();
101
+ async run() {
102
+ const { flags } = await this.parse(OAuth2Token);
103
+ const cwd = process.cwd();
104
+ const ctxPath = path.isAbsolute(flags.ctxPath)
105
+ ? flags.ctxPath
106
+ : path.join(cwd, flags.ctxPath);
107
+ const envPath = path.isAbsolute(flags.envPath)
108
+ ? flags.envPath
109
+ : path.join(cwd, flags.envPath);
110
+ this.log(chalk_1.default.bold(chalk_1.default.gray(`Reading ${ctxPath}...`)));
111
+ const ctx = await (0, utils_1.readJsonFile)(ctxPath);
112
+ // Load environment variables from .env file
113
+ const envConfig = await (0, utils_1.loadEnvConfig)(envPath);
114
+ // Extract OAuth2 configuration
115
+ const config = this.extractOAuth2Config(ctx, envConfig, flags);
116
+ // Handle refresh token flow
117
+ if (flags.refresh) {
118
+ return this.handleRefreshTokenFlow(config, ctx, envConfig, ctxPath, flags.dryRun);
119
+ }
120
+ // Determine and execute grant type flow
121
+ const selectedGrant = this.determineGrantType(config, ctx, flags.grantType);
122
+ await this.executeGrantTypeFlow(selectedGrant, config, ctxPath, ctx, flags.openBrowser, flags.dryRun);
123
+ }
124
+ /**
125
+ * Extract OAuth2 configuration from context, environment, and flags
126
+ * Precedence: flags > env > context file
127
+ */
128
+ extractOAuth2Config(ctx, envConfig, flags) {
129
+ const tokenUrl = flags.tokenUrl ||
130
+ (0, utils_1.getEnvValue)(envConfig, 'TOKEN_URL') ||
131
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['token_url', 'tokenUrl', 'tokenEndpoint']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
132
+ const authorizeUrl = flags.authorizeUrl ||
133
+ (0, utils_1.getEnvValue)(envConfig, 'AUTHORIZE_URL') ||
134
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, [
135
+ 'authorize_url',
136
+ 'authorization_endpoint',
137
+ 'authorizeUrl',
138
+ ]), O.map((f) => String(f.value)), O.getOrElse(() => ''));
139
+ const deviceCodeUrl = flags.deviceCodeUrl ||
140
+ (0, utils_1.getEnvValue)(envConfig, 'DEVICE_CODE_URL') ||
141
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, [
142
+ 'device_code_url',
143
+ 'device_authorization_endpoint',
144
+ 'deviceAuthorizationUrl',
145
+ 'device_authorization_url',
146
+ ]), O.map((f) => String(f.value)), O.getOrElse(() => ''));
147
+ const clientId = flags.clientId ||
148
+ (0, utils_1.getEnvValue)(envConfig, 'CLIENT_ID') ||
149
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['client_id', 'clientId']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
150
+ const clientSecret = flags.clientSecret ||
151
+ (0, utils_1.getEnvValue)(envConfig, 'CLIENT_SECRET') ||
152
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['client_secret', 'clientSecret']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
153
+ const scope = flags.scope ||
154
+ (0, utils_1.getEnvValue)(envConfig, 'SCOPE') ||
155
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['scope', 'scopes']), O.map((f) => Array.isArray(f.value) ? f.value.join(' ') : String(f.value)), O.getOrElse(() => ''));
156
+ const audience = flags.audience ||
157
+ (0, utils_1.getEnvValue)(envConfig, 'AUDIENCE') ||
158
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['audience']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
159
+ const redirectUri = flags.redirectUri ||
160
+ (0, utils_1.getEnvValue)(envConfig, 'REDIRECT_URI') ||
161
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['redirect_uri', 'redirectUri']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
162
+ const clientAuthMethod = flags.clientAuthMethod ||
163
+ (0, utils_1.getEnvValue)(envConfig, 'CLIENT_AUTH_METHOD') ||
164
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['client_auth_method', 'clientAuthMethod']), O.map((f) => String(f.value)), O.getOrElse(() => 'body'));
165
+ const disablePkce = flags.disablePkce ||
166
+ (0, utils_1.getEnvValue)(envConfig, 'DISABLE_PKCE') === 'true' ||
167
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['disable_pkce', 'disablePkce']), O.map((f) => Boolean(f.value)), O.getOrElse(() => false));
168
+ return {
169
+ tokenUrl,
170
+ authorizeUrl: authorizeUrl || undefined,
171
+ deviceCodeUrl: deviceCodeUrl || undefined,
172
+ clientId,
173
+ clientSecret: clientSecret || undefined,
174
+ scope: scope || undefined,
175
+ audience: audience || undefined,
176
+ redirectUri: redirectUri || undefined,
177
+ clientAuthMethod: clientAuthMethod === 'basic' ||
178
+ clientAuthMethod === 'body' ||
179
+ clientAuthMethod === 'both'
180
+ ? clientAuthMethod
181
+ : 'body',
182
+ disablePkce,
183
+ };
184
+ }
185
+ /**
186
+ * Handle refresh token flow
187
+ */
188
+ async handleRefreshTokenFlow(config, ctx, envConfig, ctxPath, dryRun) {
189
+ const refreshToken = (0, utils_1.getEnvValue)(envConfig, 'REFRESH_TOKEN') ||
190
+ (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['refresh_token', 'refreshToken']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
191
+ const tokenResponse = await (0, flows_1.executeRefreshTokenFlow)(config, refreshToken, this.http);
192
+ if (dryRun) {
193
+ this.log(JSON.stringify(tokenResponse, null, 2));
194
+ return;
195
+ }
196
+ await (0, token_writer_1.writeTokens)(ctxPath, ctx, tokenResponse);
197
+ }
198
+ /**
199
+ * Determine which grant type to use
200
+ */
201
+ determineGrantType(config, ctx, flagGrantType) {
202
+ if (flagGrantType !== 'auto') {
203
+ return flagGrantType;
204
+ }
205
+ const ctxGrantType = (0, function_1.pipe)((0, utils_1.findFirstByKeys)(ctx, ['grant_type', 'grantType']), O.map((f) => String(f.value)), O.getOrElse(() => ''));
206
+ if (config.authorizeUrl) {
207
+ return 'authorization_code';
208
+ }
209
+ if (config.deviceCodeUrl) {
210
+ return 'device_code';
211
+ }
212
+ if (ctxGrantType) {
213
+ return ctxGrantType;
214
+ }
215
+ return 'client_credentials';
216
+ }
217
+ /**
218
+ * Execute the appropriate grant type flow
219
+ */
220
+ async executeGrantTypeFlow(grantType, config, ctxPath, ctx, openBrowser, dryRun) {
221
+ let tokenResponse;
222
+ switch (grantType) {
223
+ case 'client_credentials':
224
+ tokenResponse = await (0, flows_1.executeClientCredentialsFlow)(config, this.http);
225
+ break;
226
+ case 'authorization_code':
227
+ tokenResponse = await (0, flows_1.executeAuthorizationCodeFlow)(config, this.http, openBrowser);
228
+ break;
229
+ case 'device_code':
230
+ tokenResponse = await (0, flows_1.executeDeviceCodeFlow)(config, this.http, openBrowser);
231
+ break;
232
+ default:
233
+ this.error(new Error(`Unsupported grant type: ${grantType}`));
234
+ return;
235
+ }
236
+ if (dryRun) {
237
+ this.log(JSON.stringify(tokenResponse, null, 2));
238
+ return;
239
+ }
240
+ await (0, token_writer_1.writeTokens)(ctxPath, ctx, tokenResponse);
241
+ }
242
+ }
243
+ exports.default = OAuth2Token;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth2-token.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2-token.unit.test.d.ts","sourceRoot":"","sources":["../../../src/commands/connector/oauth2-token.unit.test.ts"],"names":[],"mappings":""}