Skip to content

Commit 8ea9be5

Browse files
authored
Use lamdba@edge to redirect SPA instead of 403 from S3 (#77)
* Use lamdba@edge to redirect SPA * add back the 403 test
1 parent 6833afe commit 8ea9be5

File tree

5 files changed

+106
-39
lines changed

5 files changed

+106
-39
lines changed

cloudformation/iam.yml

+33
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,34 @@ Resources:
242242
ses:Recipients:
243243
- "*@illinois.edu"
244244

245+
246+
EdgeLambdaIAMRole:
247+
Type: AWS::IAM::Role
248+
Properties:
249+
AssumeRolePolicyDocument:
250+
Version: "2012-10-17"
251+
Statement:
252+
- Effect: Allow
253+
Principal:
254+
Service: "lambda.amazonaws.com"
255+
Action: "sts:AssumeRole"
256+
- Effect: Allow
257+
Principal:
258+
Service: "edgelambda.amazonaws.com"
259+
Action: "sts:AssumeRole"
260+
Policies:
261+
- PolicyName: lambda-edge
262+
PolicyDocument:
263+
Version: "2012-10-17"
264+
Statement:
265+
- Effect: Allow
266+
Action:
267+
- "logs:CreateLogGroup"
268+
- "logs:CreateLogStream"
269+
- "logs:PutLogEvents"
270+
Resource:
271+
- Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}-edge:*
272+
245273
Outputs:
246274
MainFunctionRoleArn:
247275
Description: Main API IAM role ARN
@@ -254,3 +282,8 @@ Outputs:
254282
EntraFunctionRoleArn:
255283
Description: Entra IAM role ARN
256284
Value: !GetAtt EntraLambdaIAMRole.Arn
285+
286+
EdgeFunctionRoleArn:
287+
Description: Edge IAM role ARN
288+
Value: !GetAtt EdgeLambdaIAMRole.Arn
289+

cloudformation/logs.yml

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ Resources:
1414
LogGroupName:
1515
Fn::Sub: /aws/lambda/${LambdaFunctionName}
1616
RetentionInDays:
17-
Ref: LogRetentionDays
17+
Ref: LogRetentionDays
18+
EdgeLambdaLogGroup:
19+
Type: AWS::Logs::LogGroup
20+
Properties:
21+
LogGroupName:
22+
Fn::Sub: /aws/lambda/${LambdaFunctionName}-edge
23+
RetentionInDays: 7

cloudformation/main.yml

+33-5
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ Resources:
576576
Type: AWS::S3::Bucket
577577
Properties:
578578
BucketName: !Sub ${S3BucketPrefix}-ui
579+
WebsiteConfiguration:
580+
IndexDocument: index.html
579581

580582
CloudFrontOriginAccessIdentity:
581583
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
@@ -635,6 +637,9 @@ Resources:
635637
Cookies:
636638
Forward: none
637639
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # caching-optimized
640+
LambdaFunctionAssociations:
641+
- EventType: origin-request
642+
LambdaFunctionARN: !Ref AppFrontendEdgeLambdaVersion
638643
CacheBehaviors:
639644
- PathPattern: "/api/v1/events*"
640645
TargetOriginId: ApiGatewayOrigin
@@ -675,11 +680,6 @@ Resources:
675680
- EnvCertificateArn
676681
MinimumProtocolVersion: TLSv1.2_2021
677682
SslSupportMethod: sni-only
678-
CustomErrorResponses:
679-
- ErrorCode: 403
680-
ResponseCode: 200
681-
ResponsePagePath: /index.html
682-
ErrorCachingMinTTL: 0
683683
HttpVersion: http2
684684
PriceClass: PriceClass_100
685685

@@ -721,6 +721,34 @@ Resources:
721721
CookiesConfig:
722722
CookieBehavior: none
723723

724+
AppFrontendEdgeLambda:
725+
Type: AWS::Lambda::Function
726+
DependsOn:
727+
- AppLogGroups
728+
Properties:
729+
FunctionName: !Sub ${ApplicationPrefix}-lambda-edge
730+
Handler: "index.handler"
731+
Role: !GetAtt AppSecurityRoles.Outputs.EdgeFunctionRoleArn
732+
Runtime: nodejs22.x
733+
Code:
734+
ZipFile: |
735+
'use strict';
736+
exports.handler = async (event) => {
737+
const request = event.Records[0].cf.request;
738+
const uri = request.uri;
739+
if (!uri.startsWith('/api') && !uri.match(/\.\w+$/)) {
740+
request.uri = "/index.html";
741+
}
742+
return request;
743+
};
744+
MemorySize: 128
745+
Timeout: 5
746+
747+
AppFrontendEdgeLambdaVersion:
748+
Type: AWS::Lambda::Version
749+
Properties:
750+
FunctionName: !Ref AppFrontendEdgeLambda
751+
724752
Outputs:
725753
DomainName:
726754
Description: Domain name that the UI is hosted at

tests/live/mobileWallet.test.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ import { expect, test, describe } from "vitest";
33
const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
44

55
describe("Mobile pass issuance", async () => {
6-
// test(
7-
// "Test that passes will not be issued for non-members",
8-
// { timeout: 10000 },
9-
// async () => {
10-
// const response = await fetch(
11-
// `${baseEndpoint}/api/v1/mobileWallet/[email protected]`,
12-
// { method: "POST" },
13-
// );
14-
// expect(response.status).toBe(403);
15-
// },
16-
// );
6+
test(
7+
"Test that passes will not be issued for non-members",
8+
{ timeout: 10000 },
9+
async () => {
10+
const response = await fetch(
11+
`${baseEndpoint}/api/v1/mobileWallet/[email protected]`,
12+
{ method: "POST" },
13+
);
14+
expect(response.status).toBe(403);
15+
},
16+
);
1717
test(
1818
"Test that passes will be issued for members",
1919
{ timeout: 10000 },

tests/live/stripe.test.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,28 @@ const baseEndpoint = `https://core.aws.qa.acmuiuc.org`;
55

66
describe("Stripe live API authentication", async () => {
77
const token = await createJwt();
8-
// test(
9-
// "Test that auth is present on the GET route",
10-
// { timeout: 10000 },
11-
// async () => {
12-
// const response = await fetch(
13-
// `${baseEndpoint}/api/v1/stripe/paymentLinks`,
14-
// { method: "GET" },
15-
// );
16-
// expect(response.status).toBe(403);
17-
// },
18-
// );
19-
// test(
20-
// "Test that auth is present on the POST route",
21-
// { timeout: 10000 },
22-
// async () => {
23-
// const response = await fetch(
24-
// `${baseEndpoint}/api/v1/stripe/paymentLinks`,
25-
// { method: "POST" },
26-
// );
27-
// expect(response.status).toBe(403);
28-
// },
29-
// );
8+
test(
9+
"Test that auth is present on the GET route",
10+
{ timeout: 10000 },
11+
async () => {
12+
const response = await fetch(
13+
`${baseEndpoint}/api/v1/stripe/paymentLinks`,
14+
{ method: "GET" },
15+
);
16+
expect(response.status).toBe(403);
17+
},
18+
);
19+
test(
20+
"Test that auth is present on the POST route",
21+
{ timeout: 10000 },
22+
async () => {
23+
const response = await fetch(
24+
`${baseEndpoint}/api/v1/stripe/paymentLinks`,
25+
{ method: "POST" },
26+
);
27+
expect(response.status).toBe(403);
28+
},
29+
);
3030
test(
3131
"Test that getting existing links succeeds",
3232
{ timeout: 10000 },

0 commit comments

Comments
 (0)