From a771773800b811797de3e48553a33e26380f82f6 Mon Sep 17 00:00:00 2001 From: Kazuki Matsuda Date: Thu, 15 Jun 2023 21:31:36 +0900 Subject: [PATCH 1/5] chore: refactor jwt --- .../cr-json-web-token.ts} | 2 + .../index.ts} | 26 +- test/__snapshots__/main.test.ts.snap | 228 +++++++++--------- 3 files changed, 136 insertions(+), 120 deletions(-) rename src/{functions/gen-json-web-token.ts => json-web-token/cr-json-web-token.ts} (97%) rename src/{json-web-token.ts => json-web-token/index.ts} (71%) diff --git a/src/functions/gen-json-web-token.ts b/src/json-web-token/cr-json-web-token.ts similarity index 97% rename from src/functions/gen-json-web-token.ts rename to src/json-web-token/cr-json-web-token.ts index 5383e13..022d8b3 100644 --- a/src/functions/gen-json-web-token.ts +++ b/src/json-web-token/cr-json-web-token.ts @@ -9,6 +9,7 @@ interface Payload extends Object { role: string; } +/** Get the JWT secret */ const getJwtSecret = async (secretId: string) => { const client = new SecretsManagerClient({ region }); const cmd = new GetSecretValueCommand({ SecretId: secretId }); @@ -18,6 +19,7 @@ const getJwtSecret = async (secretId: string) => { return SecretString!; }; +/** Generate a json web token */ const generateToken = async (payload: object, secretId: string, issuer?: string, expiresIn?: string) => { const jwtSecret = await getJwtSecret(secretId); const token = jwt.sign(payload, jwtSecret, { issuer, expiresIn }); diff --git a/src/json-web-token.ts b/src/json-web-token/index.ts similarity index 71% rename from src/json-web-token.ts rename to src/json-web-token/index.ts index f2ddb61..d0400a6 100644 --- a/src/json-web-token.ts +++ b/src/json-web-token/index.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import * as cdk from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; @@ -7,8 +8,10 @@ import * as cr from 'aws-cdk-lib/custom-resources'; import { Construct } from 'constructs'; export class JwtSecret extends Secret { + /** Custom resource provider to generate a json web token */ genTokenProvider: cr.Provider; + /** Creates a new jwt secret in AWS SecretsManager. */ constructor(scope: Construct, id: string, props?: SecretProps) { super(scope, id, { description: `${cdk.Aws.STACK_NAME} - Json Web Token Secret`, @@ -19,17 +22,23 @@ export class JwtSecret extends Secret { ...props, }); - const genTokenFunction = new NodejsFunction(this, 'GenerateTokenFunction', { + /** Custom resource handler to generate a json web token */ + const jwtFunction = new NodejsFunction(this, 'JsonWebTokenFunction', { description: `${cdk.Aws.STACK_NAME} - Generate token via jwt secret`, - entry: './src/functions/gen-json-web-token.ts', + entry: path.resolve(__dirname, 'cr-json-web-token.ts'), runtime: lambda.Runtime.NODEJS_18_X, - environment: { JWT_SECRET_ARN: this.secretArn }, + environment: { + JWT_SECRET_ARN: this.secretArn, + }, }); - this.grantRead(genTokenFunction); - this.genTokenProvider = new cr.Provider(this, 'GenerateTokenProvider', { onEventHandler: genTokenFunction }); + /** Allow the function to read the jwt secret */ + this.grantRead(jwtFunction); + + this.genTokenProvider = new cr.Provider(this, 'GenerateTokenProvider', { onEventHandler: jwtFunction }); } + /** Generate a new token in ParameterStore. */ genApiKey(id: string, props: ApiKeyProps) { const apiKey = new ApiKey(this, id, props); return apiKey; @@ -43,9 +52,12 @@ interface ApiKeyProps { } class ApiKey extends Construct { + /** Token value */ value: string; + /** ParameterStore of the token */ ssmParameter: ssm.StringParameter; + /** Json Web Token */ constructor(scope: JwtSecret, id: string, props: ApiKeyProps) { super(scope, id); @@ -54,7 +66,8 @@ class ApiKey extends Construct { const issuer = props.issuer; const expiresIn = props.expiresIn; - const token = new cdk.CustomResource(this, 'Token', { + /** String value of Json Web Token */ + const token = new cdk.CustomResource(this, 'Resource', { serviceToken: jwtSecret.genTokenProvider.serviceToken, resourceType: 'Custom::JsonWebToken', properties: { @@ -63,6 +76,7 @@ class ApiKey extends Construct { ExpiresIn: expiresIn, }, }); + this.value = token.getAttString('Value'); this.ssmParameter = new ssm.StringParameter(this, 'Parameter', { diff --git a/test/__snapshots__/main.test.ts.snap b/test/__snapshots__/main.test.ts.snap index d938f89..fc90441 100644 --- a/test/__snapshots__/main.test.ts.snap +++ b/test/__snapshots__/main.test.ts.snap @@ -724,7 +724,7 @@ Object { }, "Value": Object { "Fn::GetAtt": Array [ - "JwtSecretAnonKeyTokenE92DEEB0", + "JwtSecretAnonKey63F37A1E", "Value", ], }, @@ -5327,6 +5327,24 @@ Object { }, "Type": "AWS::IAM::Role", }, + "JwtSecretAnonKey63F37A1E": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "ExpiresIn": "10y", + "Issuer": "supabase", + "Payload": Object { + "role": "anon", + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA", + "Arn", + ], + }, + }, + "Type": "Custom::JsonWebToken", + "UpdateReplacePolicy": "Delete", + }, "JwtSecretAnonKeyParameter532DCC06": Object { "Properties": Object { "Description": Object { @@ -5355,31 +5373,13 @@ Object { "Type": "String", "Value": Object { "Fn::GetAtt": Array [ - "JwtSecretAnonKeyTokenE92DEEB0", + "JwtSecretAnonKey63F37A1E", "Value", ], }, }, "Type": "AWS::SSM::Parameter", }, - "JwtSecretAnonKeyTokenE92DEEB0": Object { - "DeletionPolicy": "Delete", - "Properties": Object { - "ExpiresIn": "10y", - "Issuer": "supabase", - "Payload": Object { - "role": "anon", - }, - "ServiceToken": Object { - "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA", - "Arn", - ], - }, - }, - "Type": "Custom::JsonWebToken", - "UpdateReplacePolicy": "Delete", - }, "JwtSecretB8834B39": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -5402,49 +5402,42 @@ Object { "Type": "AWS::SecretsManager::Secret", "UpdateReplacePolicy": "Delete", }, - "JwtSecretGenerateTokenFunctionB37EE16F": Object { + "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA": Object { "DependsOn": Array [ - "JwtSecretGenerateTokenFunctionServiceRoleDefaultPolicy91E418F9", - "JwtSecretGenerateTokenFunctionServiceRole220DD241", + "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4", + "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", ], "Properties": Object { "Code": Object { "S3Bucket": Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "49a4da4521fed06d10ad6c9d54a5e5db2b59a697400681dbb6a326d6cbcef94f.zip", - }, - "Description": Object { - "Fn::Join": Array [ - "", - Array [ - Object { - "Ref": "AWS::StackName", - }, - " - Generate token via jwt secret", - ], - ], + "S3Key": "8e3d635893ea17fa3158623489cd42c680fad925b38de1ef51cb10d84f6e245e.zip", }, + "Description": "AWS CDK resource provider framework - onEvent (Supabase/JwtSecret/GenerateTokenProvider)", "Environment": Object { "Variables": Object { - "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", - "JWT_SECRET_ARN": Object { - "Ref": "JwtSecretB8834B39", + "USER_ON_EVENT_FUNCTION_ARN": Object { + "Fn::GetAtt": Array [ + "JwtSecretJsonWebTokenFunctionF8BA9D2A", + "Arn", + ], }, }, }, - "Handler": "index.handler", + "Handler": "framework.onEvent", "Role": Object { "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenFunctionServiceRole220DD241", + "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", "Arn", ], }, - "Runtime": "nodejs18.x", + "Runtime": "nodejs14.x", + "Timeout": 900, }, "Type": "AWS::Lambda::Function", }, - "JwtSecretGenerateTokenFunctionServiceRole220DD241": Object { + "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A": Object { "Properties": Object { "AssumeRolePolicyDocument": Object { "Statement": Array [ @@ -5475,68 +5468,91 @@ Object { }, "Type": "AWS::IAM::Role", }, - "JwtSecretGenerateTokenFunctionServiceRoleDefaultPolicy91E418F9": Object { + "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4": Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ Object { - "Action": Array [ - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - ], + "Action": "lambda:InvokeFunction", "Effect": "Allow", - "Resource": Object { - "Ref": "JwtSecretB8834B39", - }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "JwtSecretJsonWebTokenFunctionF8BA9D2A", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "JwtSecretJsonWebTokenFunctionF8BA9D2A", + "Arn", + ], + }, + ":*", + ], + ], + }, + ], }, ], "Version": "2012-10-17", }, - "PolicyName": "JwtSecretGenerateTokenFunctionServiceRoleDefaultPolicy91E418F9", + "PolicyName": "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4", "Roles": Array [ Object { - "Ref": "JwtSecretGenerateTokenFunctionServiceRole220DD241", + "Ref": "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", }, ], }, "Type": "AWS::IAM::Policy", }, - "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA": Object { + "JwtSecretJsonWebTokenFunctionF8BA9D2A": Object { "DependsOn": Array [ - "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4", - "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", + "JwtSecretJsonWebTokenFunctionServiceRoleDefaultPolicyFEC3E7BA", + "JwtSecretJsonWebTokenFunctionServiceRole17CF8128", ], "Properties": Object { "Code": Object { "S3Bucket": Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "8e3d635893ea17fa3158623489cd42c680fad925b38de1ef51cb10d84f6e245e.zip", + "S3Key": "baf945a52f8e02b1131009f27d8b23a50b9eacc020ff363093145c8e6f5dbc01.zip", + }, + "Description": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Ref": "AWS::StackName", + }, + " - Generate token via jwt secret", + ], + ], }, - "Description": "AWS CDK resource provider framework - onEvent (Supabase/JwtSecret/GenerateTokenProvider)", "Environment": Object { "Variables": Object { - "USER_ON_EVENT_FUNCTION_ARN": Object { - "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenFunctionB37EE16F", - "Arn", - ], + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "JWT_SECRET_ARN": Object { + "Ref": "JwtSecretB8834B39", }, }, }, - "Handler": "framework.onEvent", + "Handler": "index.handler", "Role": Object { "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", + "JwtSecretJsonWebTokenFunctionServiceRole17CF8128", "Arn", ], }, - "Runtime": "nodejs14.x", - "Timeout": 900, + "Runtime": "nodejs18.x", }, "Type": "AWS::Lambda::Function", }, - "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A": Object { + "JwtSecretJsonWebTokenFunctionServiceRole17CF8128": Object { "Properties": Object { "AssumeRolePolicyDocument": Object { "Statement": Array [ @@ -5567,48 +5583,50 @@ Object { }, "Type": "AWS::IAM::Role", }, - "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4": Object { + "JwtSecretJsonWebTokenFunctionServiceRoleDefaultPolicyFEC3E7BA": Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ Object { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Resource": Array [ - Object { - "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenFunctionB37EE16F", - "Arn", - ], - }, - Object { - "Fn::Join": Array [ - "", - Array [ - Object { - "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenFunctionB37EE16F", - "Arn", - ], - }, - ":*", - ], - ], - }, + "Action": Array [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", ], + "Effect": "Allow", + "Resource": Object { + "Ref": "JwtSecretB8834B39", + }, }, ], "Version": "2012-10-17", }, - "PolicyName": "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleDefaultPolicy14F466A4", + "PolicyName": "JwtSecretJsonWebTokenFunctionServiceRoleDefaultPolicyFEC3E7BA", "Roles": Array [ Object { - "Ref": "JwtSecretGenerateTokenProviderframeworkonEventServiceRoleD6B5B68A", + "Ref": "JwtSecretJsonWebTokenFunctionServiceRole17CF8128", }, ], }, "Type": "AWS::IAM::Policy", }, + "JwtSecretServiceRoleKeyF0F6C193": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "ExpiresIn": "10y", + "Issuer": "supabase", + "Payload": Object { + "role": "service_role", + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA", + "Arn", + ], + }, + }, + "Type": "Custom::JsonWebToken", + "UpdateReplacePolicy": "Delete", + }, "JwtSecretServiceRoleKeyParameterB65536EB": Object { "Properties": Object { "Description": Object { @@ -5637,31 +5655,13 @@ Object { "Type": "String", "Value": Object { "Fn::GetAtt": Array [ - "JwtSecretServiceRoleKeyToken50CB4154", + "JwtSecretServiceRoleKeyF0F6C193", "Value", ], }, }, "Type": "AWS::SSM::Parameter", }, - "JwtSecretServiceRoleKeyToken50CB4154": Object { - "DeletionPolicy": "Delete", - "Properties": Object { - "ExpiresIn": "10y", - "Issuer": "supabase", - "Payload": Object { - "role": "service_role", - }, - "ServiceToken": Object { - "Fn::GetAtt": Array [ - "JwtSecretGenerateTokenProviderframeworkonEvent34DD1AAA", - "Arn", - ], - }, - }, - "Type": "Custom::JsonWebToken", - "UpdateReplacePolicy": "Delete", - }, "KongLogs4BD50491": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -9166,7 +9166,7 @@ applications: "Name": "SUPABASE_ANON_KEY", "Value": Object { "Fn::GetAtt": Array [ - "JwtSecretAnonKeyTokenE92DEEB0", + "JwtSecretAnonKey63F37A1E", "Value", ], }, @@ -9175,7 +9175,7 @@ applications: "Name": "SUPABASE_SERVICE_KEY", "Value": Object { "Fn::GetAtt": Array [ - "JwtSecretServiceRoleKeyToken50CB4154", + "JwtSecretServiceRoleKeyF0F6C193", "Value", ], }, From c793405c8a702733046472712a7b8de7c8890014 Mon Sep 17 00:00:00 2001 From: Kazuki Matsuda Date: Fri, 16 Jun 2023 10:28:02 +0900 Subject: [PATCH 2/5] chore: refactor ses smtp password --- src/amazon-ses-smtp.ts | 113 ------- .../cr-smtp-password.ts} | 0 src/amazon-ses-smtp/index.ts | 93 ++++++ src/supabase-stack.ts | 59 +++- test/__snapshots__/main.test.ts.snap | 278 +++++++++--------- 5 files changed, 281 insertions(+), 262 deletions(-) delete mode 100644 src/amazon-ses-smtp.ts rename src/{functions/gen-smtp-password.ts => amazon-ses-smtp/cr-smtp-password.ts} (100%) create mode 100644 src/amazon-ses-smtp/index.ts diff --git a/src/amazon-ses-smtp.ts b/src/amazon-ses-smtp.ts deleted file mode 100644 index fe2ac1a..0000000 --- a/src/amazon-ses-smtp.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; -import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; -import * as cr from 'aws-cdk-lib/custom-resources'; -import { Construct } from 'constructs'; -import { WorkMailStack } from './aws-workmail'; - -export class Smtp extends Construct { - cfnParameters: { - region: cdk.CfnParameter; - email: cdk.CfnParameter; - enableTestDomain: cdk.CfnParameter; - }; - secret: Secret; - host: string; - port: number; - email: string; - - constructor(scope: Construct, id: string) { - super(scope, id); - - this.cfnParameters = { - region: new cdk.CfnParameter(this, 'Region', { - description: 'Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.', - type: 'String', - default: 'us-west-2', - allowedValues: ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1'], - }), - email: new cdk.CfnParameter(this, 'Email', { - description: 'This is the email address the emails are sent from. If Amazon WorkMail is enabled, it set "noreply@supabase-.awsapps.com"', - type: 'String', - default: 'noreply@example.com', - allowedPattern: '^[\\x20-\\x45]?[\\w-\\+]+(\\.[\\w]+)*@[\\w-]+(\\.[\\w]+)*(\\.[a-z]{2,})$', - constraintDescription: 'must be a valid email address', - }), - enableTestDomain: new cdk.CfnParameter(this, 'EnableTestDomain', { - description: 'Enable test e-mail domain "xxx.awsapps.com" with Amazon WorkMail.', - type: 'String', - default: 'false', - allowedValues: ['true', 'false'], - }), - }; - - const region = this.cfnParameters.region.valueAsString; - const workMailEnabled = new cdk.CfnCondition(this, 'WorkMailEnabled', { expression: cdk.Fn.conditionEquals(this.cfnParameters.enableTestDomain, 'true') }); - - new cdk.CfnRule(this, 'CheckWorkMailRegion', { - ruleCondition: workMailEnabled.expression, - assertions: [{ - assert: cdk.Fn.conditionContains(['us-east-1', 'us-west-2', 'eu-west-1'], region), - assertDescription: 'Amazon WorkMail is supported only in us-east-1, us-west-2 or eu-west-1. Please change Amazon SES Region.', - }], - }); - - const sendEmailPolicy = new iam.Policy(this, 'SendEmailPolicy', { - statements: [ - new iam.PolicyStatement({ - actions: ['ses:SendRawEmail'], - resources: ['*'], - }), - ], - }); - - const user = new iam.User(this, 'User'); - user.attachInlinePolicy(sendEmailPolicy); - - const accessKey = new iam.CfnAccessKey(this, 'AccessKey', { userName: user.userName }); - - const genPasswordFunction = new NodejsFunction(this, 'GenPasswordFunction', { - description: 'Supabase - Generate SMTP Password Function', - entry: './src/functions/gen-smtp-password.ts', - runtime: lambda.Runtime.NODEJS_18_X, - }); - - const genPasswordProvider = new cr.Provider(this, 'GenPasswordProvider', { onEventHandler: genPasswordFunction }); - - const password = new cdk.CustomResource(this, 'Password', { - resourceType: 'Custom::Password', - serviceToken: genPasswordProvider.serviceToken, - properties: { - Region: region, - SecretAccessKey: accessKey.attrSecretAccessKey, - }, - }); - - const stackId = cdk.Fn.select(2, cdk.Fn.split('/', cdk.Aws.STACK_ID)); - - const workMail = new WorkMailStack(this, 'WorkMail', { - description: 'Amazon WorkMail for Test Domain', - organization: { region: region, alias: stackId }, - }); - const workMailUser = workMail.organization.addUser('Supabase', password.getAttString('Password')); - (workMail.node.defaultChild as cdk.CfnStack).cfnOptions.condition = workMailEnabled; - - this.host = cdk.Fn.conditionIf(workMailEnabled.logicalId, `smtp.mail.${region}.awsapps.com`, `email-smtp.${region}.amazonaws.com`).toString(); - this.port = 465; - this.email = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), this.cfnParameters.email.valueAsString).toString(); - const username = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), accessKey.ref).toString(); - - this.secret = new Secret(this, 'Secret', { - secretName: `${cdk.Aws.STACK_NAME}${id}Secret`, - description: 'Supabase - SMTP Secret', - secretObjectValue: { - username: cdk.SecretValue.unsafePlainText(username), - password: cdk.SecretValue.resourceAttribute(password.getAttString('Password')), - host: cdk.SecretValue.unsafePlainText(this.host), - }, - }); - - } -} diff --git a/src/functions/gen-smtp-password.ts b/src/amazon-ses-smtp/cr-smtp-password.ts similarity index 100% rename from src/functions/gen-smtp-password.ts rename to src/amazon-ses-smtp/cr-smtp-password.ts diff --git a/src/amazon-ses-smtp/index.ts b/src/amazon-ses-smtp/index.ts new file mode 100644 index 0000000..427f8b5 --- /dev/null +++ b/src/amazon-ses-smtp/index.ts @@ -0,0 +1,93 @@ +import * as path from 'path'; +import * as cdk from 'aws-cdk-lib'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; +import * as cr from 'aws-cdk-lib/custom-resources'; +import { Construct } from 'constructs'; +import { WorkMailStack } from '../aws-workmail'; + +interface SesSmtpProps { + region: string; + email: string; + workMailEnabled: cdk.CfnCondition; +} + +export class SesSmtp extends Construct { + secret: Secret; + host: string; + port: number; + email: string; + + constructor(scope: Construct, id: string, props: SesSmtpProps) { + super(scope, id); + + const { region, email, workMailEnabled } = props; + + /** IAM Policy to send email via Amazon SES */ + const sendEmailPolicy = new iam.Policy(this, 'SendEmailPolicy', { + statements: [ + new iam.PolicyStatement({ + actions: ['ses:SendRawEmail'], + resources: ['*'], + }), + ], + }); + + /** IAM User to send email via Amazon SES */ + const user = new iam.User(this, 'User'); + user.attachInlinePolicy(sendEmailPolicy); + + /** SMTP username */ + const accessKey = new iam.CfnAccessKey(this, 'AccessKey', { userName: user.userName }); + + /** Custom resource handler to generate a SMTP password */ + const passwordFunction = new NodejsFunction(this, 'PasswordFunction', { + description: 'Supabase - Generate SMTP Password Function', + entry: path.resolve(__dirname, 'cr-smtp-password.ts'), + runtime: lambda.Runtime.NODEJS_18_X, + }); + + /** Custom resource provider to generate a SMTP password */ + const passwordProvider = new cr.Provider(this, 'PasswordProvider', { onEventHandler: passwordFunction }); + + /** SMTP password */ + const password = new cdk.CustomResource(this, 'Password', { + resourceType: 'Custom::Password', + serviceToken: passwordProvider.serviceToken, + properties: { + Region: region, + SecretAccessKey: accessKey.attrSecretAccessKey, + }, + }); + + const stackId = cdk.Fn.select(2, cdk.Fn.split('/', cdk.Aws.STACK_ID)); + + /** Amazon WorkMail Stack */ + const workMail = new WorkMailStack(this, 'WorkMail', { + description: 'Amazon WorkMail for Test Domain', + organization: { region: region, alias: stackId }, + }); + + /** The mail user on WorkMail */ + const workMailUser = workMail.organization.addUser('Supabase', password.getAttString('Password')); + (workMail.node.defaultChild as cdk.CfnStack).cfnOptions.condition = workMailEnabled; + + this.host = cdk.Fn.conditionIf(workMailEnabled.logicalId, `smtp.mail.${region}.awsapps.com`, `email-smtp.${region}.amazonaws.com`).toString(); + this.port = 465; + this.email = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), email).toString(); + const username = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), accessKey.ref).toString(); + + this.secret = new Secret(this, 'Secret', { + secretName: `${cdk.Aws.STACK_NAME}${id}Secret`, + description: 'Supabase - SMTP Secret', + secretObjectValue: { + username: cdk.SecretValue.unsafePlainText(username), + password: cdk.SecretValue.resourceAttribute(password.getAttString('Password')), + host: cdk.SecretValue.unsafePlainText(this.host), + }, + }); + + } +} diff --git a/src/supabase-stack.ts b/src/supabase-stack.ts index 523edcc..21f9e84 100644 --- a/src/supabase-stack.ts +++ b/src/supabase-stack.ts @@ -8,7 +8,7 @@ import * as rds from 'aws-cdk-lib/aws-rds'; import * as s3 from 'aws-cdk-lib/aws-s3'; import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; import { Construct } from 'constructs'; -import { Smtp } from './amazon-ses-smtp'; +import { SesSmtp } from './amazon-ses-smtp'; import { AmplifyHosting } from './aws-amplify-hosting'; import { PrefixList } from './aws-prefix-list'; import { ForceDeployJob } from './ecs-force-deploy-job'; @@ -79,6 +79,14 @@ export class SupabaseStack extends FargateStack { maxValue: 128, }); + const senderEmail = new cdk.CfnParameter(this, 'Email', { + description: 'This is the email address the emails are sent from. If Amazon WorkMail is enabled, it set "noreply@supabase-.awsapps.com"', + type: 'String', + default: 'noreply@example.com', + allowedPattern: '^[\\x20-\\x45]?[\\w-\\+]+(\\.[\\w]+)*@[\\w-]+(\\.[\\w]+)*(\\.[a-z]{2,})$', + constraintDescription: 'must be a valid email address', + }); + const senderName = new cdk.CfnParameter(this, 'SenderName', { description: 'The From email sender name for all emails sent.', type: 'String', @@ -134,6 +142,33 @@ export class SupabaseStack extends FargateStack { maxValue: 128, }); + /** The region name for Amazon SES */ + const sesRegion = new cdk.CfnParameter(this, 'Region', { + description: 'Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.', + type: 'String', + default: 'us-west-2', + allowedValues: ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1'], + }); + + /** The flag for Amazon WorkMail */ + const enableWorkMail = new cdk.CfnParameter(this, 'EnableWorkMail', { + description: 'Enable test e-mail domain "xxx.awsapps.com" with Amazon WorkMail.', + type: 'String', + default: 'false', + allowedValues: ['true', 'false'], + }); + /** CFn condition for Amazon WorkMail */ + const workMailEnabled = new cdk.CfnCondition(this, 'WorkMailEnabled', { expression: cdk.Fn.conditionEquals(enableWorkMail, 'true') }); + + /** CFn rule for Amazon WorkMail region */ + new cdk.CfnRule(this, 'CheckWorkMailRegion', { + ruleCondition: workMailEnabled.expression, + assertions: [{ + assert: cdk.Fn.conditionContains(['us-east-1', 'us-west-2', 'eu-west-1'], sesRegion.valueAsString), + assertDescription: 'Amazon WorkMail is supported only in us-east-1, us-west-2 or eu-west-1. Please change Amazon SES Region.', + }], + }); + /** VPC for Containers and Database */ const vpc = new Vpc(this, 'VPC', { natGateways: 1 }); @@ -151,12 +186,16 @@ export class SupabaseStack extends FargateStack { vpc, }); - /** SMTP Credentials */ - const smtp = new Smtp(this, 'Smtp'); - /** PostgreSQL Database with Secrets */ const db = new SupabaseDatabase(this, 'Database', { vpc }); + /** SMTP Credentials */ + const smtp = new SesSmtp(this, 'Smtp', { + region: sesRegion.valueAsString, + email: senderEmail.valueAsString, + workMailEnabled: workMailEnabled, + }); + // Overwrite ACU (db.cluster.node.defaultChild as rds.CfnDBCluster).serverlessV2ScalingConfiguration = { minCapacity: minACU.valueAsNumber, @@ -616,10 +655,10 @@ export class SupabaseStack extends FargateStack { { Label: { default: 'Supabase - Auth E-mail Settings' }, Parameters: [ - smtp.cfnParameters.email.logicalId, + senderEmail.logicalId, senderName.logicalId, - smtp.cfnParameters.region.logicalId, - smtp.cfnParameters.enableTestDomain.logicalId, + sesRegion.logicalId, + enableWorkMail.logicalId, ], }, { @@ -718,10 +757,10 @@ export class SupabaseStack extends FargateStack { [redirectUrls.logicalId]: { default: 'Redirect URLs' }, [jwtExpiryLimit.logicalId]: { default: 'JWT expiry limit' }, [passwordMinLength.logicalId]: { default: 'Min password length' }, - [smtp.cfnParameters.email.logicalId]: { default: 'Sender Email Address' }, + [senderEmail.logicalId]: { default: 'Sender Email Address' }, [senderName.logicalId]: { default: 'Sender Name' }, - [smtp.cfnParameters.region.logicalId]: { default: 'Amazon SES Region' }, - [smtp.cfnParameters.enableTestDomain.logicalId]: { default: 'Enable Test E-mail Domain (via Amazon WorkMail)' }, + [sesRegion.logicalId]: { default: 'Amazon SES Region' }, + [enableWorkMail.logicalId]: { default: 'Enable Test E-mail Domain (via Amazon WorkMail)' }, [authImageUri.logicalId]: { default: 'Auth API Image URI - GoTrue' }, [restImageUri.logicalId]: { default: 'Rest API Image URI - PostgREST' }, diff --git a/test/__snapshots__/main.test.ts.snap b/test/__snapshots__/main.test.ts.snap index fc90441..4728f00 100644 --- a/test/__snapshots__/main.test.ts.snap +++ b/test/__snapshots__/main.test.ts.snap @@ -103,20 +103,20 @@ Object { "0", ], }, - "SmtpWorkMailEnabled0A4066BB": Object { + "StorageServiceDisabled05C15E2C": Object { "Fn::Equals": Array [ Object { - "Ref": "SmtpEnableTestDomain9DFEAF4D", + "Ref": "StorageMinTaskCount07E65703", }, - "true", + "0", ], }, - "StorageServiceDisabled05C15E2C": Object { + "WorkMailEnabled": Object { "Fn::Equals": Array [ Object { - "Ref": "StorageMinTaskCount07E65703", + "Ref": "EnableWorkMail", }, - "0", + "true", ], }, }, @@ -375,10 +375,10 @@ Object { "default": "Supabase - Auth E-mail Settings", }, "Parameters": Array [ - "SmtpEmailC79E1B85", + "Email", "SenderName", - "SmtpRegionC7E863E7", - "SmtpEnableTestDomain9DFEAF4D", + "Region", + "EnableWorkMail", ], }, Object { @@ -569,6 +569,12 @@ Object { "DisableSignup": Object { "default": "Disable User Signups", }, + "Email": Object { + "default": "Sender Email Address", + }, + "EnableWorkMail": Object { + "default": "Enable Test E-mail Domain (via Amazon WorkMail)", + }, "GraphQLMaxTaskCount62412A02": Object { "default": "Maximum Fargate Task Count", }, @@ -638,6 +644,9 @@ Object { "RedirectUrls": Object { "default": "Redirect URLs", }, + "Region": Object { + "default": "Amazon SES Region", + }, "RestImageUri": Object { "default": "Rest API Image URI - PostgREST", }, @@ -656,15 +665,6 @@ Object { "SiteUrl": Object { "default": "Site URL", }, - "SmtpEmailC79E1B85": Object { - "default": "Sender Email Address", - }, - "SmtpEnableTestDomain9DFEAF4D": Object { - "default": "Enable Test E-mail Domain (via Amazon WorkMail)", - }, - "SmtpRegionC7E863E7": Object { - "default": "Amazon SES Region", - }, "StorageImageUri": Object { "default": "Storage API Image URI", }, @@ -917,6 +917,22 @@ Object { "Description": "When signup is disabled the only way to create new users is through invites. Defaults to false, all signups enabled.", "Type": "String", }, + "Email": Object { + "AllowedPattern": "^[\\\\x20-\\\\x45]?[\\\\w-\\\\+]+(\\\\.[\\\\w]+)*@[\\\\w-]+(\\\\.[\\\\w]+)*(\\\\.[a-z]{2,})$", + "ConstraintDescription": "must be a valid email address", + "Default": "noreply@example.com", + "Description": "This is the email address the emails are sent from. If Amazon WorkMail is enabled, it set \\"noreply@supabase-.awsapps.com\\"", + "Type": "String", + }, + "EnableWorkMail": Object { + "AllowedValues": Array [ + "true", + "false", + ], + "Default": "false", + "Description": "Enable test e-mail domain \\"xxx.awsapps.com\\" with Amazon WorkMail.", + "Type": "String", + }, "GraphQLMaxTaskCount62412A02": Object { "Default": 20, "Description": "Maximum fargate task count", @@ -1095,6 +1111,30 @@ Object { "Description": "URLs that auth providers are permitted to redirect to post authentication", "Type": "String", }, + "Region": Object { + "AllowedValues": Array [ + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + "ap-south-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "eu-north-1", + "sa-east-1", + ], + "Default": "us-west-2", + "Description": "Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.", + "Type": "String", + }, "RestImageUri": Object { "Default": "public.ecr.aws/supabase/postgrest:v10.1.2", "Description": "https://gallery.ecr.aws/supabase/postgrest", @@ -1136,46 +1176,6 @@ Object { "Description": "The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails.", "Type": "String", }, - "SmtpEmailC79E1B85": Object { - "AllowedPattern": "^[\\\\x20-\\\\x45]?[\\\\w-\\\\+]+(\\\\.[\\\\w]+)*@[\\\\w-]+(\\\\.[\\\\w]+)*(\\\\.[a-z]{2,})$", - "ConstraintDescription": "must be a valid email address", - "Default": "noreply@example.com", - "Description": "This is the email address the emails are sent from. If Amazon WorkMail is enabled, it set \\"noreply@supabase-.awsapps.com\\"", - "Type": "String", - }, - "SmtpEnableTestDomain9DFEAF4D": Object { - "AllowedValues": Array [ - "true", - "false", - ], - "Default": "false", - "Description": "Enable test e-mail domain \\"xxx.awsapps.com\\" with Amazon WorkMail.", - "Type": "String", - }, - "SmtpRegionC7E863E7": Object { - "AllowedValues": Array [ - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "ap-south-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-southeast-1", - "ap-southeast-2", - "ca-central-1", - "eu-central-1", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "eu-north-1", - "sa-east-1", - ], - "Default": "us-west-2", - "Description": "Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.", - "Type": "String", - }, "StorageImageUri": Object { "Default": "public.ecr.aws/supabase/storage-api:v0.40.1", "Description": "https://gallery.ecr.aws/supabase/storage-api", @@ -1896,7 +1896,7 @@ Object { "Name": "GOTRUE_SMTP_ADMIN_EMAIL", "Value": Object { "Fn::If": Array [ - "SmtpWorkMailEnabled0A4066BB", + "WorkMailEnabled", Object { "Fn::GetAtt": Array [ "SmtpWorkMailNestedStackWorkMailNestedStackResource042ECB25", @@ -1904,7 +1904,7 @@ Object { ], }, Object { - "Ref": "SmtpEmailC79E1B85", + "Ref": "Email", }, ], }, @@ -1913,14 +1913,14 @@ Object { "Name": "GOTRUE_SMTP_HOST", "Value": Object { "Fn::If": Array [ - "SmtpWorkMailEnabled0A4066BB", + "WorkMailEnabled", Object { "Fn::Join": Array [ "", Array [ "smtp.mail.", Object { - "Ref": "SmtpRegionC7E863E7", + "Ref": "Region", }, ".awsapps.com", ], @@ -1932,7 +1932,7 @@ Object { Array [ "email-smtp.", Object { - "Ref": "SmtpRegionC7E863E7", + "Ref": "Region", }, ".amazonaws.com", ], @@ -7921,16 +7921,38 @@ Object { }, "Type": "AWS::IAM::AccessKey", }, - "SmtpGenPasswordFunction54B1181B": Object { + "SmtpPasswordBA01E518": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "Region": Object { + "Ref": "Region", + }, + "SecretAccessKey": Object { + "Fn::GetAtt": Array [ + "SmtpAccessKeyCCAD8B7D", + "SecretAccessKey", + ], + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "SmtpPasswordProviderframeworkonEvent430EB845", + "Arn", + ], + }, + }, + "Type": "Custom::Password", + "UpdateReplacePolicy": "Delete", + }, + "SmtpPasswordFunctionDC49B7CC": Object { "DependsOn": Array [ - "SmtpGenPasswordFunctionServiceRole670F2216", + "SmtpPasswordFunctionServiceRoleA0A9C3A3", ], "Properties": Object { "Code": Object { "S3Bucket": Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "S3Key": "f313788ca3f2fa052e96d53301df52b707c280b900d594a906b624fac83c5505.zip", + "S3Key": "cbb9c0c24fa0ee781b61f07de9aab9f7fbd9d5fec8eed79f7c53870781adaf38.zip", }, "Description": "Supabase - Generate SMTP Password Function", "Environment": Object { @@ -7941,7 +7963,7 @@ Object { "Handler": "index.handler", "Role": Object { "Fn::GetAtt": Array [ - "SmtpGenPasswordFunctionServiceRole670F2216", + "SmtpPasswordFunctionServiceRoleA0A9C3A3", "Arn", ], }, @@ -7949,7 +7971,7 @@ Object { }, "Type": "AWS::Lambda::Function", }, - "SmtpGenPasswordFunctionServiceRole670F2216": Object { + "SmtpPasswordFunctionServiceRoleA0A9C3A3": Object { "Properties": Object { "AssumeRolePolicyDocument": Object { "Statement": Array [ @@ -7980,10 +8002,10 @@ Object { }, "Type": "AWS::IAM::Role", }, - "SmtpGenPasswordProviderframeworkonEvent4C861253": Object { + "SmtpPasswordProviderframeworkonEvent430EB845": Object { "DependsOn": Array [ - "SmtpGenPasswordProviderframeworkonEventServiceRoleDefaultPolicy2D91E6D6", - "SmtpGenPasswordProviderframeworkonEventServiceRoleAF796632", + "SmtpPasswordProviderframeworkonEventServiceRoleDefaultPolicyD17BD380", + "SmtpPasswordProviderframeworkonEventServiceRoleF0717454", ], "Properties": Object { "Code": Object { @@ -7992,12 +8014,12 @@ Object { }, "S3Key": "8e3d635893ea17fa3158623489cd42c680fad925b38de1ef51cb10d84f6e245e.zip", }, - "Description": "AWS CDK resource provider framework - onEvent (Supabase/Smtp/GenPasswordProvider)", + "Description": "AWS CDK resource provider framework - onEvent (Supabase/Smtp/PasswordProvider)", "Environment": Object { "Variables": Object { "USER_ON_EVENT_FUNCTION_ARN": Object { "Fn::GetAtt": Array [ - "SmtpGenPasswordFunction54B1181B", + "SmtpPasswordFunctionDC49B7CC", "Arn", ], }, @@ -8006,7 +8028,7 @@ Object { "Handler": "framework.onEvent", "Role": Object { "Fn::GetAtt": Array [ - "SmtpGenPasswordProviderframeworkonEventServiceRoleAF796632", + "SmtpPasswordProviderframeworkonEventServiceRoleF0717454", "Arn", ], }, @@ -8015,38 +8037,7 @@ Object { }, "Type": "AWS::Lambda::Function", }, - "SmtpGenPasswordProviderframeworkonEventServiceRoleAF796632": Object { - "Properties": Object { - "AssumeRolePolicyDocument": Object { - "Statement": Array [ - Object { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": Object { - "Service": "lambda.amazonaws.com", - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": Array [ - Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "SmtpGenPasswordProviderframeworkonEventServiceRoleDefaultPolicy2D91E6D6": Object { + "SmtpPasswordProviderframeworkonEventServiceRoleDefaultPolicyD17BD380": Object { "Properties": Object { "PolicyDocument": Object { "Statement": Array [ @@ -8056,7 +8047,7 @@ Object { "Resource": Array [ Object { "Fn::GetAtt": Array [ - "SmtpGenPasswordFunction54B1181B", + "SmtpPasswordFunctionDC49B7CC", "Arn", ], }, @@ -8066,7 +8057,7 @@ Object { Array [ Object { "Fn::GetAtt": Array [ - "SmtpGenPasswordFunction54B1181B", + "SmtpPasswordFunctionDC49B7CC", "Arn", ], }, @@ -8079,36 +8070,45 @@ Object { ], "Version": "2012-10-17", }, - "PolicyName": "SmtpGenPasswordProviderframeworkonEventServiceRoleDefaultPolicy2D91E6D6", + "PolicyName": "SmtpPasswordProviderframeworkonEventServiceRoleDefaultPolicyD17BD380", "Roles": Array [ Object { - "Ref": "SmtpGenPasswordProviderframeworkonEventServiceRoleAF796632", + "Ref": "SmtpPasswordProviderframeworkonEventServiceRoleF0717454", }, ], }, "Type": "AWS::IAM::Policy", }, - "SmtpPasswordBA01E518": Object { - "DeletionPolicy": "Delete", + "SmtpPasswordProviderframeworkonEventServiceRoleF0717454": Object { "Properties": Object { - "Region": Object { - "Ref": "SmtpRegionC7E863E7", - }, - "SecretAccessKey": Object { - "Fn::GetAtt": Array [ - "SmtpAccessKeyCCAD8B7D", - "SecretAccessKey", - ], - }, - "ServiceToken": Object { - "Fn::GetAtt": Array [ - "SmtpGenPasswordProviderframeworkonEvent4C861253", - "Arn", + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, ], + "Version": "2012-10-17", }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], }, - "Type": "Custom::Password", - "UpdateReplacePolicy": "Delete", + "Type": "AWS::IAM::Role", }, "SmtpSecretF89CC16B": Object { "DeletionPolicy": "Delete", @@ -8132,7 +8132,7 @@ Object { "{\\"username\\":\\"", Object { "Fn::If": Array [ - "SmtpWorkMailEnabled0A4066BB", + "WorkMailEnabled", Object { "Fn::GetAtt": Array [ "SmtpWorkMailNestedStackWorkMailNestedStackResource042ECB25", @@ -8154,14 +8154,14 @@ Object { "\\",\\"host\\":\\"", Object { "Fn::If": Array [ - "SmtpWorkMailEnabled0A4066BB", + "WorkMailEnabled", Object { "Fn::Join": Array [ "", Array [ "smtp.mail.", Object { - "Ref": "SmtpRegionC7E863E7", + "Ref": "Region", }, ".awsapps.com", ], @@ -8173,7 +8173,7 @@ Object { Array [ "email-smtp.", Object { - "Ref": "SmtpRegionC7E863E7", + "Ref": "Region", }, ".amazonaws.com", ], @@ -8214,19 +8214,19 @@ Object { "Type": "AWS::IAM::User", }, "SmtpWorkMailNestedStackWorkMailNestedStackResource042ECB25": Object { - "Condition": "SmtpWorkMailEnabled0A4066BB", + "Condition": "WorkMailEnabled", "DeletionPolicy": "Delete", "Properties": Object { "Parameters": Object { + "referencetoSupabaseRegion386497C3Ref": Object { + "Ref": "Region", + }, "referencetoSupabaseSmtpPassword76B56B34Password": Object { "Fn::GetAtt": Array [ "SmtpPasswordBA01E518", "Password", ], }, - "referencetoSupabaseSmtpRegion60DF7AC5Ref": Object { - "Ref": "SmtpRegionC7E863E7", - }, }, "TemplateURL": Object { "Fn::Join": Array [ @@ -8244,7 +8244,7 @@ Object { Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "/1a3d0d3aa98dee7a568addbbca4c8fbad57d036f0cd8d38840d1c8f604157613.json", + "/8dcc222a2010aa781e92b0169875eb0ea7ec8f08ac0cb76ae67b8830fa51caee.json", ], ], }, @@ -10154,7 +10154,7 @@ applications: }, ], }, - "SmtpCheckWorkMailRegion2094F9E6": Object { + "CheckWorkMailRegion": Object { "Assertions": Array [ Object { "Assert": Object { @@ -10165,7 +10165,7 @@ applications: "eu-west-1", ], Object { - "Ref": "SmtpRegionC7E863E7", + "Ref": "Region", }, ], }, @@ -10175,7 +10175,7 @@ applications: "RuleCondition": Object { "Fn::Equals": Array [ Object { - "Ref": "SmtpEnableTestDomain9DFEAF4D", + "Ref": "EnableWorkMail", }, "true", ], From 48f03a355a2821f6786531f72f0852b119aad072 Mon Sep 17 00:00:00 2001 From: Kazuki Matsuda Date: Fri, 16 Jun 2023 10:28:54 +0900 Subject: [PATCH 3/5] chore: change removal policy of DB --- src/supabase-db/index.ts | 3 ++- test/__snapshots__/main.test.ts.snap | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/supabase-db/index.ts b/src/supabase-db/index.ts index e514024..36d900b 100644 --- a/src/supabase-db/index.ts +++ b/src/supabase-db/index.ts @@ -50,13 +50,14 @@ export class SupabaseDatabase extends Construct { }); this.cluster = new rds.DatabaseCluster(this, 'Cluster', { + removalPolicy: cdk.RemovalPolicy.DESTROY, engine, parameterGroup, + vpc, writer: rds.ClusterInstance.serverlessV2('Instance1'), readers: [ rds.ClusterInstance.serverlessV2('Instance2', { scaleWithWriter: true }), ], - vpc, credentials: rds.Credentials.fromGeneratedSecret('supabase_admin', { secretName: `${cdk.Aws.STACK_NAME}-${id}-supabase_admin`, }), diff --git a/test/__snapshots__/main.test.ts.snap b/test/__snapshots__/main.test.ts.snap index 4728f00..479826b 100644 --- a/test/__snapshots__/main.test.ts.snap +++ b/test/__snapshots__/main.test.ts.snap @@ -3168,7 +3168,7 @@ Object { "UpdateReplacePolicy": "Delete", }, "DatabaseCluster5B53A178": Object { - "DeletionPolicy": "Snapshot", + "DeletionPolicy": "Delete", "Properties": Object { "CopyTagsToSnapshot": true, "DBClusterParameterGroupName": Object { @@ -3213,7 +3213,7 @@ Object { ], }, "Type": "AWS::RDS::DBCluster", - "UpdateReplacePolicy": "Snapshot", + "UpdateReplacePolicy": "Delete", }, "DatabaseClusterInstance1E154D1E9": Object { "DeletionPolicy": "Delete", From bc82a6462f196b163b6d5e335ea08a293de35fcf Mon Sep 17 00:00:00 2001 From: Kazuki Matsuda Date: Fri, 16 Jun 2023 13:42:05 +0900 Subject: [PATCH 4/5] fix: chane param name --- src/supabase-stack.ts | 2 +- test/__snapshots__/main.test.ts.snap | 74 ++++++++++++++-------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/supabase-stack.ts b/src/supabase-stack.ts index 21f9e84..7bfd26e 100644 --- a/src/supabase-stack.ts +++ b/src/supabase-stack.ts @@ -143,7 +143,7 @@ export class SupabaseStack extends FargateStack { }); /** The region name for Amazon SES */ - const sesRegion = new cdk.CfnParameter(this, 'Region', { + const sesRegion = new cdk.CfnParameter(this, 'SesRegion', { description: 'Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.', type: 'String', default: 'us-west-2', diff --git a/test/__snapshots__/main.test.ts.snap b/test/__snapshots__/main.test.ts.snap index 479826b..959f2ec 100644 --- a/test/__snapshots__/main.test.ts.snap +++ b/test/__snapshots__/main.test.ts.snap @@ -377,7 +377,7 @@ Object { "Parameters": Array [ "Email", "SenderName", - "Region", + "SesRegion", "EnableWorkMail", ], }, @@ -644,9 +644,6 @@ Object { "RedirectUrls": Object { "default": "Redirect URLs", }, - "Region": Object { - "default": "Amazon SES Region", - }, "RestImageUri": Object { "default": "Rest API Image URI - PostgREST", }, @@ -662,6 +659,9 @@ Object { "SenderName": Object { "default": "Sender Name", }, + "SesRegion": Object { + "default": "Amazon SES Region", + }, "SiteUrl": Object { "default": "Site URL", }, @@ -1111,30 +1111,6 @@ Object { "Description": "URLs that auth providers are permitted to redirect to post authentication", "Type": "String", }, - "Region": Object { - "AllowedValues": Array [ - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "ap-south-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-southeast-1", - "ap-southeast-2", - "ca-central-1", - "eu-central-1", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "eu-north-1", - "sa-east-1", - ], - "Default": "us-west-2", - "Description": "Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.", - "Type": "String", - }, "RestImageUri": Object { "Default": "public.ecr.aws/supabase/postgrest:v10.1.2", "Description": "https://gallery.ecr.aws/supabase/postgrest", @@ -1171,6 +1147,30 @@ Object { "Description": "The From email sender name for all emails sent.", "Type": "String", }, + "SesRegion": Object { + "AllowedValues": Array [ + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + "ap-south-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "eu-north-1", + "sa-east-1", + ], + "Default": "us-west-2", + "Description": "Amazon SES used for SMTP server. If you want to use Amazon WorkMail, need to set us-east-1, us-west-2 or eu-west-1.", + "Type": "String", + }, "SiteUrl": Object { "Default": "http://localhost:3000", "Description": "The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails.", @@ -1920,7 +1920,7 @@ Object { Array [ "smtp.mail.", Object { - "Ref": "Region", + "Ref": "SesRegion", }, ".awsapps.com", ], @@ -1932,7 +1932,7 @@ Object { Array [ "email-smtp.", Object { - "Ref": "Region", + "Ref": "SesRegion", }, ".amazonaws.com", ], @@ -7925,7 +7925,7 @@ Object { "DeletionPolicy": "Delete", "Properties": Object { "Region": Object { - "Ref": "Region", + "Ref": "SesRegion", }, "SecretAccessKey": Object { "Fn::GetAtt": Array [ @@ -8161,7 +8161,7 @@ Object { Array [ "smtp.mail.", Object { - "Ref": "Region", + "Ref": "SesRegion", }, ".awsapps.com", ], @@ -8173,7 +8173,7 @@ Object { Array [ "email-smtp.", Object { - "Ref": "Region", + "Ref": "SesRegion", }, ".amazonaws.com", ], @@ -8218,8 +8218,8 @@ Object { "DeletionPolicy": "Delete", "Properties": Object { "Parameters": Object { - "referencetoSupabaseRegion386497C3Ref": Object { - "Ref": "Region", + "referencetoSupabaseSesRegion184A3193Ref": Object { + "Ref": "SesRegion", }, "referencetoSupabaseSmtpPassword76B56B34Password": Object { "Fn::GetAtt": Array [ @@ -8244,7 +8244,7 @@ Object { Object { "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", }, - "/8dcc222a2010aa781e92b0169875eb0ea7ec8f08ac0cb76ae67b8830fa51caee.json", + "/14bdec4b6b627991a1b28cf002b89694348c304d35f02b1f71b9445d364d7c7d.json", ], ], }, @@ -10165,7 +10165,7 @@ applications: "eu-west-1", ], Object { - "Ref": "Region", + "Ref": "SesRegion", }, ], }, From dac81305d76ceb70bf9453446bda13e6b42db726 Mon Sep 17 00:00:00 2001 From: Kazuki Matsuda Date: Fri, 16 Jun 2023 13:42:16 +0900 Subject: [PATCH 5/5] chore: add comments --- src/amazon-ses-smtp/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/amazon-ses-smtp/index.ts b/src/amazon-ses-smtp/index.ts index 427f8b5..c4c8283 100644 --- a/src/amazon-ses-smtp/index.ts +++ b/src/amazon-ses-smtp/index.ts @@ -69,14 +69,21 @@ export class SesSmtp extends Construct { description: 'Amazon WorkMail for Test Domain', organization: { region: region, alias: stackId }, }); + // Add condition + (workMail.node.defaultChild as cdk.CfnStack).cfnOptions.condition = workMailEnabled; /** The mail user on WorkMail */ const workMailUser = workMail.organization.addUser('Supabase', password.getAttString('Password')); - (workMail.node.defaultChild as cdk.CfnStack).cfnOptions.condition = workMailEnabled; this.host = cdk.Fn.conditionIf(workMailEnabled.logicalId, `smtp.mail.${region}.awsapps.com`, `email-smtp.${region}.amazonaws.com`).toString(); this.port = 465; this.email = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), email).toString(); + + /** + * SMTP username + * + * If WorkMail is enabled, use the WorkMail user's email address. + */ const username = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), accessKey.ref).toString(); this.secret = new Secret(this, 'Secret', {