@@ -7,6 +7,7 @@ import * as apigateway from 'aws-cdk-lib/aws-apigateway';
77import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery' ;
88import * as iam from 'aws-cdk-lib/aws-iam' ;
99import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2' ;
10+ import * as s3 from 'aws-cdk-lib/aws-s3' ;
1011
1112import { Construct } from 'constructs' ;
1213import { AttributeType , BillingMode , Table } from 'aws-cdk-lib/aws-dynamodb' ;
@@ -515,6 +516,83 @@ export class BackendStack extends cdk.Stack {
515516 ] ,
516517 } ) ;
517518
519+ // Create S3 bucket for file uploads
520+ const uploadBucket = new s3 . Bucket ( this , `${ appName } UploadBucket-${ props . environment } ` , {
521+ bucketName : `${ appName . toLowerCase ( ) } -uploads-${ props . environment } -${ this . account } ` ,
522+ removalPolicy : RemovalPolicy . RETAIN ,
523+ versioned : true , // Enable versioning in production
524+ lifecycleRules : [
525+ {
526+ noncurrentVersionExpiration : cdk . Duration . days ( 7 ) ,
527+ // Move objects to infrequent access after 30 days
528+ transitions : [
529+ {
530+ storageClass : s3 . StorageClass . INFREQUENT_ACCESS ,
531+ transitionAfter : cdk . Duration . days ( 30 ) ,
532+ } ,
533+ ] ,
534+ } ,
535+ ] ,
536+ cors : [
537+ {
538+ allowedMethods : [
539+ s3 . HttpMethods . GET ,
540+ s3 . HttpMethods . POST ,
541+ s3 . HttpMethods . PUT ,
542+ s3 . HttpMethods . DELETE ,
543+ ] ,
544+ allowedOrigins : [ '*' ] , // In production, you should restrict this to your domain
545+ allowedHeaders : [ '*' ] ,
546+ maxAge : 3000 ,
547+ } ,
548+ ] ,
549+ blockPublicAccess : s3 . BlockPublicAccess . BLOCK_ALL , // Block all public access for security
550+ } ) ;
551+
552+ // Create a policy for authenticated users to upload files
553+ const uploadPolicy = new iam . PolicyStatement ( {
554+ effect : iam . Effect . ALLOW ,
555+ actions : [ 's3:PutObject' , 's3:GetObject' , 's3:DeleteObject' ] ,
556+ resources : [
557+ `${ uploadBucket . bucketArn } /*` ,
558+ ] ,
559+ conditions : {
560+ // Restrict uploads to PDF and JPG files
561+ 'StringLike' : {
562+ 's3:x-amz-content-type' : [
563+ 'application/pdf' ,
564+ 'image/jpeg' ,
565+ 'image/jpg'
566+ ]
567+ }
568+ }
569+ } ) ;
570+
571+ // Create an IAM role for authenticated users
572+ const authenticatedRole = new iam . Role ( this , `${ appName } AuthenticatedRole-${ props . environment } ` , {
573+ assumedBy : new iam . FederatedPrincipal (
574+ 'cognito-identity.amazonaws.com' ,
575+ {
576+ StringEquals : {
577+ 'cognito-identity.amazonaws.com:aud' : userPool . userPoolId ,
578+ } ,
579+ 'ForAnyValue:StringLike' : {
580+ 'cognito-identity.amazonaws.com:amr' : 'authenticated' ,
581+ } ,
582+ } ,
583+ 'sts:AssumeRoleWithWebIdentity'
584+ ) ,
585+ } ) ;
586+
587+ // Attach the upload policy to the authenticated role
588+ authenticatedRole . addToPolicy ( uploadPolicy ) ;
589+
590+ // Add environment variable to the container for the S3 bucket name
591+ container . addEnvironment ( 'S3_UPLOAD_BUCKET' , uploadBucket . bucketName ) ;
592+
593+ // Grant the task role access to the S3 bucket
594+ uploadBucket . grantReadWrite ( taskRole ) ;
595+
518596 // Outputs
519597 new cdk . CfnOutput ( this , 'ReportsTableName' , {
520598 value : reportsTable . tableName ,
@@ -535,5 +613,11 @@ export class BackendStack extends cdk.Stack {
535613 value : nlb . loadBalancerDnsName ,
536614 description : 'Network Load Balancer DNS Name' ,
537615 } ) ;
616+
617+ // Add S3 bucket name to outputs
618+ new cdk . CfnOutput ( this , 'UploadBucketName' , {
619+ value : uploadBucket . bucketName ,
620+ description : 'S3 Bucket for file uploads' ,
621+ } ) ;
538622 }
539623}
0 commit comments