@friggframework/admin-scripts 2.0.0--canary.517.f04156f.0 → 2.0.0--canary.517.300ded3.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.
@@ -1,49 +1,11 @@
1
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
2
-
3
1
  /**
4
2
  * Admin API Key Authentication Middleware
5
3
  *
6
- * Validates admin API keys for script endpoints.
7
- * Expects: Authorization: Bearer <api-key>
4
+ * Re-exports shared admin auth middleware from @friggframework/core.
5
+ * Uses simple ENV-based API key validation.
6
+ * Expects: x-frigg-admin-api-key header
8
7
  */
9
- async function adminAuthMiddleware(req, res, next) {
10
- try {
11
- const authHeader = req.headers.authorization;
12
-
13
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
14
- return res.status(401).json({
15
- error: 'Missing or invalid Authorization header',
16
- code: 'MISSING_AUTH'
17
- });
18
- }
19
-
20
- const apiKey = authHeader.substring(7); // Remove 'Bearer '
21
- const commands = createAdminScriptCommands();
22
- const result = await commands.validateAdminApiKey(apiKey);
23
-
24
- if (result.error) {
25
- return res.status(result.error).json({
26
- error: result.reason,
27
- code: result.code
28
- });
29
- }
30
-
31
- // Attach validated key info to request for audit trail
32
- req.adminApiKey = result.apiKey;
33
- req.adminAudit = {
34
- apiKeyName: result.apiKey.name,
35
- apiKeyLast4: result.apiKey.keyLast4,
36
- ipAddress: req.ip || req.connection?.remoteAddress || 'unknown'
37
- };
38
8
 
39
- next();
40
- } catch (error) {
41
- console.error('Admin auth middleware error:', error);
42
- res.status(500).json({
43
- error: 'Authentication failed',
44
- code: 'AUTH_ERROR'
45
- });
46
- }
47
- }
9
+ const { validateAdminApiKey } = require('@friggframework/core/handlers/middleware/admin-auth');
48
10
 
49
- module.exports = { adminAuthMiddleware };
11
+ module.exports = { validateAdminApiKey };
@@ -1,6 +1,6 @@
1
1
  const express = require('express');
2
2
  const serverless = require('serverless-http');
3
- const { adminAuthMiddleware } = require('./admin-auth-middleware');
3
+ const { validateAdminApiKey } = require('./admin-auth-middleware');
4
4
  const { getScriptFactory } = require('../application/script-factory');
5
5
  const { createScriptRunner } = require('../application/script-runner');
6
6
  const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
