From 4f78ff4734aecdadb7ff8238577d5a5e42435047 Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Thu, 20 Mar 2025 17:40:13 +0000 Subject: [PATCH 1/2] Use lamdba@edge to redirect SPA --- cloudformation/iam.yml | 33 +++++++++++++++++++++++++++++++++ cloudformation/logs.yml | 8 +++++++- cloudformation/main.yml | 38 +++++++++++++++++++++++++++++++++----- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/cloudformation/iam.yml b/cloudformation/iam.yml index 431e15c4..cdad734f 100644 --- a/cloudformation/iam.yml +++ b/cloudformation/iam.yml @@ -242,6 +242,34 @@ Resources: ses:Recipients: - "*@illinois.edu" + + EdgeLambdaIAMRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: "lambda.amazonaws.com" + Action: "sts:AssumeRole" + - Effect: Allow + Principal: + Service: "edgelambda.amazonaws.com" + Action: "sts:AssumeRole" + Policies: + - PolicyName: lambda-edge + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "logs:CreateLogGroup" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + Resource: + - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}-edge:* + Outputs: MainFunctionRoleArn: Description: Main API IAM role ARN @@ -254,3 +282,8 @@ Outputs: EntraFunctionRoleArn: Description: Entra IAM role ARN Value: !GetAtt EntraLambdaIAMRole.Arn + + EdgeFunctionRoleArn: + Description: Edge IAM role ARN + Value: !GetAtt EdgeLambdaIAMRole.Arn + diff --git a/cloudformation/logs.yml b/cloudformation/logs.yml index a9871c65..ecc01c57 100644 --- a/cloudformation/logs.yml +++ b/cloudformation/logs.yml @@ -14,4 +14,10 @@ Resources: LogGroupName: Fn::Sub: /aws/lambda/${LambdaFunctionName} RetentionInDays: - Ref: LogRetentionDays \ No newline at end of file + Ref: LogRetentionDays + EdgeLambdaLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: + Fn::Sub: /aws/lambda/${LambdaFunctionName}-edge + RetentionInDays: 7 diff --git a/cloudformation/main.yml b/cloudformation/main.yml index 022b2c87..f6385bc9 100644 --- a/cloudformation/main.yml +++ b/cloudformation/main.yml @@ -576,6 +576,8 @@ Resources: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${S3BucketPrefix}-ui + WebsiteConfiguration: + IndexDocument: index.html CloudFrontOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity @@ -635,6 +637,9 @@ Resources: Cookies: Forward: none CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # caching-optimized + LambdaFunctionAssociations: + - EventType: origin-request + LambdaFunctionARN: !Ref AppFrontendEdgeLambdaVersion CacheBehaviors: - PathPattern: "/api/v1/events*" TargetOriginId: ApiGatewayOrigin @@ -675,11 +680,6 @@ Resources: - EnvCertificateArn MinimumProtocolVersion: TLSv1.2_2021 SslSupportMethod: sni-only - CustomErrorResponses: - - ErrorCode: 403 - ResponseCode: 200 - ResponsePagePath: /index.html - ErrorCachingMinTTL: 0 HttpVersion: http2 PriceClass: PriceClass_100 @@ -721,6 +721,34 @@ Resources: CookiesConfig: CookieBehavior: none + AppFrontendEdgeLambda: + Type: AWS::Lambda::Function + DependsOn: + - AppLogGroups + Properties: + FunctionName: !Sub ${ApplicationPrefix}-lambda-edge + Handler: "index.handler" + Role: !GetAtt AppSecurityRoles.Outputs.EdgeFunctionRoleArn + Runtime: nodejs22.x + Code: + ZipFile: | + 'use strict'; + exports.handler = async (event) => { + const request = event.Records[0].cf.request; + const uri = request.uri; + if (!uri.startsWith('/api') && !uri.match(/\.\w+$/)) { + request.uri = "/index.html"; + } + return request; + }; + MemorySize: 128 + Timeout: 5 + + AppFrontendEdgeLambdaVersion: + Type: AWS::Lambda::Version + Properties: + FunctionName: !Ref AppFrontendEdgeLambda + Outputs: DomainName: Description: Domain name that the UI is hosted at From b20e86fe7d5f2edb0b086c7b79cab5ee9b4b7a96 Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Thu, 20 Mar 2025 17:40:56 +0000 Subject: [PATCH 2/2] add back the 403 test --- tests/live/mobileWallet.test.ts | 22 ++++++++--------- tests/live/stripe.test.ts | 44 ++++++++++++++++----------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/live/mobileWallet.test.ts b/tests/live/mobileWallet.test.ts index 8bd24112..f5c9d290 100644 --- a/tests/live/mobileWallet.test.ts +++ b/tests/live/mobileWallet.test.ts @@ -3,17 +3,17 @@ import { expect, test, describe } from "vitest"; const baseEndpoint = `https://core.aws.qa.acmuiuc.org`; describe("Mobile pass issuance", async () => { - // test( - // "Test that passes will not be issued for non-members", - // { timeout: 10000 }, - // async () => { - // const response = await fetch( - // `${baseEndpoint}/api/v1/mobileWallet/membership?email=notamemberatall@illinois.edu`, - // { method: "POST" }, - // ); - // expect(response.status).toBe(403); - // }, - // ); + test( + "Test that passes will not be issued for non-members", + { timeout: 10000 }, + async () => { + const response = await fetch( + `${baseEndpoint}/api/v1/mobileWallet/membership?email=notamemberatall@illinois.edu`, + { method: "POST" }, + ); + expect(response.status).toBe(403); + }, + ); test( "Test that passes will be issued for members", { timeout: 10000 }, diff --git a/tests/live/stripe.test.ts b/tests/live/stripe.test.ts index b7710a55..590ef474 100644 --- a/tests/live/stripe.test.ts +++ b/tests/live/stripe.test.ts @@ -5,28 +5,28 @@ const baseEndpoint = `https://core.aws.qa.acmuiuc.org`; describe("Stripe live API authentication", async () => { const token = await createJwt(); - // test( - // "Test that auth is present on the GET route", - // { timeout: 10000 }, - // async () => { - // const response = await fetch( - // `${baseEndpoint}/api/v1/stripe/paymentLinks`, - // { method: "GET" }, - // ); - // expect(response.status).toBe(403); - // }, - // ); - // test( - // "Test that auth is present on the POST route", - // { timeout: 10000 }, - // async () => { - // const response = await fetch( - // `${baseEndpoint}/api/v1/stripe/paymentLinks`, - // { method: "POST" }, - // ); - // expect(response.status).toBe(403); - // }, - // ); + test( + "Test that auth is present on the GET route", + { timeout: 10000 }, + async () => { + const response = await fetch( + `${baseEndpoint}/api/v1/stripe/paymentLinks`, + { method: "GET" }, + ); + expect(response.status).toBe(403); + }, + ); + test( + "Test that auth is present on the POST route", + { timeout: 10000 }, + async () => { + const response = await fetch( + `${baseEndpoint}/api/v1/stripe/paymentLinks`, + { method: "POST" }, + ); + expect(response.status).toBe(403); + }, + ); test( "Test that getting existing links succeeds", { timeout: 10000 },