Skip to content

Commit 4db06f6

Browse files
committed
chore: wip
chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip
1 parent 47eb79d commit 4db06f6

File tree

17 files changed

+227
-138
lines changed

17 files changed

+227
-138
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { ListBucketsCommandOutput } from '@aws-sdk/client-s3'
2+
import { S3 } from '@aws-sdk/client-s3'
3+
import { SES } from '@aws-sdk/client-ses'
4+
import { ExitCode } from '@stacksjs/types'
5+
import type { AWSError } from 'aws-sdk'
6+
import process from 'node:process'
7+
8+
const ses = new SES({ apiVersion: '2010-12-01' })
9+
const s3 = new S3({ apiVersion: '2006-03-01' })
10+
11+
// oddly, this somehow does not play well within a CDK construct
12+
// so we need to use the AWS SDK directly to create the rule
13+
// unsure whether it's an AWS or implementation issue
14+
15+
// need to query S3 to get the bucket name, based on whether the name contains -email-
16+
// which is indicative of the email bucket created by the CDK construct
17+
let bucketName: string | undefined
18+
s3.listBuckets((err: AWSError, data: ListBucketsCommandOutput) => {
19+
if (err) {
20+
// eslint-disable-next-line no-console
21+
console.log('Error', err)
22+
}
23+
else {
24+
console.log('Data', data)
25+
if (data.Buckets) {
26+
bucketName = data.Buckets.find(bucket => bucket.Name && bucket.Name.includes('-email-'))?.Name
27+
// eslint-disable-next-line no-console
28+
console.log('Bucket Name:', bucketName)
29+
30+
if (!bucketName) {
31+
// eslint-disable-next-line no-console
32+
console.log('Stacks Email Bucket not found')
33+
process.exit(ExitCode.FatalError)
34+
}
35+
36+
const params = {
37+
Rule: {
38+
Actions: [
39+
{
40+
S3Action: {
41+
BucketName: bucketName,
42+
ObjectKeyPrefix: 'tmp/email_in',
43+
},
44+
},
45+
],
46+
Enabled: true,
47+
Name: 'Inbound',
48+
ScanEnabled: true,
49+
TlsPolicy: 'Require',
50+
},
51+
RuleSetName: 'your-rule-set-name',
52+
}
53+
54+
ses.createReceiptRule(params, (err: AWSError, data: any) => {
55+
if (err)
56+
// eslint-disable-next-line no-console
57+
console.log(err, err.stack)
58+
else
59+
// eslint-disable-next-line no-console
60+
console.log(data)
61+
})
62+
}
63+
}
64+
})