@@ -11,7 +11,7 @@ const { ScheduleManagementUseCase } = require('../application/schedule-managemen
11
11
  const router = express.Router();
12
12
 
13
13
  // Apply auth middleware to all admin routes
14
- router.use(adminAuthMiddleware);
14
+ router.use(validateAdminApiKey);
15
15
 
16
16
  /**
17
17
  * Create ScheduleManagementUseCase instance
@@ -87,10 +87,10 @@ router.get('/scripts/:scriptName', async (req, res) => {
87
87
  });
88
88
 
89
89
  /**
90
- * POST /admin/scripts/:scriptName/execute
90
+ * POST /admin/scripts/:scriptName
91
91
  * Execute a script (sync, async, or dry-run)
92
92
  */
93
- router.post('/scripts/:scriptName/execute', async (req, res) => {
93
+ router.post('/scripts/:scriptName', async (req, res) => {
94
94
  try {
95
95
  const { scriptName } = req.params;
96
96
  const { params = {}, mode = 'async', dryRun = false } = req.body;
@@ -110,7 +110,6 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
110
110
  trigger: 'MANUAL',
111
111
  mode: 'sync',
112
112
  dryRun: true,
113
- audit: req.adminAudit,
114
113
  });
115
114
  return res.json(result);
116
115
  }
@@ -121,20 +120,18 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
121
120
  const result = await runner.execute(scriptName, params, {
122
121
  trigger: 'MANUAL',
123
122
  mode: 'sync',
124
- audit: req.adminAudit,
125
123
  });
126
124
  return res.json(result);
127
125
  }
128
126
 
129
127
  // Async execution - queue and return immediately
130
128
  const commands = createAdminScriptCommands();
131
- const execution = await commands.createScriptExecution({
129
+ const execution = await commands.createAdminProcess({
132
130
  scriptName,
133
131
  scriptVersion: factory.get(scriptName).Definition.version,
134
132
  trigger: 'MANUAL',
135
133
  mode: 'async',
136
134
  input: params,
137
- audit: req.adminAudit,
138
135
  });
139
136
 
140
137
  // Queue the execution
@@ -161,14 +158,14 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
161
158
  });
162
159
 
163
160
  /**
164
- * GET /admin/executions/:executionId
165
- * Get execution status
161
+ * GET /admin/scripts/:scriptName/executions/:executionId
162
+ * Get execution status for specific script
166
163
  */
167
- router.get('/executions/:executionId', async (req, res) => {
164
+ router.get('/scripts/:scriptName/executions/:executionId', async (req, res) => {
168
165
  try {
169
166
  const { executionId } = req.params;
170
167
  const commands = createAdminScriptCommands();
171
- const execution = await commands.findScriptExecutionById(executionId);
168
+ const execution = await commands.findAdminProcessById(executionId);
172
169
 
173
170
  if (execution.error) {
174
171
  return res.status(execution.error).json({
@@ -185,12 +182,13 @@ router.get('/executions/:executionId', async (req, res) => {
185
182
  });
186
183
 
187
184
  /**
188
- * GET /admin/executions
189
- * List recent executions
185
+ * GET /admin/scripts/:scriptName/executions
186
+ * List recent executions for specific script
190
187
  */
191
- router.get('/executions', async (req, res) => {
188
+ router.get('/scripts/:scriptName/executions', async (req, res) => {
192
189
  try {
193
- const { scriptName, status, limit = 50 } = req.query;
190
+ const { scriptName } = req.params;
191
+ const { status, limit = 50 } = req.query;
194
192
  const commands = createAdminScriptCommands();
195
193
 
196
194
  const executions = await commands.findRecentExecutions({
@@ -21,7 +21,7 @@ async function handler(event) {
21
21
 
22
22
  // If executionId provided (async from API), update existing record
23
23
  if (executionId) {
24
- await commands.updateScriptExecutionStatus(executionId, 'RUNNING');
24
+ await commands.updateAdminProcessState(executionId, 'RUNNING');
25
25
  }
26
26
 
27
27
  const result = await runner.execute(scriptName, params, {
@@ -45,7 +45,7 @@ async function handler(event) {
45
45
  if (executionId) {
46
46
  const commands = createAdminScriptCommands();
47
47
  await commands
48
- .completeScriptExecution(executionId, {
48
+ .completeAdminProcess(executionId, {
49
49
  status: 'FAILED',
50
50
  error: {
51
51
  name: error.name,
@@ -1,313 +0,0 @@
1
- const {
2
- createDryRunHttpClient,
3
- injectDryRunHttpClient,
4
- sanitizeHeaders,
5
- sanitizeData,
6
- detectService,
7
- } = require('../dry-run-http-interceptor');
8
-
9
- describe('Dry-Run HTTP Interceptor', () => {
10
- describe('sanitizeHeaders', () => {
11
- test('should redact authorization headers', () => {
12
- const headers = {
13
- 'Content-Type': 'application/json',
14
- Authorization: 'Bearer secret-token',
15
- 'X-API-Key': 'api-key-123',
16
- 'User-Agent': 'frigg/1.0',
17
- };
18
-
19
- const sanitized = sanitizeHeaders(headers);
20
-
21
- expect(sanitized['Content-Type']).toBe('application/json');
22
- expect(sanitized['User-Agent']).toBe('frigg/1.0');
23
- expect(sanitized.Authorization).toBe('[REDACTED]');
24
- expect(sanitized['X-API-Key']).toBe('[REDACTED]');
25
- });
26
-
27
- test('should handle case variations', () => {
28
- const headers = {
29
- authorization: 'Bearer token',
30
- Authorization: 'Bearer token',
31
- 'x-api-key': 'key1',
32
- 'X-API-Key': 'key2',
33
- };
34
-
35
- const sanitized = sanitizeHeaders(headers);
36
-
37
- expect(sanitized.authorization).toBe('[REDACTED]');
38
- expect(sanitized.Authorization).toBe('[REDACTED]');
39
- expect(sanitized['x-api-key']).toBe('[REDACTED]');
40
- expect(sanitized['X-API-Key']).toBe('[REDACTED]');
41
- });
42
-
43
- test('should handle null/undefined', () => {
44
- expect(sanitizeHeaders(null)).toEqual({});
45
- expect(sanitizeHeaders(undefined)).toEqual({});
46
- expect(sanitizeHeaders({})).toEqual({});
47
- });
48
- });
49
-
50
- describe('detectService', () => {
51
- test('should detect CRM services', () => {
52
- expect(detectService('https://api.hubapi.com')).toBe('HubSpot');
53
- expect(detectService('https://login.salesforce.com')).toBe('Salesforce');
54
- expect(detectService('https://api.pipedrive.com')).toBe('Pipedrive');
55
- expect(detectService('https://api.attio.com')).toBe('Attio');
56
- });
57
-
58
- test('should detect communication services', () => {
59
- expect(detectService('https://slack.com/api')).toBe('Slack');
60
- expect(detectService('https://discord.com/api')).toBe('Discord');
61
- expect(detectService('https://graph.teams.microsoft.com')).toBe('Microsoft Teams');
62
- });
63
-
64
- test('should detect project management tools', () => {
65
- expect(detectService('https://app.asana.com/api')).toBe('Asana');
66
- expect(detectService('https://api.monday.com')).toBe('Monday.com');
67
- expect(detectService('https://api.trello.com')).toBe('Trello');
68
- });
69
-
70
- test('should return unknown for unrecognized services', () => {
71
- expect(detectService('https://example.com/api')).toBe('unknown');
72
- expect(detectService(null)).toBe('unknown');
73
- expect(detectService(undefined)).toBe('unknown');
74
- });
75
-
76
- test('should be case insensitive', () => {
77
- expect(detectService('HTTPS://API.HUBSPOT.COM')).toBe('HubSpot');
78
- expect(detectService('https://API.SLACK.COM')).toBe('Slack');
79
- });
80
- });
81
-
82
- describe('sanitizeData', () => {
83
- test('should redact sensitive fields', () => {
84
- const data = {
85
- name: 'Test User',
86
- email: 'test@example.com',
87
- password: 'secret123',
88
- apiToken: 'token-abc',
89
- authKey: 'key-xyz',
90
- };
91
-
92
- const sanitized = sanitizeData(data);
93
-
94
- expect(sanitized.name).toBe('Test User');
95
- expect(sanitized.email).toBe('test@example.com');
96
- expect(sanitized.password).toBe('[REDACTED]');
97
- expect(sanitized.apiToken).toBe('[REDACTED]');
98
- expect(sanitized.authKey).toBe('[REDACTED]');
99
- });
100
-
101
- test('should handle nested objects', () => {
102
- const data = {
103
- user: {
104
- name: 'Test',
105
- credentials: {
106
- password: 'secret',
107
- token: 'abc123',
108
- },
109
- },
110
- };
111
-
112
- const sanitized = sanitizeData(data);
113
-
114
- expect(sanitized.user.name).toBe('Test');
115
- expect(sanitized.user.credentials.password).toBe('[REDACTED]');
116
- expect(sanitized.user.credentials.token).toBe('[REDACTED]');
117
- });
118
-
119
- test('should handle arrays', () => {
120
- const data = [
121
- { id: '1', password: 'secret1' },
122
- { id: '2', apiKey: 'key2' },
123
- ];
124
-
125
- const sanitized = sanitizeData(data);
126
-
127
- expect(sanitized[0].id).toBe('1');
128
- expect(sanitized[0].password).toBe('[REDACTED]');
129
- expect(sanitized[1].apiKey).toBe('[REDACTED]');
130
- });
131
-
132
- test('should preserve primitives', () => {
133
- expect(sanitizeData('string')).toBe('string');
134
- expect(sanitizeData(123)).toBe(123);
135
- expect(sanitizeData(true)).toBe(true);
136
- expect(sanitizeData(null)).toBe(null);
137
- expect(sanitizeData(undefined)).toBe(undefined);
138
- });
139
- });
140
-
141
- describe('createDryRunHttpClient', () => {
142
- let operationLog;
143
-
144
- beforeEach(() => {
145
- operationLog = [];
146
- });
147
-
148
- test('should log GET requests', async () => {
149
- const client = createDryRunHttpClient(operationLog);
150
-
151
- const response = await client.get('/contacts', {
152
- baseURL: 'https://api.hubapi.com',
153
- headers: { Authorization: 'Bearer token' },
154
- });
155
-
156
- expect(operationLog).toHaveLength(1);
157
- expect(operationLog[0]).toMatchObject({
158
- operation: 'HTTP_REQUEST',
159
- method: 'GET',
160
- url: 'https://api.hubapi.com/contacts',
161
- service: 'HubSpot',
162
- });
163
-
164
- expect(operationLog[0].headers.Authorization).toBe('[REDACTED]');
165
- expect(response.data._dryRun).toBe(true);
166
- });
167
-
168
- test('should log POST requests with data', async () => {
169
- const client = createDryRunHttpClient(operationLog);
170
-
171
- const postData = {
172
- name: 'John Doe',
173
- email: 'john@example.com',
174
- password: 'secret123',
175
- };
176
-
177
- await client.post('/users', postData, {
178
- baseURL: 'https://api.example.com',
179
- });
180
-
181
- expect(operationLog).toHaveLength(1);
182
- expect(operationLog[0].method).toBe('POST');
183
- expect(operationLog[0].data.name).toBe('John Doe');
184
- expect(operationLog[0].data.email).toBe('john@example.com');
185
- expect(operationLog[0].data.password).toBe('[REDACTED]');
186
- });
187
-
188
- test('should log PUT requests', async () => {
189
- const client = createDryRunHttpClient(operationLog);
190
-
191
- await client.put('/users/123', { status: 'active' }, {
192
- baseURL: 'https://api.example.com',
193
- });
194
-
195
- expect(operationLog).toHaveLength(1);
196
- expect(operationLog[0].method).toBe('PUT');
197
- expect(operationLog[0].data.status).toBe('active');
198
- });
199
-
200
- test('should log PATCH requests', async () => {
201
- const client = createDryRunHttpClient(operationLog);
202
-
203
- await client.patch('/users/123', { name: 'Updated' });
204
-
205
- expect(operationLog).toHaveLength(1);
206
- expect(operationLog[0].method).toBe('PATCH');
207
- });
208
-
209
- test('should log DELETE requests', async () => {
210
- const client = createDryRunHttpClient(operationLog);
211
-
212
- await client.delete('/users/123', {
213
- baseURL: 'https://api.example.com',
214
- });
215
-
216
- expect(operationLog).toHaveLength(1);
217
- expect(operationLog[0].method).toBe('DELETE');
218
- });
219
-
220
- test('should return mock response', async () => {
221
- const client = createDryRunHttpClient(operationLog);
222
-
223
- const response = await client.get('/test');
224
-
225
- expect(response.status).toBe(200);
226
- expect(response.statusText).toContain('Dry-Run');
227
- expect(response.data._dryRun).toBe(true);
228
- expect(response.headers['x-dry-run']).toBe('true');
229
- });
230
-
231
- test('should include query params in log', async () => {
232
- const client = createDryRunHttpClient(operationLog);
233
-
234
- await client.get('/search', {
235
- baseURL: 'https://api.example.com',
236
- params: { q: 'test', limit: 10 },
237
- });
238
-
239
- expect(operationLog[0].params).toEqual({ q: 'test', limit: 10 });
240
- });
241
- });
242
-
243
- describe('injectDryRunHttpClient', () => {
244
- let operationLog;
245
- let dryRunClient;
246
-
247
- beforeEach(() => {
248
- operationLog = [];
249
- dryRunClient = createDryRunHttpClient(operationLog);
250
- });
251
-
252
- test('should inject into primary API module', () => {
253
- const integrationInstance = {
254
- primary: {
255
- api: {
256
- _httpClient: { get: jest.fn() },
257
- },
258
- },
259
- };
260
-
261
- injectDryRunHttpClient(integrationInstance, dryRunClient);
262
-
263
- expect(integrationInstance.primary.api._httpClient).toBe(dryRunClient);
264
- });
265
-
266
- test('should inject into target API module', () => {
267
- const integrationInstance = {
268
- target: {
269
- api: {
270
- _httpClient: { get: jest.fn() },
271
- },
272
- },
273
- };
274
-
275
- injectDryRunHttpClient(integrationInstance, dryRunClient);
276
-
277
- expect(integrationInstance.target.api._httpClient).toBe(dryRunClient);
278
- });
279
-
280
- test('should inject into both primary and target', () => {
281
- const integrationInstance = {
282
- primary: {
283
- api: { _httpClient: { get: jest.fn() } },
284
- },
285
- target: {
286
- api: { _httpClient: { get: jest.fn() } },
287
- },
288
- };
289
-
290
- injectDryRunHttpClient(integrationInstance, dryRunClient);
291
-
292
- expect(integrationInstance.primary.api._httpClient).toBe(dryRunClient);
293
- expect(integrationInstance.target.api._httpClient).toBe(dryRunClient);
294
- });
295
-
296
- test('should handle missing api modules gracefully', () => {
297
- const integrationInstance = {
298
- primary: {},
299
- target: null,
300
- };
301
-
302
- expect(() => {
303
- injectDryRunHttpClient(integrationInstance, dryRunClient);
304
- }).not.toThrow();
305
- });
306
-
307
- test('should handle null integration instance', () => {
308
- expect(() => {
309
- injectDryRunHttpClient(null, dryRunClient);
310
- }).not.toThrow();
311
- });
312
- });
313
- });