Skip to content

Commit 27e7c3e

Browse files
authored
Merge pull request #44 from supabase-community/refactor-smtp-and-jwt
chore: Refactoring
2 parents 17effb4 + dac8130 commit 27e7c3e

File tree

8 files changed

+410
-367
lines changed

8 files changed

+410
-367
lines changed

src/amazon-ses-smtp.ts

Lines changed: 0 additions & 113 deletions
This file was deleted.
File renamed without changes.

src/amazon-ses-smtp/index.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import * as path from 'path';
2+
import * as cdk from 'aws-cdk-lib';
3+
import * as iam from 'aws-cdk-lib/aws-iam';
4+
import * as lambda from 'aws-cdk-lib/aws-lambda';
5+
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
6+
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
7+
import * as cr from 'aws-cdk-lib/custom-resources';
8+
import { Construct } from 'constructs';
9+
import { WorkMailStack } from '../aws-workmail';
10+
11+
interface SesSmtpProps {
12+
region: string;
13+
email: string;
14+
workMailEnabled: cdk.CfnCondition;
15+
}
16+
17+
export class SesSmtp extends Construct {
18+
secret: Secret;
19+
host: string;
20+
port: number;
21+
email: string;
22+
23+
constructor(scope: Construct, id: string, props: SesSmtpProps) {
24+
super(scope, id);
25+
26+
const { region, email, workMailEnabled } = props;
27+
28+
/** IAM Policy to send email via Amazon SES */
29+
const sendEmailPolicy = new iam.Policy(this, 'SendEmailPolicy', {
30+
statements: [
31+
new iam.PolicyStatement({
32+
actions: ['ses:SendRawEmail'],
33+
resources: ['*'],
34+
}),
35+
],
36+
});
37+
38+
/** IAM User to send email via Amazon SES */
39+
const user = new iam.User(this, 'User');
40+
user.attachInlinePolicy(sendEmailPolicy);
41+
42+
/** SMTP username */
43+
const accessKey = new iam.CfnAccessKey(this, 'AccessKey', { userName: user.userName });
44+
45+
/** Custom resource handler to generate a SMTP password */
46+
const passwordFunction = new NodejsFunction(this, 'PasswordFunction', {
47+
description: 'Supabase - Generate SMTP Password Function',
48+
entry: path.resolve(__dirname, 'cr-smtp-password.ts'),
49+
runtime: lambda.Runtime.NODEJS_18_X,
50+
});
51+
52+
/** Custom resource provider to generate a SMTP password */
53+
const passwordProvider = new cr.Provider(this, 'PasswordProvider', { onEventHandler: passwordFunction });
54+
55+
/** SMTP password */
56+
const password = new cdk.CustomResource(this, 'Password', {
57+
resourceType: 'Custom::Password',
58+
serviceToken: passwordProvider.serviceToken,
59+
properties: {
60+
Region: region,
61+
SecretAccessKey: accessKey.attrSecretAccessKey,
62+
},
63+
});
64+
65+
const stackId = cdk.Fn.select(2, cdk.Fn.split('/', cdk.Aws.STACK_ID));
66+
67+
/** Amazon WorkMail Stack */
68+
const workMail = new WorkMailStack(this, 'WorkMail', {
69+
description: 'Amazon WorkMail for Test Domain',
70+
organization: { region: region, alias: stackId },
71+
});
72+
// Add condition
73+
(workMail.node.defaultChild as cdk.CfnStack).cfnOptions.condition = workMailEnabled;
74+
75+
/** The mail user on WorkMail */
76+
const workMailUser = workMail.organization.addUser('Supabase', password.getAttString('Password'));
77+
78+
this.host = cdk.Fn.conditionIf(workMailEnabled.logicalId, `smtp.mail.${region}.awsapps.com`, `email-smtp.${region}.amazonaws.com`).toString();
79+
this.port = 465;
80+
this.email = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), email).toString();
81+
82+
/**
83+
* SMTP username
84+
*
85+
* If WorkMail is enabled, use the WorkMail user's email address.
86+
*/
87+
const username = cdk.Fn.conditionIf(workMailEnabled.logicalId, workMailUser.getAtt('Email'), accessKey.ref).toString();
88+
89+
this.secret = new Secret(this, 'Secret', {
90+
secretName: `${cdk.Aws.STACK_NAME}${id}Secret`,
91+
description: 'Supabase - SMTP Secret',
92+
secretObjectValue: {
93+
username: cdk.SecretValue.unsafePlainText(username),
94+
password: cdk.SecretValue.resourceAttribute(password.getAttString('Password')),
95+
host: cdk.SecretValue.unsafePlainText(this.host),
96+
},
97+
});
98+
99+
}
100+
}

src/functions/gen-json-web-token.ts renamed to src/json-web-token/cr-json-web-token.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface Payload extends Object {
99
role: string;
1010
}
1111

