@friggframework/admin-scripts 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.35ee143.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 (30) hide show
  1. package/index.js +2 -2
  2. package/package.json +6 -9
  3. package/src/application/__tests__/admin-frigg-commands.test.js +19 -19
  4. package/src/application/__tests__/admin-script-base.test.js +2 -2
  5. package/src/application/__tests__/script-runner.test.js +146 -16
  6. package/src/application/admin-frigg-commands.js +8 -8
  7. package/src/application/admin-script-base.js +3 -5
  8. package/src/application/script-runner.js +125 -129
  9. package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +168 -0
  10. package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +114 -0
  11. package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +201 -0
  12. package/src/application/use-cases/delete-schedule-use-case.js +108 -0
  13. package/src/application/use-cases/get-effective-schedule-use-case.js +78 -0
  14. package/src/application/use-cases/index.js +18 -0
  15. package/src/application/use-cases/upsert-schedule-use-case.js +127 -0
  16. package/src/builtins/__tests__/integration-health-check.test.js +1 -1
  17. package/src/builtins/__tests__/oauth-token-refresh.test.js +1 -1
  18. package/src/builtins/integration-health-check.js +1 -1
  19. package/src/builtins/oauth-token-refresh.js +1 -1
  20. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +32 -95
  21. package/src/infrastructure/__tests__/admin-script-router.test.js +46 -47
  22. package/src/infrastructure/admin-auth-middleware.js +5 -43
  23. package/src/infrastructure/admin-script-router.js +38 -32
  24. package/src/infrastructure/script-executor-handler.js +2 -2
  25. package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
  26. package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
  27. package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
  28. package/src/application/dry-run-http-interceptor.js +0 -296
  29. package/src/application/dry-run-repository-wrapper.js +0 -261
  30. package/src/application/schedule-management-use-case.js +0 -230
@@ -1,22 +1,17 @@
1
- const { adminAuthMiddleware } = require('../admin-auth-middleware');
1
+ const { validateAdminApiKey } = require('../admin-auth-middleware');
2
2
 
