@friggframework/devtools 2.0.0-next.64 ā 2.0.0-next.65
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/frigg-cli/auth-command/CLAUDE.md +293 -0
- package/frigg-cli/auth-command/README.md +450 -0
- package/frigg-cli/auth-command/api-key-flow.js +153 -0
- package/frigg-cli/auth-command/auth-tester.js +344 -0
- package/frigg-cli/auth-command/credential-storage.js +182 -0
- package/frigg-cli/auth-command/index.js +256 -0
- package/frigg-cli/auth-command/json-schema-form.js +67 -0
- package/frigg-cli/auth-command/module-loader.js +172 -0
- package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
- package/frigg-cli/auth-command/oauth-flow.js +195 -0
- package/frigg-cli/auth-command/utils/browser.js +30 -0
- package/frigg-cli/index.js +36 -1
- package/package.json +7 -7
- package/test/mock-api.js +1 -3
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# Frigg Authenticator
|
|
2
|
+
|
|
3
|
+
A CLI tool for testing OAuth2 and API-Key authentication flows in Frigg API modules without deploying full infrastructure.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Frigg Authenticator allows API module developers to:
|
|
8
|
+
- Test OAuth2 authentication flows end-to-end
|
|
9
|
+
- Test API-Key authentication
|
|
10
|
+
- Verify `requiredAuthMethods` work correctly
|
|
11
|
+
- Save credentials for reuse in tests
|
|
12
|
+
- Debug authentication issues quickly
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The authenticator is included in `@friggframework/devtools` and available via the Frigg CLI:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @friggframework/devtools
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Testing OAuth2 Modules
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Navigate to your API module directory
|
|
28
|
+
cd packages/api-module-attio
|
|
29
|
+
|
|
30
|
+
# Ensure .env has required OAuth credentials
|
|
31
|
+
cat .env
|
|
32
|
+
# ATTIO_CLIENT_ID=your_client_id
|
|
33
|
+
# ATTIO_CLIENT_SECRET=your_client_secret
|
|
34
|
+
# ATTIO_SCOPE=read:objects write:objects
|
|
35
|
+
# REDIRECT_URI=http://localhost:3333
|
|
36
|
+
|
|
37
|
+
# Run the auth test
|
|
38
|
+
frigg auth test .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This will:
|
|
42
|
+
1. Load your module and validate its definition
|
|
43
|
+
2. Start a local callback server on port 3333
|
|
44
|
+
3. Open your browser to the OAuth authorization page
|
|
45
|
+
4. Capture the callback and exchange code for tokens
|
|
46
|
+
5. Run `testAuthRequest` to verify authentication works
|
|
47
|
+
6. Save credentials to `.frigg-credentials.json`
|
|
48
|
+
|
|
49
|
+
### Testing API-Key Modules
|
|
50
|
+
|
|
51
|
+
API-Key modules with `getAuthorizationRequirements` will render an interactive JSON Schema form:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Navigate to API module directory
|
|
55
|
+
cd packages/api-module-quo
|
|
56
|
+
|
|
57
|
+
# Run auth test - renders interactive form
|
|
58
|
+
frigg auth test .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Interactive Form Example:**
|
|
62
|
+
```
|
|
63
|
+
š Quo API Authorization
|
|
64
|
+
|
|
65
|
+
(Your Quo API key)
|
|
66
|
+
API Key: ********************************
|
|
67
|
+
|
|
68
|
+
š API-Key Authentication Flow
|
|
69
|
+
|
|
70
|
+
Module: quo
|
|
71
|
+
ā API key configured
|
|
72
|
+
Fetching entity details...
|
|
73
|
+
ā Entity details retrieved
|
|
74
|
+
Entity: Quo Workspace (API Key Hash)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The form:
|
|
78
|
+
- Displays title from `jsonSchema.title`
|
|
79
|
+
- Shows help text from `ui:help` before each field
|
|
80
|
+
- Masks password fields (`ui:widget: 'password'`) with `*`
|
|
81
|
+
- Validates required fields
|
|
82
|
+
|
|
83
|
+
**Using `--api-key` flag (bypasses interactive form):**
|
|
84
|
+
```bash
|
|
85
|
+
frigg auth test ./my-api-key-module --api-key YOUR_API_KEY
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Commands
|
|
89
|
+
|
|
90
|
+
### `frigg auth test <module>`
|
|
91
|
+
|
|
92
|
+
Test authentication for an API module.
|
|
93
|
+
|
|
94
|
+
**Arguments:**
|
|
95
|
+
- `<module>` - Module path or name. Can be:
|
|
96
|
+
- `.` - Current directory
|
|
97
|
+
- `./path/to/module` - Relative path
|
|
98
|
+
- `/absolute/path/to/module` - Absolute path
|
|
99
|
+
- `attio` - Short name (resolves to `@friggframework/api-module-attio`)
|
|
100
|
+
- `@friggframework/api-module-attio` - Full package name
|
|
101
|
+
|
|
102
|
+
**Options:**
|
|
103
|
+
| Option | Default | Description |
|
|
104
|
+
|--------|---------|-------------|
|
|
105
|
+
| `--api-key <key>` | - | API key (bypasses interactive form) |
|
|
106
|
+
| `--port <port>` | `3333` | Callback server port |
|
|
107
|
+
| `--no-browser` | `false` | Don't auto-open browser (print URL instead) |
|
|
108
|
+
| `--timeout <seconds>` | `300` | OAuth callback timeout |
|
|
109
|
+
| `-v, --verbose` | `false` | Enable verbose output |
|
|
110
|
+
|
|
111
|
+
**Examples:**
|
|
112
|
+
```bash
|
|
113
|
+
# Test current directory module
|
|
114
|
+
frigg auth test .
|
|
115
|
+
|
|
116
|
+
# Test with custom port
|
|
117
|
+
frigg auth test . --port 8080
|
|
118
|
+
|
|
119
|
+
# Test without opening browser
|
|
120
|
+
frigg auth test . --no-browser
|
|
121
|
+
|
|
122
|
+
# Test API-Key module
|
|
123
|
+
frigg auth test . --api-key sk_live_xxxxx
|
|
124
|
+
|
|
125
|
+
# Verbose output for debugging
|
|
126
|
+
frigg auth test . --verbose
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `frigg auth list`
|
|
130
|
+
|
|
131
|
+
List all saved credentials.
|
|
132
|
+
|
|
133
|
+
**Options:**
|
|
134
|
+
| Option | Description |
|
|
135
|
+
|--------|-------------|
|
|
136
|
+
| `--json` | Output as JSON |
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```bash
|
|
140
|
+
frigg auth list
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Output:
|
|
144
|
+
```
|
|
145
|
+
āāāāāāāāāāā¬āāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāā
|
|
146
|
+
ā Module ā Auth Type ā Entity ā Has Access Token ā Has Refresh Token ā Saved At ā
|
|
147
|
+
āāāāāāāāāāā¼āāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāā¤
|
|
148
|
+
ā attio ā oauth2 ā Left Hook Dev ā ā ā - ā 12/23/2025, 7:34 PM ā
|
|
149
|
+
ā quo ā apiKey ā Quo Workspace ā ā ā - ā 12/23/2025, 8:00 PM ā
|
|
150
|
+
āāāāāāāāāāā“āāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāā
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `frigg auth get <module>`
|
|
154
|
+
|
|
155
|
+
Get credentials for a specific module.
|
|
156
|
+
|
|
157
|
+
**Options:**
|
|
158
|
+
| Option | Description |
|
|
159
|
+
|--------|-------------|
|
|
160
|
+
| `--json` | Output as JSON (for scripts) |
|
|
161
|
+
| `--export` | Output as environment variables |
|
|
162
|
+
|
|
163
|
+
**Examples:**
|
|
164
|
+
```bash
|
|
165
|
+
# Display formatted credentials
|
|
166
|
+
frigg auth get attio
|
|
167
|
+
|
|
168
|
+
# Get as JSON for scripts
|
|
169
|
+
frigg auth get attio --json
|
|
170
|
+
|
|
171
|
+
# Export as environment variables
|
|
172
|
+
eval $(frigg auth get attio --export)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `frigg auth delete [module]`
|
|
176
|
+
|
|
177
|
+
Delete saved credentials.
|
|
178
|
+
|
|
179
|
+
**Options:**
|
|
180
|
+
| Option | Description |
|
|
181
|
+
|--------|-------------|
|
|
182
|
+
| `--all` | Delete all credentials |
|
|
183
|
+
| `-y, --yes` | Skip confirmation |
|
|
184
|
+
|
|
185
|
+
**Examples:**
|
|
186
|
+
```bash
|
|
187
|
+
# Delete specific module credentials
|
|
188
|
+
frigg auth delete attio
|
|
189
|
+
|
|
190
|
+
# Delete all credentials
|
|
191
|
+
frigg auth delete --all
|
|
192
|
+
|
|
193
|
+
# Skip confirmation
|
|
194
|
+
frigg auth delete attio -y
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Credential Storage
|
|
198
|
+
|
|
199
|
+
Credentials are saved to `.frigg-credentials.json`:
|
|
200
|
+
- **Project-local**: If run in a Frigg project, saves to project root
|
|
201
|
+
- **Global**: Otherwise saves to `~/.frigg-credentials.json`
|
|
202
|
+
|
|
203
|
+
The file is automatically added to `.gitignore` when saving locally.
|
|
204
|
+
|
|
205
|
+
### Credential Format
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"_meta": {
|
|
210
|
+
"version": 1,
|
|
211
|
+
"warning": "DO NOT COMMIT THIS FILE - contains sensitive credentials"
|
|
212
|
+
},
|
|
213
|
+
"modules": {
|
|
214
|
+
"attio": {
|
|
215
|
+
"authType": "oauth2",
|
|
216
|
+
"tokens": {
|
|
217
|
+
"access_token": "xxx...",
|
|
218
|
+
"refresh_token": null,
|
|
219
|
+
"accessTokenExpire": "2025-12-24T19:34:50.468Z"
|
|
220
|
+
},
|
|
221
|
+
"entity": {
|
|
222
|
+
"identifiers": {
|
|
223
|
+
"externalId": "workspace-id",
|
|
224
|
+
"user": "cli-test-user"
|
|
225
|
+
},
|
|
226
|
+
"details": {
|
|
227
|
+
"name": "My Workspace"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"obtainedAt": "2025-12-23T19:34:51.097Z",
|
|
231
|
+
"savedAt": "2025-12-23T19:34:51.713Z"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Using Credentials in Tests
|
|
238
|
+
|
|
239
|
+
### Direct Import
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const { CredentialStorage } = require('@friggframework/devtools/frigg-cli/auth-command/credential-storage');
|
|
243
|
+
|
|
244
|
+
describe('Attio Integration', () => {
|
|
245
|
+
let api;
|
|
246
|
+
|
|
247
|
+
beforeAll(async () => {
|
|
248
|
+
const storage = new CredentialStorage();
|
|
249
|
+
const credentials = await storage.get('attio');
|
|
250
|
+
|
|
251
|
+
if (!credentials) {
|
|
252
|
+
throw new Error('Run "frigg auth test attio" first');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { Api } = require('@friggframework/api-module-attio');
|
|
256
|
+
api = new Api({
|
|
257
|
+
...credentials.tokens,
|
|
258
|
+
...credentials.apiParams,
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should list objects', async () => {
|
|
263
|
+
const result = await api.listObjects();
|
|
264
|
+
expect(result.data).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Environment Variables
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Export credentials as env vars
|
|
273
|
+
eval $(frigg auth get attio --export)
|
|
274
|
+
|
|
275
|
+
# Now available as:
|
|
276
|
+
# ATTIO_ACCESS_TOKEN=xxx
|
|
277
|
+
# ATTIO_EXTERNAL_ID=workspace-id
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Module Requirements
|
|
281
|
+
|
|
282
|
+
For the authenticator to work, your module must have:
|
|
283
|
+
|
|
284
|
+
### Required Definition Fields
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
const Definition = {
|
|
288
|
+
API: Api, // API class (extends OAuth2Requester or ApiKeyRequester)
|
|
289
|
+
moduleName: 'my-module', // Unique module name
|
|
290
|
+
requiredAuthMethods: {
|
|
291
|
+
getToken, // Exchange auth code for tokens (OAuth2)
|
|
292
|
+
getEntityDetails, // Get entity info after auth
|
|
293
|
+
getCredentialDetails, // Get credential info
|
|
294
|
+
testAuthRequest, // Verify auth works
|
|
295
|
+
apiPropertiesToPersist, // Fields to persist
|
|
296
|
+
},
|
|
297
|
+
env: {
|
|
298
|
+
client_id: process.env.MY_MODULE_CLIENT_ID,
|
|
299
|
+
client_secret: process.env.MY_MODULE_CLIENT_SECRET,
|
|
300
|
+
scope: process.env.MY_MODULE_SCOPE,
|
|
301
|
+
redirect_uri: process.env.REDIRECT_URI,
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Required Environment Variables
|
|
307
|
+
|
|
308
|
+
Create a `.env` file in your module directory:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# OAuth2 modules
|
|
312
|
+
MY_MODULE_CLIENT_ID=your_client_id
|
|
313
|
+
MY_MODULE_CLIENT_SECRET=your_client_secret
|
|
314
|
+
MY_MODULE_SCOPE=read write
|
|
315
|
+
REDIRECT_URI=http://localhost:3333
|
|
316
|
+
|
|
317
|
+
# API-Key modules - use interactive form or --api-key flag
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### JSON Schema Form for API-Key Modules
|
|
321
|
+
|
|
322
|
+
API-Key modules can define `getAuthorizationRequirements` to enable interactive CLI forms:
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
// definition.js
|
|
326
|
+
const Definition = {
|
|
327
|
+
// ...
|
|
328
|
+
requiredAuthMethods: {
|
|
329
|
+
getAuthorizationRequirements: (api) => ({
|
|
330
|
+
type: 'apiKey',
|
|
331
|
+
data: {
|
|
332
|
+
jsonSchema: {
|
|
333
|
+
title: 'My API Authorization',
|
|
334
|
+
type: 'object',
|
|
335
|
+
required: ['apiKey'],
|
|
336
|
+
properties: {
|
|
337
|
+
apiKey: { type: 'string', title: 'API Key' }
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
uiSchema: {
|
|
341
|
+
apiKey: {
|
|
342
|
+
'ui:widget': 'password', // Masks input with *
|
|
343
|
+
'ui:help': 'Your API key from the dashboard'
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}),
|
|
348
|
+
// ... other methods
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Multi-Field Example (e.g., ConnectWise):**
|
|
354
|
+
```javascript
|
|
355
|
+
getAuthorizationRequirements: (api) => ({
|
|
356
|
+
type: 'apiKey',
|
|
357
|
+
data: {
|
|
358
|
+
jsonSchema: {
|
|
359
|
+
title: 'ConnectWise Authentication',
|
|
360
|
+
type: 'object',
|
|
361
|
+
required: ['companyId', 'publicKey', 'privateKey'],
|
|
362
|
+
properties: {
|
|
363
|
+
companyId: { type: 'string', title: 'Company ID' },
|
|
364
|
+
publicKey: { type: 'string', title: 'Public Key' },
|
|
365
|
+
privateKey: { type: 'string', title: 'Private Key' },
|
|
366
|
+
siteUrl: { type: 'string', title: 'Site URL' }
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
uiSchema: {
|
|
370
|
+
companyId: { 'ui:help': 'The Company ID you use to login' },
|
|
371
|
+
publicKey: { 'ui:help': 'From My Account > API Keys' },
|
|
372
|
+
privateKey: { 'ui:widget': 'password', 'ui:help': 'Your private key' },
|
|
373
|
+
siteUrl: { 'ui:help': 'e.g., https://na.myconnectwise.net' }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Supported UI Schema Options:**
|
|
380
|
+
- `ui:widget: 'password'` - Masks input with `*` characters
|
|
381
|
+
- `ui:help` - Displays help text before the field prompt
|
|
382
|
+
|
|
383
|
+
## Troubleshooting
|
|
384
|
+
|
|
385
|
+
### Port Already in Use
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
Error: Port 3333 is already in use.
|
|
389
|
+
Try using a different port: frigg auth test <module> --port <different-port>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Solution:** Use a different port with `--port 8080`
|
|
393
|
+
|
|
394
|
+
### Module Not Found
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
Error: Could not find module: my-module
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Solution:**
|
|
401
|
+
- Use `.` for current directory
|
|
402
|
+
- Use absolute path
|
|
403
|
+
- Ensure module exports `Definition`
|
|
404
|
+
|
|
405
|
+
### OAuth Callback Timeout
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
Error: OAuth callback timeout after 300 seconds.
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Solution:**
|
|
412
|
+
- Complete authorization in browser faster
|
|
413
|
+
- Increase timeout with `--timeout 600`
|
|
414
|
+
- Check redirect URI matches callback server
|
|
415
|
+
|
|
416
|
+
### Invalid Module Definition
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
Error: Module validation failed:
|
|
420
|
+
- Missing required auth method: testAuthRequest
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Solution:** Ensure your module's `requiredAuthMethods` has all required methods.
|
|
424
|
+
|
|
425
|
+
## Architecture
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
auth-command/
|
|
429
|
+
āāā index.js # Main command handler
|
|
430
|
+
āāā module-loader.js # Dynamic module loading & validation
|
|
431
|
+
āāā credential-storage.js # .frigg-credentials.json persistence
|
|
432
|
+
āāā oauth-callback-server.js # Local HTTP server for OAuth callbacks
|
|
433
|
+
āāā oauth-flow.js # OAuth2 flow orchestration
|
|
434
|
+
āāā api-key-flow.js # API-Key authentication flow
|
|
435
|
+
āāā json-schema-form.js # Interactive JSON Schema form renderer
|
|
436
|
+
āāā auth-tester.js # Run testAuthRequest & sample API calls
|
|
437
|
+
āāā utils/
|
|
438
|
+
āāā browser.js # Cross-platform browser opening
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Security Considerations
|
|
442
|
+
|
|
443
|
+
1. **Credentials are stored in plain text** - Only use for development/testing
|
|
444
|
+
2. **Auto-adds to .gitignore** - Prevents accidental commits
|
|
445
|
+
3. **CSRF protection** - OAuth state parameter prevents attacks
|
|
446
|
+
4. **Local callback only** - Server only listens on localhost
|
|
447
|
+
|
|
448
|
+
## Contributing
|
|
449
|
+
|
|
450
|
+
See the main [Frigg Contributing Guide](https://github.com/friggframework/frigg/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { renderJsonSchemaForm } = require('./json-schema-form');
|
|
3
|
+
|
|
4
|
+
async function runApiKeyFlow(definition, ApiClass, providedApiKey, options) {
|
|
5
|
+
const moduleName = definition.moduleName || definition.getName?.() || 'unknown';
|
|
6
|
+
|
|
7
|
+
let apiKey = providedApiKey;
|
|
8
|
+
let formData = null;
|
|
9
|
+
|
|
10
|
+
// If no API key provided, check for getAuthorizationRequirements to render form
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
if (definition.requiredAuthMethods?.getAuthorizationRequirements) {
|
|
13
|
+
// Create temporary API instance to call getAuthorizationRequirements
|
|
14
|
+
const tempApi = new ApiClass({ ...definition.env });
|
|
15
|
+
const authReqs = definition.requiredAuthMethods.getAuthorizationRequirements(tempApi);
|
|
16
|
+
|
|
17
|
+
if (authReqs?.data?.jsonSchema) {
|
|
18
|
+
// Render the JSON schema form
|
|
19
|
+
formData = await renderJsonSchemaForm(
|
|
20
|
+
authReqs.data.jsonSchema,
|
|
21
|
+
authReqs.data.uiSchema
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Extract API key from form data - try common field names
|
|
25
|
+
apiKey = formData.apiKey || formData.api_key ||
|
|
26
|
+
formData.access_token || formData.token;
|
|
27
|
+
|
|
28
|
+
// If still no API key found, use the first value from the form
|
|
29
|
+
if (!apiKey && Object.keys(formData).length > 0) {
|
|
30
|
+
apiKey = Object.values(formData)[0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
throw new Error('No API key provided in form');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`--api-key is required for API-Key modules without getAuthorizationRequirements.\n` +
|
|
42
|
+
`Usage: frigg auth test ${moduleName} --api-key YOUR_API_KEY`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(chalk.blue('\nš API-Key Authentication Flow\n'));
|
|
48
|
+
console.log(chalk.gray(`Module: ${moduleName}`));
|
|
49
|
+
|
|
50
|
+
// 1. Create API instance with environment params
|
|
51
|
+
const apiParams = {
|
|
52
|
+
...definition.env,
|
|
53
|
+
};
|
|
54
|
+
const api = new ApiClass(apiParams);
|
|
55
|
+
|
|
56
|
+
// 2. Set API key using available methods
|
|
57
|
+
let apiKeySet = false;
|
|
58
|
+
|
|
59
|
+
// Try different methods to set the API key
|
|
60
|
+
if (definition.requiredAuthMethods?.setAuthParams) {
|
|
61
|
+
// Some modules have setAuthParams in definition
|
|
62
|
+
await definition.requiredAuthMethods.setAuthParams(api, {
|
|
63
|
+
apiKey,
|
|
64
|
+
data: { apiKey, api_key: apiKey, access_token: apiKey }
|
|
65
|
+
});
|
|
66
|
+
apiKeySet = true;
|
|
67
|
+
} else if (typeof api.setApiKey === 'function') {
|
|
68
|
+
// Standard ApiKeyRequester method
|
|
69
|
+
api.setApiKey(apiKey);
|
|
70
|
+
apiKeySet = true;
|
|
71
|
+
} else if (typeof api.setAuthParams === 'function') {
|
|
72
|
+
// Alternative method name
|
|
73
|
+
await api.setAuthParams({ apiKey, api_key: apiKey, access_token: apiKey });
|
|
74
|
+
apiKeySet = true;
|
|
75
|
+
} else {
|
|
76
|
+
// Direct property assignment as fallback
|
|
77
|
+
api.api_key = apiKey;
|
|
78
|
+
api.access_token = apiKey;
|
|
79
|
+
apiKeySet = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!apiKeySet) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Could not set API key for module ${moduleName}.\n` +
|
|
85
|
+
`Module does not have setApiKey(), setAuthParams(), or setAuthParams in requiredAuthMethods.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(chalk.green('ā API key configured'));
|
|
90
|
+
|
|
91
|
+
// 3. Get entity details
|
|
92
|
+
console.log(chalk.gray('Fetching entity details...'));
|
|
93
|
+
|
|
94
|
+
let entityDetails;
|
|
95
|
+
if (definition.requiredAuthMethods?.getEntityDetails) {
|
|
96
|
+
try {
|
|
97
|
+
entityDetails = await definition.requiredAuthMethods.getEntityDetails(
|
|
98
|
+
api,
|
|
99
|
+
{},
|
|
100
|
+
{ api_key: apiKey },
|
|
101
|
+
'cli-test-user'
|
|
102
|
+
);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.log(chalk.yellow(` Warning: getEntityDetails failed: ${err.message}`));
|
|
105
|
+
entityDetails = {
|
|
106
|
+
identifiers: { externalId: 'unknown', user: 'cli-test-user' },
|
|
107
|
+
details: { name: 'API Key Authentication' }
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
entityDetails = {
|
|
112
|
+
identifiers: { externalId: 'unknown', user: 'cli-test-user' },
|
|
113
|
+
details: { name: 'API Key Authentication' }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(chalk.green('ā Entity details retrieved'));
|
|
118
|
+
|
|
119
|
+
if (entityDetails?.details?.name) {
|
|
120
|
+
console.log(chalk.gray(` Entity: ${entityDetails.details.name}`));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 4. Get credential details
|
|
124
|
+
let credentialDetails = {};
|
|
125
|
+
if (definition.requiredAuthMethods?.getCredentialDetails) {
|
|
126
|
+
try {
|
|
127
|
+
credentialDetails = await definition.requiredAuthMethods.getCredentialDetails(
|
|
128
|
+
api,
|
|
129
|
+
'cli-test-user'
|
|
130
|
+
);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.log(chalk.yellow(` Warning: Could not get credential details: ${err.message}`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 5. Return credentials object
|
|
137
|
+
return {
|
|
138
|
+
apiKey,
|
|
139
|
+
entity: entityDetails,
|
|
140
|
+
credential: credentialDetails,
|
|
141
|
+
apiParams: sanitizeApiParams(apiParams),
|
|
142
|
+
obtainedAt: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function sanitizeApiParams(params) {
|
|
147
|
+
// Remove sensitive data that shouldn't be stored in readable form
|
|
148
|
+
const sanitized = { ...params };
|
|
149
|
+
delete sanitized.client_secret;
|
|
150
|
+
return sanitized;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { runApiKeyFlow };
|