12+
/** Get the JWT secret */
1213
const getJwtSecret = async (secretId: string) => {
1314
const client = new SecretsManagerClient({ region });
1415
const cmd = new GetSecretValueCommand({ SecretId: secretId });
@@ -18,6 +19,7 @@ const getJwtSecret = async (secretId: string) => {
1819
return SecretString!;
1920
};
2021

22+
/** Generate a json web token */
2123
const generateToken = async (payload: object, secretId: string, issuer?: string, expiresIn?: string) => {
2224
const jwtSecret = await getJwtSecret(secretId);
2325
const token = jwt.sign(payload, jwtSecret, { issuer, expiresIn });

src/json-web-token.ts renamed to src/json-web-token/index.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as path from 'path';
12
import * as cdk from 'aws-cdk-lib';
23
import * as lambda from 'aws-cdk-lib/aws-lambda';
34
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
@@ -7,8 +8,10 @@ import * as cr from 'aws-cdk-lib/custom-resources';
78
import { Construct } from 'constructs';
89

910
export class JwtSecret extends Secret {
11+
/** Custom resource provider to generate a json web token */
1012
genTokenProvider: cr.Provider;
1113

14+
/** Creates a new jwt secret in AWS SecretsManager. */
1215
constructor(scope: Construct, id: string, props?: SecretProps) {
1316
super(scope, id, {
1417
description: `${cdk.Aws.STACK_NAME} - Json Web Token Secret`,
@@ -19,17 +22,23 @@ export class JwtSecret extends Secret {
1922
...props,
2023
});
2124

22-
const genTokenFunction = new NodejsFunction(this, 'GenerateTokenFunction', {
25+
/** Custom resource handler to generate a json web token */
26+
const jwtFunction = new NodejsFunction(this, 'JsonWebTokenFunction', {
2327
description: `${cdk.Aws.STACK_NAME} - Generate token via jwt secret`,
24-
entry: './src/functions/gen-json-web-token.ts',
28+
entry: path.resolve(__dirname, 'cr-json-web-token.ts'),
2529
runtime: lambda.Runtime.NODEJS_18_X,
26-
environment: { JWT_SECRET_ARN: this.secretArn },
30+
environment: {
31+
JWT_SECRET_ARN: this.secretArn,
32+
},
2733
});
28-
this.grantRead(genTokenFunction);
2934

30-
this.genTokenProvider = new cr.Provider(this, 'GenerateTokenProvider', { onEventHandler: genTokenFunction });
35+
/** Allow the function to read the jwt secret */
36+
this.grantRead(jwtFunction);
37+
38+
this.genTokenProvider = new cr.Provider(this, 'GenerateTokenProvider', { onEventHandler: jwtFunction });
3139
}
3240

41+
/** Generate a new token in ParameterStore. */
3342
genApiKey(id: string, props: ApiKeyProps) {
3443
const apiKey = new ApiKey(this, id, props);
3544
return apiKey;
@@ -43,9 +52,12 @@ interface ApiKeyProps {
4352
}
4453

4554
class ApiKey extends Construct {
55+
/** Token value */
4656
value: string;
57+
/** ParameterStore of the token */
4758
ssmParameter: ssm.StringParameter;
4859

60+
/** Json Web Token */
4961
constructor(scope: JwtSecret, id: string, props: ApiKeyProps) {
5062
super(scope, id);
5163

@@ -54,7 +66,8 @@ class ApiKey extends Construct {
5466
const issuer = props.issuer;
5567
const expiresIn = props.expiresIn;
5668

57-
const token = new cdk.CustomResource(this, 'Token', {
69+
/** String value of Json Web Token */
70+
const token = new cdk.CustomResource(this, 'Resource', {
5871
serviceToken: jwtSecret.genTokenProvider.serviceToken,
5972
resourceType: 'Custom::JsonWebToken',
6073
properties: {
@@ -63,6 +76,7 @@ class ApiKey extends Construct {
6376
ExpiresIn: expiresIn,
6477
},
6578
});
79+
6680
this.value = token.getAttString('Value');
6781

6882
this.ssmParameter = new ssm.StringParameter(this, 'Parameter', {

src/supabase-db/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ export class SupabaseDatabase extends Construct {
5050
});
5151

5252
this.cluster = new rds.DatabaseCluster(this, 'Cluster', {
53+
removalPolicy: cdk.RemovalPolicy.DESTROY,
5354
engine,
5455
parameterGroup,
56+
vpc,
5557
writer: rds.ClusterInstance.serverlessV2('Instance1'),
5658
readers: [
5759
rds.ClusterInstance.serverlessV2('Instance2', { scaleWithWriter: true }),
5860
],
59-
vpc,
6061
credentials: rds.Credentials.fromGeneratedSecret('supabase_admin', {
6162
secretName: `${cdk.Aws.STACK_NAME}-${id}-supabase_admin`,
6263
}),

0 commit comments

Comments
 (0)