3
- // Mock the admin script commands
4
- jest.mock('@friggframework/core/application/commands/admin-script-commands', () => ({
5
- createAdminScriptCommands: jest.fn(),
6
- }));
7
-
8
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
9
-
10
- describe('adminAuthMiddleware', () => {
3
+ describe('validateAdminApiKey', () => {
11
4
  let mockReq;
12
5
  let mockRes;
13
6
  let mockNext;
14
- let mockCommands;
7
+ let originalEnv;
15
8
 
16
9
  beforeEach(() => {
10
+ originalEnv = process.env.ADMIN_API_KEY;
11
+ process.env.ADMIN_API_KEY = 'test-admin-key-123';
12
+
17
13
  mockReq = {
18
14
  headers: {},
19
- ip: '127.0.0.1',
20
15
  };
21
16
 
22
17
  mockRes = {
@@ -25,124 +20,66 @@ describe('adminAuthMiddleware', () => {
25
20
  };
26
21
 
27
22
  mockNext = jest.fn();
28
-
29
- mockCommands = {
30
- validateAdminApiKey: jest.fn(),
31
- };
32
-
33
- createAdminScriptCommands.mockReturnValue(mockCommands);
34
23
  });
35
24
 
36
25
  afterEach(() => {
26
+ if (originalEnv) {
27
+ process.env.ADMIN_API_KEY = originalEnv;
28
+ } else {
29
+ delete process.env.ADMIN_API_KEY;
30
+ }
37
31
  jest.clearAllMocks();
38
32
  });
39
33
 
40
- describe('Authorization header validation', () => {
41
- it('should reject request without Authorization header', async () => {
42
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
43
-
44
- expect(mockRes.status).toHaveBeenCalledWith(401);
45
- expect(mockRes.json).toHaveBeenCalledWith({
46
- error: 'Missing or invalid Authorization header',
47
- code: 'MISSING_AUTH',
48
- });
49
- expect(mockNext).not.toHaveBeenCalled();
50
- });
51
-
52
- it('should reject request with invalid Authorization format', async () => {
53
- mockReq.headers.authorization = 'InvalidFormat key123';
34
+ describe('Environment configuration', () => {
35
+ it('should reject when ADMIN_API_KEY not configured', () => {
36
+ delete process.env.ADMIN_API_KEY;
54
37
 
55
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
38
+ validateAdminApiKey(mockReq, mockRes, mockNext);
56
39
 
57
40
  expect(mockRes.status).toHaveBeenCalledWith(401);
58
41
  expect(mockRes.json).toHaveBeenCalledWith({
59
- error: 'Missing or invalid Authorization header',
60
- code: 'MISSING_AUTH',
42
+ error: 'Unauthorized',
43
+ message: 'Admin API key not configured',
61
44
  });
62
45
  expect(mockNext).not.toHaveBeenCalled();
63
46
  });
64
47
  });
65
48
 
66
- describe('API key validation', () => {
67
- it('should reject request with invalid API key', async () => {
68
- mockReq.headers.authorization = 'Bearer invalid-key';
69
- mockCommands.validateAdminApiKey.mockResolvedValue({
70
- error: 401,
71
- reason: 'Invalid API key',
72
- code: 'INVALID_API_KEY',
73
- });
74
-
75
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
49
+ describe('Header validation', () => {
50
+ it('should reject request without x-frigg-admin-api-key header', () => {
51
+ validateAdminApiKey(mockReq, mockRes, mockNext);
76
52
 
77
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('invalid-key');
78
53
  expect(mockRes.status).toHaveBeenCalledWith(401);
79
54
  expect(mockRes.json).toHaveBeenCalledWith({
80
- error: 'Invalid API key',
81
- code: 'INVALID_API_KEY',
55
+ error: 'Unauthorized',
56
+ message: 'x-frigg-admin-api-key header required',
82
57
  });
83
58
  expect(mockNext).not.toHaveBeenCalled();
84
59
  });
60
+ });
85
61
 
86
- it('should reject request with expired API key', async () => {
87
- mockReq.headers.authorization = 'Bearer expired-key';
88
- mockCommands.validateAdminApiKey.mockResolvedValue({
89
- error: 401,
90
- reason: 'API key has expired',
91
- code: 'EXPIRED_API_KEY',
92
- });
62
+ describe('API key validation', () => {
63
+ it('should reject request with invalid API key', () => {
64
+ mockReq.headers['x-frigg-admin-api-key'] = 'invalid-key';
93
65
 
94
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
66
+ validateAdminApiKey(mockReq, mockRes, mockNext);
95
67
 
96
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('expired-key');
97
68
  expect(mockRes.status).toHaveBeenCalledWith(401);
98
69
  expect(mockRes.json).toHaveBeenCalledWith({
99
- error: 'API key has expired',
100
- code: 'EXPIRED_API_KEY',
70
+ error: 'Unauthorized',
71
+ message: 'Invalid admin API key',
101
72
  });
102
73
  expect(mockNext).not.toHaveBeenCalled();
103
74
  });
104
75
 
105
- it('should accept request with valid API key', async () => {
106
- const validKey = 'valid-api-key-123';
107
- mockReq.headers.authorization = `Bearer ${validKey}`;
108
- mockCommands.validateAdminApiKey.mockResolvedValue({
109
- valid: true,
110
- apiKey: {
111
- id: 'key-id-1',
112
- name: 'test-key',
113
- keyLast4: 'e123',
114
- },
115
- });
76
+ it('should accept request with valid API key', () => {
77
+ mockReq.headers['x-frigg-admin-api-key'] = 'test-admin-key-123';
116
78
 
117
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
79
+ validateAdminApiKey(mockReq, mockRes, mockNext);
118
80
 
119
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith(validKey);
120
- expect(mockReq.adminApiKey).toBeDefined();
121
- expect(mockReq.adminApiKey.name).toBe('test-key');
122
- expect(mockReq.adminAudit).toBeDefined();
123
- expect(mockReq.adminAudit.apiKeyName).toBe('test-key');
124
- expect(mockReq.adminAudit.apiKeyLast4).toBe('e123');
125
- expect(mockReq.adminAudit.ipAddress).toBe('127.0.0.1');
126
81
  expect(mockNext).toHaveBeenCalled();
127
82
  expect(mockRes.status).not.toHaveBeenCalled();
128
83
  });
129
84
  });
130
-
131
- describe('Error handling', () => {
132
- it('should handle validation errors gracefully', async () => {
133
- mockReq.headers.authorization = 'Bearer some-key';
134
- mockCommands.validateAdminApiKey.mockRejectedValue(
135
- new Error('Database error')
136
- );
137
-
138
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
139
-
140
- expect(mockRes.status).toHaveBeenCalledWith(500);
141
- expect(mockRes.json).toHaveBeenCalledWith({
142
- error: 'Authentication failed',
143
- code: 'AUTH_ERROR',
144
- });
145
- expect(mockNext).not.toHaveBeenCalled();
146
- });
147
- });
148
85
  });
@@ -4,13 +4,8 @@ const { AdminScriptBase } = require('../../application/admin-script-base');
4
4
 
5
5
  // Mock dependencies
6
6
  jest.mock('../admin-auth-middleware', () => ({
7
- adminAuthMiddleware: (req, res, next) => {
8
- // Mock auth - attach admin audit info
9
- req.adminAudit = {
10
- apiKeyName: 'test-key',
11
- apiKeyLast4: '1234',
12
- ipAddress: '127.0.0.1',
13
- };
7
+ validateAdminApiKey: (req, res, next) => {
8
+ // Mock auth - no audit trail with simplified auth
14
9
  next();
15
10
  },
16
11
  }));
@@ -59,8 +54,8 @@ describe('Admin Script Router', () => {
59
54
  };
60
55
 
61
56
  mockCommands = {
62
- createScriptExecution: jest.fn(),
63
- findScriptExecutionById: jest.fn(),
57
+ createAdminProcess: jest.fn(),
58
+ findAdminProcessById: jest.fn(),
64
59
  findRecentExecutions: jest.fn(),
65
60
  };
66
61
 
@@ -104,7 +99,7 @@ describe('Admin Script Router', () => {
104
99
  version: '1.0.0',
105
100
  description: 'Test script',
106
101
  category: 'test',
107
- requiresIntegrationFactory: false,
102
+ requireIntegrationInstance: false,
108
103
  schedule: null,
109
104
  });
110
105
  });
@@ -143,7 +138,7 @@ describe('Admin Script Router', () => {
143
138
  });
144
139
  });
145
140
 
146
- describe('POST /admin/scripts/:scriptName/execute', () => {
141
+ describe('POST /admin/scripts/:scriptName', () => {
147
142
  it('should execute script synchronously', async () => {
148
143
  mockRunner.execute.mockResolvedValue({
149
144
  executionId: 'exec-123',
@@ -154,7 +149,7 @@ describe('Admin Script Router', () => {
154
149
  });
155
150
 
156
151
  const response = await request(app)
157
- .post('/admin/scripts/test-script/execute')
152
+ .post('/admin/scripts/test-script')
158
153
  .send({
159
154
  params: { foo: 'bar' },
160
155
  mode: 'sync',
@@ -174,12 +169,12 @@ describe('Admin Script Router', () => {
174
169
  });
175
170
 
176
171
  it('should queue script for async execution', async () => {
177
- mockCommands.createScriptExecution.mockResolvedValue({
172
+ mockCommands.createAdminProcess.mockResolvedValue({
178
173
  id: 'exec-456',
179
174
  });
180
175
 
181
176
  const response = await request(app)
182
- .post('/admin/scripts/test-script/execute')
177
+ .post('/admin/scripts/test-script')
183
178
  .send({
184
179
  params: { foo: 'bar' },
185
180
  mode: 'async',
@@ -198,12 +193,12 @@ describe('Admin Script Router', () => {
198
193
  });
199
194
 
200
195
  it('should default to async mode', async () => {
201
- mockCommands.createScriptExecution.mockResolvedValue({
196
+ mockCommands.createAdminProcess.mockResolvedValue({
202
197
  id: 'exec-789',
203
198
  });
204
199
 
205
200
  const response = await request(app)
206
- .post('/admin/scripts/test-script/execute')
201
+ .post('/admin/scripts/test-script')
207
202
  .send({
208
203
  params: { foo: 'bar' },
209
204
  });
@@ -216,7 +211,7 @@ describe('Admin Script Router', () => {
216
211
  mockFactory.has.mockReturnValue(false);
217
212
 
218
213
  const response = await request(app)
219
- .post('/admin/scripts/non-existent/execute')
214
+ .post('/admin/scripts/non-existent')
220
215
  .send({
221
216
  params: {},
222
217
  });
@@ -226,15 +221,15 @@ describe('Admin Script Router', () => {
226
221
  });
227
222
  });
228
223
 
229
- describe('GET /admin/executions/:executionId', () => {
224
+ describe('GET /admin/scripts/:scriptName/executions/:executionId', () => {
230
225
  it('should return execution details', async () => {
231
- mockCommands.findScriptExecutionById.mockResolvedValue({
226
+ mockCommands.findAdminProcessById.mockResolvedValue({
232
227
  id: 'exec-123',
233
228
  scriptName: 'test-script',
234
229
  status: 'COMPLETED',
235
230
  });
236
231
 
237
- const response = await request(app).get('/admin/executions/exec-123');
232
+ const response = await request(app).get('/admin/scripts/test-script/executions/exec-123');
238
233
 
239
234
  expect(response.status).toBe(200);
240
235
  expect(response.body.id).toBe('exec-123');
@@ -242,14 +237,14 @@ describe('Admin Script Router', () => {
242
237
  });
243
238
 
244
239
  it('should return 404 for non-existent execution', async () => {
245
- mockCommands.findScriptExecutionById.mockResolvedValue({
240
+ mockCommands.findAdminProcessById.mockResolvedValue({
246
241
  error: 404,
247
242
  reason: 'Execution not found',
248
243
  code: 'EXECUTION_NOT_FOUND',
249
244
  });
250
245
 
251
246
  const response = await request(app).get(
252
- '/admin/executions/non-existent'
247
+ '/admin/scripts/test-script/executions/non-existent'
253
248
  );
254
249
 
255
250
  expect(response.status).toBe(404);
@@ -257,24 +252,28 @@ describe('Admin Script Router', () => {
257
252
  });
258
253
  });
259
254
 
260
- describe('GET /admin/executions', () => {
261
- it('should list recent executions', async () => {
255
+ describe('GET /admin/scripts/:scriptName/executions', () => {
256
+ it('should list executions for specific script', async () => {
262
257
  mockCommands.findRecentExecutions.mockResolvedValue([
263
258
  { id: 'exec-1', scriptName: 'test-script', status: 'COMPLETED' },
264
259
  { id: 'exec-2', scriptName: 'test-script', status: 'RUNNING' },
265
260
  ]);
266
261
 
267
- const response = await request(app).get('/admin/executions');
262
+ const response = await request(app).get('/admin/scripts/test-script/executions');
268
263
 
269
264
  expect(response.status).toBe(200);
270
265
  expect(response.body.executions).toHaveLength(2);
266
+ expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
267
+ scriptName: 'test-script',
268
+ limit: 50,
269
+ });
271
270
  });
272
271
 
273
272
  it('should accept query parameters', async () => {
274
273
  mockCommands.findRecentExecutions.mockResolvedValue([]);
275
274
 
276
275
  await request(app).get(
277
- '/admin/executions?scriptName=test-script&status=COMPLETED&limit=10'
276
+ '/admin/scripts/test-script/executions?status=COMPLETED&limit=10'
278
277
  );
279
278
 
280
279
  expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
@@ -294,8 +293,8 @@ describe('Admin Script Router', () => {
294
293
  timezone: 'America/New_York',
295
294
  lastTriggeredAt: new Date('2025-01-01T09:00:00Z'),
296
295
  nextTriggerAt: new Date('2025-01-02T09:00:00Z'),
297
- awsScheduleArn: 'arn:aws:events:us-east-1:123456789012:rule/test',
298
- awsScheduleName: 'test-script-schedule',
296
+ externalScheduleId: 'arn:aws:events:us-east-1:123456789012:rule/test',
297
+ externalScheduleName: 'test-script-schedule',
299
298
  createdAt: new Date('2025-01-01T00:00:00Z'),
300
299
  updatedAt: new Date('2025-01-01T00:00:00Z'),
301
300
  };
@@ -471,7 +470,7 @@ describe('Admin Script Router', () => {
471
470
  };
472
471
 
473
472
  mockCommands.upsertSchedule = jest.fn().mockResolvedValue(newSchedule);
474
- mockCommands.updateScheduleAwsInfo = jest.fn().mockResolvedValue(newSchedule);
473
+ mockCommands.updateScheduleExternalInfo = jest.fn().mockResolvedValue(newSchedule);
475
474
  mockSchedulerAdapter.createSchedule.mockResolvedValue({
476
475
  scheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
477
476
  scheduleName: 'frigg-script-test-script',
@@ -491,11 +490,11 @@ describe('Admin Script Router', () => {
491
490
  cronExpression: '0 12 * * *',
492
491
  timezone: 'America/Los_Angeles',
493
492
  });
494
- expect(mockCommands.updateScheduleAwsInfo).toHaveBeenCalledWith('test-script', {
495
- awsScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
496
- awsScheduleName: 'frigg-script-test-script',
493
+ expect(mockCommands.updateScheduleExternalInfo).toHaveBeenCalledWith('test-script', {
494
+ externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
495
+ externalScheduleName: 'frigg-script-test-script',
497
496
  });
498
- expect(response.body.schedule.awsScheduleArn).toBe('arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script');
497
+ expect(response.body.schedule.externalScheduleId).toBe('arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script');
499
498
  });
500
499
 
501
500
  it('should delete EventBridge schedule when disabling existing schedule', async () => {
@@ -504,14 +503,14 @@ describe('Admin Script Router', () => {
504
503
  enabled: false,
505
504
  cronExpression: null,
506
505
  timezone: 'UTC',
507
- awsScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
508
- awsScheduleName: 'frigg-script-test-script',
506
+ externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
507
+ externalScheduleName: 'frigg-script-test-script',
509
508
  createdAt: new Date(),
510
509
  updatedAt: new Date(),
511
510
  };
512
511
 
513
512
  mockCommands.upsertSchedule = jest.fn().mockResolvedValue(existingSchedule);
514
- mockCommands.updateScheduleAwsInfo = jest.fn().mockResolvedValue(existingSchedule);
513
+ mockCommands.updateScheduleExternalInfo = jest.fn().mockResolvedValue(existingSchedule);
515
514
  mockSchedulerAdapter.deleteSchedule.mockResolvedValue();
516
515
 
517
516
  const response = await request(app)
@@ -522,9 +521,9 @@ describe('Admin Script Router', () => {
522
521
 
523
522
  expect(response.status).toBe(200);
524
523
  expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith('test-script');
525
- expect(mockCommands.updateScheduleAwsInfo).toHaveBeenCalledWith('test-script', {
526
- awsScheduleArn: null,
527
- awsScheduleName: null,
524
+ expect(mockCommands.updateScheduleExternalInfo).toHaveBeenCalledWith('test-script', {
525
+ externalScheduleId: null,
526
+ externalScheduleName: null,
528
527
  });
529
528
  });
530
529
 
@@ -633,7 +632,7 @@ describe('Admin Script Router', () => {
633
632
  expect(response.body.code).toBe('SCRIPT_NOT_FOUND');
634
633
  });
635
634
 
636
- it('should delete EventBridge schedule when AWS rule exists', async () => {
635
+ it('should delete EventBridge schedule when external rule exists', async () => {
637
636
  mockCommands.deleteSchedule = jest.fn().mockResolvedValue({
638
637
  acknowledged: true,
639
638
  deletedCount: 1,
@@ -641,8 +640,8 @@ describe('Admin Script Router', () => {
641
640
  scriptName: 'test-script',
642
641
  enabled: true,
643
642
  cronExpression: '0 12 * * *',
644
- awsScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
645
- awsScheduleName: 'frigg-script-test-script',
643
+ externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
644
+ externalScheduleName: 'frigg-script-test-script',
646
645
  },
647
646
  });
648
647
  mockSchedulerAdapter.deleteSchedule.mockResolvedValue();
@@ -655,7 +654,7 @@ describe('Admin Script Router', () => {
655
654
  expect(mockSchedulerAdapter.deleteSchedule).toHaveBeenCalledWith('test-script');
656
655
  });
657
656
 
658
- it('should not call scheduler when no AWS rule exists', async () => {
657
+ it('should not call scheduler when no external rule exists', async () => {
659
658
  mockCommands.deleteSchedule = jest.fn().mockResolvedValue({
660
659
  acknowledged: true,
661
660
  deletedCount: 1,
@@ -663,7 +662,7 @@ describe('Admin Script Router', () => {
663
662
  scriptName: 'test-script',
664
663
  enabled: true,
665
664
  cronExpression: '0 12 * * *',
666
- // No awsScheduleArn
665
+ // No externalScheduleId
667
666
  },
668
667
  });
669
668
 
@@ -683,10 +682,10 @@ describe('Admin Script Router', () => {
683
682
  scriptName: 'test-script',
684
683
  enabled: true,
685
684
  cronExpression: '0 12 * * *',
686
- awsScheduleArn: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
685
+ externalScheduleId: 'arn:aws:scheduler:us-east-1:123456789012:schedule/frigg-admin-scripts/frigg-script-test-script',
687
686
  },
688
687
  });
689
- mockSchedulerAdapter.deleteSchedule.mockRejectedValue(new Error('AWS Scheduler delete failed'));
688
+ mockSchedulerAdapter.deleteSchedule.mockRejectedValue(new Error('Scheduler delete failed'));
690
689
 
691
690
  const response = await request(app).delete(
692
691
  '/admin/scripts/test-script/schedule'
@@ -695,7 +694,7 @@ describe('Admin Script Router', () => {
695
694
  // Request should succeed despite scheduler error
696
695
  expect(response.status).toBe(200);
697
696
  expect(response.body.success).toBe(true);
698
- expect(response.body.schedulerWarning).toBe('AWS Scheduler delete failed');
697
+ expect(response.body.schedulerWarning).toBe('Scheduler delete failed');
699
698
  });
700
699
  });
701
700
  });
@@ -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 };