Skip to content

Commit

Permalink
388: Add UI stack and hook up CORS origin (#819)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswilty committed Oct 22, 2024
1 parent 94688aa commit 7bd3b5b
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 5 deletions.
10 changes: 9 additions & 1 deletion cloud/bin/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
resourceDescription,
stackName,
ApiStack,
UiStack,
} from '../lib';

const app = new App();
Expand All @@ -22,11 +23,17 @@ const tags = {
const generateStackName = stackName(app);
const generateDescription = resourceDescription(app);

// Don't need this stack, yet... Or ever? Will ask Pete C.
const uiStack = new UiStack(app, generateStackName('ui'), {
tags,
description: generateDescription('UI stack'),
});

// Don't need this stack yet.
/*
const authStack = new AuthStack(app, generateStackName('auth'), {
tags,
description: generateDescription('Auth stack'),
webappUrl: uiStack.cloudfrontUrl,
});
*/

Expand All @@ -36,4 +43,5 @@ new ApiStack(app, generateStackName('api'), {
// userPool: authStack.userPool,
// userPoolClient: authStack.userPoolClient,
// userPoolDomain: authStack.userPoolDomain,
webappUrl: uiStack.cloudfrontUrl,
});
4 changes: 3 additions & 1 deletion cloud/lib/api-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ type ApiStackProps = StackProps & {
// userPool: UserPool;
// userPoolClient: UserPoolClient;
// userPoolDomain: UserPoolDomain;
webappUrl: string;
};

export class ApiStack extends Stack {
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props);
// TODO Enable cognito auth
//const { userPool, userPoolClient, userPoolDomain } = props;
const { /*userPool, userPoolClient, userPoolDomain,*/ webappUrl } = props;

const generateResourceName = resourceName(scope);

Expand Down Expand Up @@ -68,6 +69,7 @@ export class ApiStack extends Stack {
environment: {
NODE_ENV: 'production',
PORT: `${containerPort}`,
CORS_ALLOW_ORIGIN: webappUrl,
},
secrets: {
OPENAI_API_KEY: EnvSecret.fromSecretsManager(
Expand Down
11 changes: 8 additions & 3 deletions cloud/lib/auth-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import { Construct } from 'constructs';

import { resourceName } from './resourceNamingUtils';

type AuthStackProps = StackProps & {
webappUrl: string;
};

export class AuthStack extends Stack {
userPool: UserPool;
userPoolClient: UserPoolClient;
userPoolDomain: UserPoolDomain;

constructor(scope: Construct, id: string, props: StackProps) {
constructor(scope: Construct, id: string, props: AuthStackProps) {
super(scope, id, props);

const azureTenantId = process.env.AZURE_TENANT_ID;
Expand Down Expand Up @@ -83,6 +87,7 @@ export class AuthStack extends Stack {
},
});

const callbackUrls = [`${props.webappUrl}/`];
const userPoolClientName = generateResourceName('userpool-client');
this.userPoolClient = this.userPool.addClient(userPoolClientName, {
userPoolClientName,
Expand All @@ -98,8 +103,8 @@ export class AuthStack extends Stack {
authorizationCodeGrant: true,
},
scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE],
//callbackUrls,
//logoutUrls: callbackUrls,
callbackUrls,
logoutUrls: callbackUrls,
},
accessTokenValidity: Duration.minutes(60),
idTokenValidity: Duration.minutes(60),
Expand Down
1 change: 1 addition & 0 deletions cloud/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './resourceNamingUtils';
export { ApiStack } from './api-stack';
export { AuthStack } from './auth-stack';
export { UiStack } from './ui-stack';
102 changes: 102 additions & 0 deletions cloud/lib/ui-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
AllowedMethods,
CacheCookieBehavior,
CachePolicy,
Distribution,
OriginAccessIdentity,
SecurityPolicyProtocol,
ViewerProtocolPolicy,
} from 'aws-cdk-lib/aws-cloudfront';
import { S3Origin } from 'aws-cdk-lib/aws-cloudfront-origins';
import {
CfnOutput,
Duration,
RemovalPolicy,
Stack,
StackProps,
} from 'aws-cdk-lib/core';
import * as iam from 'aws-cdk-lib/aws-iam';
import {
BlockPublicAccess,
Bucket,
BucketEncryption,
} from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

import { resourceName } from './resourceNamingUtils';

export class UiStack extends Stack {
public readonly cloudfrontUrl: string;

constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);

const generateResourceName = resourceName(scope);

// allow s3 to be secured
const cloudfrontOAI = new OriginAccessIdentity(
this,
generateResourceName('cloudfront-OAI')
);

//HostBucket
const bucketName = generateResourceName('host-bucket');
const hostBucket = new Bucket(this, bucketName, {
bucketName,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
encryption: BucketEncryption.S3_MANAGED,
versioned: false,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
hostBucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ['s3:GetObject'],
resources: [hostBucket.arnForObjects('*')],
principals: [
new iam.CanonicalUserPrincipal(
cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId
),
],
})
);

//CloudFront
const cachePolicyName = generateResourceName('site-cache-policy');
const cloudFront = new Distribution(
this,
generateResourceName('site-distribution'),
{
defaultRootObject: 'index.html',
minimumProtocolVersion: SecurityPolicyProtocol.TLS_V1_2_2021,
errorResponses: [
{
httpStatus: 404,
responseHttpStatus: 200,
responsePagePath: '/index.html',
ttl: Duration.seconds(30),
},
],
defaultBehavior: {
origin: new S3Origin(hostBucket, {
originAccessIdentity: cloudfrontOAI,
}),
cachePolicy: new CachePolicy(this, cachePolicyName, {
cachePolicyName,
cookieBehavior: CacheCookieBehavior.allowList(
'prompt-injection.sid'
),
}),
compress: true,
allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
}
);
this.cloudfrontUrl = `https://${cloudFront.domainName}`;

new CfnOutput(this, 'WebURL', {
value: this.cloudfrontUrl,
});
}
}

0 comments on commit 7bd3b5b

Please sign in to comment.