@remit/storage-service 0.0.11 → 0.0.13
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/dist/backends/filesystem.d.ts +12 -0
- package/dist/backends/filesystem.js +316 -0
- package/dist/backends/s3.d.ts +3 -0
- package/dist/backends/s3.js +413 -0
- package/dist/filesystem.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/outbox-sweep.d.ts +33 -0
- package/dist/outbox-sweep.js +37 -0
- package/dist/outbox-upload-url.d.ts +44 -0
- package/{src/outbox-upload-url.ts → dist/outbox-upload-url.js} +20 -50
- package/dist/s3.d.ts +3 -0
- package/dist/s3.js +28 -0
- package/dist/signed-path.d.ts +14 -0
- package/dist/signed-path.js +46 -0
- package/dist/storage.d.ts +255 -0
- package/dist/storage.js +228 -0
- package/dist/uri.d.ts +9 -0
- package/dist/uri.js +28 -0
- package/package.json +51 -46
- package/src/backends/filesystem.ts +0 -624
- package/src/backends/s3.test.ts +0 -601
- package/src/backends/s3.ts +0 -761
- package/src/index.ts +0 -60
- package/src/outbox-attachment-retrieve.test.ts +0 -56
- package/src/outbox-sweep.test.ts +0 -196
- package/src/outbox-sweep.ts +0 -103
- package/src/outbox-upload-url.test.ts +0 -290
- package/src/s3.ts +0 -36
- package/src/signed-path.ts +0 -86
- package/src/storage.test.ts +0 -886
- package/src/storage.ts +0 -866
- package/src/uri.ts +0 -47
- package/tsconfig.json +0 -8
- /package/{src/filesystem.ts → dist/filesystem.d.ts} +0 -0
package/src/s3.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { S3Client } from "@aws-sdk/client-s3";
|
|
2
|
-
import { createFilesystemStorageService } from "./backends/filesystem.js";
|
|
3
|
-
import { createS3StorageService } from "./backends/s3.js";
|
|
4
|
-
import type { StorageService } from "./storage.js";
|
|
5
|
-
|
|
6
|
-
export { createS3StorageService } from "./backends/s3.js";
|
|
7
|
-
|
|
8
|
-
export const createStorageService = (): StorageService => {
|
|
9
|
-
const bucketName = process.env.S3_BUCKET_NAME;
|
|
10
|
-
|
|
11
|
-
if (bucketName) {
|
|
12
|
-
const client = new S3Client({
|
|
13
|
-
endpoint: process.env.S3_ENDPOINT,
|
|
14
|
-
// The SDK default (WHEN_SUPPORTED) hangs a CRC32 of an empty body off
|
|
15
|
-
// every presigned PUT as signed query params. Real S3 tolerates it;
|
|
16
|
-
// MinIO and LocalStack reject the request, and S3_ENDPOINT exists to
|
|
17
|
-
// point at exactly those.
|
|
18
|
-
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
19
|
-
});
|
|
20
|
-
return createS3StorageService(client, bucketName);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const basePath = process.env.STORAGE_LOCAL_PATH ?? ".remit/storage";
|
|
24
|
-
// BETTER_AUTH_URL is the deployment's own public base — the value the token
|
|
25
|
-
// issuer and the JWKS discovery document already agree on — so a minted
|
|
26
|
-
// upload URL resolves from wherever the browser reached the API. Both it and
|
|
27
|
-
// the signing secret are required on the self-host stack; without them a
|
|
28
|
-
// mint fails loud inside the backend rather than handing out a URL that
|
|
29
|
-
// cannot be verified.
|
|
30
|
-
const origin = process.env.BETTER_AUTH_URL;
|
|
31
|
-
const signingSecret = process.env.BETTER_AUTH_SECRET;
|
|
32
|
-
return createFilesystemStorageService(
|
|
33
|
-
basePath,
|
|
34
|
-
origin && signingSecret ? { origin, signingSecret } : undefined,
|
|
35
|
-
);
|
|
36
|
-
};
|
package/src/signed-path.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The HMAC scheme behind every signed storage URL this stack mints.
|
|
5
|
-
*
|
|
6
|
-
* It started as the read side — `/content/*` is served against a signature
|
|
7
|
-
* because a bearer token cannot ride on an `<img src>` inside email HTML — and
|
|
8
|
-
* the write side needs exactly the same capability: a URL that grants access to
|
|
9
|
-
* one path, for a while, and is unforgeable without the key. Rather than a
|
|
10
|
-
* second scheme, both use this one with a different derivation label, so a
|
|
11
|
-
* signature minted to read content can never be replayed as permission to
|
|
12
|
-
* write, and vice versa.
|
|
13
|
-
*
|
|
14
|
-
* The signed message is the storage path followed by every claim in a fixed
|
|
15
|
-
* order. Because the path is part of the message, a signature minted for one
|
|
16
|
-
* account's object cannot be presented for another's.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const deriveSigningKey = (masterSecret: string, label: string): Buffer =>
|
|
20
|
-
createHmac("sha256", masterSecret).update(label).digest();
|
|
21
|
-
|
|
22
|
-
const canonicalMessage = (
|
|
23
|
-
relativePath: string,
|
|
24
|
-
claims: readonly number[],
|
|
25
|
-
): string => [relativePath, ...claims].join("\n");
|
|
26
|
-
|
|
27
|
-
const computeSignature = (
|
|
28
|
-
key: Buffer,
|
|
29
|
-
relativePath: string,
|
|
30
|
-
claims: readonly number[],
|
|
31
|
-
): string =>
|
|
32
|
-
createHmac("sha256", key)
|
|
33
|
-
.update(canonicalMessage(relativePath, claims))
|
|
34
|
-
.digest("base64url");
|
|
35
|
-
|
|
36
|
-
export const signStoragePath = (
|
|
37
|
-
masterSecret: string,
|
|
38
|
-
label: string,
|
|
39
|
-
relativePath: string,
|
|
40
|
-
claims: readonly number[],
|
|
41
|
-
): string =>
|
|
42
|
-
computeSignature(deriveSigningKey(masterSecret, label), relativePath, claims);
|
|
43
|
-
|
|
44
|
-
export type SignedPathFailure =
|
|
45
|
-
| "missing"
|
|
46
|
-
| "malformed"
|
|
47
|
-
| "expired"
|
|
48
|
-
| "bad-signature";
|
|
49
|
-
|
|
50
|
-
export type SignedPathResult =
|
|
51
|
-
| { valid: true }
|
|
52
|
-
| { valid: false; reason: SignedPathFailure };
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Verify a presented signature against the path and claims it must cover. The
|
|
56
|
-
* first claim is always the expiry, in unix seconds. Pure, so the decision can
|
|
57
|
-
* be unit-tested without a live server.
|
|
58
|
-
*/
|
|
59
|
-
export const verifyStoragePath = (
|
|
60
|
-
masterSecret: string,
|
|
61
|
-
label: string,
|
|
62
|
-
relativePath: string,
|
|
63
|
-
claims: readonly number[],
|
|
64
|
-
sig: string | undefined,
|
|
65
|
-
nowSeconds: number,
|
|
66
|
-
): SignedPathResult => {
|
|
67
|
-
if (!sig || claims.length === 0) return { valid: false, reason: "missing" };
|
|
68
|
-
|
|
69
|
-
const [exp] = claims;
|
|
70
|
-
if (!claims.every((claim) => Number.isInteger(claim) && claim > 0)) {
|
|
71
|
-
return { valid: false, reason: "malformed" };
|
|
72
|
-
}
|
|
73
|
-
if (exp < nowSeconds) return { valid: false, reason: "expired" };
|
|
74
|
-
|
|
75
|
-
const expected = Buffer.from(
|
|
76
|
-
signStoragePath(masterSecret, label, relativePath, claims),
|
|
77
|
-
);
|
|
78
|
-
const presented = Buffer.from(sig);
|
|
79
|
-
if (expected.length !== presented.length) {
|
|
80
|
-
return { valid: false, reason: "bad-signature" };
|
|
81
|
-
}
|
|
82
|
-
if (!timingSafeEqual(expected, presented)) {
|
|
83
|
-
return { valid: false, reason: "bad-signature" };
|
|
84
|
-
}
|
|
85
|
-
return { valid: true };
|
|
86
|
-
};
|