.stacks/core/cloud/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@
5454
"prepublishOnly": "bun run build"
5555
},
5656
"peerDependencies": {
57+
"@aws-lambda-powertools/logger": "^1.14.0",
58+
"@aws-lambda-powertools/metrics": "^1.14.0",
59+
"@aws-lambda-powertools/tracer": "^1.14.0",
60+
"@aws-sdk/client-cloudformation": "^3.433.0",
61+
"@aws-sdk/client-cloudfront": "^3.433.0",
62+
"@aws-sdk/client-cloudwatch-logs": "^3.433.0",
63+
"@aws-sdk/client-ec2": "^3.433.0",
64+
"@aws-sdk/client-efs": "^3.433.0",
65+
"@aws-sdk/client-iam": "^3.433.0",
66+
"@aws-sdk/client-lambda": "^3.433.0",
67+
"@aws-sdk/client-route-53-domains": "^3.433.0",
68+
"@aws-sdk/client-s3": "^3.433.0",
69+
"@aws-sdk/client-ses": "^3.433.0",
70+
"@aws-sdk/client-sesv2": "^3.433.0",
71+
"@aws-sdk/client-ssm": "^3.433.0",
5772
"@stacksjs/config": "workspace:*",
5873
"@stacksjs/logging": "workspace:*",
5974
"@stacksjs/path": "workspace:*",
@@ -76,6 +91,7 @@
7691
"@aws-sdk/client-lambda": "^3.433.0",
7792
"@aws-sdk/client-route-53-domains": "^3.433.0",
7893
"@aws-sdk/client-s3": "^3.433.0",
94+
"@aws-sdk/client-ses": "^3.433.0",
7995
"@aws-sdk/client-sesv2": "^3.433.0",
8096
"@aws-sdk/client-ssm": "^3.433.0",
8197
"@stacksjs/config": "workspace:*",

.stacks/core/cloud/src/cloud.ts

Lines changed: 48 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import {
1717
aws_certificatemanager as acm,
1818
aws_backup as backup,
1919
aws_cloudfront as cloudfront,
20-
// custom_resources,
2120
aws_ec2 as ec2,
2221
aws_efs as efs,
2322
aws_iam as iam,
23+
// custom_resources,
2424
aws_kms as kms,
2525
aws_lambda as lambda,
2626
aws_cloudfront_origins as origins,
@@ -577,6 +577,23 @@ export class StacksCloud extends Stack {
577577
// },
578578
// })
579579
// }
580+
// also add
581+
// }, {
582+
// "name": "AWSManagedRulesAnonymousIpList",
583+
// "priority": 40,
584+
// "overrideAction": "none",
585+
// "excludedRules": []
586+
// }, {
587+
// "name": "AWSManagedRulesLinuxRuleSet",
588+
// "priority": 50,
589+
// "overrideAction": "none",
590+
// "excludedRules": []
591+
// }, {
592+
// "name": "AWSManagedRulesUnixRuleSet",
593+
// "priority": 60,
594+
// "overrideAction": "none",
595+
// "excludedRules": [],
596+
// }];
580597

581598
return rules
582599
}
@@ -756,32 +773,14 @@ export class StacksCloud extends Stack {
756773
this.storage.emailBucket = this.createBucket('email')
757774

758775
const sesPrincipal = new iam.ServicePrincipal('ses.amazonaws.com')
759-
const ruleSetName = `${this.appName}-${appEnv}-email`
776+
const ruleSetName = `${this.appName}-${appEnv}-email-receipt-rule-set-${timestamp}`
777+
const receiptRuleName = `${this.appName}-${appEnv}-email-receipt-rule-${timestamp}`
778+
760779
const ruleSet = new ses.CfnReceiptRuleSet(this, 'SESReceiptRuleSet', {
761780
ruleSetName,
762781
})
763782

764-
const ruleName = 'Inbound'
765-
// const receiptRule = new ses.CfnReceiptRule(this, 'SESReceiptRule', {
766-
new ses.CfnReceiptRule(this, 'SESReceiptRule', {
767-
ruleSetName: ruleSet.ref,
768-
rule: {
769-
name: ruleName,
770-
enabled: true,
771-
actions: [
772-
{
773-
s3Action: {
774-
bucketName: this.storage.emailBucket.bucketName,
775-
kmsKeyArn: this.encryptionKey.keyArn,
776-
objectKeyPrefix: 'tmp/email_in',
777-
},
778-
},
779-
],
780-
scanEnabled: config.email.server?.scan || true,
781-
tlsPolicy: 'Require',
782-
},
783-
})
784-
783+
// add a policy to the S3 bucket to allow the SES service to put objects into it
785784
this.storage.emailBucket.addToResourcePolicy(
786785
new iam.PolicyStatement({
787786
sid: `AllowSESToPutObject`,
@@ -792,39 +791,38 @@ export class StacksCloud extends Stack {
792791
's3:PutObjectAcl',
793792
],
794793
resources: [
794+
this.storage.emailBucket.bucketArn,
795795
`${this.storage.emailBucket.bucketArn}/*`,
796796
],
797797
conditions: {
798798
StringEquals: {
799799
'aws:SourceAccount': Stack.of(this).account,
800800
},
801801
ArnLike: {
802-
'aws:SourceArn': `arn:aws:ses:${this.region}:${Stack.of(this).account}:receipt-rule-set/${ruleSetName}:receipt-rule/${ruleName}`,
802+
'aws:SourceArn': `arn:aws:ses:${this.region}:${Stack.of(this).account}:receipt-rule-set/${ruleSet.ref}:receipt-rule/${receiptRuleName}`,
803803
},
804804
},
805805
}),
806806
)
807807

808-
this.storage.emailBucket.addToResourcePolicy(
809-
new iam.PolicyStatement({
810-
sid: `AllowSESToEncryptMessagesBelongingToThisAccount`,
811-
effect: iam.Effect.ALLOW,
812-
principals: [sesPrincipal],
813-
actions: [
814-
'kms:Decrypt',
815-
'kms:GenerateDataKey*',
816-
],
817-
resources: ['*'],
818-
conditions: {
819-
StringEquals: {
820-
'aws:SourceAccount': Stack.of(this).account,
821-
},
822-
ArnLike: {
823-
'aws:SourceArn': `arn:aws:ses:${this.region}:${Stack.of(this).account}:receipt-rule-set/${ruleSetName}:receipt-rule/${ruleName}`,
824-
},
825-
},
826-
}),
827-
)
808+
new ses.CfnReceiptRule(this, 'SESReceiptRule', {
809+
ruleSetName: ruleSet.ref,
810+
rule: {
811+
name: receiptRuleName,
812+
enabled: true,
813+
// actions: [
814+
// {
815+
// s3Action: {
816+
// bucketName: this.storage.emailBucket.bucketName,
817+
// kmsKeyArn: this.encryptionKey.keyArn,
818+
// objectKeyPrefix: 'tmp/email_in',
819+
// },
820+
// },
821+
// ],
822+
scanEnabled: config.email.server?.scan || true,
823+
tlsPolicy: 'Require',
824+
},
825+
})
828826

829827
const iamGroup = new iam.Group(this, 'IAMGroup', {
830828
groupName: `${this.appName}-${appEnv}-email-management-s3-group`,
@@ -849,7 +847,7 @@ export class StacksCloud extends Stack {
849847
's3:PutObjectVersionAcl',
850848
],
851849
resources: [
852-
`${this.storage.emailBucket.bucketArn}`,
850+
this.storage.emailBucket.bucketArn,
853851
`${this.storage.emailBucket.bucketArn}/*`,
854852
],
855853
})
@@ -859,9 +857,6 @@ export class StacksCloud extends Stack {
859857
statements: [policyStatement, listBucketsPolicyStatement],
860858
})
861859

862-
const cfnBucketPolicy = this.storage.emailBucket.node.findChild('Policy').node.findChild('Resource')
863-
ruleSet.node.addDependency(cfnBucketPolicy)
864-
865860
iamGroup.attachInlinePolicy(policy)
866861

867862
// Create a SES domain identity
@@ -1051,22 +1046,6 @@ export class StacksCloud extends Stack {
10511046

10521047
lambdaEmailConverterRole.addToPolicy(converterS3PolicyStatement)
10531048

1054-
this.storage.emailBucket.addToResourcePolicy(new iam.PolicyStatement({
1055-
sid: `AllowSESToInvokeLambda`,
1056-
principals: [sesPrincipal],
1057-
actions: [
1058-
'lambda:InvokeFunction',
1059-
],
1060-
conditions: {
1061-
StringEquals: {
1062-
'aws:SourceAccount': Stack.of(this).account,
1063-
},
1064-
ArnLike: {
1065-
'aws:SourceArn': `arn:aws:ses:${this.region}:${Stack.of(this).account}:receipt-rule-set/${ruleSetName}:receipt-rule/${ruleName}`,
1066-
},
1067-
},
1068-
}))
1069-
10701049
this.storage.emailBucket.addEventNotification(s3.EventType.OBJECT_CREATED_PUT, new s3n.LambdaDestination(lambdaEmailInbound), { prefix: 'tmp/email_in' })
10711050
this.storage.emailBucket.addEventNotification(s3.EventType.OBJECT_CREATED_PUT, new s3n.LambdaDestination(lambdaEmailOutbound), { prefix: 'tmp/email_out/json' })
10721051
this.storage.emailBucket.addEventNotification(s3.EventType.OBJECT_CREATED_COPY, new s3n.LambdaDestination(lambdaEmailConverter), { prefix: 'sent/' })
@@ -1202,6 +1181,7 @@ export class StacksCloud extends Stack {
12021181
versioned: true,
12031182
removalPolicy: RemovalPolicy.DESTROY,
12041183
autoDeleteObjects: true,
1184+
encryption: s3.BucketEncryption.KMS,
12051185
encryptionKey: this.encryptionKey,
12061186
enforceSSL: true,
12071187
publicReadAccess: false,
@@ -1231,13 +1211,14 @@ export class StacksCloud extends Stack {
12311211
removalPolicy: RemovalPolicy.DESTROY,
12321212
autoDeleteObjects: true,
12331213
encryptionKey: this.encryptionKey,
1214+
encryption: s3.BucketEncryption.KMS,
12341215
lifecycleRules: [
12351216
{
12361217
id: '24h',
1218+
enabled: true,
12371219
expiration: Duration.days(1),
12381220
noncurrentVersionExpiration: Duration.days(1),
12391221
prefix: 'today/',
1240-
enabled: true,
12411222
},
12421223
{
12431224
id: 'Intelligent transition for Inbox',
@@ -1280,6 +1261,8 @@ export class StacksCloud extends Stack {
12801261
versioned: true,
12811262
autoDeleteObjects: true,
12821263
removalPolicy: RemovalPolicy.DESTROY,
1264+
// encryption: s3.BucketEncryption.KMS, // does encryption for public files make sense?
1265+
// encryptionKey: this.encryptionKey,
12831266
})
12841267

12851268
Tags.of(bucket).add('daily-backup', 'true')

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
_Beta coming soon._
1212

13-
The goal of the framework is to _help you_ create & maintain frontends, backends, and clouds—without having to worry about the boilerplate. Stacks is a rapid application development framework, meeting all your full stack needs.
13+
The goal of the framework is to _help you_ create & maintain frontends, backends, and clouds—without having to worry about the boilerplate. Stacks is a rapid development framework, meeting all your full stack needs.
1414

1515
- Web & Desktop applications
1616
- Serverless & traditional APIs

bun.lockb

7.45 KB
Binary file not shown.

config/docs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ const sidebar = {
160160
{
161161
text: 'Let’s Build',
162162
items: [
163-
{ text: 'Build an API', link: '/bootcamp/api' },
164163
{ text: 'Build a Frontend', link: '/bootcamp/frontend' },
164+
{ text: 'Build an API', link: '/bootcamp/api' },
165165
{ text: 'Build a Documentation', link: '/bootcamp/docs' },
166166
{
167167
text: 'Build a Library',
@@ -175,6 +175,7 @@ const sidebar = {
175175
{ text: 'Build a CLI', link: '/bootcamp/cli' },
176176
{ text: 'Build a Desktop App', link: '/bootcamp/desktop' },
177177
{ text: 'Extend the Dashboard', link: '/bootcamp/dashboard' },
178+
{ text: 'Extend the Cloud', link: '/bootcamp/cloud' },
178179
],
179180
},
180181
{ text: 'Linting & Formatting', link: '/bootcamp/linting' },

docs/bootcamp/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# How to build an API?
2+
3+
wip

docs/bootcamp/cli.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# How to build an CLI?
2+
3+
wip

docs/bootcamp/cloud.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Extending the Cloud
2+
3+
wip

docs/bootcamp/dashboard.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Extending the Dashboard
2+
3+
wip

0 commit comments

Comments
 (0)