@@ -5,12 +5,17 @@ import * as logs from 'aws-cdk-lib/aws-logs';
55import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2' ;
66import * as cognito from 'aws-cdk-lib/aws-cognito' ;
77import * as elbv2_actions from 'aws-cdk-lib/aws-elasticloadbalancingv2-actions' ;
8+ import * as acm from 'aws-cdk-lib/aws-certificatemanager' ;
9+ import * as route53 from 'aws-cdk-lib/aws-route53' ;
10+ import * as targets from 'aws-cdk-lib/aws-route53-targets' ;
811import { Construct } from 'constructs' ;
912import { AttributeType , BillingMode , Table } from 'aws-cdk-lib/aws-dynamodb' ;
1013import { RemovalPolicy } from 'aws-cdk-lib' ;
1114
1215interface BackendStackProps extends cdk . StackProps {
1316 environment : string ;
17+ domainName ?: string ; // Optional domain name for certificate
18+ hostedZoneId ?: string ; // Optional hosted zone ID for domain
1419}
1520
1621export class BackendStack extends cdk . Stack {
@@ -21,23 +26,14 @@ export class BackendStack extends cdk.Stack {
2126 const appName = 'AIMedicalReport' ;
2227
2328 // Look up existing VPC or create a new one
24- let vpc : ec2 . IVpc ;
25- try {
26- vpc = ec2 . Vpc . fromLookup ( this , `${ appName } VPC` , {
27- isDefault : false ,
28- vpcName : `${ appName } VPC` ,
29- } ) ;
30- } catch {
31- vpc = new ec2 . Vpc ( this , `${ appName } VPC` , {
32- vpcName : `${ appName } VPC` ,
33- maxAzs : isProd ? 3 : 2 ,
34- } ) ;
35- }
29+ const vpc : ec2 . IVpc = new ec2 . Vpc ( this , `${ appName } VPC` , {
30+ vpcName : `${ appName } VPC-${ props . environment } ` ,
31+ maxAzs : 2 ,
32+ } ) ;
3633
37- // Look up existing ECS Cluster or create a new one
3834 const cluster = new ecs . Cluster ( this , `${ appName } Cluster` , {
3935 vpc,
40- clusterName : `${ appName } Cluster` ,
36+ clusterName : `${ appName } Cluster- ${ props . environment } ` ,
4137 containerInsights : true ,
4238 } ) ;
4339
@@ -49,13 +45,17 @@ export class BackendStack extends cdk.Stack {
4945 } ) ;
5046
5147 // Task Definition
52- const taskDefinition = new ecs . FargateTaskDefinition ( this , `${ appName } TaskDef` , {
53- memoryLimitMiB : isProd ? 1024 : 512 ,
54- cpu : isProd ? 512 : 256 ,
55- } ) ;
48+ const taskDefinition = new ecs . FargateTaskDefinition (
49+ this ,
50+ `${ appName } TaskDef-${ props . environment } ` ,
51+ {
52+ memoryLimitMiB : isProd ? 1024 : 512 ,
53+ cpu : isProd ? 512 : 256 ,
54+ } ,
55+ ) ;
5656
5757 // Container
58- const container = taskDefinition . addContainer ( `${ appName } Container` , {
58+ const container = taskDefinition . addContainer ( `${ appName } Container- ${ props . environment } ` , {
5959 image : ecs . ContainerImage . fromAsset ( '../backend/' , {
6060 file : 'Dockerfile.prod' ,
6161 buildArgs : {
@@ -83,48 +83,87 @@ export class BackendStack extends cdk.Stack {
8383 const userPool = cognito . UserPool . fromUserPoolId (
8484 this ,
8585 `${ appName } UserPool` ,
86- 'ai-cognito-medical-reports-user-pool ' ,
86+ 'us-east-1_PszlvSmWc ' ,
8787 ) ;
8888
8989 // Create a Cognito domain if it doesn't exist
90- const userPoolDomain = new cognito . UserPoolDomain ( this , `${ appName } UserPoolDomain` , {
91- userPool,
92- cognitoDomain : {
93- domainPrefix : `${ appName . toLowerCase ( ) } -auth` ,
94- } ,
95- } ) ;
96-
97- // Create a Cognito User Pool Client for the ALB
98- const userPoolClient = new cognito . UserPoolClient ( this , `${ appName } UserPoolClient` , {
99- userPool,
100- generateSecret : true ,
101- authFlows : {
102- userPassword : true ,
103- userSrp : true ,
104- } ,
105- oAuth : {
106- flows : {
107- authorizationCodeGrant : true ,
90+ const userPoolDomain = new cognito . UserPoolDomain (
91+ this ,
92+ `${ appName } UserPoolDomain-${ props . environment } ` ,
93+ {
94+ userPool,
95+ cognitoDomain : {
96+ domainPrefix : `${ appName . toLowerCase ( ) } -auth-${ props . environment } -modus` ,
10897 } ,
109- callbackUrls : [ `http://${ appName . toLowerCase ( ) } .example.com/oauth2/idpresponse` ] , // Update with your actual domain
11098 } ,
111- } ) ;
99+ ) ;
112100
113101 // Create ALB
114- const alb = new elbv2 . ApplicationLoadBalancer ( this , `${ appName } ALB` , {
102+ const alb = new elbv2 . ApplicationLoadBalancer ( this , `${ appName } ALB- ${ props . environment } ` , {
115103 vpc,
116104 internetFacing : true ,
117105 loadBalancerName : `${ appName } -${ props . environment } ` ,
118106 } ) ;
119107
108+ // HTTPS IMPLEMENTATION - CERTIFICATE
109+ let certificate ;
110+ if ( props . domainName && props . hostedZoneId ) {
111+ // If domain name is provided, create or import certificate
112+ const hostedZone = route53 . HostedZone . fromHostedZoneAttributes ( this , 'HostedZone' , {
113+ hostedZoneId : props . hostedZoneId ,
114+ zoneName : props . domainName ,
115+ } ) ;
116+
117+ certificate = new acm . Certificate ( this , `${ appName } Certificate-${ props . environment } ` , {
118+ domainName : props . domainName ,
119+ validation : acm . CertificateValidation . fromDns ( hostedZone ) ,
120+ } ) ;
121+
122+ // Create DNS record for ALB
123+ new route53 . ARecord ( this , `${ appName } AliasRecord-${ props . environment } ` , {
124+ zone : hostedZone ,
125+ recordName : props . domainName ,
126+ target : route53 . RecordTarget . fromAlias ( new targets . LoadBalancerTarget ( alb ) ) ,
127+ } ) ;
128+ } else {
129+ // For development or when no domain is provided, generate a self-signed certificate
130+ certificate = new acm . Certificate ( this , `${ appName } SelfSignedCert-${ props . environment } ` , {
131+ domainName : alb . loadBalancerDnsName ,
132+ validation : acm . CertificateValidation . fromDns ( ) ,
133+ } ) ;
134+ }
135+
136+ // Create a Cognito User Pool Client for the ALB
137+ const userPoolClient = new cognito . UserPoolClient (
138+ this ,
139+ `${ appName } UserPoolClient-${ props . environment } ` ,
140+ {
141+ userPool,
142+ generateSecret : true ,
143+ authFlows : {
144+ userPassword : true ,
145+ userSrp : true ,
146+ } ,
147+ oAuth : {
148+ flows : {
149+ authorizationCodeGrant : true ,
150+ } ,
151+ // Update callback URLs to use HTTPS
152+ callbackUrls : props . domainName
153+ ? [ `https://${ props . domainName } /oauth2/idpresponse` ]
154+ : [ `https://${ alb . loadBalancerDnsName } /oauth2/idpresponse` ] ,
155+ } ,
156+ } ,
157+ ) ;
158+
120159 // Create Fargate Service
121- const fargateService = new ecs . FargateService ( this , `${ appName } Service` , {
160+ const fargateService = new ecs . FargateService ( this , `${ appName } Service- ${ props . environment } ` , {
122161 cluster,
123162 taskDefinition,
124163 desiredCount : isProd ? 2 : 1 ,
125164 assignPublicIp : false ,
126165 securityGroups : [
127- new ec2 . SecurityGroup ( this , `${ appName } ServiceSG` , {
166+ new ec2 . SecurityGroup ( this , `${ appName } ServiceSG- ${ props . environment } ` , {
128167 vpc,
129168 allowAllOutbound : true ,
130169 } ) ,
@@ -146,23 +185,31 @@ export class BackendStack extends cdk.Stack {
146185 }
147186
148187 // Create ALB Target Group
149- const targetGroup = new elbv2 . ApplicationTargetGroup ( this , `${ appName } TargetGroup` , {
150- vpc,
151- port : 3000 ,
152- protocol : elbv2 . ApplicationProtocol . HTTP ,
153- targetType : elbv2 . TargetType . IP ,
154- healthCheck : {
155- path : '/health' ,
156- interval : cdk . Duration . seconds ( 30 ) ,
157- timeout : cdk . Duration . seconds ( 5 ) ,
188+ const targetGroup = new elbv2 . ApplicationTargetGroup (
189+ this ,
190+ `${ appName } TargetGroup-${ props . environment } ` ,
191+ {
192+ vpc,
193+ port : 3000 ,
194+ protocol : elbv2 . ApplicationProtocol . HTTP ,
195+ targetType : elbv2 . TargetType . IP ,
196+ healthCheck : {
197+ path : '/health' ,
198+ interval : cdk . Duration . seconds ( 30 ) ,
199+ timeout : cdk . Duration . seconds ( 5 ) ,
200+ } ,
201+ targets : [ fargateService ] ,
158202 } ,
159- targets : [ fargateService ] ,
160- } ) ;
203+ ) ;
161204
162- // Create HTTP Listener
163- alb . addListener ( `${ appName } HttpListener` , {
164- port : 80 ,
165- protocol : elbv2 . ApplicationProtocol . HTTP ,
205+ // HTTPS IMPLEMENTATION - LISTENERS
206+
207+ // Create HTTPS Listener
208+ alb . addListener ( `${ appName } HttpsListener-${ props . environment } ` , {
209+ port : 443 ,
210+ protocol : elbv2 . ApplicationProtocol . HTTPS ,
211+ certificates : [ certificate ] ,
212+ sslPolicy : elbv2 . SslPolicy . RECOMMENDED ,
166213 defaultAction : new elbv2_actions . AuthenticateCognitoAction ( {
167214 userPool,
168215 userPoolClient,
@@ -172,8 +219,19 @@ export class BackendStack extends cdk.Stack {
172219 } ) ,
173220 } ) ;
174221
222+ // Create HTTP Listener that redirects to HTTPS
223+ alb . addListener ( `${ appName } HttpListener-${ props . environment } ` , {
224+ port : 80 ,
225+ protocol : elbv2 . ApplicationProtocol . HTTP ,
226+ defaultAction : elbv2 . ListenerAction . redirect ( {
227+ protocol : elbv2 . ApplicationProtocol . HTTPS ,
228+ port : '443' ,
229+ permanent : true ,
230+ } ) ,
231+ } ) ;
232+
175233 // Create DynamoDB table for reports
176- const reportsTable = new Table ( this , `${ appName } ReportsTable` , {
234+ const reportsTable = new Table ( this , `${ appName } ReportsTable- ${ props . environment } ` , {
177235 tableName : `${ appName } ReportsTable${ props . environment } ` ,
178236 partitionKey : {
179237 name : 'userId' ,
0 commit comments