@3sln/trove 0.0.5 → 0.0.8

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.
Files changed (33) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/packages/core/src/collections/index.js +146 -1
  4. package/packages/core/src/encryption/envelope.js +444 -0
  5. package/packages/core/src/encryption/exposure.js +101 -0
  6. package/packages/core/src/encryption/keys.js +88 -0
  7. package/packages/core/src/encryption/policy.js +112 -0
  8. package/packages/core/src/encryption/rotation.js +313 -0
  9. package/packages/core/src/index.js +17 -1
  10. package/packages/core/src/links.js +85 -0
  11. package/packages/core/src/metadata/memory.js +3 -1
  12. package/packages/core/src/metadata/sqlite.js +27 -7
  13. package/packages/core/src/scan.js +35 -1
  14. package/packages/core/src/storage/cost.js +228 -0
  15. package/packages/core/src/storage/drivers.js +13 -4
  16. package/packages/core/src/storage/registry.js +37 -4
  17. package/packages/core/src/uploads.js +197 -15
  18. package/packages/core/src/vfs.js +148 -9
  19. package/packages/server/src/engine/providers/core.js +38 -3
  20. package/packages/server/src/index.js +8 -0
  21. package/packages/server/src/routes.js +29 -1
  22. package/packages/web/dist/assets/main-y778bpte.js +356 -0
  23. package/packages/web/dist/assets/{main-f0f2tfhp.js.map → main-y778bpte.js.map} +10 -8
  24. package/packages/web/dist/assets/styles-nfy8t3n1.css +1 -0
  25. package/packages/web/dist/index.html +9 -3
  26. package/packages/web/dist/sw.js +1 -1
  27. package/packages/web/src/bl/actions.js +20 -4
  28. package/packages/web/src/bl/services.js +64 -4
  29. package/packages/web/src/platform/api.js +134 -11
  30. package/packages/web/src/styles.css +3 -0
  31. package/packages/web/src/ui/components/overlays.js +13 -0
  32. package/packages/web/dist/assets/main-f0f2tfhp.js +0 -356
  33. package/packages/web/dist/assets/styles-d3cyysgp.css +0 -1
@@ -5,6 +5,7 @@
5
5
  import { Router, json, parseRange } from './router.js';
6
6
  import {
7
7
  TroveError, assertSafePluginSql, concatBytes, metadataUrl, publicOrigin,
8
+ shouldEncrypt,
8
9
  } from '@3sln/trove/core';
9
10
  import { parseContribUri, CORE_DOMAIN } from '@3sln/trove/core/plugins/identity.js';
10
11
 
@@ -444,10 +445,20 @@ export function createRouter() {
444
445
 
445
446
  // --- uploads ---------------------------------------------------------------
446
447
 
447
- r.post('/api/collections/:collection/uploads', [], async (ctx) => {
448
+ r.post('/api/collections/:collection/uploads', ['collections', 'vfs'], async (ctx) => {
448
449
  const b = await body(ctx.req);
449
450
  if (!b.name) throw TroveError.invalid('name is required');
450
451
  const collection = await ctx.access.collection(scopedCollection(ctx), 'write');
452
+ // An upload onto an encrypted collection is handed the collection's key, so the client
453
+ // can seal the bytes before they reach the bucket. That key decrypts EVERYTHING in the
454
+ // collection, which makes it a read capability however it arrives — and `write` does
455
+ // not imply `read` here (only `admin` expands). Without this, a write-only API key,
456
+ // which the key model explicitly supports, could ask for a plan for a one-byte file and
457
+ // receive the means to decrypt the whole collection.
458
+ //
459
+ // Refused rather than quietly narrowed to a plaintext upload: silently storing in the
460
+ // clear on a collection someone set up to be encrypted is the worse failure.
461
+ await assertReadIfKeyed(ctx, b);
451
462
  return uploadDescriptor(await collection.createUpload({
452
463
  name: b.name, size: Number(b.size ?? 0), contentType: b.contentType,
453
464
  overwrite: b.overwrite === true,
@@ -1150,6 +1161,23 @@ async function assertTaskAccess(ctx, task, what) {
1150
1161
  * manage API keys, however broadly it was scoped, because a key that can mint keys can
1151
1162
  * outlive its own revocation. Then the ordinary admin check on the principal.
1152
1163
  */
1164
+
1165
+ /**
1166
+ * An upload plan that will carry the collection key needs `read`, not merely `write`.
1167
+ *
1168
+ * Checked before the session is created, so a refusal costs nothing and leaves no orphan.
1169
+ */
1170
+ async function assertReadIfKeyed(ctx, body) {
1171
+ if (!ctx.collections?.encryptionFor) return;
1172
+ const collectionId = scopedCollection(ctx);
1173
+ const encryption = await ctx.collections.encryptionFor(collectionId);
1174
+ if (!encryption?.enabled) return;
1175
+ const contentType = body.contentType || ctx.vfs?.guessContentType?.(body.name) || '';
1176
+ if (!shouldEncrypt(encryption, { name: body.name, contentType })) return;
1177
+ // Throws if the caller does not hold read on this collection.
1178
+ await ctx.access.collection(collectionId, 'read');
1179
+ }
1180
+
1153
1181
  function requireHumanAdmin(ctx, action) {
1154
1182
  if (ctx.grant) {
1155
1183
  throw TroveError.forbidden(`An API key cannot ${action} — sign in as an administrator`);