Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#81 #85

Merged
merged 9 commits into from
May 1, 2024
204 changes: 25 additions & 179 deletions src/backend/lib/backend-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,192 +3,38 @@ import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as cognito from "aws-cdk-lib/aws-cognito";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import { ApiStack } from './constructs/api';
import { AuthStack } from './constructs/auth';
import { WebHostingStack } from './constructs/web';

interface BackendStackProps extends cdk.StackProps { }

export class BackendStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: BackendStackProps) {
super(scope, id, props);
// ウェブホスティングスタックのインスタンス化
new WebHostingStack(this, 'WebHostingStack');

// 認証機能スタックのインスタンス化
new AuthStack(this, 'AuthStack');

// 認証機能スタックのインスタンス化
new ApiStack(this, 'ApiStack');

// new dynamodb.Table(this, `DiaryContentsTable`, {
// partitionKey: {
// name: "user_id",
// type: dynamodb.AttributeType.STRING,
// },
// sortKey: {
// name: "date",
// type: dynamodb.AttributeType.STRING,
// },
// removalPolicy: cdk.RemovalPolicy.DESTROY,
// pointInTimeRecovery: true,
// });

const logBucket = new s3.Bucket(this, `LogBucket`, {
removalPolicy: cdk.RemovalPolicy.DESTROY,
enforceSSL: true,
serverAccessLogsPrefix: "log/",
});

new dynamodb.Table(this, `DiaryContentsTable`, {
partitionKey: {
name: "user_id",
type: dynamodb.AttributeType.STRING,
},
sortKey: {
name: "date",
type: dynamodb.AttributeType.STRING,
},
removalPolicy: cdk.RemovalPolicy.DESTROY,
pointInTimeRecovery: true,
});

const userPool = new cognito.UserPool(this, `DiaryUserPool`, {
userPoolName: `diary-user-pool`,
signInAliases: {
email: true,
},
selfSignUpEnabled: true,
autoVerify: {
email: true,
},
userVerification: {
emailSubject: 'メールアドレスを認証してください。',
emailBody: 'ご登録ありがとうございます。 あなたの認証コードは {####} です。',
emailStyle: cognito.VerificationEmailStyle.CODE,
},
standardAttributes: {
familyName: {
mutable: false,
required: true,
},
givenName: {
mutable: false,
required: true,
},
address: {
mutable: true,
required: false,
},
gender: {
mutable: true,
required: true,
},
email: {
mutable: false,
required: true,
},
},
passwordPolicy: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
},
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
mfa: cognito.Mfa.REQUIRED,
mfaSecondFactor: {
sms: true,
otp: true,
},
removalPolicy: cdk.RemovalPolicy.DESTROY,
});

const userPoolClient = new cognito.UserPoolClient(this, "DiaryUserPoolClient", {
userPool,
userPoolClientName: "diary-userpool-client",
authFlows: {
adminUserPassword: true,
custom: true,
userSrp: true,
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.COGNITO,
],
});

// Cognito Identity Pool
const identityPool = new cognito.CfnIdentityPool(this, "IdentityPool", {
allowUnauthenticatedIdentities: false, // Don't allow unathenticated users
cognitoIdentityProviders: [
{
clientId: userPoolClient.userPoolClientId,
providerName: userPool.userPoolProviderName,
},
],
});

userPool.addDomain('UserPoolDomain', {
cognitoDomain: { domainPrefix: 'dairy-851725642854' },
});

// Hosting S3 & CloudFront
const websiteBucket = new s3.Bucket(this, 'diary-hosting-bucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
enforceSSL: true,
serverAccessLogsBucket: logBucket,
serverAccessLogsPrefix: "DiaryHostingBucketLog/",
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
websiteIndexDocument: 'index.html',
cors: [
{
allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.HEAD],
allowedOrigins: ["*"],
allowedHeaders: ["*"],
},
],
});

const cfnOriginAccessControl = new cdk.aws_cloudfront.CfnOriginAccessControl(
this,
"OriginAccessControl",
{
originAccessControlConfig: {
name: "OriginAccessControlForAppBucket",
originAccessControlOriginType: "s3",
signingBehavior: "always",
signingProtocol: "sigv4",
description: "S3 Access Control",
},
}
);


const distribution =new cdk.aws_cloudfront.Distribution(this, 'distro', {
defaultBehavior: {
origin: new cdk.aws_cloudfront_origins.S3Origin(websiteBucket),
viewerProtocolPolicy: cdk.aws_cloudfront.ViewerProtocolPolicy.HTTPS_ONLY
},
defaultRootObject: "index.html",
// enableLogging: true, // Optional, this is implied if logBucket is specified
// logBucket: logBucket,
// logFilePrefix: 'distribution/',
// logIncludesCookies: true,
geoRestriction: cdk.aws_cloudfront.GeoRestriction.allowlist('US', 'JP'),
});

