@aws-blocks/bb-file-bucket 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +36 -5
- package/README.md +26 -3
- package/dist/file-server.d.ts.map +1 -1
- package/dist/file-server.js +10 -1
- package/dist/file-server.test.js +16 -0
- package/dist/index.cdk.d.ts +2 -2
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +115 -25
- package/dist/index.cdk.test.js +261 -3
- package/dist/index.mock.d.ts.map +1 -1
- package/dist/index.mock.js +41 -1
- package/dist/index.test.js +85 -1
- package/dist/types.d.ts +69 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -3
- package/src/file-server.test.ts +22 -0
- package/src/file-server.ts +10 -1
- package/src/index.cdk.test.ts +347 -3
- package/src/index.cdk.ts +130 -25
- package/src/index.mock.ts +49 -2
- package/src/index.test.ts +131 -1
- package/src/types.ts +66 -5
- package/src/version.ts +1 -1
package/dist/index.cdk.test.js
CHANGED
|
@@ -10,13 +10,22 @@
|
|
|
10
10
|
import { test } from 'node:test';
|
|
11
11
|
import assert from 'node:assert';
|
|
12
12
|
import * as cdk from 'aws-cdk-lib';
|
|
13
|
-
import { Template } from 'aws-cdk-lib/assertions';
|
|
14
|
-
import {
|
|
13
|
+
import { Template, Match } from 'aws-cdk-lib/assertions';
|
|
14
|
+
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
|
|
15
|
+
import { Scope, DEFAULT_NODE_RUNTIME, BlocksPresets } from '@aws-blocks/core/cdk';
|
|
15
16
|
import { FileBucket } from './index.cdk.js';
|
|
17
|
+
// Minimal BlocksStack-shaped parent. The production code path uses BlocksStack,
|
|
18
|
+
// which exposes the shared `executionRole` (blocks grant to it) plus `handler`,
|
|
19
|
+
// both living inside a `cdk.Stack`. We reproduce them here so FileBucket can
|
|
20
|
+
// call grantReadWrite(this.executionRole) and still synth into a real stack. It
|
|
21
|
+
// also carries `defaults` — Building Blocks resolve `scope.defaults` by walking
|
|
22
|
+
// up to the owning BlocksStack/BlocksBackend, falling back to
|
|
23
|
+
// `globalThis.CURRENT_BLOCKS_STACK`, which is this stub in these tests.
|
|
16
24
|
class StubBlocksStack extends cdk.Stack {
|
|
17
25
|
handler;
|
|
18
26
|
executionRole;
|
|
19
27
|
id;
|
|
28
|
+
defaults = BlocksPresets.production;
|
|
20
29
|
constructor(scope, id) {
|
|
21
30
|
super(scope, id);
|
|
22
31
|
this.id = id;
|
|
@@ -32,11 +41,12 @@ class StubBlocksStack extends cdk.Stack {
|
|
|
32
41
|
});
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
|
-
function setup() {
|
|
44
|
+
function setup(defaults = BlocksPresets.production) {
|
|
36
45
|
const app = new cdk.App();
|
|
37
46
|
// S3 bucket names must be lowercase. The default-mode FileBucket derives
|
|
38
47
|
// its bucket name from the scope chain, so keep ids lowercase.
|
|
39
48
|
const stack = new StubBlocksStack(app, 'teststack');
|
|
49
|
+
stack.defaults = defaults;
|
|
40
50
|
const parent = new Scope('app');
|
|
41
51
|
return { stack, parent };
|
|
42
52
|
}
|
|
@@ -72,3 +82,251 @@ test('CDK: fromExisting skips derived-name validation even when the chain is ove
|
|
|
72
82
|
bucket: FileBucket.fromExisting('preexisting-bucket-123'),
|
|
73
83
|
}));
|
|
74
84
|
});
|
|
85
|
+
// ── Security hardening: secure defaults ─────────────────────────────────────
|
|
86
|
+
test('CDK: default FileBucket enforces SSL (aws:SecureTransport deny)', () => {
|
|
87
|
+
const { stack, parent } = setup();
|
|
88
|
+
new FileBucket(parent, 'uploads');
|
|
89
|
+
const template = Template.fromStack(stack);
|
|
90
|
+
// enforceSSL:true makes CDK attach a bucket policy denying non-TLS requests.
|
|
91
|
+
template.hasResourceProperties('AWS::S3::BucketPolicy', Match.objectLike({
|
|
92
|
+
PolicyDocument: Match.objectLike({
|
|
93
|
+
Statement: Match.arrayWith([
|
|
94
|
+
Match.objectLike({
|
|
95
|
+
Effect: 'Deny',
|
|
96
|
+
Condition: { Bool: { 'aws:SecureTransport': 'false' } },
|
|
97
|
+
}),
|
|
98
|
+
]),
|
|
99
|
+
}),
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
test('CDK: default FileBucket enables versioning (new secure default)', () => {
|
|
103
|
+
const { stack, parent } = setup();
|
|
104
|
+
new FileBucket(parent, 'uploads');
|
|
105
|
+
const template = Template.fromStack(stack);
|
|
106
|
+
template.hasResourceProperties('AWS::S3::Bucket', Match.objectLike({
|
|
107
|
+
VersioningConfiguration: { Status: 'Enabled' },
|
|
108
|
+
}));
|
|
109
|
+
});
|
|
110
|
+
test('CDK: versioned:false opt-out disables versioning', () => {
|
|
111
|
+
const { stack, parent } = setup();
|
|
112
|
+
new FileBucket(parent, 'uploads', { versioned: false });
|
|
113
|
+
const template = Template.fromStack(stack);
|
|
114
|
+
// No VersioningConfiguration is emitted when versioning is disabled.
|
|
115
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
116
|
+
const props = Object.values(buckets)[0].Properties ?? {};
|
|
117
|
+
assert.strictEqual(props.VersioningConfiguration, undefined);
|
|
118
|
+
});
|
|
119
|
+
// ── Posture routed through BlocksDefaults (PR review comment C) ──────────────
|
|
120
|
+
test('CDK: default FileBucket adopts the SANDBOX removal posture (DESTROY + autoDelete)', () => {
|
|
121
|
+
const { stack, parent } = setup(BlocksPresets.sandbox);
|
|
122
|
+
new FileBucket(parent, 'uploads');
|
|
123
|
+
const template = Template.fromStack(stack);
|
|
124
|
+
// DESTROY removal policy plus the auto-delete custom resource CDK wires in
|
|
125
|
+
// only when autoDeleteObjects is true.
|
|
126
|
+
template.hasResource('AWS::S3::Bucket', { DeletionPolicy: 'Delete' });
|
|
127
|
+
template.resourceCountIs('Custom::S3AutoDeleteObjects', 1);
|
|
128
|
+
});
|
|
129
|
+
test('CDK: default FileBucket adopts the PRODUCTION removal posture (RETAIN, no autoDelete)', () => {
|
|
130
|
+
const { stack, parent } = setup(BlocksPresets.production);
|
|
131
|
+
new FileBucket(parent, 'uploads');
|
|
132
|
+
const template = Template.fromStack(stack);
|
|
133
|
+
template.hasResource('AWS::S3::Bucket', { DeletionPolicy: 'Retain' });
|
|
134
|
+
template.resourceCountIs('Custom::S3AutoDeleteObjects', 0);
|
|
135
|
+
});
|
|
136
|
+
test('CDK: per-block removalPolicy overrides the resolved default', () => {
|
|
137
|
+
const { stack, parent } = setup(BlocksPresets.production);
|
|
138
|
+
new FileBucket(parent, 'uploads', { removalPolicy: 'destroy' });
|
|
139
|
+
const template = Template.fromStack(stack);
|
|
140
|
+
// Per-block 'destroy' wins over the production RETAIN default and enables
|
|
141
|
+
// autoDeleteObjects alongside it.
|
|
142
|
+
template.hasResource('AWS::S3::Bucket', { DeletionPolicy: 'Delete' });
|
|
143
|
+
template.resourceCountIs('Custom::S3AutoDeleteObjects', 1);
|
|
144
|
+
});
|
|
145
|
+
test('CDK: accessLogging provisions a locked-down log bucket with lifecycle + logging config', () => {
|
|
146
|
+
const { stack, parent } = setup();
|
|
147
|
+
new FileBucket(parent, 'uploads', { accessLogging: true });
|
|
148
|
+
const template = Template.fromStack(stack);
|
|
149
|
+
// Main bucket + dedicated access-log bucket.
|
|
150
|
+
template.resourceCountIs('AWS::S3::Bucket', 2);
|
|
151
|
+
// Both buckets are TLS-enforced: enforceSSL:true attaches a bucket policy
|
|
152
|
+
// with an aws:SecureTransport deny to EACH bucket (main + log bucket), so
|
|
153
|
+
// the log bucket is provably locked down, not just the main one.
|
|
154
|
+
template.resourceCountIs('AWS::S3::BucketPolicy', 2);
|
|
155
|
+
const policies = template.findResources('AWS::S3::BucketPolicy');
|
|
156
|
+
for (const policy of Object.values(policies)) {
|
|
157
|
+
assert.ok(policy.Properties.PolicyDocument.Statement.some((s) => s.Effect === 'Deny' &&
|
|
158
|
+
s.Condition?.Bool?.['aws:SecureTransport'] === 'false'), 'every bucket (main + log) must have an enforceSSL deny statement');
|
|
159
|
+
}
|
|
160
|
+
// Distinguish the two buckets: the main bucket carries the derived
|
|
161
|
+
// BucketName; the log bucket does not. Prove the MAIN bucket's
|
|
162
|
+
// LoggingConfiguration points at the LOG bucket specifically.
|
|
163
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
164
|
+
const entries = Object.entries(buckets);
|
|
165
|
+
const mainEntry = entries.find(([, r]) => r.Properties.BucketName !== undefined);
|
|
166
|
+
const logEntry = entries.find(([id]) => id !== mainEntry?.[0]);
|
|
167
|
+
assert.ok(mainEntry && logEntry, 'expected one named main bucket and one log bucket');
|
|
168
|
+
const [logLogicalId, logResource] = logEntry;
|
|
169
|
+
// Main bucket delivers its access logs to the log bucket under access-logs/.
|
|
170
|
+
assert.deepStrictEqual(mainEntry[1].Properties.LoggingConfiguration, { DestinationBucketName: { Ref: logLogicalId }, LogFilePrefix: 'access-logs/' });
|
|
171
|
+
// Log bucket expires access logs after the framework logRetention default
|
|
172
|
+
// (production preset => ONE_YEAR => 365 days).
|
|
173
|
+
assert.ok(logResource.Properties.LifecycleConfiguration.Rules.some((r) => r.ExpirationInDays === 365 && r.Status === 'Enabled'), 'log bucket must expire access logs after the production logRetention (365 days)');
|
|
174
|
+
// Log bucket blocks all public access.
|
|
175
|
+
assert.deepStrictEqual(logResource.Properties.PublicAccessBlockConfiguration, {
|
|
176
|
+
BlockPublicAcls: true,
|
|
177
|
+
BlockPublicPolicy: true,
|
|
178
|
+
IgnorePublicAcls: true,
|
|
179
|
+
RestrictPublicBuckets: true,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
test('CDK: accessLogging resolves from defaults — a preset opting in creates the log bucket with no per-block option', () => {
|
|
183
|
+
const { stack, parent } = setup({ ...BlocksPresets.production, accessLogging: true });
|
|
184
|
+
new FileBucket(parent, 'uploads');
|
|
185
|
+
const template = Template.fromStack(stack);
|
|
186
|
+
// No per-block accessLogging option, yet the resolved default enables it.
|
|
187
|
+
template.resourceCountIs('AWS::S3::Bucket', 2);
|
|
188
|
+
});
|
|
189
|
+
test('CDK: access-log lifecycle expiration equals Duration.days(defaults.logRetention) — sandbox = 7', () => {
|
|
190
|
+
const { stack, parent } = setup({ ...BlocksPresets.sandbox, accessLogging: true });
|
|
191
|
+
new FileBucket(parent, 'uploads', { removalPolicy: 'retain' });
|
|
192
|
+
const template = Template.fromStack(stack);
|
|
193
|
+
// The per-block 'retain' override wins over the sandbox DESTROY default: the
|
|
194
|
+
// main bucket is retained on teardown.
|
|
195
|
+
template.hasResource('AWS::S3::Bucket', { DeletionPolicy: 'Retain' });
|
|
196
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
197
|
+
const logEntry = Object.values(buckets).find((r) => r.Properties.BucketName === undefined);
|
|
198
|
+
assert.ok(logEntry, 'expected a log bucket');
|
|
199
|
+
assert.ok(logEntry.Properties.LifecycleConfiguration.Rules.some((r) => r.ExpirationInDays === 7 && r.Status === 'Enabled'), 'log bucket must expire access logs after the sandbox logRetention (ONE_WEEK = 7 days)');
|
|
200
|
+
});
|
|
201
|
+
test('CDK: no access-log bucket is created when accessLogging resolves false', () => {
|
|
202
|
+
const { stack, parent } = setup();
|
|
203
|
+
new FileBucket(parent, 'uploads');
|
|
204
|
+
const template = Template.fromStack(stack);
|
|
205
|
+
template.resourceCountIs('AWS::S3::Bucket', 1);
|
|
206
|
+
});
|
|
207
|
+
test('CDK: logRetention INFINITE omits the access-log lifecycle rule (logs kept indefinitely)', () => {
|
|
208
|
+
const { stack, parent } = setup({
|
|
209
|
+
...BlocksPresets.production,
|
|
210
|
+
logRetention: RetentionDays.INFINITE,
|
|
211
|
+
accessLogging: true,
|
|
212
|
+
});
|
|
213
|
+
new FileBucket(parent, 'uploads');
|
|
214
|
+
const template = Template.fromStack(stack);
|
|
215
|
+
// Main bucket + log bucket both exist.
|
|
216
|
+
template.resourceCountIs('AWS::S3::Bucket', 2);
|
|
217
|
+
// The access-LOG bucket (the one without a derived BucketName) must carry NO
|
|
218
|
+
// LifecycleConfiguration at all — INFINITE means "never expire", so the rule
|
|
219
|
+
// is omitted rather than emitted at a spurious 9999-day expiry.
|
|
220
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
221
|
+
const logEntry = Object.values(buckets).find((r) => r.Properties.BucketName === undefined);
|
|
222
|
+
assert.ok(logEntry, 'expected a log bucket');
|
|
223
|
+
assert.strictEqual(logEntry.Properties.LifecycleConfiguration, undefined, 'INFINITE logRetention must omit the access-log LifecycleConfiguration entirely');
|
|
224
|
+
});
|
|
225
|
+
test('CDK: per-block accessLogging:false overrides a defaults-enabled preset (?? treats explicit false as non-nullish)', () => {
|
|
226
|
+
const { stack, parent } = setup({ ...BlocksPresets.production, accessLogging: true });
|
|
227
|
+
new FileBucket(parent, 'uploads', { accessLogging: false });
|
|
228
|
+
const template = Template.fromStack(stack);
|
|
229
|
+
// Defaults opt logging in, but the explicit per-block `false` wins: only the
|
|
230
|
+
// main bucket is provisioned, no dedicated access-log bucket.
|
|
231
|
+
template.resourceCountIs('AWS::S3::Bucket', 1);
|
|
232
|
+
});
|
|
233
|
+
// ── Noncurrent-version expiration (PR review comment A) ──────────────────────
|
|
234
|
+
test('CDK: versioned bucket expires noncurrent versions after the default 90 days', () => {
|
|
235
|
+
const { stack, parent } = setup();
|
|
236
|
+
new FileBucket(parent, 'uploads');
|
|
237
|
+
const template = Template.fromStack(stack);
|
|
238
|
+
template.hasResourceProperties('AWS::S3::Bucket', Match.objectLike({
|
|
239
|
+
VersioningConfiguration: { Status: 'Enabled' },
|
|
240
|
+
LifecycleConfiguration: Match.objectLike({
|
|
241
|
+
Rules: Match.arrayWith([
|
|
242
|
+
Match.objectLike({
|
|
243
|
+
NoncurrentVersionExpiration: { NoncurrentDays: 90 },
|
|
244
|
+
Status: 'Enabled',
|
|
245
|
+
}),
|
|
246
|
+
]),
|
|
247
|
+
}),
|
|
248
|
+
}));
|
|
249
|
+
});
|
|
250
|
+
test('CDK: noncurrentVersionExpirationDays is honored', () => {
|
|
251
|
+
const { stack, parent } = setup();
|
|
252
|
+
new FileBucket(parent, 'uploads', { noncurrentVersionExpirationDays: 30 });
|
|
253
|
+
const template = Template.fromStack(stack);
|
|
254
|
+
template.hasResourceProperties('AWS::S3::Bucket', Match.objectLike({
|
|
255
|
+
LifecycleConfiguration: Match.objectLike({
|
|
256
|
+
Rules: Match.arrayWith([
|
|
257
|
+
Match.objectLike({ NoncurrentVersionExpiration: { NoncurrentDays: 30 } }),
|
|
258
|
+
]),
|
|
259
|
+
}),
|
|
260
|
+
}));
|
|
261
|
+
});
|
|
262
|
+
test('CDK: noncurrentVersionExpirationDays of 0 throws at synth', () => {
|
|
263
|
+
const { parent } = setup();
|
|
264
|
+
assert.throws(() => new FileBucket(parent, 'uploads', { noncurrentVersionExpirationDays: 0 }), (err) => err instanceof Error &&
|
|
265
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
266
|
+
/got 0/.test(err.message));
|
|
267
|
+
});
|
|
268
|
+
test('CDK: negative noncurrentVersionExpirationDays throws at synth', () => {
|
|
269
|
+
const { parent } = setup();
|
|
270
|
+
assert.throws(() => new FileBucket(parent, 'uploads', { noncurrentVersionExpirationDays: -1 }), (err) => err instanceof Error &&
|
|
271
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
272
|
+
/got -1/.test(err.message));
|
|
273
|
+
});
|
|
274
|
+
test('CDK: non-integer noncurrentVersionExpirationDays throws at synth', () => {
|
|
275
|
+
const { parent } = setup();
|
|
276
|
+
assert.throws(() => new FileBucket(parent, 'uploads', { noncurrentVersionExpirationDays: 1.5 }), (err) => err instanceof Error &&
|
|
277
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message));
|
|
278
|
+
});
|
|
279
|
+
test('CDK: versioned:false bucket has NO noncurrent-version expiration rule', () => {
|
|
280
|
+
const { stack, parent } = setup();
|
|
281
|
+
new FileBucket(parent, 'uploads', { versioned: false });
|
|
282
|
+
const template = Template.fromStack(stack);
|
|
283
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
284
|
+
const props = (Object.values(buckets)[0].Properties ?? {});
|
|
285
|
+
const rules = props.LifecycleConfiguration?.Rules ?? [];
|
|
286
|
+
assert.ok(!rules.some((r) => r.NoncurrentVersionExpiration !== undefined), 'a non-versioned bucket must not carry a noncurrent-version expiration rule');
|
|
287
|
+
});
|
|
288
|
+
test('CDK: noncurrentVersionExpirationDays FORMAT is validated even when versioned:false', () => {
|
|
289
|
+
// The format guard is decoupled from the `versioned` gate: a malformed value
|
|
290
|
+
// must fail loudly at synth regardless of whether versioning is on, rather
|
|
291
|
+
// than being silently ignored because the rule would not be applied.
|
|
292
|
+
for (const bad of [0, -1, 1.5]) {
|
|
293
|
+
const { parent } = setup();
|
|
294
|
+
assert.throws(() => new FileBucket(parent, 'uploads', { versioned: false, noncurrentVersionExpirationDays: bad }), (err) => err instanceof Error &&
|
|
295
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
296
|
+
new RegExp(`got ${bad}`).test(err.message), `versioned:false with noncurrentVersionExpirationDays=${bad} must throw at synth`);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
test('CDK: versioned:false with NO noncurrent option does not throw and adds no noncurrent rule', () => {
|
|
300
|
+
const { stack, parent } = setup();
|
|
301
|
+
assert.doesNotThrow(() => new FileBucket(parent, 'uploads', { versioned: false }));
|
|
302
|
+
const template = Template.fromStack(stack);
|
|
303
|
+
const buckets = template.findResources('AWS::S3::Bucket');
|
|
304
|
+
const props = (Object.values(buckets)[0].Properties ?? {});
|
|
305
|
+
const rules = props.LifecycleConfiguration?.Rules ?? [];
|
|
306
|
+
assert.ok(!rules.some((r) => r.NoncurrentVersionExpiration !== undefined), 'versioned:false with no noncurrent option must add no noncurrent-version expiration rule');
|
|
307
|
+
});
|
|
308
|
+
// ── CORS synth guard (unchanged behavior) ───────────────────────────────────
|
|
309
|
+
test('CDK: wildcard-origin CORS with a mutating method throws at synth', () => {
|
|
310
|
+
const { parent } = setup();
|
|
311
|
+
assert.throws(() => new FileBucket(parent, 'uploads', {
|
|
312
|
+
corsRules: [
|
|
313
|
+
{ allowedOrigins: ['*'], allowedMethods: ['GET', 'PUT'] },
|
|
314
|
+
],
|
|
315
|
+
}), (err) => err instanceof Error &&
|
|
316
|
+
/\*/.test(err.message) &&
|
|
317
|
+
/PUT/.test(err.message));
|
|
318
|
+
});
|
|
319
|
+
test('CDK: wildcard-origin CORS with only safe methods is allowed', () => {
|
|
320
|
+
const { parent } = setup();
|
|
321
|
+
assert.doesNotThrow(() => new FileBucket(parent, 'uploads', {
|
|
322
|
+
corsRules: [{ allowedOrigins: ['*'], allowedMethods: ['GET', 'HEAD'] }],
|
|
323
|
+
}));
|
|
324
|
+
});
|
|
325
|
+
test('CDK: explicit-origin CORS with a mutating method is allowed', () => {
|
|
326
|
+
const { parent } = setup();
|
|
327
|
+
assert.doesNotThrow(() => new FileBucket(parent, 'uploads', {
|
|
328
|
+
corsRules: [
|
|
329
|
+
{ allowedOrigins: ['https://app.example.com'], allowedMethods: ['PUT', 'POST'] },
|
|
330
|
+
],
|
|
331
|
+
}));
|
|
332
|
+
});
|
package/dist/index.mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAWpD,OAAO,KAAK,EACX,iBAAiB,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EACzD,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EACxC,kBAAkB,EAAE,gBAAgB,EAAE,eAAe,EACrD,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EACjD,MAAM,YAAY,CAAC;AAEpB,YAAY,EACX,iBAAiB,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EACxE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,EACjE,kBAAkB,EAAE,gBAAgB,EAAE,eAAe,EACrD,sBAAsB,EAAE,oBAAoB,EAC5C,mBAAmB,EAAE,sBAAsB,EAAE,sBAAsB,EACnE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,GACjD,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"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"}
|
package/dist/index.mock.js
CHANGED
|
@@ -13,6 +13,14 @@ import { BB_NAME, BB_VERSION } from './version.js';
|
|
|
13
13
|
export { FileBucketErrors } from './errors.js';
|
|
14
14
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
15
15
|
const MAX_KEY_BYTES = 1024; // S3 key limit
|
|
16
|
+
// Mirrors of the CDK synth-time-guard constants (index.cdk.ts). Duplicated here
|
|
17
|
+
// rather than imported — the CDK entry (index.cdk.ts) pulls in aws-cdk-lib and
|
|
18
|
+
// must never be imported into the mock runtime. Kept byte-identical to the CDK
|
|
19
|
+
// definitions so the two guards below reject exactly what `cdk synth` rejects.
|
|
20
|
+
/** Default number of days after which noncurrent object versions expire. */
|
|
21
|
+
const DEFAULT_NONCURRENT_VERSION_EXPIRATION_DAYS = 90;
|
|
22
|
+
/** HTTP methods that mutate bucket state; unsafe to expose to wildcard origins. */
|
|
23
|
+
const MUTATING_CORS_METHODS = ['PUT', 'POST', 'DELETE'];
|
|
16
24
|
function blocksError(name, message) {
|
|
17
25
|
const err = new Error(`${name}: ${message}`);
|
|
18
26
|
err.name = name;
|
|
@@ -57,9 +65,41 @@ export class FileBucket extends Scope {
|
|
|
57
65
|
// parity with the CDK path.
|
|
58
66
|
if (!options?.bucket)
|
|
59
67
|
validateBucketName(this.fullId);
|
|
68
|
+
// Mirror the CDK's two synth-time guards (index.cdk.ts) VERBATIM so a
|
|
69
|
+
// local/unit run rejects exactly what `cdk synth` would. Gated on
|
|
70
|
+
// `!options?.bucket` alongside validateBucketName: the CDK's
|
|
71
|
+
// external-bucket branch returns before these checks, so a wrapped
|
|
72
|
+
// bucket bypasses them here too. Plain `throw new Error(...)` (no
|
|
73
|
+
// `name`, no blocksError factory) to match the CDK path exactly — using
|
|
74
|
+
// blocksError() would set an `error.name` the CDK guards don't, breaking
|
|
75
|
+
// mock↔cdk parity.
|
|
76
|
+
if (!options?.bucket) {
|
|
77
|
+
// Reject unsafe CORS: a wildcard origin ('*') combined with a mutating
|
|
78
|
+
// method (PUT/POST/DELETE) lets any site issue state-changing
|
|
79
|
+
// cross-origin requests.
|
|
80
|
+
for (const rule of options?.corsRules ?? []) {
|
|
81
|
+
if (rule.allowedOrigins.includes('*')) {
|
|
82
|
+
const mutating = rule.allowedMethods.filter(m => MUTATING_CORS_METHODS.includes(m));
|
|
83
|
+
if (mutating.length > 0) {
|
|
84
|
+
throw new Error(`FileBucket "${this.fullId}": CORS rule with wildcard origin '*' must not allow mutating method(s) ${mutating.join(', ')}. ` +
|
|
85
|
+
`Specify explicit allowedOrigins (e.g. 'https://app.example.com') for ${mutating.join(', ')} instead of '*'.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Reject a non-positive or non-integer noncurrent-version expiration.
|
|
90
|
+
// The FORMAT is validated regardless of `versioned` (matching CDK) so
|
|
91
|
+
// a malformed value is caught even when versioning is off.
|
|
92
|
+
if (options?.noncurrentVersionExpirationDays !== undefined) {
|
|
93
|
+
const days = options.noncurrentVersionExpirationDays;
|
|
94
|
+
if (!Number.isInteger(days) || days <= 0) {
|
|
95
|
+
throw new Error(`FileBucket "${this.fullId}": noncurrentVersionExpirationDays must be a positive integer (got ${days}). ` +
|
|
96
|
+
`Omit it to use the default of ${DEFAULT_NONCURRENT_VERSION_EXPIRATION_DAYS} days.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
60
100
|
this.log = options?.logger ?? new Logger(this, 'logger', { level: 'error' });
|
|
61
101
|
this.dataDir = getMockDataDir(this);
|
|
62
|
-
this.versioned = options?.versioned ??
|
|
102
|
+
this.versioned = options?.versioned ?? true;
|
|
63
103
|
this.registerClientMiddleware('@aws-blocks/bb-file-bucket/middleware');
|
|
64
104
|
this.registerDevAttachment('@aws-blocks/bb-file-bucket/file-server');
|
|
65
105
|
registerSdkIdentifiers(this.fullId, { bucketName: `mock-${this.fullId}` });
|
package/dist/index.test.js
CHANGED
|
@@ -298,8 +298,11 @@ test('versioned: listVersions returns newest first', async () => {
|
|
|
298
298
|
});
|
|
299
299
|
// ── Static type checks: conditional version types ───────────────────────────
|
|
300
300
|
function _conditionalVersionTypeChecks() {
|
|
301
|
-
|
|
301
|
+
// Explicit opt-out selects the non-versioned option types.
|
|
302
|
+
const plain = new FileBucket(scope, 'plain', { versioned: false });
|
|
302
303
|
const versioned = new FileBucket(scope, 'versioned', { versioned: true });
|
|
304
|
+
// No options now defaults to versioned-aware typings (Default: true).
|
|
305
|
+
const dflt = new FileBucket(scope, 'dflt');
|
|
303
306
|
// Non-versioned: get/delete accept no options
|
|
304
307
|
plain.get('file.txt');
|
|
305
308
|
plain.delete('file.txt');
|
|
@@ -315,4 +318,85 @@ function _conditionalVersionTypeChecks() {
|
|
|
315
318
|
versioned.delete('file.txt');
|
|
316
319
|
versioned.delete('file.txt', { versionId: 'v1' });
|
|
317
320
|
versioned.getUrl('file.txt', { versionId: 'v1', expiresIn: 600 });
|
|
321
|
+
// Default (no options) is versioned-aware: versionId is accepted.
|
|
322
|
+
dflt.get('file.txt');
|
|
323
|
+
dflt.get('file.txt', { versionId: 'v1' });
|
|
324
|
+
dflt.delete('file.txt', { versionId: 'v1' });
|
|
325
|
+
dflt.getUrl('file.txt', { versionId: 'v1', expiresIn: 600 });
|
|
318
326
|
}
|
|
327
|
+
// ── Versioning on by default (new secure default) ───────────────────────────
|
|
328
|
+
test('default (no options) bucket is versioned: put creates versions', async () => {
|
|
329
|
+
const bucket = new FileBucket(scope, 'default-versioned');
|
|
330
|
+
await bucket.put('file.txt', 'v1');
|
|
331
|
+
await bucket.put('file.txt', 'v2');
|
|
332
|
+
const versions = await bucket.listVersions('file.txt');
|
|
333
|
+
assert.strictEqual(versions.length, 2);
|
|
334
|
+
assert.strictEqual(versions[0].isCurrent, true);
|
|
335
|
+
});
|
|
336
|
+
test('versioned:false opt-out disables versioning at runtime', async () => {
|
|
337
|
+
const bucket = new FileBucket(scope, 'optout-versioned', { versioned: false });
|
|
338
|
+
await bucket.put('file.txt', 'v1');
|
|
339
|
+
await bucket.put('file.txt', 'v2');
|
|
340
|
+
const versions = await bucket.listVersions('file.txt');
|
|
341
|
+
assert.strictEqual(versions.length, 0);
|
|
342
|
+
const file = await bucket.get('file.txt');
|
|
343
|
+
assert.ok(file);
|
|
344
|
+
assert.strictEqual(file.body.toString(), 'v2');
|
|
345
|
+
});
|
|
346
|
+
// ── Synth-time validation parity (mock mirrors the CDK's index.cdk.ts guards) ─
|
|
347
|
+
// The mock reproduces the CDK's two synth-time guards VERBATIM so a local/unit
|
|
348
|
+
// run fails the same way `cdk synth` would (mock↔cdk parity). Mirrors the
|
|
349
|
+
// corresponding cases in index.cdk.test.ts.
|
|
350
|
+
test('mock: noncurrentVersionExpirationDays of 0 throws', () => {
|
|
351
|
+
assert.throws(() => new FileBucket(scope, 'uploads', { noncurrentVersionExpirationDays: 0 }), (err) => err instanceof Error &&
|
|
352
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
353
|
+
/got 0/.test(err.message));
|
|
354
|
+
});
|
|
355
|
+
test('mock: negative noncurrentVersionExpirationDays throws', () => {
|
|
356
|
+
assert.throws(() => new FileBucket(scope, 'uploads', { noncurrentVersionExpirationDays: -1 }), (err) => err instanceof Error &&
|
|
357
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
358
|
+
/got -1/.test(err.message));
|
|
359
|
+
});
|
|
360
|
+
test('mock: non-integer noncurrentVersionExpirationDays throws', () => {
|
|
361
|
+
assert.throws(() => new FileBucket(scope, 'uploads', { noncurrentVersionExpirationDays: 1.5 }), (err) => err instanceof Error &&
|
|
362
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message));
|
|
363
|
+
});
|
|
364
|
+
test('mock: noncurrentVersionExpirationDays FORMAT is validated even when versioned:false', () => {
|
|
365
|
+
// Format guard is decoupled from the `versioned` gate (matches CDK): a
|
|
366
|
+
// malformed value must fail regardless of whether versioning is on.
|
|
367
|
+
assert.throws(() => new FileBucket(scope, 'uploads', { versioned: false, noncurrentVersionExpirationDays: 0 }), (err) => err instanceof Error &&
|
|
368
|
+
/noncurrentVersionExpirationDays must be a positive integer/.test(err.message) &&
|
|
369
|
+
/got 0/.test(err.message));
|
|
370
|
+
});
|
|
371
|
+
test('mock: valid noncurrentVersionExpirationDays does not throw', () => {
|
|
372
|
+
assert.doesNotThrow(() => new FileBucket(scope, 'uploads', { noncurrentVersionExpirationDays: 30 }));
|
|
373
|
+
});
|
|
374
|
+
test('mock: wildcard-origin CORS with a mutating method throws', () => {
|
|
375
|
+
assert.throws(() => new FileBucket(scope, 'uploads', {
|
|
376
|
+
corsRules: [{ allowedOrigins: ['*'], allowedMethods: ['GET', 'PUT'] }],
|
|
377
|
+
}), (err) => err instanceof Error &&
|
|
378
|
+
/\*/.test(err.message) &&
|
|
379
|
+
/PUT/.test(err.message));
|
|
380
|
+
});
|
|
381
|
+
test('mock: wildcard-origin CORS with only safe methods is allowed', () => {
|
|
382
|
+
assert.doesNotThrow(() => new FileBucket(scope, 'uploads', {
|
|
383
|
+
corsRules: [{ allowedOrigins: ['*'], allowedMethods: ['GET', 'HEAD'] }],
|
|
384
|
+
}));
|
|
385
|
+
});
|
|
386
|
+
test('mock: explicit-origin CORS with a mutating method is allowed', () => {
|
|
387
|
+
assert.doesNotThrow(() => new FileBucket(scope, 'uploads', {
|
|
388
|
+
corsRules: [{ allowedOrigins: ['https://app.example.com'], allowedMethods: ['PUT', 'POST'] }],
|
|
389
|
+
}));
|
|
390
|
+
});
|
|
391
|
+
test('mock: when both guards are tripped, the CORS guard fires first (matches CDK order)', () => {
|
|
392
|
+
// The CDK (index.cdk.ts) evaluates the CORS guard before the noncurrent
|
|
393
|
+
// check, so a config that violates BOTH must surface the CORS message — not
|
|
394
|
+
// the noncurrent one — in the mock too, keeping mock↔cdk parity.
|
|
395
|
+
assert.throws(() => new FileBucket(scope, 'uploads', {
|
|
396
|
+
noncurrentVersionExpirationDays: 0,
|
|
397
|
+
corsRules: [{ allowedOrigins: ['*'], allowedMethods: ['PUT'] }],
|
|
398
|
+
}), (err) => err instanceof Error &&
|
|
399
|
+
/CORS rule with wildcard origin/.test(err.message) &&
|
|
400
|
+
/PUT/.test(err.message) &&
|
|
401
|
+
!/noncurrentVersionExpirationDays/.test(err.message));
|
|
402
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -4,13 +4,61 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { ChildLogger } from '@aws-blocks/bb-logger';
|
|
6
6
|
export interface FileBucketOptions {
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Enable object versioning. Default: true. Pass `false` to opt out.
|
|
9
|
+
*
|
|
10
|
+
* Note for the version-aware method typings (`get`/`delete`/`getUrl`/
|
|
11
|
+
* `getFileHandle`): selecting the NON-versioned (optionless) typings
|
|
12
|
+
* requires the literal `versioned: false`. A non-literal `boolean` value
|
|
13
|
+
* (e.g. one widened from a variable) — or omitting the option entirely —
|
|
14
|
+
* resolves to the versioned-aware typings, which match the default-on
|
|
15
|
+
* runtime behavior. See {@link GetOptionsFor} et al.
|
|
16
|
+
*/
|
|
8
17
|
versioned?: boolean;
|
|
9
18
|
/** CORS rules for browser-based access. */
|
|
10
19
|
corsRules?: CorsRule[];
|
|
11
20
|
/** Lifecycle rules for automatic object expiration or transitions. */
|
|
12
21
|
lifecycleRules?: LifecycleRule[];
|
|
13
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Enable S3 server access logging. Default: false (opt-in).
|
|
24
|
+
*
|
|
25
|
+
* When `true`, a dedicated, locked-down log bucket is provisioned
|
|
26
|
+
* (all public access blocked, S3-managed encryption, SSL enforced) and
|
|
27
|
+
* the main bucket delivers its access logs there under the
|
|
28
|
+
* `access-logs/` prefix. Access logs are expired automatically after the
|
|
29
|
+
* stack-wide `logRetention` default (see `BlocksDefaults.logRetention`);
|
|
30
|
+
* `RetentionDays.INFINITE` keeps them indefinitely (no expiry rule).
|
|
31
|
+
*
|
|
32
|
+
* Resolves from the stack `defaults.accessLogging` when omitted, so a
|
|
33
|
+
* production-postured stack can opt every FileBucket into logging without
|
|
34
|
+
* a per-block option. Ignored by the mock and browser runtimes (no AWS
|
|
35
|
+
* resource).
|
|
36
|
+
*/
|
|
37
|
+
accessLogging?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Days after which NONCURRENT object versions are permanently expired,
|
|
40
|
+
* bounding the storage cost that versioning would otherwise let grow
|
|
41
|
+
* unbounded. Only applies when versioning is enabled (the default).
|
|
42
|
+
* Default: 90.
|
|
43
|
+
*
|
|
44
|
+
* Must be a positive integer; a non-positive or non-integer value is
|
|
45
|
+
* rejected at synth. There is no "disable" sentinel — noncurrent-version
|
|
46
|
+
* expiration is always on for a versioned bucket to cap version growth. To
|
|
47
|
+
* opt out entirely, disable versioning (`versioned: false`), which drops
|
|
48
|
+
* the rule. Ignored by the mock and browser runtimes (no AWS resource).
|
|
49
|
+
*/
|
|
50
|
+
noncurrentVersionExpirationDays?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Wrap an existing S3 bucket instead of creating one.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* When set, FileBucket binds to the supplied bucket as-is and returns early:
|
|
56
|
+
* NONE of the secure defaults this construct normally applies are applied to
|
|
57
|
+
* an externally-supplied bucket — not `enforceSSL`, versioning + noncurrent-version
|
|
58
|
+
* expiration, server access logging, `blockPublicAccess`, encryption, nor the
|
|
59
|
+
* wildcard-CORS guard. You own that bucket's security posture; configure these
|
|
60
|
+
* on the bucket itself (or via its own CDK construct) before wrapping it.
|
|
61
|
+
*/
|
|
14
62
|
bucket?: ExternalBucketRef;
|
|
15
63
|
/**
|
|
16
64
|
* CDK removal behavior for the underlying S3 bucket. When omitted,
|
|
@@ -74,24 +122,32 @@ export interface VersionedGetUrlOptions extends GetUrlOptions {
|
|
|
74
122
|
/**
|
|
75
123
|
* Resolves the get options type based on whether versioning is enabled.
|
|
76
124
|
* Versioned buckets accept `{ versionId }`, non-versioned accept no options.
|
|
125
|
+
* Versioning is on by default, so only an explicit `versioned: false` selects
|
|
126
|
+
* the non-versioned (optionless) shape. A non-literal `boolean` (widened from
|
|
127
|
+
* a variable) or an absent `versioned` resolves to the versioned-aware shape,
|
|
128
|
+
* matching the default-on runtime behavior.
|
|
77
129
|
*/
|
|
78
130
|
export type GetOptionsFor<O extends FileBucketOptions> = O extends {
|
|
79
|
-
versioned:
|
|
80
|
-
} ?
|
|
131
|
+
versioned: false;
|
|
132
|
+
} ? undefined : VersionedGetOptions;
|
|
81
133
|
/**
|
|
82
134
|
* Resolves the delete options type based on whether versioning is enabled.
|
|
83
135
|
* Versioned buckets accept `{ versionId }`, non-versioned accept no options.
|
|
136
|
+
* Versioning is on by default, so only an explicit `versioned: false` selects
|
|
137
|
+
* the non-versioned (optionless) shape.
|
|
84
138
|
*/
|
|
85
139
|
export type DeleteOptionsFor<O extends FileBucketOptions> = O extends {
|
|
86
|
-
versioned:
|
|
87
|
-
} ?
|
|
140
|
+
versioned: false;
|
|
141
|
+
} ? undefined : VersionedDeleteOptions;
|
|
88
142
|
/**
|
|
89
143
|
* Resolves the getUrl/getFileHandle options type based on whether versioning is enabled.
|
|
90
144
|
* Versioned buckets accept `{ expiresIn, versionId }`, non-versioned accept `{ expiresIn }`.
|
|
145
|
+
* Versioning is on by default, so only an explicit `versioned: false` selects
|
|
146
|
+
* the non-versioned shape.
|
|
91
147
|
*/
|
|
92
148
|
export type GetUrlOptionsFor<O extends FileBucketOptions> = O extends {
|
|
93
|
-
versioned:
|
|
94
|
-
} ?
|
|
149
|
+
versioned: false;
|
|
150
|
+
} ? GetUrlOptions : VersionedGetUrlOptions;
|
|
95
151
|
export interface FileContent {
|
|
96
152
|
/** The file body as a Buffer. */
|
|
97
153
|
body: Buffer;
|
|
@@ -136,6 +192,11 @@ export interface LifecycleRule {
|
|
|
136
192
|
/** Days after creation to transition to Infrequent Access. */
|
|
137
193
|
transitionToIaDays?: number;
|
|
138
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* A reference to a pre-existing S3 bucket to wrap via {@link FileBucketOptions.bucket}
|
|
197
|
+
* (see that field's remarks — a wrapped bucket does not receive FileBucket's
|
|
198
|
+
* secure defaults). Produced by `FileBucket.fromExisting(bucketName)`.
|
|
199
|
+
*/
|
|
139
200
|
export interface ExternalBucketRef {
|
|
140
201
|
readonly __brand: 'ExternalBucketRef';
|
|
141
202
|
readonly bucketName: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IACjC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-file-bucket",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"aws-blocks",
|
|
6
|
+
"storage",
|
|
7
|
+
"s3",
|
|
8
|
+
"file-upload"
|
|
9
|
+
],
|
|
4
10
|
"repository": {
|
|
5
11
|
"type": "git",
|
|
6
12
|
"url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
|
|
@@ -50,8 +56,8 @@
|
|
|
50
56
|
"test": "node --test dist/index.test.js dist/index.cdk.test.js dist/scan.test.js dist/file-server.test.js dist/url-encoding.test.js dist/path-containment.test.js dist/bucket-name.test.js"
|
|
51
57
|
},
|
|
52
58
|
"dependencies": {
|
|
53
|
-
"@aws-blocks/bb-logger": "^0.
|
|
54
|
-
"@aws-blocks/core": "^0.
|
|
59
|
+
"@aws-blocks/bb-logger": "^0.2.0",
|
|
60
|
+
"@aws-blocks/core": "^0.5.0",
|
|
55
61
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
56
62
|
"@aws-sdk/s3-request-presigner": "^3.0.0"
|
|
57
63
|
},
|