@streetjs/storage 1.0.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.
- package/LICENSE +21 -0
- package/README.md +719 -0
- package/dist/access.d.ts +95 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +114 -0
- package/dist/access.js.map +1 -0
- package/dist/cli/commands.d.ts +88 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +369 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/generators.d.ts +41 -0
- package/dist/cli/generators.d.ts.map +1 -0
- package/dist/cli/generators.js +119 -0
- package/dist/cli/generators.js.map +1 -0
- package/dist/directory.d.ts +88 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +148 -0
- package/dist/directory.js.map +1 -0
- package/dist/driver.d.ts +83 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +16 -0
- package/dist/driver.js.map +1 -0
- package/dist/drivers/azure.d.ts +239 -0
- package/dist/drivers/azure.d.ts.map +1 -0
- package/dist/drivers/azure.js +476 -0
- package/dist/drivers/azure.js.map +1 -0
- package/dist/drivers/backblaze.d.ts +105 -0
- package/dist/drivers/backblaze.d.ts.map +1 -0
- package/dist/drivers/backblaze.js +279 -0
- package/dist/drivers/backblaze.js.map +1 -0
- package/dist/drivers/gcs.d.ts +239 -0
- package/dist/drivers/gcs.d.ts.map +1 -0
- package/dist/drivers/gcs.js +454 -0
- package/dist/drivers/gcs.js.map +1 -0
- package/dist/drivers/local.d.ts +126 -0
- package/dist/drivers/local.d.ts.map +1 -0
- package/dist/drivers/local.js +321 -0
- package/dist/drivers/local.js.map +1 -0
- package/dist/drivers/memory.d.ts +104 -0
- package/dist/drivers/memory.d.ts.map +1 -0
- package/dist/drivers/memory.js +197 -0
- package/dist/drivers/memory.js.map +1 -0
- package/dist/drivers/minio.d.ts +90 -0
- package/dist/drivers/minio.d.ts.map +1 -0
- package/dist/drivers/minio.js +276 -0
- package/dist/drivers/minio.js.map +1 -0
- package/dist/drivers/r2.d.ts +100 -0
- package/dist/drivers/r2.d.ts.map +1 -0
- package/dist/drivers/r2.js +240 -0
- package/dist/drivers/r2.js.map +1 -0
- package/dist/drivers/s3-base.d.ts +270 -0
- package/dist/drivers/s3-base.d.ts.map +1 -0
- package/dist/drivers/s3-base.js +436 -0
- package/dist/drivers/s3-base.js.map +1 -0
- package/dist/drivers/s3.d.ts +111 -0
- package/dist/drivers/s3.d.ts.map +1 -0
- package/dist/drivers/s3.js +311 -0
- package/dist/drivers/s3.js.map +1 -0
- package/dist/drivers/supabase.d.ts +246 -0
- package/dist/drivers/supabase.d.ts.map +1 -0
- package/dist/drivers/supabase.js +502 -0
- package/dist/drivers/supabase.js.map +1 -0
- package/dist/errors.d.ts +115 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +131 -0
- package/dist/errors.js.map +1 -0
- package/dist/examples/storage-demo.d.ts +53 -0
- package/dist/examples/storage-demo.d.ts.map +1 -0
- package/dist/examples/storage-demo.js +242 -0
- package/dist/examples/storage-demo.js.map +1 -0
- package/dist/facade.d.ts +192 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +898 -0
- package/dist/facade.js.map +1 -0
- package/dist/image.d.ts +94 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +224 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/events.d.ts +108 -0
- package/dist/integrations/events.d.ts.map +1 -0
- package/dist/integrations/events.js +111 -0
- package/dist/integrations/events.js.map +1 -0
- package/dist/integrations/queue.d.ts +89 -0
- package/dist/integrations/queue.d.ts.map +1 -0
- package/dist/integrations/queue.js +91 -0
- package/dist/integrations/queue.js.map +1 -0
- package/dist/integrations/realtime.d.ts +95 -0
- package/dist/integrations/realtime.d.ts.map +1 -0
- package/dist/integrations/realtime.js +90 -0
- package/dist/integrations/realtime.js.map +1 -0
- package/dist/lifecycle.d.ts +137 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +240 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/metadata.d.ts +109 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +139 -0
- package/dist/metadata.js.map +1 -0
- package/dist/multipart.d.ts +108 -0
- package/dist/multipart.d.ts.map +1 -0
- package/dist/multipart.js +200 -0
- package/dist/multipart.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +175 -0
- package/dist/observability.js.map +1 -0
- package/dist/plugin.d.ts +82 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +100 -0
- package/dist/plugin.js.map +1 -0
- package/dist/resumable.d.ts +118 -0
- package/dist/resumable.d.ts.map +1 -0
- package/dist/resumable.js +223 -0
- package/dist/resumable.js.map +1 -0
- package/dist/search.d.ts +57 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +172 -0
- package/dist/search.js.map +1 -0
- package/dist/signed-url.d.ts +110 -0
- package/dist/signed-url.d.ts.map +1 -0
- package/dist/signed-url.js +237 -0
- package/dist/signed-url.js.map +1 -0
- package/dist/testing/index.d.ts +219 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +393 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tests/access.test.d.ts +2 -0
- package/dist/tests/access.test.d.ts.map +1 -0
- package/dist/tests/access.test.js +270 -0
- package/dist/tests/access.test.js.map +1 -0
- package/dist/tests/azure-driver.test.d.ts +2 -0
- package/dist/tests/azure-driver.test.d.ts.map +1 -0
- package/dist/tests/azure-driver.test.js +183 -0
- package/dist/tests/azure-driver.test.js.map +1 -0
- package/dist/tests/backblaze-driver.test.d.ts +2 -0
- package/dist/tests/backblaze-driver.test.d.ts.map +1 -0
- package/dist/tests/backblaze-driver.test.js +185 -0
- package/dist/tests/backblaze-driver.test.js.map +1 -0
- package/dist/tests/bridge-degradation.test.d.ts +2 -0
- package/dist/tests/bridge-degradation.test.d.ts.map +1 -0
- package/dist/tests/bridge-degradation.test.js +138 -0
- package/dist/tests/bridge-degradation.test.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +127 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/contract.d.ts +131 -0
- package/dist/tests/contract.d.ts.map +1 -0
- package/dist/tests/contract.js +222 -0
- package/dist/tests/contract.js.map +1 -0
- package/dist/tests/contract.test.d.ts +2 -0
- package/dist/tests/contract.test.d.ts.map +1 -0
- package/dist/tests/contract.test.js +110 -0
- package/dist/tests/contract.test.js.map +1 -0
- package/dist/tests/directory.test.d.ts +2 -0
- package/dist/tests/directory.test.d.ts.map +1 -0
- package/dist/tests/directory.test.js +125 -0
- package/dist/tests/directory.test.js.map +1 -0
- package/dist/tests/errors.test.d.ts +2 -0
- package/dist/tests/errors.test.d.ts.map +1 -0
- package/dist/tests/errors.test.js +93 -0
- package/dist/tests/errors.test.js.map +1 -0
- package/dist/tests/events-bridge.test.d.ts +2 -0
- package/dist/tests/events-bridge.test.d.ts.map +1 -0
- package/dist/tests/events-bridge.test.js +228 -0
- package/dist/tests/events-bridge.test.js.map +1 -0
- package/dist/tests/example-smoke.test.d.ts +2 -0
- package/dist/tests/example-smoke.test.d.ts.map +1 -0
- package/dist/tests/example-smoke.test.js +34 -0
- package/dist/tests/example-smoke.test.js.map +1 -0
- package/dist/tests/gcs-driver.test.d.ts +2 -0
- package/dist/tests/gcs-driver.test.d.ts.map +1 -0
- package/dist/tests/gcs-driver.test.js +197 -0
- package/dist/tests/gcs-driver.test.js.map +1 -0
- package/dist/tests/image.test.d.ts +2 -0
- package/dist/tests/image.test.d.ts.map +1 -0
- package/dist/tests/image.test.js +102 -0
- package/dist/tests/image.test.js.map +1 -0
- package/dist/tests/lifecycle.test.d.ts +2 -0
- package/dist/tests/lifecycle.test.d.ts.map +1 -0
- package/dist/tests/lifecycle.test.js +171 -0
- package/dist/tests/lifecycle.test.js.map +1 -0
- package/dist/tests/local-driver.test.d.ts +2 -0
- package/dist/tests/local-driver.test.d.ts.map +1 -0
- package/dist/tests/local-driver.test.js +153 -0
- package/dist/tests/local-driver.test.js.map +1 -0
- package/dist/tests/local-streaming.test.d.ts +2 -0
- package/dist/tests/local-streaming.test.d.ts.map +1 -0
- package/dist/tests/local-streaming.test.js +121 -0
- package/dist/tests/local-streaming.test.js.map +1 -0
- package/dist/tests/memory-driver.test.d.ts +2 -0
- package/dist/tests/memory-driver.test.d.ts.map +1 -0
- package/dist/tests/memory-driver.test.js +180 -0
- package/dist/tests/memory-driver.test.js.map +1 -0
- package/dist/tests/memory-streaming.test.d.ts +2 -0
- package/dist/tests/memory-streaming.test.d.ts.map +1 -0
- package/dist/tests/memory-streaming.test.js +92 -0
- package/dist/tests/memory-streaming.test.js.map +1 -0
- package/dist/tests/minio-driver.test.d.ts +2 -0
- package/dist/tests/minio-driver.test.d.ts.map +1 -0
- package/dist/tests/minio-driver.test.js +146 -0
- package/dist/tests/minio-driver.test.js.map +1 -0
- package/dist/tests/non-s3-integration.test.d.ts +2 -0
- package/dist/tests/non-s3-integration.test.d.ts.map +1 -0
- package/dist/tests/non-s3-integration.test.js +201 -0
- package/dist/tests/non-s3-integration.test.js.map +1 -0
- package/dist/tests/observability.registration.test.d.ts +2 -0
- package/dist/tests/observability.registration.test.d.ts.map +1 -0
- package/dist/tests/observability.registration.test.js +103 -0
- package/dist/tests/observability.registration.test.js.map +1 -0
- package/dist/tests/observability.test.d.ts +2 -0
- package/dist/tests/observability.test.d.ts.map +1 -0
- package/dist/tests/observability.test.js +240 -0
- package/dist/tests/observability.test.js.map +1 -0
- package/dist/tests/plugin.test.d.ts +2 -0
- package/dist/tests/plugin.test.d.ts.map +1 -0
- package/dist/tests/plugin.test.js +112 -0
- package/dist/tests/plugin.test.js.map +1 -0
- package/dist/tests/prop-concurrent.test.d.ts +2 -0
- package/dist/tests/prop-concurrent.test.d.ts.map +1 -0
- package/dist/tests/prop-concurrent.test.js +59 -0
- package/dist/tests/prop-concurrent.test.js.map +1 -0
- package/dist/tests/prop-copy.test.d.ts +2 -0
- package/dist/tests/prop-copy.test.d.ts.map +1 -0
- package/dist/tests/prop-copy.test.js +63 -0
- package/dist/tests/prop-copy.test.js.map +1 -0
- package/dist/tests/prop-delete.test.d.ts +2 -0
- package/dist/tests/prop-delete.test.d.ts.map +1 -0
- package/dist/tests/prop-delete.test.js +35 -0
- package/dist/tests/prop-delete.test.js.map +1 -0
- package/dist/tests/prop-directory.test.d.ts +2 -0
- package/dist/tests/prop-directory.test.d.ts.map +1 -0
- package/dist/tests/prop-directory.test.js +118 -0
- package/dist/tests/prop-directory.test.js.map +1 -0
- package/dist/tests/prop-driver-conformance.test.d.ts +2 -0
- package/dist/tests/prop-driver-conformance.test.d.ts.map +1 -0
- package/dist/tests/prop-driver-conformance.test.js +110 -0
- package/dist/tests/prop-driver-conformance.test.js.map +1 -0
- package/dist/tests/prop-driver-equivalence.test.d.ts +2 -0
- package/dist/tests/prop-driver-equivalence.test.d.ts.map +1 -0
- package/dist/tests/prop-driver-equivalence.test.js +163 -0
- package/dist/tests/prop-driver-equivalence.test.js.map +1 -0
- package/dist/tests/prop-lifecycle.test.d.ts +2 -0
- package/dist/tests/prop-lifecycle.test.d.ts.map +1 -0
- package/dist/tests/prop-lifecycle.test.js +83 -0
- package/dist/tests/prop-lifecycle.test.js.map +1 -0
- package/dist/tests/prop-list.test.d.ts +2 -0
- package/dist/tests/prop-list.test.d.ts.map +1 -0
- package/dist/tests/prop-list.test.js +53 -0
- package/dist/tests/prop-list.test.js.map +1 -0
- package/dist/tests/prop-metadata.test.d.ts +2 -0
- package/dist/tests/prop-metadata.test.d.ts.map +1 -0
- package/dist/tests/prop-metadata.test.js +87 -0
- package/dist/tests/prop-metadata.test.js.map +1 -0
- package/dist/tests/prop-move.test.d.ts +2 -0
- package/dist/tests/prop-move.test.d.ts.map +1 -0
- package/dist/tests/prop-move.test.js +67 -0
- package/dist/tests/prop-move.test.js.map +1 -0
- package/dist/tests/prop-multipart-abort.test.d.ts +2 -0
- package/dist/tests/prop-multipart-abort.test.d.ts.map +1 -0
- package/dist/tests/prop-multipart-abort.test.js +48 -0
- package/dist/tests/prop-multipart-abort.test.js.map +1 -0
- package/dist/tests/prop-multipart.test.d.ts +2 -0
- package/dist/tests/prop-multipart.test.d.ts.map +1 -0
- package/dist/tests/prop-multipart.test.js +65 -0
- package/dist/tests/prop-multipart.test.js.map +1 -0
- package/dist/tests/prop-resumable.test.d.ts +2 -0
- package/dist/tests/prop-resumable.test.d.ts.map +1 -0
- package/dist/tests/prop-resumable.test.js +78 -0
- package/dist/tests/prop-resumable.test.js.map +1 -0
- package/dist/tests/prop-search.test.d.ts +2 -0
- package/dist/tests/prop-search.test.d.ts.map +1 -0
- package/dist/tests/prop-search.test.js +120 -0
- package/dist/tests/prop-search.test.js.map +1 -0
- package/dist/tests/prop-signed-url.test.d.ts +2 -0
- package/dist/tests/prop-signed-url.test.d.ts.map +1 -0
- package/dist/tests/prop-signed-url.test.js +74 -0
- package/dist/tests/prop-signed-url.test.js.map +1 -0
- package/dist/tests/prop-streaming.test.d.ts +2 -0
- package/dist/tests/prop-streaming.test.d.ts.map +1 -0
- package/dist/tests/prop-streaming.test.js +34 -0
- package/dist/tests/prop-streaming.test.js.map +1 -0
- package/dist/tests/prop-upload-download.test.d.ts +2 -0
- package/dist/tests/prop-upload-download.test.d.ts.map +1 -0
- package/dist/tests/prop-upload-download.test.js +30 -0
- package/dist/tests/prop-upload-download.test.js.map +1 -0
- package/dist/tests/prop-validation.test.d.ts +2 -0
- package/dist/tests/prop-validation.test.d.ts.map +1 -0
- package/dist/tests/prop-validation.test.js +50 -0
- package/dist/tests/prop-validation.test.js.map +1 -0
- package/dist/tests/prop-versioning.test.d.ts +2 -0
- package/dist/tests/prop-versioning.test.d.ts.map +1 -0
- package/dist/tests/prop-versioning.test.js +74 -0
- package/dist/tests/prop-versioning.test.js.map +1 -0
- package/dist/tests/queue-bridge.test.d.ts +2 -0
- package/dist/tests/queue-bridge.test.d.ts.map +1 -0
- package/dist/tests/queue-bridge.test.js +111 -0
- package/dist/tests/queue-bridge.test.js.map +1 -0
- package/dist/tests/r2-driver.test.d.ts +2 -0
- package/dist/tests/r2-driver.test.d.ts.map +1 -0
- package/dist/tests/r2-driver.test.js +143 -0
- package/dist/tests/r2-driver.test.js.map +1 -0
- package/dist/tests/realtime-bridge.test.d.ts +2 -0
- package/dist/tests/realtime-bridge.test.d.ts.map +1 -0
- package/dist/tests/realtime-bridge.test.js +106 -0
- package/dist/tests/realtime-bridge.test.js.map +1 -0
- package/dist/tests/resumable.test.d.ts +2 -0
- package/dist/tests/resumable.test.d.ts.map +1 -0
- package/dist/tests/resumable.test.js +104 -0
- package/dist/tests/resumable.test.js.map +1 -0
- package/dist/tests/s3-base.test.d.ts +2 -0
- package/dist/tests/s3-base.test.d.ts.map +1 -0
- package/dist/tests/s3-base.test.js +289 -0
- package/dist/tests/s3-base.test.js.map +1 -0
- package/dist/tests/s3-integration.test.d.ts +2 -0
- package/dist/tests/s3-integration.test.d.ts.map +1 -0
- package/dist/tests/s3-integration.test.js +270 -0
- package/dist/tests/s3-integration.test.js.map +1 -0
- package/dist/tests/s3.test.d.ts +2 -0
- package/dist/tests/s3.test.d.ts.map +1 -0
- package/dist/tests/s3.test.js +203 -0
- package/dist/tests/s3.test.js.map +1 -0
- package/dist/tests/scaffold.test.d.ts +2 -0
- package/dist/tests/scaffold.test.d.ts.map +1 -0
- package/dist/tests/scaffold.test.js +13 -0
- package/dist/tests/scaffold.test.js.map +1 -0
- package/dist/tests/search.test.d.ts +2 -0
- package/dist/tests/search.test.d.ts.map +1 -0
- package/dist/tests/search.test.js +106 -0
- package/dist/tests/search.test.js.map +1 -0
- package/dist/tests/supabase-driver.test.d.ts +2 -0
- package/dist/tests/supabase-driver.test.d.ts.map +1 -0
- package/dist/tests/supabase-driver.test.js +196 -0
- package/dist/tests/supabase-driver.test.js.map +1 -0
- package/dist/tests/testing-utilities.test.d.ts +2 -0
- package/dist/tests/testing-utilities.test.d.ts.map +1 -0
- package/dist/tests/testing-utilities.test.js +138 -0
- package/dist/tests/testing-utilities.test.js.map +1 -0
- package/dist/tests/validation.test.d.ts +2 -0
- package/dist/tests/validation.test.d.ts.map +1 -0
- package/dist/tests/validation.test.js +135 -0
- package/dist/tests/validation.test.js.map +1 -0
- package/dist/tests/versioning.test.d.ts +2 -0
- package/dist/tests/versioning.test.d.ts.map +1 -0
- package/dist/tests/versioning.test.js +128 -0
- package/dist/tests/versioning.test.js.map +1 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +105 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +225 -0
- package/dist/validation.js.map +1 -0
- package/dist/versioning.d.ts +100 -0
- package/dist/versioning.d.ts.map +1 -0
- package/dist/versioning.js +179 -0
- package/dist/versioning.js.map +1 -0
- package/package.json +137 -0
package/dist/search.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — attribute search / filtering over the flat key space
|
|
3
|
+
* (task 18.1).
|
|
4
|
+
*
|
|
5
|
+
* Object stores expose only a flat key space plus per-object metadata, so
|
|
6
|
+
* search is implemented as a provider-agnostic filter over the two mandatory
|
|
7
|
+
* driver primitives {@link StorageDriver.list} and {@link StorageDriver.stat}:
|
|
8
|
+
*
|
|
9
|
+
* 1. Enumerate candidate keys with `driver.list(prefix)` (the prefix filter is
|
|
10
|
+
* pushed down to the driver; an absent prefix lists the whole key space).
|
|
11
|
+
* 2. For each candidate, read its typed metadata with `driver.stat(key)`.
|
|
12
|
+
* 3. Keep the object only when it satisfies **every** supplied filter
|
|
13
|
+
* (conjunctive / AND semantics — Requirement 16.2); when no object satisfies
|
|
14
|
+
* the filters the result is the empty set (Requirement 16.3).
|
|
15
|
+
*
|
|
16
|
+
* The supported filters mirror {@link SearchFilters} (Requirement 16.1):
|
|
17
|
+
*
|
|
18
|
+
* - `prefix` — key begins with the prefix (pushed to `driver.list`).
|
|
19
|
+
* - `contentType` — exact content-type match.
|
|
20
|
+
* - `owner` — exact owner match.
|
|
21
|
+
* - `tenant` — exact tenant match.
|
|
22
|
+
* - `minSize` — size ≥ minSize (inclusive lower bound).
|
|
23
|
+
* - `maxSize` — size ≤ maxSize (inclusive upper bound).
|
|
24
|
+
* - `updatedAfter` — updatedAt ≥ updatedAfter (inclusive lower time bound).
|
|
25
|
+
* - `updatedBefore` — updatedAt ≤ updatedBefore (inclusive upper time bound).
|
|
26
|
+
* - `metadata` — every supplied custom field deep-equals the object's
|
|
27
|
+
* corresponding `custom` field.
|
|
28
|
+
*
|
|
29
|
+
* Reserved internal key spaces used by the cross-cutting layers
|
|
30
|
+
* (`.versions/`, `.multipart/`, `.resumable/`, `.archive/`) are excluded from
|
|
31
|
+
* results unless the supplied `prefix` explicitly targets one of them, so a
|
|
32
|
+
* broad search returns only user-visible objects and never leaks framework
|
|
33
|
+
* bookkeeping keys.
|
|
34
|
+
*
|
|
35
|
+
* The result preserves the {@link StorageListItem} shape returned by
|
|
36
|
+
* `driver.list`, so `search` composes with the rest of the facade surface.
|
|
37
|
+
*
|
|
38
|
+
* _Requirements: 16.1, 16.2, 16.3_
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Reserved key prefixes that hold framework bookkeeping (version snapshots,
|
|
42
|
+
* multipart parts, resumable session bytes, archived copies). These are hidden
|
|
43
|
+
* from search results unless the caller explicitly searches within one of them.
|
|
44
|
+
*/
|
|
45
|
+
const RESERVED_KEY_PREFIXES = [
|
|
46
|
+
".versions/",
|
|
47
|
+
".multipart/",
|
|
48
|
+
".resumable/",
|
|
49
|
+
".archive/",
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Return the stored objects that satisfy **every** supplied filter, evaluated
|
|
53
|
+
* over `driver.list` + `driver.stat`.
|
|
54
|
+
*
|
|
55
|
+
* The prefix filter is delegated to `driver.list`; the remaining filters are
|
|
56
|
+
* applied against each candidate's metadata. Objects whose metadata cannot be
|
|
57
|
+
* read (e.g. removed concurrently between `list` and `stat`) are skipped. When
|
|
58
|
+
* no object satisfies the filters, an empty array is returned (Requirement
|
|
59
|
+
* 16.3).
|
|
60
|
+
*
|
|
61
|
+
* @param driver The storage driver to search over.
|
|
62
|
+
* @param filters The conjunctive set of {@link SearchFilters} to apply.
|
|
63
|
+
* @returns The matching objects as {@link StorageListItem}s.
|
|
64
|
+
*/
|
|
65
|
+
export async function searchObjects(driver, filters = {}) {
|
|
66
|
+
const prefix = filters.prefix ?? "";
|
|
67
|
+
const candidates = await driver.list(prefix);
|
|
68
|
+
const matches = [];
|
|
69
|
+
for (const item of candidates) {
|
|
70
|
+
// Defensive re-filter: never surface keys outside the requested prefix even
|
|
71
|
+
// if a driver interprets `list` loosely.
|
|
72
|
+
if (prefix !== "" && !item.key.startsWith(prefix)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// Hide reserved bookkeeping keys unless the search explicitly targets them.
|
|
76
|
+
if (isReservedKey(item.key) && !prefixTargetsReserved(prefix)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const metadata = await driver.stat(item.key);
|
|
80
|
+
if (metadata === null) {
|
|
81
|
+
// Object vanished between list and stat, or has no metadata — not a match.
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (matchesFilters(metadata, filters)) {
|
|
85
|
+
matches.push(item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return matches;
|
|
89
|
+
}
|
|
90
|
+
/** Whether `key` lives in one of the reserved internal key spaces. */
|
|
91
|
+
function isReservedKey(key) {
|
|
92
|
+
return RESERVED_KEY_PREFIXES.some((reserved) => key.startsWith(reserved));
|
|
93
|
+
}
|
|
94
|
+
/** Whether the requested `prefix` explicitly targets a reserved key space. */
|
|
95
|
+
function prefixTargetsReserved(prefix) {
|
|
96
|
+
return RESERVED_KEY_PREFIXES.some((reserved) => prefix.startsWith(reserved));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Return true only when `metadata` satisfies every supplied filter (AND
|
|
100
|
+
* semantics). Absent filter fields impose no constraint.
|
|
101
|
+
*/
|
|
102
|
+
function matchesFilters(metadata, filters) {
|
|
103
|
+
if (filters.contentType !== undefined && metadata.contentType !== filters.contentType) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if (filters.owner !== undefined && metadata.owner !== filters.owner) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (filters.tenant !== undefined && metadata.tenant !== filters.tenant) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (filters.minSize !== undefined && metadata.size < filters.minSize) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (filters.maxSize !== undefined && metadata.size > filters.maxSize) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (filters.updatedAfter !== undefined && metadata.updatedAt < filters.updatedAfter) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (filters.updatedBefore !== undefined && metadata.updatedAt > filters.updatedBefore) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (filters.metadata !== undefined && !matchesCustomMetadata(metadata.custom, filters.metadata)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Return true when every field required by `required` is present in the
|
|
131
|
+
* object's `custom` metadata and deep-equals the required value.
|
|
132
|
+
*/
|
|
133
|
+
function matchesCustomMetadata(custom, required) {
|
|
134
|
+
if (custom === null || typeof custom !== "object") {
|
|
135
|
+
return Object.keys(required).length === 0;
|
|
136
|
+
}
|
|
137
|
+
const source = custom;
|
|
138
|
+
for (const [field, value] of Object.entries(required)) {
|
|
139
|
+
if (!deepEqual(source[field], value)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Structural deep-equality used to compare custom metadata values. Handles
|
|
147
|
+
* primitives, arrays, and plain objects so nested custom fields compare by
|
|
148
|
+
* value rather than reference.
|
|
149
|
+
*/
|
|
150
|
+
function deepEqual(a, b) {
|
|
151
|
+
if (Object.is(a, b)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
158
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return a.every((element, index) => deepEqual(element, b[index]));
|
|
162
|
+
}
|
|
163
|
+
const aObj = a;
|
|
164
|
+
const bObj = b;
|
|
165
|
+
const aKeys = Object.keys(aObj);
|
|
166
|
+
const bKeys = Object.keys(bObj);
|
|
167
|
+
if (aKeys.length !== bKeys.length) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return aKeys.every((key) => Object.prototype.hasOwnProperty.call(bObj, key) && deepEqual(aObj[key], bObj[key]));
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — the signed URL service.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Signed_URL} authorizes a single object operation (GET, PUT, or
|
|
5
|
+
* DELETE) on a single key until an expiry instant, letting clients act directly
|
|
6
|
+
* against storage without proxying through the application server
|
|
7
|
+
* (Requirement 8). {@link SignedUrlService} provides this capability in a
|
|
8
|
+
* **provider-agnostic** way:
|
|
9
|
+
*
|
|
10
|
+
* - When the backing {@link StorageDriver} implements the optional native
|
|
11
|
+
* `signedUrl` capability, the service delegates minting and verification to
|
|
12
|
+
* the driver so a provider with first-class signed-URL support (e.g. S3) is
|
|
13
|
+
* used directly.
|
|
14
|
+
* - Otherwise the service **simulates** signed URLs with an HMAC computed over
|
|
15
|
+
* the `(key, op, expiry)` tuple (plus the accepted options) using
|
|
16
|
+
* `config.signingSecret` (`node:crypto` HMAC-SHA256), mirroring the zero-
|
|
17
|
+
* dependency scheme described in the design. Verification recomputes the HMAC
|
|
18
|
+
* and checks the expiry against an injected {@link Clock} (default
|
|
19
|
+
* `systemClock`), so time is deterministic in tests (Requirement 8.3, 8.4).
|
|
20
|
+
*
|
|
21
|
+
* Semantics (Requirement 8):
|
|
22
|
+
* - {@link SignedUrlService.sign} mints a URL authorizing **exactly** the
|
|
23
|
+
* requested operation on the given key (Requirement 8.1) and accepts options
|
|
24
|
+
* for expiration, request headers, content type, maximum size, and custom
|
|
25
|
+
* metadata (Requirement 8.2).
|
|
26
|
+
* - {@link SignedUrlService.verify} returns a {@link SignedUrlVerification}
|
|
27
|
+
* whose `valid` flag is `true` only when the URL is well-formed, its signature
|
|
28
|
+
* matches, its authorized operation matches the operation being attempted, and
|
|
29
|
+
* it is used strictly before its expiry (Requirements 8.3, 8.4). Otherwise
|
|
30
|
+
* `valid` is `false` with a `reason` of `'expired'`, `'operation-mismatch'`,
|
|
31
|
+
* `'signature-mismatch'`, or `'malformed'`.
|
|
32
|
+
*
|
|
33
|
+
* The module depends only on the driver contract, the shared type surface, the
|
|
34
|
+
* error hierarchy, and `node:crypto`, keeping the dependency direction acyclic.
|
|
35
|
+
*
|
|
36
|
+
* _Requirements: 8.1, 8.2, 8.3, 8.4_
|
|
37
|
+
*/
|
|
38
|
+
import { type Clock } from "streetjs";
|
|
39
|
+
import type { StorageDriver } from "./driver.js";
|
|
40
|
+
import type { SignedOperation, SignedUrlOptions, SignedUrlVerification } from "./types.js";
|
|
41
|
+
/** Options for constructing a {@link SignedUrlService}. */
|
|
42
|
+
export interface SignedUrlServiceOptions {
|
|
43
|
+
/**
|
|
44
|
+
* The HMAC key used to sign and verify simulated URLs. Required for the
|
|
45
|
+
* simulated path; minting throws a {@link StorageConfigError} when it is
|
|
46
|
+
* absent and the driver has no native `signedUrl` capability.
|
|
47
|
+
*/
|
|
48
|
+
readonly signingSecret?: string;
|
|
49
|
+
/** Injected clock for deterministic expiry checks in tests. Default `systemClock`. */
|
|
50
|
+
readonly clock?: Clock;
|
|
51
|
+
/**
|
|
52
|
+
* The backing driver. When it exposes a native `signedUrl` capability, minting
|
|
53
|
+
* and verification delegate to it instead of using the HMAC simulation.
|
|
54
|
+
*/
|
|
55
|
+
readonly driver?: StorageDriver;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Provider-agnostic signed URL service built on the driver contract and an HMAC
|
|
59
|
+
* simulation.
|
|
60
|
+
*
|
|
61
|
+
* A single instance is held by the facade and bound to one {@link StorageDriver}
|
|
62
|
+
* for its lifetime, so the native-vs-simulated decision (based on whether the
|
|
63
|
+
* driver exposes a `signedUrl` capability) is stable across every call.
|
|
64
|
+
*/
|
|
65
|
+
export declare class SignedUrlService {
|
|
66
|
+
/** The HMAC signing secret for the simulated path, when configured. */
|
|
67
|
+
private readonly signingSecret?;
|
|
68
|
+
/** Injected clock used to stamp and check expiry. */
|
|
69
|
+
private readonly clock;
|
|
70
|
+
/** The driver's native signed-URL capability, when present. */
|
|
71
|
+
private readonly native?;
|
|
72
|
+
constructor(options?: SignedUrlServiceOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Mint a URL authorizing **exactly** the operation `op` on `key`
|
|
75
|
+
* (Requirement 8.1). The URL carries the accepted options — expiration,
|
|
76
|
+
* request headers, content type, maximum size, and custom metadata
|
|
77
|
+
* (Requirement 8.2) — and, for the simulated path, an HMAC over the whole
|
|
78
|
+
* payload so its `(key, op, expiry)` tuple and options are tamper-evident.
|
|
79
|
+
*
|
|
80
|
+
* The expiry instant is computed from the injected clock plus
|
|
81
|
+
* `options.expiresInMs` (default {@link DEFAULT_EXPIRES_IN_MS}).
|
|
82
|
+
*
|
|
83
|
+
* @throws StorageConfigError when no native capability is available and no
|
|
84
|
+
* `signingSecret` was configured, since a URL cannot be signed without a key.
|
|
85
|
+
*/
|
|
86
|
+
sign(key: string, op: SignedOperation, options?: SignedUrlOptions): Promise<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Verify a signed `url` for the operation being attempted, returning a
|
|
89
|
+
* {@link SignedUrlVerification}.
|
|
90
|
+
*
|
|
91
|
+
* `valid` is `true` only when the URL is well-formed, its signature matches,
|
|
92
|
+
* its authorized operation equals `expectedOp` (when supplied), and it is used
|
|
93
|
+
* strictly before its expiry relative to `now` (Requirements 8.3, 8.4). When
|
|
94
|
+
* `expectedOp` is omitted the operation check is skipped and only signature and
|
|
95
|
+
* expiry are enforced. `now` defaults to the injected clock so tests can drive
|
|
96
|
+
* expiry deterministically.
|
|
97
|
+
*
|
|
98
|
+
* Failure reasons are reported precisely: `'malformed'` for an unparseable
|
|
99
|
+
* URL, `'signature-mismatch'` for a tampered/incorrectly signed URL,
|
|
100
|
+
* `'operation-mismatch'` when the authorized op differs from `expectedOp`, and
|
|
101
|
+
* `'expired'` when `now` is at or after the expiry.
|
|
102
|
+
*/
|
|
103
|
+
verify(url: string, expectedOp?: SignedOperation, now?: number): SignedUrlVerification;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the signing secret or throw a descriptive {@link StorageConfigError}.
|
|
106
|
+
* Used only on the simulated minting path, where a secret is mandatory.
|
|
107
|
+
*/
|
|
108
|
+
private requireSigningSecret;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=signed-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signed-url.d.ts","sourceRoot":"","sources":["../src/signed-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAKH,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoB3F,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AA2BD;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IAC3B,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IAExC,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAsB;gBAElC,OAAO,GAAE,uBAA4B;IAMjD;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IAyB7F;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,qBAAqB;IA6CtF;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAS7B"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — the signed URL service.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Signed_URL} authorizes a single object operation (GET, PUT, or
|
|
5
|
+
* DELETE) on a single key until an expiry instant, letting clients act directly
|
|
6
|
+
* against storage without proxying through the application server
|
|
7
|
+
* (Requirement 8). {@link SignedUrlService} provides this capability in a
|
|
8
|
+
* **provider-agnostic** way:
|
|
9
|
+
*
|
|
10
|
+
* - When the backing {@link StorageDriver} implements the optional native
|
|
11
|
+
* `signedUrl` capability, the service delegates minting and verification to
|
|
12
|
+
* the driver so a provider with first-class signed-URL support (e.g. S3) is
|
|
13
|
+
* used directly.
|
|
14
|
+
* - Otherwise the service **simulates** signed URLs with an HMAC computed over
|
|
15
|
+
* the `(key, op, expiry)` tuple (plus the accepted options) using
|
|
16
|
+
* `config.signingSecret` (`node:crypto` HMAC-SHA256), mirroring the zero-
|
|
17
|
+
* dependency scheme described in the design. Verification recomputes the HMAC
|
|
18
|
+
* and checks the expiry against an injected {@link Clock} (default
|
|
19
|
+
* `systemClock`), so time is deterministic in tests (Requirement 8.3, 8.4).
|
|
20
|
+
*
|
|
21
|
+
* Semantics (Requirement 8):
|
|
22
|
+
* - {@link SignedUrlService.sign} mints a URL authorizing **exactly** the
|
|
23
|
+
* requested operation on the given key (Requirement 8.1) and accepts options
|
|
24
|
+
* for expiration, request headers, content type, maximum size, and custom
|
|
25
|
+
* metadata (Requirement 8.2).
|
|
26
|
+
* - {@link SignedUrlService.verify} returns a {@link SignedUrlVerification}
|
|
27
|
+
* whose `valid` flag is `true` only when the URL is well-formed, its signature
|
|
28
|
+
* matches, its authorized operation matches the operation being attempted, and
|
|
29
|
+
* it is used strictly before its expiry (Requirements 8.3, 8.4). Otherwise
|
|
30
|
+
* `valid` is `false` with a `reason` of `'expired'`, `'operation-mismatch'`,
|
|
31
|
+
* `'signature-mismatch'`, or `'malformed'`.
|
|
32
|
+
*
|
|
33
|
+
* The module depends only on the driver contract, the shared type surface, the
|
|
34
|
+
* error hierarchy, and `node:crypto`, keeping the dependency direction acyclic.
|
|
35
|
+
*
|
|
36
|
+
* _Requirements: 8.1, 8.2, 8.3, 8.4_
|
|
37
|
+
*/
|
|
38
|
+
import { Buffer } from "node:buffer";
|
|
39
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
40
|
+
import { systemClock } from "streetjs";
|
|
41
|
+
import { StorageConfigError } from "./errors.js";
|
|
42
|
+
/**
|
|
43
|
+
* The scheme prefix of a simulated signed URL. The remainder of the URL is
|
|
44
|
+
* `<base64url(payload)>.<hmacHex>`, where neither component can contain a `.`
|
|
45
|
+
* (base64url has no `.`; a hex digest has no `.`), so the last `.` is an
|
|
46
|
+
* unambiguous separator.
|
|
47
|
+
*/
|
|
48
|
+
const SIGNED_URL_SCHEME = "streetjs-signed://";
|
|
49
|
+
/** The HMAC digest algorithm used to sign simulated URLs. */
|
|
50
|
+
const HMAC_ALGORITHM = "sha256";
|
|
51
|
+
/**
|
|
52
|
+
* The default validity window for a minted URL when `options.expiresInMs` is
|
|
53
|
+
* omitted (15 minutes). Chosen to be short-lived by default so an accidentally
|
|
54
|
+
* leaked URL is only briefly usable.
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_EXPIRES_IN_MS = 15 * 60 * 1000;
|
|
57
|
+
/**
|
|
58
|
+
* Provider-agnostic signed URL service built on the driver contract and an HMAC
|
|
59
|
+
* simulation.
|
|
60
|
+
*
|
|
61
|
+
* A single instance is held by the facade and bound to one {@link StorageDriver}
|
|
62
|
+
* for its lifetime, so the native-vs-simulated decision (based on whether the
|
|
63
|
+
* driver exposes a `signedUrl` capability) is stable across every call.
|
|
64
|
+
*/
|
|
65
|
+
export class SignedUrlService {
|
|
66
|
+
/** The HMAC signing secret for the simulated path, when configured. */
|
|
67
|
+
signingSecret;
|
|
68
|
+
/** Injected clock used to stamp and check expiry. */
|
|
69
|
+
clock;
|
|
70
|
+
/** The driver's native signed-URL capability, when present. */
|
|
71
|
+
native;
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.signingSecret = options.signingSecret;
|
|
74
|
+
this.clock = options.clock ?? systemClock;
|
|
75
|
+
this.native = options.driver?.signedUrl;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Mint a URL authorizing **exactly** the operation `op` on `key`
|
|
79
|
+
* (Requirement 8.1). The URL carries the accepted options — expiration,
|
|
80
|
+
* request headers, content type, maximum size, and custom metadata
|
|
81
|
+
* (Requirement 8.2) — and, for the simulated path, an HMAC over the whole
|
|
82
|
+
* payload so its `(key, op, expiry)` tuple and options are tamper-evident.
|
|
83
|
+
*
|
|
84
|
+
* The expiry instant is computed from the injected clock plus
|
|
85
|
+
* `options.expiresInMs` (default {@link DEFAULT_EXPIRES_IN_MS}).
|
|
86
|
+
*
|
|
87
|
+
* @throws StorageConfigError when no native capability is available and no
|
|
88
|
+
* `signingSecret` was configured, since a URL cannot be signed without a key.
|
|
89
|
+
*/
|
|
90
|
+
async sign(key, op, options = {}) {
|
|
91
|
+
if (this.native !== undefined) {
|
|
92
|
+
return this.native.sign(key, op, options);
|
|
93
|
+
}
|
|
94
|
+
const secret = this.requireSigningSecret();
|
|
95
|
+
const expiresInMs = options.expiresInMs ?? DEFAULT_EXPIRES_IN_MS;
|
|
96
|
+
const expiry = this.clock() + expiresInMs;
|
|
97
|
+
const payload = {
|
|
98
|
+
v: 1,
|
|
99
|
+
key,
|
|
100
|
+
op,
|
|
101
|
+
expiry,
|
|
102
|
+
...(options.headers !== undefined ? { headers: options.headers } : {}),
|
|
103
|
+
...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
|
|
104
|
+
...(options.maxSize !== undefined ? { maxSize: options.maxSize } : {}),
|
|
105
|
+
...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
|
|
106
|
+
};
|
|
107
|
+
const encoded = encodePayload(payload);
|
|
108
|
+
const signature = hmacHex(secret, encoded);
|
|
109
|
+
return `${SIGNED_URL_SCHEME}${encoded}.${signature}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Verify a signed `url` for the operation being attempted, returning a
|
|
113
|
+
* {@link SignedUrlVerification}.
|
|
114
|
+
*
|
|
115
|
+
* `valid` is `true` only when the URL is well-formed, its signature matches,
|
|
116
|
+
* its authorized operation equals `expectedOp` (when supplied), and it is used
|
|
117
|
+
* strictly before its expiry relative to `now` (Requirements 8.3, 8.4). When
|
|
118
|
+
* `expectedOp` is omitted the operation check is skipped and only signature and
|
|
119
|
+
* expiry are enforced. `now` defaults to the injected clock so tests can drive
|
|
120
|
+
* expiry deterministically.
|
|
121
|
+
*
|
|
122
|
+
* Failure reasons are reported precisely: `'malformed'` for an unparseable
|
|
123
|
+
* URL, `'signature-mismatch'` for a tampered/incorrectly signed URL,
|
|
124
|
+
* `'operation-mismatch'` when the authorized op differs from `expectedOp`, and
|
|
125
|
+
* `'expired'` when `now` is at or after the expiry.
|
|
126
|
+
*/
|
|
127
|
+
verify(url, expectedOp, now) {
|
|
128
|
+
if (this.native !== undefined) {
|
|
129
|
+
return this.native.verify(url, now ?? this.clock());
|
|
130
|
+
}
|
|
131
|
+
const secret = this.signingSecret;
|
|
132
|
+
if (secret === undefined) {
|
|
133
|
+
// Without a secret no simulated URL could have been minted, so any URL is
|
|
134
|
+
// treated as unverifiable rather than silently valid.
|
|
135
|
+
return { valid: false, reason: "malformed" };
|
|
136
|
+
}
|
|
137
|
+
const parsed = parseSignedUrl(url);
|
|
138
|
+
if (parsed === undefined) {
|
|
139
|
+
return { valid: false, reason: "malformed" };
|
|
140
|
+
}
|
|
141
|
+
const { encoded, signature } = parsed;
|
|
142
|
+
const expected = hmacHex(secret, encoded);
|
|
143
|
+
if (!timingSafeEqualHex(signature, expected)) {
|
|
144
|
+
return { valid: false, reason: "signature-mismatch" };
|
|
145
|
+
}
|
|
146
|
+
const payload = decodePayload(encoded);
|
|
147
|
+
if (payload === undefined) {
|
|
148
|
+
return { valid: false, reason: "malformed" };
|
|
149
|
+
}
|
|
150
|
+
if (expectedOp !== undefined && payload.op !== expectedOp) {
|
|
151
|
+
return {
|
|
152
|
+
valid: false,
|
|
153
|
+
reason: "operation-mismatch",
|
|
154
|
+
key: payload.key,
|
|
155
|
+
op: payload.op,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const at = now ?? this.clock();
|
|
159
|
+
if (at >= payload.expiry) {
|
|
160
|
+
return { valid: false, reason: "expired", key: payload.key, op: payload.op };
|
|
161
|
+
}
|
|
162
|
+
return { valid: true, key: payload.key, op: payload.op };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resolve the signing secret or throw a descriptive {@link StorageConfigError}.
|
|
166
|
+
* Used only on the simulated minting path, where a secret is mandatory.
|
|
167
|
+
*/
|
|
168
|
+
requireSigningSecret() {
|
|
169
|
+
if (this.signingSecret === undefined || this.signingSecret === "") {
|
|
170
|
+
throw new StorageConfigError("Signed URLs require a non-empty \"signingSecret\" in the storage configuration " +
|
|
171
|
+
"when the driver has no native signed-URL capability.");
|
|
172
|
+
}
|
|
173
|
+
return this.signingSecret;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Encode a payload as a compact base64url JSON string. */
|
|
177
|
+
function encodePayload(payload) {
|
|
178
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
179
|
+
}
|
|
180
|
+
/** Decode a base64url JSON payload, returning `undefined` when it is malformed. */
|
|
181
|
+
function decodePayload(encoded) {
|
|
182
|
+
try {
|
|
183
|
+
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
|
184
|
+
const value = JSON.parse(json);
|
|
185
|
+
if (!isSignedPayload(value)) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Structural guard that a decoded value carries the required payload fields. */
|
|
195
|
+
function isSignedPayload(value) {
|
|
196
|
+
if (typeof value !== "object" || value === null) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const candidate = value;
|
|
200
|
+
return (candidate.v === 1 &&
|
|
201
|
+
typeof candidate.key === "string" &&
|
|
202
|
+
(candidate.op === "GET" || candidate.op === "PUT" || candidate.op === "DELETE") &&
|
|
203
|
+
typeof candidate.expiry === "number" &&
|
|
204
|
+
Number.isFinite(candidate.expiry));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Parse a simulated signed URL into its encoded payload and signature, returning
|
|
208
|
+
* `undefined` when the scheme is wrong or the separator is missing.
|
|
209
|
+
*/
|
|
210
|
+
function parseSignedUrl(url) {
|
|
211
|
+
if (typeof url !== "string" || !url.startsWith(SIGNED_URL_SCHEME)) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const body = url.slice(SIGNED_URL_SCHEME.length);
|
|
215
|
+
const separator = body.lastIndexOf(".");
|
|
216
|
+
if (separator <= 0 || separator >= body.length - 1) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return { encoded: body.slice(0, separator), signature: body.slice(separator + 1) };
|
|
220
|
+
}
|
|
221
|
+
/** Compute the lowercase HMAC-SHA256 hex digest of `data` under `secret`. */
|
|
222
|
+
function hmacHex(secret, data) {
|
|
223
|
+
return createHmac(HMAC_ALGORITHM, secret).update(data).digest("hex");
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Constant-time comparison of two hex signature strings. Falls back to `false`
|
|
227
|
+
* for length mismatches (which `timingSafeEqual` would otherwise throw on).
|
|
228
|
+
*/
|
|
229
|
+
function timingSafeEqualHex(a, b) {
|
|
230
|
+
const bufferA = Buffer.from(a, "utf8");
|
|
231
|
+
const bufferB = Buffer.from(b, "utf8");
|
|
232
|
+
if (bufferA.length !== bufferB.length) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return timingSafeEqual(bufferA, bufferB);
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=signed-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signed-url.js","sourceRoot":"","sources":["../src/signed-url.ts"],"names":[],"mappings":"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"}
|