const websiteBucketPolicyStatement = new cdk.aws_iam.PolicyStatement({
actions: ["s3:GetObject"],
effect: cdk.aws_iam.Effect.ALLOW,
principals: [new cdk.aws_iam.ServicePrincipal("cloudfront.amazonaws.com")],
resources: [`${websiteBucket.bucketArn}/*`],
conditions: {
StringEquals: {
"AWS:SourceArn": `arn:aws:cloudfront::${this.account}:distribution/${distribution.distributionId}`,
},
},
});

websiteBucket.addToResourcePolicy(websiteBucketPolicyStatement);

const cfnDistribution = distribution.node.defaultChild as cdk.aws_cloudfront.CfnDistribution
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', cfnOriginAccessControl.getAtt('Id'))
cfnDistribution.addPropertyOverride('DistributionConfig.Origins.0.DomainName', websiteBucket.bucketRegionalDomainName)
cfnDistribution.addOverride('Properties.DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', "")
cfnDistribution.addPropertyDeletionOverride('DistributionConfig.Origins.0.CustomOriginConfig')

// new cdk.aws_s3_deployment.BucketDeployment(this, 'WebsiteDeploy', {
// sources: [
// cdk.aws_s3_deployment.Source.data(
// '/index.html',
// '<html><body><h1>Hello World</h1></body></html>'
// ),
// // cdk.aws_s3_deployment.Source.data(
// // '/error.html',
// // '<html><body><h1>Error!!!!!!!!!!!!!</h1></body></html>'
// // ),
// ],
// destinationBucket: websiteBucket,
// distribution: distribution,
// distributionPaths: ['/*'],
// accessControl: s3.BucketAccessControl.PUBLIC_READ_WRITE
// });

}
}
36 changes: 36 additions & 0 deletions src/backend/lib/constructs/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";

export class ApiStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// const backendLambda = new lambda.Function(this, 'BackendLambda', {
// runtime: lambda.Runtime.NODEJS_14_X,
// code: lambda.Code.fromAsset('path/to/lambda/code'),
// handler: 'index.handler',
// });

// const api = new apigateway.RestApi(this, 'ApiGateway', {
// restApiName: 'ServiceAPI',
// });

// const lambdaIntegration = new apigateway.LambdaIntegration(backendLambda);
// api.root.addMethod('GET', lambdaIntegration);
new dynamodb.Table(this, `DiaryContentsTable`, {
partitionKey: {
name: "user_id",
type: dynamodb.AttributeType.STRING,
},
sortKey: {
name: "date",
type: dynamodb.AttributeType.STRING,
},
removalPolicy: cdk.RemovalPolicy.DESTROY,
pointInTimeRecovery: true,
});
}
}
75 changes: 75 additions & 0 deletions src/backend/lib/constructs/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag';

export class AuthStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const userPool = new cognito.UserPool(this, 'UserPool', {
userPoolName: 'app-user-pool',
selfSignUpEnabled: true,
autoVerify: { email: true },
userVerification: {
emailSubject: 'Verify your email for our app!',
emailBody: 'Thanks for signing up! Your verification code is {####}',
emailStyle: cognito.VerificationEmailStyle.CODE,
},
signInAliases: { email: true },
passwordPolicy: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
},
mfa: cognito.Mfa.OPTIONAL,
mfaSecondFactor: {
sms: true,
otp: true,
},
});

const userPoolClient = new cognito.UserPoolClient(this, "DiaryUserPoolClient", {
userPool,
userPoolClientName: "diary-userpool-client",
authFlows: {
adminUserPassword: true,
custom: true,
userSrp: true,
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.COGNITO,
],
});

const identityPool = new cognito.CfnIdentityPool(this, 'IdentityPool', {
allowUnauthenticatedIdentities: false,
cognitoIdentityProviders: [{
clientId: userPoolClient.userPoolClientId,
providerName: userPool.userPoolProviderName,
}],
});

userPool.addDomain('UserPoolDomain', {
cognitoDomain: { domainPrefix: 'dairy-851725642854' },
});

NagSuppressions.addStackSuppressions(this, [
{
id: 'AwsSolutions-COG2',
reason: '一般ユーザにMFAはいらないと判断した.',
},
{
id: 'AwsSolutions-COG3',
reason: 'このプロジェクトではAdvancedSecurityModeをENFORCEDに設定する必要はないと判断した。',
},
{
id: 'AwsSolutions-IAM5',
reason: '暫定的にオフにしているが、本番環境では適切なIAMポリシーを設定すること。',
},

])
}
}
Loading
Loading