diff --git a/backendAWS/bin/backend_aws.ts b/backendAWS/bin/backend_aws.ts index 62db5f825f0c3873c9f81d26fe8badc567cbd658..be02dd4ba15941d2219f7ffd6d534fdd236e079c 100644 --- a/backendAWS/bin/backend_aws.ts +++ b/backendAWS/bin/backend_aws.ts @@ -27,9 +27,11 @@ const storageStack = new StorageStack(app, 'ContactApp-StorageStack', { region: process.env.CDK_DEFAULT_REGION, }, }); + const lambdaStack = new LambdaStack(app, 'ContactApp-LambdaStack', { contactsTable: storageStack.contactsTable, contactsBucket: storageStack.contactsBucket, + cloudfrontImagesDistribution: storageStack.cloudfrontImagesDistribution, env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, @@ -48,16 +50,8 @@ new ApiGatewayStack(app, 'ContactApp-ApiGatewayStack', { }, }); -/* new CloudFrontStack(app, 'ContactApp-CloudFrontStack', { - contactsBucket: storageStack.contactsBucket, - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION, - }, -}); - */ - new FrontendStack(app, 'ContactApp-FrontendStack', { + contactsBucket: storageStack.contactsBucket, env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, diff --git a/backendAWS/functions/create-contact/index.ts b/backendAWS/functions/create-contact/index.ts index 002eef47875358fdaca314d3376c25fe8c5c2b1f..bbea234c090cc4547343e96359df2bc840af21b4 100644 --- a/backendAWS/functions/create-contact/index.ts +++ b/backendAWS/functions/create-contact/index.ts @@ -6,6 +6,7 @@ const dynamoDb = new DynamoDB.DocumentClient(); const s3 = new S3(); const tableName = process.env.TABLE_NAME; const bucketName = process.env.BUCKET_NAME; +const cloudfrontDomain = process.env.CLOUDFRONT_DOMAIN; export const handler = async (event: any) => { const body = JSON.parse(event.body); @@ -40,7 +41,7 @@ export const handler = async (event: any) => { phone: body.phone, email: body.email, isFavorite: body.isFavorite || false, - imageUrl: `https://${bucketName}.s3.amazonaws.com/${imageKey}`, + imageUrl: `https://${cloudfrontDomain}/${imageKey}?timestamp=${Date.now()}`, }; // Store contact in DynamoDB diff --git a/backendAWS/functions/update-contact/index.ts b/backendAWS/functions/update-contact/index.ts index 8735500fa41523a932b862f92a081f267edf63b6..f117d2ad93061026c5fdeeca5643572f61e6fccd 100644 --- a/backendAWS/functions/update-contact/index.ts +++ b/backendAWS/functions/update-contact/index.ts @@ -5,6 +5,7 @@ const dynamoDb = new DynamoDB.DocumentClient(); const s3 = new S3(); const tableName = process.env.TABLE_NAME; const bucketName = process.env.BUCKET_NAME; +const cloudfrontDomain = process.env.CLOUDFRONT_DOMAIN; export const handler = async (event: any) => { const body = JSON.parse(event.body); @@ -42,10 +43,11 @@ export const handler = async (event: any) => { Key: imageKey, Body: imageBuffer, ContentType: 'image/jpeg', + CacheControl: 'max-age=0, must-revalidate', }) .promise(); - imageUrl = `https://${bucketName}.s3.amazonaws.com/${imageKey}`; + imageUrl = `https://${cloudfrontDomain}/${imageKey}?timestamp=${Date.now()}`; } // Update the contact in DynamoDB diff --git a/backendAWS/lib/cloudfront_stack.ts b/backendAWS/lib/cloudfront_stack.ts deleted file mode 100644 index 3b64122e00d909fd894adc6e05ccc72d3b1a0e50..0000000000000000000000000000000000000000 --- a/backendAWS/lib/cloudfront_stack.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; -import * as s3 from 'aws-cdk-lib/aws-s3'; -import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; - -interface CloudFrontStackProps extends cdk.StackProps { - contactsBucket: s3.Bucket; -} - -export class CloudFrontStack extends cdk.Stack { - constructor(scope: cdk.App, id: string, props: CloudFrontStackProps) { - super(scope, id, props); - - // Create an Origin Access Control (OAC) - const originAccessControl = new cloudfront.CfnOriginAccessControl( - this, - 'ContactsAppOAC', - { - originAccessControlConfig: { - name: 'ContactsAppOAC', - originAccessControlOriginType: 's3', - signingBehavior: 'always', // Always sign requests - signingProtocol: 'sigv4', - }, - } - ); - - //CloudFront Distribution for Images - /* - const cloudFrontDistribution = new cloudfront.CfnDistribution( - this, - 'ContactsAppDistribution', - { - distributionConfig: { - enabled: true, - defaultRootObject: '', // Optional: Redirect root paths to index.html - origins: [ - { - id: 'S3Origin', - domainName: props.contactsBucket.bucketRegionalDomainName, // Use the regional domain name of the S3 bucket - originAccessControlId: originAccessControl.attrId, // Attach the OAC - s3OriginConfig: {}, // Required when using S3 as the origin - }, - ], - defaultCacheBehavior: { - targetOriginId: 'S3Origin', - viewerProtocolPolicy: 'redirect-to-https', // Enforce HTTPS - allowedMethods: ['GET', 'HEAD'], - cachedMethods: ['GET', 'HEAD'], - forwardedValues: { - queryString: false, - cookies: { forward: 'none' }, - }, - }, - priceClass: 'PriceClass_100', // Adjust to your requirements - viewerCertificate: { - cloudFrontDefaultCertificate: true, - }, - }, - } - ); - - props.contactsBucket.addToResourcePolicy( - new PolicyStatement({ - actions: ['s3:GetObject'], - resources: [`${props.contactsBucket.bucketArn}/*`], - principals: [ - new cdk.aws_iam.ServicePrincipal('cloudfront.amazonaws.com'), - ], - conditions: { - StringEquals: { - 'AWS:SourceArn': `arn:aws:cloudfront::${this.account}:distribution/${cloudFrontDistribution.ref}`, - }, - }, - }) - ); - - cdk.Tags.of(cloudFrontDistribution).add('Service', 'CloudFront'); */ - } -} diff --git a/backendAWS/lib/frontend_stack.ts b/backendAWS/lib/frontend_stack.ts index 2ffc1397a44e1f3445577af21a058c7afa2fe29f..e05fcc0ec060fa6839e22d4b1eccd62c91be7cea 100644 --- a/backendAWS/lib/frontend_stack.ts +++ b/backendAWS/lib/frontend_stack.ts @@ -5,8 +5,14 @@ import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; import { S3StaticWebsiteOrigin } from 'aws-cdk-lib/aws-cloudfront-origins'; import { join } from 'path'; +interface FrontendStackProps extends cdk.StackProps { + contactsBucket: s3.Bucket; +} + export class FrontendStack extends cdk.Stack { - constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + public readonly cloudfrontDistribution: cloudfront.Distribution; + + constructor(scope: cdk.App, id: string, props: FrontendStackProps) { super(scope, id, props); // Create an S3 bucket for the frontend @@ -39,7 +45,7 @@ export class FrontendStack extends cdk.Stack { }); // Create CloudFront distribution - const cloudfrontDistribution = new cloudfront.Distribution( + this.cloudfrontDistribution = new cloudfront.Distribution( this, 'FrontendDistribution', { @@ -53,7 +59,7 @@ export class FrontendStack extends cdk.Stack { // Output the CloudFront URL new cdk.CfnOutput(this, 'CloudFrontClientAppURL', { - value: `https://${cloudfrontDistribution.distributionDomainName}`, + value: `https://${this.cloudfrontDistribution.distributionDomainName}`, description: 'CloudFront URL for frontend', }); } diff --git a/backendAWS/lib/lambda_stack.ts b/backendAWS/lib/lambda_stack.ts index f9d591707b69dc0edea266c29862078b560814c7..6bdb031c02b8dde09e61fb323210e8d655c93660 100644 --- a/backendAWS/lib/lambda_stack.ts +++ b/backendAWS/lib/lambda_stack.ts @@ -3,12 +3,14 @@ import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; import * as path from 'path'; import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; interface LambdaStackProps extends cdk.StackProps { contactsTable: dynamodb.Table; contactsBucket: s3.Bucket; + cloudfrontImagesDistribution: cloudfront.Distribution; } export class LambdaStack extends cdk.Stack { @@ -20,7 +22,8 @@ export class LambdaStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: LambdaStackProps) { super(scope, id, props); - const { contactsTable, contactsBucket } = props; + const { contactsTable, contactsBucket, cloudfrontImagesDistribution } = + props; //Create Contact Function Lambda this.createContactFunction = new lambdaNodejs.NodejsFunction( @@ -34,6 +37,8 @@ export class LambdaStack extends cdk.Stack { environment: { TABLE_NAME: contactsTable.tableName, BUCKET_NAME: contactsBucket.bucketName, + CLOUDFRONT_DOMAIN: + cloudfrontImagesDistribution.distributionDomainName, }, } ); @@ -64,6 +69,8 @@ export class LambdaStack extends cdk.Stack { handler: 'handler', environment: { TABLE_NAME: contactsTable.tableName, + CLOUDFRONT_DOMAIN: + cloudfrontImagesDistribution.distributionDomainName, }, } ); @@ -90,6 +97,8 @@ export class LambdaStack extends cdk.Stack { environment: { TABLE_NAME: contactsTable.tableName, BUCKET_NAME: contactsBucket.bucketName, + CLOUDFRONT_DOMAIN: + cloudfrontImagesDistribution.distributionDomainName, }, } ); @@ -121,6 +130,8 @@ export class LambdaStack extends cdk.Stack { environment: { TABLE_NAME: contactsTable.tableName, BUCKET_NAME: contactsBucket.bucketName, + CLOUDFRONT_DOMAIN: + cloudfrontImagesDistribution.distributionDomainName, }, } ); diff --git a/backendAWS/lib/storage_stack.ts b/backendAWS/lib/storage_stack.ts index 18db14c7461fd2a918817092b7ad071b3c54db4d..8dbc00352e1f4a3ff49e826f0b66009444035642 100644 --- a/backendAWS/lib/storage_stack.ts +++ b/backendAWS/lib/storage_stack.ts @@ -1,10 +1,13 @@ import * as cdk from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; export class StorageStack extends cdk.Stack { public readonly contactsTable: dynamodb.Table; public readonly contactsBucket: s3.Bucket; + public readonly cloudfrontImagesDistribution: cloudfront.Distribution; constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); @@ -27,6 +30,43 @@ export class StorageStack extends cdk.Stack { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); + // Create CloudFront for Images distribution + this.cloudfrontImagesDistribution = new cloudfront.Distribution( + this, + 'CloudfrontImagesDistribution', + { + defaultBehavior: { + origin: origins.S3BucketOrigin.withOriginAccessControl( + this.contactsBucket + ), + viewerProtocolPolicy: + cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, // Redirect HTTP to HTTPS + cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, // Adjust Cache Policy + }, + } + ); + + this.contactsBucket.addToResourcePolicy( + new cdk.aws_iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [`${this.contactsBucket.bucketArn}/*`], + principals: [ + new cdk.aws_iam.ServicePrincipal('cloudfront.amazonaws.com'), + ], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:aws:cloudfront::${ + cdk.Stack.of(this).account + }:distribution/${this.cloudfrontImagesDistribution.distributionId}`, + }, + }, + }) + ); + cdk.Tags.of(this.contactsBucket).add('Service', 'Storage'); + cdk.Tags.of(this.cloudfrontImagesDistribution).add( + 'Service', + 'CloudFrontImagesDistribution' + ); } } diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts index 9fa51bfff5c9e8fb786141b4779d0f95cf1e1f3f..d574f09c86a4afcf51ec6a425a919e96e812c59a 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/constants/index.ts @@ -6,4 +6,4 @@ export const poolData = { ClientId: '42nc47ndifaeqe861c16eaj30s', }; export const baseURL = - 'https://yiwft1rh0l.execute-api.eu-north-1.amazonaws.com/prod/'; + 'https://enl05xtmpb.execute-api.eu-north-1.amazonaws.com/prod/';