@friggframework/devtools 2.0.0--canary.397.fe6d7a2.0 → 2.0.0--canary.405.45825cd.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,6 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { composeServerlessDefinition } = require('./serverless-template');
|
|
4
|
+
|
|
4
5
|
const { findNearestBackendPackageJson } = require('@friggframework/core');
|
|
5
6
|
|
|
6
7
|
function createFriggInfrastructure() {
|
|
@@ -18,7 +19,14 @@ function createFriggInfrastructure() {
|
|
|
18
19
|
const backend = require(backendFilePath);
|
|
19
20
|
const appDefinition = backend.Definition;
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
+
// const serverlessTemplate = require(path.resolve(
|
|
23
|
+
// __dirname,
|
|
24
|
+
// './serverless-template.js'
|
|
25
|
+
// ));
|
|
26
|
+
const definition = composeServerlessDefinition(
|
|
27
|
+
appDefinition,
|
|
28
|
+
backend.IntegrationFactory
|
|
29
|
+
);
|
|
22
30
|
|
|
23
31
|
return {
|
|
24
32
|
...definition,
|
|
@@ -102,7 +102,325 @@ const modifyHandlerPaths = (functions) => {
|
|
|
102
102
|
return modifiedFunctions;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
// Helper function to create VPC infrastructure resources
|
|
106
|
+
const createVPCInfrastructure = (AppDefinition) => {
|
|
107
|
+
const vpcResources = {
|
|
108
|
+
// VPC
|
|
109
|
+
FriggVPC: {
|
|
110
|
+
Type: 'AWS::EC2::VPC',
|
|
111
|
+
Properties: {
|
|
112
|
+
CidrBlock: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
|
|
113
|
+
EnableDnsHostnames: true,
|
|
114
|
+
EnableDnsSupport: true,
|
|
115
|
+
Tags: [
|
|
116
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' }
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Internet Gateway
|
|
122
|
+
FriggInternetGateway: {
|
|
123
|
+
Type: 'AWS::EC2::InternetGateway',
|
|
124
|
+
Properties: {
|
|
125
|
+
Tags: [
|
|
126
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Attach Internet Gateway to VPC
|
|
132
|
+
FriggVPCGatewayAttachment: {
|
|
133
|
+
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
134
|
+
Properties: {
|
|
135
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
136
|
+
InternetGatewayId: { Ref: 'FriggInternetGateway' }
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Public Subnet for NAT Gateway
|
|
141
|
+
FriggPublicSubnet: {
|
|
142
|
+
Type: 'AWS::EC2::Subnet',
|
|
143
|
+
Properties: {
|
|
144
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
145
|
+
CidrBlock: '10.0.1.0/24',
|
|
146
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
147
|
+
MapPublicIpOnLaunch: true,
|
|
148
|
+
Tags: [
|
|
149
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' }
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// Private Subnet 1 for Lambda
|
|
155
|
+
FriggPrivateSubnet1: {
|
|
156
|
+
Type: 'AWS::EC2::Subnet',
|
|
157
|
+
Properties: {
|
|
158
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
159
|
+
CidrBlock: '10.0.2.0/24',
|
|
160
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
161
|
+
Tags: [
|
|
162
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// Private Subnet 2 for Lambda (different AZ for redundancy)
|
|
168
|
+
FriggPrivateSubnet2: {
|
|
169
|
+
Type: 'AWS::EC2::Subnet',
|
|
170
|
+
Properties: {
|
|
171
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
172
|
+
CidrBlock: '10.0.3.0/24',
|
|
173
|
+
AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
|
|
174
|
+
Tags: [
|
|
175
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' }
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// Elastic IP for NAT Gateway
|
|
181
|
+
FriggNATGatewayEIP: {
|
|
182
|
+
Type: 'AWS::EC2::EIP',
|
|
183
|
+
Properties: {
|
|
184
|
+
Domain: 'vpc',
|
|
185
|
+
Tags: [
|
|
186
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
DependsOn: 'FriggVPCGatewayAttachment'
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// NAT Gateway for private subnet internet access
|
|
193
|
+
FriggNATGateway: {
|
|
194
|
+
Type: 'AWS::EC2::NatGateway',
|
|
195
|
+
Properties: {
|
|
196
|
+
AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
197
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
198
|
+
Tags: [
|
|
199
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Public Route Table
|
|
205
|
+
FriggPublicRouteTable: {
|
|
206
|
+
Type: 'AWS::EC2::RouteTable',
|
|
207
|
+
Properties: {
|
|
208
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
209
|
+
Tags: [
|
|
210
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// Public Route to Internet Gateway
|
|
216
|
+
FriggPublicRoute: {
|
|
217
|
+
Type: 'AWS::EC2::Route',
|
|
218
|
+
Properties: {
|
|
219
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
220
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
221
|
+
GatewayId: { Ref: 'FriggInternetGateway' }
|
|
222
|
+
},
|
|
223
|
+
DependsOn: 'FriggVPCGatewayAttachment'
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// Associate Public Subnet with Public Route Table
|
|
227
|
+
FriggPublicSubnetRouteTableAssociation: {
|
|
228
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
229
|
+
Properties: {
|
|
230
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
231
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' }
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// Private Route Table
|
|
236
|
+
FriggPrivateRouteTable: {
|
|
237
|
+
Type: 'AWS::EC2::RouteTable',
|
|
238
|
+
Properties: {
|
|
239
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
240
|
+
Tags: [
|
|
241
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' }
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Private Route to NAT Gateway
|
|
247
|
+
FriggPrivateRoute: {
|
|
248
|
+
Type: 'AWS::EC2::Route',
|
|
249
|
+
Properties: {
|
|
250
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' },
|
|
251
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
252
|
+
NatGatewayId: { Ref: 'FriggNATGateway' }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// Associate Private Subnet 1 with Private Route Table
|
|
257
|
+
FriggPrivateSubnet1RouteTableAssociation: {
|
|
258
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
259
|
+
Properties: {
|
|
260
|
+
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
261
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' }
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// Associate Private Subnet 2 with Private Route Table
|
|
266
|
+
FriggPrivateSubnet2RouteTableAssociation: {
|
|
267
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
268
|
+
Properties: {
|
|
269
|
+
SubnetId: { Ref: 'FriggPrivateSubnet2' },
|
|
270
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' }
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// Security Group for Lambda functions
|
|
275
|
+
FriggLambdaSecurityGroup: {
|
|
276
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
277
|
+
Properties: {
|
|
278
|
+
GroupDescription: 'Security group for Frigg Lambda functions',
|
|
279
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
280
|
+
SecurityGroupEgress: [
|
|
281
|
+
{
|
|
282
|
+
IpProtocol: 'tcp',
|
|
283
|
+
FromPort: 443,
|
|
284
|
+
ToPort: 443,
|
|
285
|
+
CidrIp: '0.0.0.0/0',
|
|
286
|
+
Description: 'HTTPS outbound'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
IpProtocol: 'tcp',
|
|
290
|
+
FromPort: 80,
|
|
291
|
+
ToPort: 80,
|
|
292
|
+
CidrIp: '0.0.0.0/0',
|
|
293
|
+
Description: 'HTTP outbound'
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
IpProtocol: 'tcp',
|
|
297
|
+
FromPort: 27017,
|
|
298
|
+
ToPort: 27017,
|
|
299
|
+
CidrIp: '0.0.0.0/0',
|
|
300
|
+
Description: 'MongoDB Atlas TLS outbound'
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
IpProtocol: 'tcp',
|
|
304
|
+
FromPort: 53,
|
|
305
|
+
ToPort: 53,
|
|
306
|
+
CidrIp: '0.0.0.0/0',
|
|
307
|
+
Description: 'DNS TCP'
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
IpProtocol: 'udp',
|
|
311
|
+
FromPort: 53,
|
|
312
|
+
ToPort: 53,
|
|
313
|
+
CidrIp: '0.0.0.0/0',
|
|
314
|
+
Description: 'DNS UDP'
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
Tags: [
|
|
318
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' }
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Add VPC Endpoints for cost optimization
|
|
325
|
+
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
326
|
+
// S3 Gateway Endpoint (free)
|
|
327
|
+
vpcResources.FriggS3VPCEndpoint = {
|
|
328
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
329
|
+
Properties: {
|
|
330
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
331
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
332
|
+
VpcEndpointType: 'Gateway',
|
|
333
|
+
RouteTableIds: [
|
|
334
|
+
{ Ref: 'FriggPrivateRouteTable' }
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// DynamoDB Gateway Endpoint (free)
|
|
340
|
+
vpcResources.FriggDynamoDBVPCEndpoint = {
|
|
341
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
342
|
+
Properties: {
|
|
343
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
344
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
345
|
+
VpcEndpointType: 'Gateway',
|
|
346
|
+
RouteTableIds: [
|
|
347
|
+
{ Ref: 'FriggPrivateRouteTable' }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
353
|
+
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
|
|
354
|
+
vpcResources.FriggKMSVPCEndpoint = {
|
|
355
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
356
|
+
Properties: {
|
|
357
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
358
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.kms',
|
|
359
|
+
VpcEndpointType: 'Interface',
|
|
360
|
+
SubnetIds: [
|
|
361
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
362
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
363
|
+
],
|
|
364
|
+
SecurityGroupIds: [
|
|
365
|
+
{ Ref: 'FriggVPCEndpointSecurityGroup' }
|
|
366
|
+
],
|
|
367
|
+
PrivateDnsEnabled: true
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Secrets Manager Interface Endpoint (paid, but useful for secrets)
|
|
373
|
+
vpcResources.FriggSecretsManagerVPCEndpoint = {
|
|
374
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
375
|
+
Properties: {
|
|
376
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
377
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
378
|
+
VpcEndpointType: 'Interface',
|
|
379
|
+
SubnetIds: [
|
|
380
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
381
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
382
|
+
],
|
|
383
|
+
SecurityGroupIds: [
|
|
384
|
+
{ Ref: 'FriggVPCEndpointSecurityGroup' }
|
|
385
|
+
],
|
|
386
|
+
PrivateDnsEnabled: true
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Security Group for VPC Endpoints
|
|
391
|
+
vpcResources.FriggVPCEndpointSecurityGroup = {
|
|
392
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
393
|
+
Properties: {
|
|
394
|
+
GroupDescription: 'Security group for Frigg VPC Endpoints',
|
|
395
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
396
|
+
SecurityGroupIngress: [
|
|
397
|
+
{
|
|
398
|
+
IpProtocol: 'tcp',
|
|
399
|
+
FromPort: 443,
|
|
400
|
+
ToPort: 443,
|
|
401
|
+
SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
|
|
402
|
+
Description: 'HTTPS from Lambda'
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
Tags: [
|
|
406
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' }
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return vpcResources;
|
|
413
|
+
};
|
|
414
|
+
|
|
105
415
|
const composeServerlessDefinition = (AppDefinition) => {
|
|
416
|
+
// Define CORS configuration to be used across all endpoints
|
|
417
|
+
const corsConfig = {
|
|
418
|
+
origin: '*',
|
|
419
|
+
headers: '*',
|
|
420
|
+
methods: ['ANY'],
|
|
421
|
+
allowCredentials: true,
|
|
422
|
+
};
|
|
423
|
+
|
|
106
424
|
const definition = {
|
|
107
425
|
frameworkVersion: '>=3.17.0',
|
|
108
426
|
service: AppDefinition.name || 'create-frigg-app',
|
|
@@ -207,21 +525,21 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
207
525
|
http: {
|
|
208
526
|
path: '/api/integrations',
|
|
209
527
|
method: 'ANY',
|
|
210
|
-
cors:
|
|
528
|
+
cors: corsConfig,
|
|
211
529
|
},
|
|
212
530
|
},
|
|
213
531
|
{
|
|
214
532
|
http: {
|
|
215
533
|
path: '/api/integrations/{proxy+}',
|
|
216
534
|
method: 'ANY',
|
|
217
|
-
cors:
|
|
535
|
+
cors: corsConfig,
|
|
218
536
|
},
|
|
219
537
|
},
|
|
220
538
|
{
|
|
221
539
|
http: {
|
|
222
540
|
path: '/api/authorize',
|
|
223
541
|
method: 'ANY',
|
|
224
|
-
cors:
|
|
542
|
+
cors: corsConfig,
|
|
225
543
|
},
|
|
226
544
|
},
|
|
227
545
|
],
|
|
@@ -233,7 +551,26 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
233
551
|
http: {
|
|
234
552
|
path: '/user/{proxy+}',
|
|
235
553
|
method: 'ANY',
|
|
236
|
-
cors:
|
|
554
|
+
cors: corsConfig,
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
},
|
|
559
|
+
health: {
|
|
560
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
561
|
+
events: [
|
|
562
|
+
{
|
|
563
|
+
http: {
|
|
564
|
+
path: '/health',
|
|
565
|
+
method: 'GET',
|
|
566
|
+
cors: corsConfig,
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
http: {
|
|
571
|
+
path: '/health/{proxy+}',
|
|
572
|
+
method: 'GET',
|
|
573
|
+
cors: corsConfig,
|
|
237
574
|
},
|
|
238
575
|
},
|
|
239
576
|
],
|
|
@@ -329,28 +666,154 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
329
666
|
},
|
|
330
667
|
};
|
|
331
668
|
|
|
669
|
+
// Configure BASE_URL based on custom domain or API Gateway
|
|
670
|
+
if (process.env.CUSTOM_DOMAIN) {
|
|
671
|
+
|
|
672
|
+
// Configure custom domain
|
|
673
|
+
definition.custom.customDomain = {
|
|
674
|
+
domainName: process.env.CUSTOM_DOMAIN,
|
|
675
|
+
basePath: process.env.CUSTOM_BASE_PATH || '',
|
|
676
|
+
stage: '${self:provider.stage}',
|
|
677
|
+
createRoute53Record: process.env.CREATE_ROUTE53_RECORD !== 'false', // Default true
|
|
678
|
+
certificateName: process.env.CERTIFICATE_NAME || process.env.CUSTOM_DOMAIN,
|
|
679
|
+
endpointType: process.env.ENDPOINT_TYPE || 'edge', // edge, regional, or private
|
|
680
|
+
securityPolicy: process.env.SECURITY_POLICY || 'tls_1_2',
|
|
681
|
+
apiType: 'rest',
|
|
682
|
+
autoDomain: process.env.AUTO_DOMAIN === 'true', // Auto create domain if it doesn't exist
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
// Set BASE_URL to custom domain
|
|
686
|
+
definition.provider.environment.BASE_URL = `https://${process.env.CUSTOM_DOMAIN}`;
|
|
687
|
+
} else {
|
|
688
|
+
// Default BASE_URL using API Gateway generated URL
|
|
689
|
+
definition.provider.environment.BASE_URL = {
|
|
690
|
+
'Fn::Join': [
|
|
691
|
+
'',
|
|
692
|
+
[
|
|
693
|
+
'https://',
|
|
694
|
+
{ Ref: 'ApiGatewayRestApi' },
|
|
695
|
+
'.execute-api.',
|
|
696
|
+
{ Ref: 'AWS::Region' },
|
|
697
|
+
'.amazonaws.com/',
|
|
698
|
+
'${self:provider.stage}',
|
|
699
|
+
],
|
|
700
|
+
],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// REDIRECT_PATH is required for OAuth integrations
|
|
705
|
+
if (!process.env.REDIRECT_PATH) {
|
|
706
|
+
throw new Error(
|
|
707
|
+
'REDIRECT_PATH environment variable is required. ' +
|
|
708
|
+
'Please set REDIRECT_PATH in your .env file (e.g., REDIRECT_PATH=/oauth/callback)'
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Set REDIRECT_URI based on domain configuration
|
|
713
|
+
if (process.env.CUSTOM_DOMAIN) {
|
|
714
|
+
definition.provider.environment.REDIRECT_URI = `https://${process.env.CUSTOM_DOMAIN}${process.env.REDIRECT_PATH}`;
|
|
715
|
+
} else {
|
|
716
|
+
definition.provider.environment.REDIRECT_URI = {
|
|
717
|
+
'Fn::Join': [
|
|
718
|
+
'',
|
|
719
|
+
[
|
|
720
|
+
'https://',
|
|
721
|
+
{ Ref: 'ApiGatewayRestApi' },
|
|
722
|
+
'.execute-api.',
|
|
723
|
+
{ Ref: 'AWS::Region' },
|
|
724
|
+
'.amazonaws.com/',
|
|
725
|
+
'${self:provider.stage}',
|
|
726
|
+
process.env.REDIRECT_PATH,
|
|
727
|
+
],
|
|
728
|
+
],
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Add REDIRECT_URI to CloudFormation outputs
|
|
733
|
+
definition.resources.Outputs = {
|
|
734
|
+
RedirectURI: {
|
|
735
|
+
Description: 'OAuth Redirect URI to register with providers',
|
|
736
|
+
Value: definition.provider.environment.REDIRECT_URI,
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
|
|
332
740
|
// KMS Configuration based on App Definition
|
|
333
741
|
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
|
|
334
|
-
//
|
|
742
|
+
// Provision a dedicated KMS key and wire it automatically
|
|
743
|
+
definition.resources.Resources.FriggKMSKey = {
|
|
744
|
+
Type: 'AWS::KMS::Key',
|
|
745
|
+
Properties: {
|
|
746
|
+
EnableKeyRotation: true,
|
|
747
|
+
KeyPolicy: {
|
|
748
|
+
Version: '2012-10-17',
|
|
749
|
+
Statement: [
|
|
750
|
+
{
|
|
751
|
+
Sid: 'AllowRootAccountAdmin',
|
|
752
|
+
Effect: 'Allow',
|
|
753
|
+
Principal: { AWS: { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root' } },
|
|
754
|
+
Action: 'kms:*',
|
|
755
|
+
Resource: '*'
|
|
756
|
+
}
|
|
757
|
+
]
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
335
762
|
definition.provider.iamRoleStatements.push({
|
|
336
763
|
Effect: 'Allow',
|
|
337
|
-
Action: [
|
|
338
|
-
|
|
339
|
-
'kms:Decrypt'
|
|
340
|
-
],
|
|
341
|
-
Resource: ['${self:custom.kmsGrants.kmsKeyId}']
|
|
764
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
765
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }]
|
|
342
766
|
});
|
|
343
767
|
|
|
344
|
-
|
|
345
|
-
definition.provider.environment.KMS_KEY_ARN = '${self:custom.kmsGrants.kmsKeyId}';
|
|
768
|
+
definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
346
769
|
|
|
347
|
-
// Add serverless-kms-grants plugin
|
|
348
770
|
definition.plugins.push('serverless-kms-grants');
|
|
771
|
+
definition.custom.kmsGrants = { kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] } };
|
|
772
|
+
}
|
|
349
773
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
};
|
|
774
|
+
// VPC Configuration based on App Definition
|
|
775
|
+
if (AppDefinition.vpc?.enable === true) {
|
|
776
|
+
// Create VPC config from App Definition or use auto-created resources
|
|
777
|
+
const vpcConfig = {};
|
|
778
|
+
|
|
779
|
+
if (AppDefinition.vpc.securityGroupIds) {
|
|
780
|
+
// User provided custom security groups
|
|
781
|
+
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
|
|
782
|
+
} else {
|
|
783
|
+
// Use auto-created security group
|
|
784
|
+
vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (AppDefinition.vpc.subnetIds) {
|
|
788
|
+
// User provided custom subnets
|
|
789
|
+
vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
|
|
790
|
+
} else {
|
|
791
|
+
// Use auto-created private subnets
|
|
792
|
+
vpcConfig.subnetIds = [
|
|
793
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
794
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
795
|
+
];
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Set VPC config for Lambda functions
|
|
799
|
+
definition.provider.vpc = vpcConfig;
|
|
800
|
+
|
|
801
|
+
// Add VPC-related IAM permissions
|
|
802
|
+
definition.provider.iamRoleStatements.push({
|
|
803
|
+
Effect: 'Allow',
|
|
804
|
+
Action: [
|
|
805
|
+
'ec2:CreateNetworkInterface',
|
|
806
|
+
'ec2:DescribeNetworkInterfaces',
|
|
807
|
+
'ec2:DeleteNetworkInterface',
|
|
808
|
+
'ec2:AttachNetworkInterface',
|
|
809
|
+
'ec2:DetachNetworkInterface'
|
|
810
|
+
],
|
|
811
|
+
Resource: '*'
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// Add VPC infrastructure resources to CloudFormation
|
|
815
|
+
const vpcResources = createVPCInfrastructure(AppDefinition);
|
|
816
|
+
Object.assign(definition.resources.Resources, vpcResources);
|
|
354
817
|
}
|
|
355
818
|
|
|
356
819
|
// Add integration-specific functions and resources
|
|
@@ -365,7 +828,7 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
365
828
|
http: {
|
|
366
829
|
path: `/api/${integrationName}-integration/{proxy+}`,
|
|
367
830
|
method: 'ANY',
|
|
368
|
-
cors:
|
|
831
|
+
cors: corsConfig,
|
|
369
832
|
},
|
|
370
833
|
},
|
|
371
834
|
],
|
|
@@ -425,4 +888,4 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
425
888
|
return definition;
|
|
426
889
|
};
|
|
427
890
|
|
|
428
|
-
module.exports = { composeServerlessDefinition };
|
|
891
|
+
module.exports = { composeServerlessDefinition };
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.405.45825cd.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@babel/eslint-parser": "^7.18.9",
|
|
7
7
|
"@babel/parser": "^7.25.3",
|
|
8
8
|
"@babel/traverse": "^7.25.3",
|
|
9
|
-
"@friggframework/test": "2.0.0--canary.
|
|
9
|
+
"@friggframework/test": "2.0.0--canary.405.45825cd.0",
|
|
10
10
|
"@hapi/boom": "^10.0.1",
|
|
11
11
|
"@inquirer/prompts": "^5.3.8",
|
|
12
12
|
"axios": "^1.7.2",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"serverless-http": "^2.7.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
31
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
30
|
+
"@friggframework/eslint-config": "2.0.0--canary.405.45825cd.0",
|
|
31
|
+
"@friggframework/prettier-config": "2.0.0--canary.405.45825cd.0",
|
|
32
32
|
"prettier": "^2.7.1",
|
|
33
33
|
"serverless": "3.39.0",
|
|
34
34
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "45825cde018fed76379aae97a58f03c5695f40c9"
|
|
64
64
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Auther,
|
|
3
|
+
ModuleConstants,
|
|
4
|
+
createObjectId,
|
|
5
|
+
connectToDatabase,
|
|
6
|
+
disconnectFromDatabase,
|
|
7
|
+
} = require('@friggframework/core');
|
|
8
|
+
const { createMockApiObject } = require("./mock-integration");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function testAutherDefinition(definition, mocks) {
|
|
12
|
+
const getModule = async (params) => {
|
|
13
|
+
const module = await Auther.getInstance({
|
|
14
|
+
definition,
|
|
15
|
+
userId: createObjectId(),
|
|
16
|
+
...params,
|
|
17
|
+
});
|
|
18
|
+
if (mocks.tokenResponse) {
|
|
19
|
+
mocks.getTokenFrom = async function(code) {
|
|
20
|
+
await this.setTokens(mocks.tokenResponse);
|
|
21
|
+
return mocks.tokenResponse
|
|
22
|
+
}
|
|
23
|
+
mocks.getTokenFromCode = mocks.getTokenFromCode || mocks.getTokenFrom
|
|
24
|
+
mocks.getTokenFromCodeBasicAuthHeader = mocks.getTokenFromCodeBasicAuthHeader || mocks.getTokenFrom
|
|
25
|
+
mocks.getTokenFromClientCredentials = mocks.getTokenFromClientCredentials || mocks.getTokenFrom
|
|
26
|
+
mocks.getTokenFromUsernamePassword = mocks.getTokenFromUsernamePassword || mocks.getTokenFrom
|
|
27
|
+
}
|
|
28
|
+
if (mocks.refreshResponse) {
|
|
29
|
+
mocks.refreshAccessToken = async function(code) {
|
|
30
|
+
await this.setTokens(mocks.refreshResponse);
|
|
31
|
+
return mocks.refreshResponse
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
module.api = createMockApiObject(jest, module.api, mocks);
|
|
35
|
+
return module
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
describe(`${definition.moduleName} Module Tests`, () => {
|
|
40
|
+
let module, authUrl;
|
|
41
|
+
beforeAll(async () => {
|
|
42
|
+
await connectToDatabase();
|
|
43
|
+
module = await getModule();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await disconnectFromDatabase();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let requirements, authCallbackParams;
|
|
51
|
+
if (definition.API.requesterType === ModuleConstants.authType.oauth2) {
|
|
52
|
+
authCallbackParams = mocks.authorizeResponse || mocks.authorizeParams;
|
|
53
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
54
|
+
it('should return auth requirements', async () => {
|
|
55
|
+
requirements = await module.getAuthorizationRequirements();
|
|
56
|
+
expect(requirements).toBeDefined();
|
|
57
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.oauth2);
|
|
58
|
+
expect(requirements.url).toBeDefined();
|
|
59
|
+
authUrl = requirements.url;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.basic) {
|
|
63
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
64
|
+
authCallbackParams = mocks.authorizeParams
|
|
65
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
66
|
+
it('should return auth requirements', async () => {
|
|
67
|
+
requirements = module.getAuthorizationRequirements();
|
|
68
|
+
expect(requirements).toBeDefined();
|
|
69
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.basic);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
} else if (definition.API.requesterType === ModuleConstants.authType.apiKey) {
|
|
73
|
+
// could also confirm authCallbackParams against the auth requirements
|
|
74
|
+
authCallbackParams = mocks.authorizeParams
|
|
75
|
+
describe('getAuthorizationRequirements() test', () => {
|
|
76
|
+
it('should return auth requirements', async () => {
|
|
77
|
+
requirements = module.getAuthorizationRequirements();
|
|
78
|
+
expect(requirements).toBeDefined();
|
|
79
|
+
expect(requirements.type).toEqual(ModuleConstants.authType.apiKey);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('Authorization requests', () => {
|
|
85
|
+
let firstRes;
|
|
86
|
+
it('processAuthorizationCallback()', async () => {
|
|
87
|
+
firstRes = await module.processAuthorizationCallback(authCallbackParams);
|
|
88
|
+
expect(firstRes).toBeDefined();
|
|
89
|
+
expect(firstRes.entity_id).toBeDefined();
|
|
90
|
+
expect(firstRes.credential_id).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('retrieves existing entity on subsequent calls', async () => {
|
|
93
|
+
const res = await module.processAuthorizationCallback(authCallbackParams);
|
|
94
|
+
expect(res).toEqual(firstRes);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Test credential retrieval and module instantiation', () => {
|
|
99
|
+
it('retrieve by entity id', async () => {
|
|
100
|
+
const newModule = await getModule({
|
|
101
|
+
userId: module.userId,
|
|
102
|
+
entityId: module.entity.id
|
|
103
|
+
});
|
|
104
|
+
expect(newModule).toBeDefined();
|
|
105
|
+
expect(newModule.entity).toBeDefined();
|
|
106
|
+
expect(newModule.credential).toBeDefined();
|
|
107
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
108
|
+
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('retrieve by credential id', async () => {
|
|
112
|
+
const newModule = await getModule({
|
|
113
|
+
userId: module.userId,
|
|
114
|
+
credentialId: module.credential.id
|
|
115
|
+
});
|
|
116
|
+
expect(newModule).toBeDefined();
|
|
117
|
+
expect(newModule.credential).toBeDefined();
|
|
118
|
+
expect(await newModule.testAuth()).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { testAutherDefinition }
|
|
125
|
+
|
package/test/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const {
|
|
1
|
+
const {testDefinitionRequiredAuthMethods} = require('./auther-definition-method-tester');
|
|
2
|
+
const {createMockIntegration, createMockApiObject} = require('./mock-integration');
|
|
3
|
+
const { testAutherDefinition } = require('./auther-definition-tester');
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
createMockIntegration,
|
|
7
8
|
createMockApiObject,
|
|
8
9
|
testDefinitionRequiredAuthMethods,
|
|
10
|
+
testAutherDefinition,
|
|
9
11
|
};
|
package/test/mock-integration.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
const {
|
|
2
|
+
Auther,
|
|
3
|
+
Credential,
|
|
4
|
+
Entity,
|
|
5
|
+
IntegrationFactory,
|
|
2
6
|
createObjectId,
|
|
3
7
|
} = require('@friggframework/core');
|
|
4
8
|
|
|
@@ -7,6 +11,7 @@ async function createMockIntegration(
|
|
|
7
11
|
userId = null,
|
|
8
12
|
config = { type: IntegrationClass.Definition.name }
|
|
9
13
|
) {
|
|
14
|
+
const integrationFactory = new IntegrationFactory([IntegrationClass]);
|
|
10
15
|
userId = userId || createObjectId();
|
|
11
16
|
|
|
12
17
|
const insertOptions = {
|
|
@@ -19,8 +24,10 @@ async function createMockIntegration(
|
|
|
19
24
|
const entities = [];
|
|
20
25
|
for (const moduleName in IntegrationClass.modules) {
|
|
21
26
|
const ModuleDef = IntegrationClass.Definition.modules[moduleName];
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const module = await Auther.getInstance({
|
|
28
|
+
definition: ModuleDef,
|
|
29
|
+
userId: userId,
|
|
30
|
+
});
|
|
24
31
|
const credential = await module.CredentialModel.findOneAndUpdate(
|
|
25
32
|
user,
|
|
26
33
|
{ $set: user },
|
|
@@ -44,8 +51,11 @@ async function createMockIntegration(
|
|
|
44
51
|
);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
const integration = await integrationFactory.createIntegration(
|
|
55
|
+
entities,
|
|
56
|
+
userId,
|
|
57
|
+
config
|
|
58
|
+
);
|
|
49
59
|
|
|
50
60
|
integration.id = integration.record._id;
|
|
51
61
|
|