@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.
- package/OAUTH2_TOKEN.md +290 -0
- package/README.md +38 -1
- package/dist/commands/connector/oauth2-token.d.ts +45 -0
- package/dist/commands/connector/oauth2-token.d.ts.map +1 -0
- package/dist/commands/connector/oauth2-token.js +243 -0
- package/dist/commands/connector/oauth2-token.unit.test.d.ts +2 -0
- package/dist/commands/connector/oauth2-token.unit.test.d.ts.map +1 -0
- package/dist/commands/connector/oauth2-token.unit.test.js +1244 -0
- package/dist/lib/oauth2-token/flows/authorization-code.d.ts +4 -0
- package/dist/lib/oauth2-token/flows/authorization-code.d.ts.map +1 -0
- package/dist/lib/oauth2-token/flows/authorization-code.js +218 -0
- package/dist/lib/oauth2-token/flows/client-credentials.d.ts +4 -0
- package/dist/lib/oauth2-token/flows/client-credentials.d.ts.map +1 -0
- package/dist/lib/oauth2-token/flows/client-credentials.js +55 -0
- package/dist/lib/oauth2-token/flows/device-code.d.ts +4 -0
- package/dist/lib/oauth2-token/flows/device-code.d.ts.map +1 -0
- package/dist/lib/oauth2-token/flows/device-code.js +143 -0
- package/dist/lib/oauth2-token/flows/index.d.ts +8 -0
- package/dist/lib/oauth2-token/flows/index.d.ts.map +1 -0
- package/dist/lib/oauth2-token/flows/index.js +14 -0
- package/dist/lib/oauth2-token/flows/refresh-token.d.ts +4 -0
- package/dist/lib/oauth2-token/flows/refresh-token.d.ts.map +1 -0
- package/dist/lib/oauth2-token/flows/refresh-token.js +60 -0
- package/dist/lib/oauth2-token/token-writer.d.ts +7 -0
- package/dist/lib/oauth2-token/token-writer.d.ts.map +1 -0
- package/dist/lib/oauth2-token/token-writer.js +83 -0
- package/dist/lib/oauth2-token/types.d.ts +34 -0
- package/dist/lib/oauth2-token/types.d.ts.map +1 -0
- package/dist/lib/oauth2-token/types.js +5 -0
- package/dist/lib/oauth2-token/utils/browser.d.ts +2 -0
- package/dist/lib/oauth2-token/utils/browser.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/browser.js +22 -0
- package/dist/lib/oauth2-token/utils/crypto.d.ts +6 -0
- package/dist/lib/oauth2-token/utils/crypto.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/crypto.js +47 -0
- package/dist/lib/oauth2-token/utils/env.d.ts +7 -0
- package/dist/lib/oauth2-token/utils/env.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/env.js +85 -0
- package/dist/lib/oauth2-token/utils/file.d.ts +4 -0
- package/dist/lib/oauth2-token/utils/file.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/file.js +40 -0
- package/dist/lib/oauth2-token/utils/index.d.ts +10 -0
- package/dist/lib/oauth2-token/utils/index.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/index.js +25 -0
- package/dist/lib/oauth2-token/utils/json.d.ts +9 -0
- package/dist/lib/oauth2-token/utils/json.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/json.js +52 -0
- package/dist/lib/oauth2-token/utils/url.d.ts +6 -0
- package/dist/lib/oauth2-token/utils/url.d.ts.map +1 -0
- package/dist/lib/oauth2-token/utils/url.js +22 -0
- package/oclif.manifest.json +150 -1
- package/package.json +11 -9
package/OAUTH2_TOKEN.md
ADDED
|
@@ -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.
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"oauth2-token.unit.test.d.ts","sourceRoot":"","sources":["../../../src/commands/connector/oauth2-token.unit.test.ts"],"names":[],"mappings":""}
|