effortless-aws 0.33.0 → 0.34.0

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.
@@ -233,9 +233,6 @@ var createTableClient = (tableName, options) => {
233
233
  };
234
234
  };
235
235
 
236
- // src/handlers/auth.ts
237
- import * as crypto from "crypto";
238
-
239
236
  // src/handlers/handler-options.ts
240
237
  var toSeconds = (d) => {
241
238
  if (typeof d === "number") return d;
@@ -250,10 +247,35 @@ var toSeconds = (d) => {
250
247
  };
251
248
 
252
249
  // src/handlers/auth.ts
250
+ import * as crypto from "crypto";
251
+ var cfBase64Encode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/=/g, "_").replace(/\//g, "~");
252
+ var signCfCookies = (policy, config) => {
253
+ const ttlSeconds = toSeconds(policy.ttl);
254
+ const expireTime = Math.floor(Date.now() / 1e3) + ttlSeconds;
255
+ const resource = config.domain === "*" ? `https://*${policy.path}` : `https://${config.domain}${policy.path}`;
256
+ const policyJson = JSON.stringify({
257
+ Statement: [{
258
+ Resource: resource,
259
+ Condition: {
260
+ DateLessThan: { "AWS:EpochTime": expireTime }
261
+ }
262
+ }]
263
+ });
264
+ const policyBase64 = cfBase64Encode(Buffer.from(policyJson, "utf-8"));
265
+ const signature = cfBase64Encode(
266
+ crypto.sign("sha1", Buffer.from(policyJson, "utf-8"), config.privateKey)
267
+ );
268
+ const cookieAttrs = `; Secure; SameSite=Lax; Path=/; Max-Age=${ttlSeconds}`;
269
+ return [
270
+ `CloudFront-Policy=${policyBase64}${cookieAttrs}`,
271
+ `CloudFront-Signature=${signature}${cookieAttrs}`,
272
+ `CloudFront-Key-Pair-Id=${config.keyPairId}${cookieAttrs}`
273
+ ];
274
+ };
253
275
  var AUTH_COOKIE_NAME = "__eff_session";
254
- var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds) => {
276
+ var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds, cfSigningConfig) => {
255
277
  const tokenCache = apiTokenCacheTtlSeconds ? /* @__PURE__ */ new Map() : void 0;
256
- const sign = (payload) => crypto.createHmac("sha256", secret).update(payload).digest("base64url");
278
+ const sign2 = (payload) => crypto.createHmac("sha256", secret).update(payload).digest("base64url");
257
279
  const cookieBase = `${AUTH_COOKIE_NAME}=`;
258
280
  const cookieAttrs = "; HttpOnly; Secure; SameSite=Lax; Path=/";
259
281
  const decodeSession = (cookieValue) => {
@@ -262,7 +284,7 @@ var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeade
262
284
  if (dot === -1) return void 0;
263
285
  const payload = cookieValue.slice(0, dot);
264
286
  const sig = cookieValue.slice(dot + 1);
265
- if (sign(payload) !== sig) return void 0;
287
+ if (sign2(payload) !== sig) return void 0;
266
288
  try {
267
289
  const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
268
290
  if (parsed.exp <= Math.floor(Date.now() / 1e3)) return void 0;
@@ -284,13 +306,16 @@ var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeade
284
306
  const seconds = options?.expiresIn ? toSeconds(options.expiresIn) : defaultExpiresIn;
285
307
  const exp = Math.floor(Date.now() / 1e3) + seconds;
286
308
  const payload = Buffer.from(JSON.stringify({ exp, ...data }), "utf-8").toString("base64url");
287
- const sig = sign(payload);
309
+ const sig = sign2(payload);
310
+ const sessionCookie = `${cookieBase}${payload}.${sig}${cookieAttrs}; Max-Age=${seconds}`;
311
+ const cfCookies = options?.cdnPolicy && cfSigningConfig ? signCfCookies(options.cdnPolicy, cfSigningConfig) : void 0;
288
312
  return {
289
313
  status: 200,
290
314
  body: { ok: true },
291
315
  headers: {
292
- "set-cookie": `${cookieBase}${payload}.${sig}${cookieAttrs}; Max-Age=${seconds}`
293
- }
316
+ "set-cookie": sessionCookie
317
+ },
318
+ ...cfCookies ? { cookies: [sessionCookie, ...cfCookies] } : {}
294
319
  };
295
320
  },
296
321
  clearSession() {
@@ -327,34 +352,91 @@ var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeade
327
352
 
328
353
  // src/runtime/bucket-client.ts
329
354
  import { S3 } from "@aws-sdk/client-s3";
330
- var createBucketClient = (bucketName) => {
331
- let client2 = null;
332
- const getClient2 = () => client2 ??= new S3({});
355
+ var createBucketMethods = (getClient2, bucketName) => ({
356
+ bucketName,
357
+ async put(key, body, options) {
358
+ await getClient2().putObject({
359
+ Bucket: bucketName,
360
+ Key: key,
361
+ Body: typeof body === "string" ? Buffer.from(body) : body,
362
+ ...options?.contentType ? { ContentType: options.contentType } : {}
363
+ });
364
+ },
365
+ async get(key) {
366
+ try {
367
+ const result = await getClient2().getObject({
368
+ Bucket: bucketName,
369
+ Key: key
370
+ });
371
+ const chunks = [];
372
+ const stream = result.Body;
373
+ for await (const chunk of stream) {
374
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
375
+ }
376
+ return {
377
+ body: Buffer.concat(chunks),
378
+ contentType: result.ContentType
379
+ };
380
+ } catch (error) {
381
+ if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
382
+ return void 0;
383
+ }
384
+ throw error;
385
+ }
386
+ },
387
+ async delete(key) {
388
+ await getClient2().deleteObject({
389
+ Bucket: bucketName,
390
+ Key: key
391
+ });
392
+ },
393
+ async list(prefix) {
394
+ const items = [];
395
+ let continuationToken;
396
+ do {
397
+ const result = await getClient2().listObjectsV2({
398
+ Bucket: bucketName,
399
+ ...prefix ? { Prefix: prefix } : {},
400
+ ...continuationToken ? { ContinuationToken: continuationToken } : {}
401
+ });
402
+ for (const obj of result.Contents ?? []) {
403
+ if (obj.Key) {
404
+ items.push({
405
+ key: obj.Key,
406
+ size: obj.Size ?? 0,
407
+ lastModified: obj.LastModified
408
+ });
409
+ }
410
+ }
411
+ continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
412
+ } while (continuationToken);
413
+ return items;
414
+ }
415
+ });
416
+ var createEntityClient = (getClient2, bucketName, entityName, cacheSeconds) => {
417
+ const entityKey = (id) => `${entityName}/${id}.json`;
333
418
  return {
334
- bucketName,
335
- async put(key, body, options) {
419
+ async put(id, data) {
336
420
  await getClient2().putObject({
337
421
  Bucket: bucketName,
338
- Key: key,
339
- Body: typeof body === "string" ? Buffer.from(body) : body,
340
- ...options?.contentType ? { ContentType: options.contentType } : {}
422
+ Key: entityKey(id),
423
+ Body: JSON.stringify(data),
424
+ ContentType: "application/json",
425
+ ...cacheSeconds != null ? { CacheControl: `public, max-age=${cacheSeconds}` } : {}
341
426
  });
342
427
  },
343
- async get(key) {
428
+ async get(id) {
344
429
  try {
345
430
  const result = await getClient2().getObject({
346
431
  Bucket: bucketName,
347
- Key: key
432
+ Key: entityKey(id)
348
433
  });
349
434
  const chunks = [];
350
435
  const stream = result.Body;
351
436
  for await (const chunk of stream) {
352
437
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
353
438
  }
354
- return {
355
- body: Buffer.concat(chunks),
356
- contentType: result.ContentType
357
- };
439
+ return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
358
440
  } catch (error) {
359
441
  if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
360
442
  return void 0;
@@ -362,28 +444,37 @@ var createBucketClient = (bucketName) => {
362
444
  throw error;
363
445
  }
364
446
  },
365
- async delete(key) {
447
+ async delete(id) {
366
448
  await getClient2().deleteObject({
367
449
  Bucket: bucketName,
368
- Key: key
450
+ Key: entityKey(id)
369
451
  });
370
452
  },
371
- async list(prefix) {
453
+ async list() {
372
454
  const items = [];
373
455
  let continuationToken;
456
+ const prefix = `${entityName}/`;
374
457
  do {
375
458
  const result = await getClient2().listObjectsV2({
376
459
  Bucket: bucketName,
377
- ...prefix ? { Prefix: prefix } : {},
460
+ Prefix: prefix,
378
461
  ...continuationToken ? { ContinuationToken: continuationToken } : {}
379
462
  });
380
463
  for (const obj of result.Contents ?? []) {
381
- if (obj.Key) {
382
- items.push({
383
- key: obj.Key,
384
- size: obj.Size ?? 0,
385
- lastModified: obj.LastModified
464
+ if (!obj.Key || !obj.Key.endsWith(".json")) continue;
465
+ const id = obj.Key.slice(prefix.length, -".json".length);
466
+ try {
467
+ const getResult = await getClient2().getObject({
468
+ Bucket: bucketName,
469
+ Key: obj.Key
386
470
  });
471
+ const chunks = [];
472
+ const stream = getResult.Body;
473
+ for await (const chunk of stream) {
474
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
475
+ }
476
+ items.push({ id, data: JSON.parse(Buffer.concat(chunks).toString("utf-8")) });
477
+ } catch {
387
478
  }
388
479
  }
389
480
  continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
@@ -392,6 +483,21 @@ var createBucketClient = (bucketName) => {
392
483
  }
393
484
  };
394
485
  };
486
+ var createBucketClient = (bucketName) => {
487
+ let client2 = null;
488
+ const getClient2 = () => client2 ??= new S3({});
489
+ return createBucketMethods(getClient2, bucketName);
490
+ };
491
+ var createBucketClientWithEntities = (bucketName, entitiesConfig) => {
492
+ let client2 = null;
493
+ const getClient2 = () => client2 ??= new S3({});
494
+ const base = createBucketMethods(getClient2, bucketName);
495
+ const result = { ...base };
496
+ for (const [entityName, config] of Object.entries(entitiesConfig)) {
497
+ result[entityName] = createEntityClient(getClient2, bucketName, entityName, config.cacheSeconds);
498
+ }
499
+ return result;
500
+ };
395
501
 
396
502
  // src/runtime/handler-utils.ts
397
503
  import { readFileSync } from "fs";
@@ -566,7 +672,17 @@ var DEP_FACTORIES = {
566
672
  const tagField = depHandler?.__spec?.tagField;
567
673
  return createTableClient(name, tagField ? { tagField } : void 0);
568
674
  },
569
- bucket: (name) => createBucketClient(name),
675
+ bucket: (name, depHandler) => {
676
+ const entities = depHandler?.__spec?.entities;
677
+ if (entities && Object.keys(entities).length > 0) {
678
+ const config = {};
679
+ for (const [entityName, entityOpts] of Object.entries(entities)) {
680
+ config[entityName] = entityOpts.cache ? { cacheSeconds: toSeconds(entityOpts.cache) } : {};
681
+ }
682
+ return createBucketClientWithEntities(name, config);
683
+ }
684
+ return createBucketClient(name);
685
+ },
570
686
  mailer: () => createEmailClient(),
571
687
  queue: (name) => createQueueClient(name),
572
688
  worker: (name) => createWorkerClient(name)
@@ -630,6 +746,25 @@ var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupA
630
746
  resolvedParams = await buildParams(handler.config);
631
747
  return resolvedParams;
632
748
  };
749
+ let resolvedCfSigningConfig = null;
750
+ const getCfSigningConfig = async () => {
751
+ if (resolvedCfSigningConfig !== null) return resolvedCfSigningConfig;
752
+ const cfSigningKeySsmPath = process.env.EFF_CF_SIGNING_KEY;
753
+ const cfKeyPairId = process.env.EFF_CF_KEY_PAIR_ID;
754
+ const cfDomain = process.env.EFF_CF_DOMAIN;
755
+ if (!cfSigningKeySsmPath || !cfKeyPairId || !cfDomain) {
756
+ resolvedCfSigningConfig = void 0;
757
+ return void 0;
758
+ }
759
+ const values = await getParameters([cfSigningKeySsmPath]);
760
+ const privateKey = values.get(cfSigningKeySsmPath);
761
+ if (!privateKey) {
762
+ resolvedCfSigningConfig = void 0;
763
+ return void 0;
764
+ }
765
+ resolvedCfSigningConfig = { privateKey, keyPairId: cfKeyPairId, domain: cfDomain };
766
+ return resolvedCfSigningConfig;
767
+ };
633
768
  const getAuthRuntime = async (setupCtx) => {
634
769
  if (resolvedAuthRuntime !== null) return resolvedAuthRuntime;
635
770
  const authOpts = setupCtx && typeof setupCtx === "object" && "auth" in setupCtx ? setupCtx.auth : void 0;
@@ -644,12 +779,14 @@ var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupA
644
779
  const cacheTtlSeconds = apiToken?.cacheTtl ? toSeconds(apiToken.cacheTtl) : void 0;
645
780
  const rawVerify = apiToken?.verify;
646
781
  const wrappedVerify = rawVerify ? (args) => rawVerify(args.value) : void 0;
782
+ const cfSigningConfig = await getCfSigningConfig();
647
783
  resolvedAuthRuntime = createAuthRuntime(
648
784
  secret,
649
785
  defaultExpires,
650
786
  wrappedVerify,
651
787
  apiToken?.header,
652
- cacheTtlSeconds
788
+ cacheTtlSeconds,
789
+ cfSigningConfig
653
790
  );
654
791
  return resolvedAuthRuntime;
655
792
  };
@@ -733,8 +870,10 @@ var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupA
733
870
 
734
871
  export {
735
872
  createTableClient,
873
+ toSeconds,
736
874
  AUTH_COOKIE_NAME,
737
875
  createBucketClient,
876
+ createBucketClientWithEntities,
738
877
  buildDeps,
739
878
  buildParams,
740
879
  createHandlerRuntime