@mieweb/pulsevault 0.0.1 → 0.1.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/CHANGELOG.md +184 -0
- package/LICENSE +15 -39
- package/OPERATIONS.md +223 -0
- package/PROTOCOL.md +312 -0
- package/README.md +532 -122
- package/augment.d.ts +1 -0
- package/augment.js +3 -0
- package/core.d.ts +1 -0
- package/core.js +7 -0
- package/dist/app.d.ts +90 -34
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +23 -33
- package/dist/app.js.map +1 -1
- package/dist/augment.d.ts +14 -7
- package/dist/augment.d.ts.map +1 -1
- package/dist/augment.js.map +1 -1
- package/dist/core.d.ts +108 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +431 -0
- package/dist/core.js.map +1 -0
- package/dist/lib/authorize.d.ts +19 -0
- package/dist/lib/authorize.d.ts.map +1 -0
- package/dist/lib/authorize.js +2 -0
- package/dist/lib/authorize.js.map +1 -0
- package/dist/lib/capability-token.d.ts +80 -0
- package/dist/lib/capability-token.d.ts.map +1 -0
- package/dist/lib/capability-token.js +126 -0
- package/dist/lib/capability-token.js.map +1 -0
- package/dist/lib/checksum.d.ts +45 -0
- package/dist/lib/checksum.d.ts.map +1 -0
- package/dist/lib/checksum.js +102 -0
- package/dist/lib/checksum.js.map +1 -0
- package/dist/lib/deeplinks.d.ts +37 -21
- package/dist/lib/deeplinks.d.ts.map +1 -1
- package/dist/lib/deeplinks.js +54 -13
- package/dist/lib/deeplinks.js.map +1 -1
- package/dist/lib/magic.d.ts +30 -5
- package/dist/lib/magic.d.ts.map +1 -1
- package/dist/lib/magic.js +51 -14
- package/dist/lib/magic.js.map +1 -1
- package/dist/lib/options.d.ts +39 -0
- package/dist/lib/options.d.ts.map +1 -0
- package/dist/lib/options.js +103 -0
- package/dist/lib/options.js.map +1 -0
- package/dist/lib/pulsevaultTus.d.ts +52 -26
- package/dist/lib/pulsevaultTus.d.ts.map +1 -1
- package/dist/lib/pulsevaultTus.js +111 -43
- package/dist/lib/pulsevaultTus.js.map +1 -1
- package/dist/lib/request.d.ts +24 -0
- package/dist/lib/request.d.ts.map +1 -0
- package/dist/lib/request.js +16 -0
- package/dist/lib/request.js.map +1 -0
- package/dist/lib/uuid.d.ts.map +1 -1
- package/dist/lib/uuid.js +1 -1
- package/dist/lib/uuid.js.map +1 -1
- package/dist/routes/pulsevault.d.ts +18 -14
- package/dist/routes/pulsevault.d.ts.map +1 -1
- package/dist/routes/pulsevault.js +127 -247
- package/dist/routes/pulsevault.js.map +1 -1
- package/dist/storage/local.d.ts +31 -10
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/local.js +166 -82
- package/dist/storage/local.js.map +1 -1
- package/dist/storage/s3.d.ts +109 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +432 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/types.d.ts +69 -17
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +35 -7
package/dist/core.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import send from '@fastify/send';
|
|
2
|
+
import { createPulsevaultTusServer, pulseVaultTusContext, artifactIdFromUploadId, } from './lib/pulsevaultTus.js';
|
|
3
|
+
import { pulseVaultError } from './lib/errors.js';
|
|
4
|
+
import { isUuid } from './lib/uuid.js';
|
|
5
|
+
import { consoleLogger } from './lib/request.js';
|
|
6
|
+
import { normalizeAllowedExtensions, validateBasePath, validateMaxUploadSize, validateUploadUnit, validateAllowedExtensions, warnIfUsingDeprecatedProjectHooks, composeValidatePayload, composeOnUploadComplete, } from './lib/options.js';
|
|
7
|
+
/** Wire protocol version this release implements. See `/capabilities` and `PROTOCOL.md`. */
|
|
8
|
+
export const PROTOCOL_VERSION = 1;
|
|
9
|
+
const MIN_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
10
|
+
const MAX_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
11
|
+
/**
|
|
12
|
+
* Pull `artifactId` (or the legacy `videoid`/`projectid` aliases), `kind`,
|
|
13
|
+
* and `relatedTo` out of a raw `Upload-Metadata` header. Format is a
|
|
14
|
+
* comma-separated list of `<key> <base64-value>` pairs (tus v1 creation
|
|
15
|
+
* extension).
|
|
16
|
+
*
|
|
17
|
+
* Alias precedence is a fixed priority (`artifactId` beats `videoid` beats
|
|
18
|
+
* `projectid`, regardless of header order) so this always agrees with
|
|
19
|
+
* `namingFunction` in `lib/pulsevaultTus.ts` — which uses the same
|
|
20
|
+
* `?? `-chain precedence to decide what's actually reserved/written to
|
|
21
|
+
* storage. If these two disagreed, `authorize()` could validate ownership of
|
|
22
|
+
* a different artifactId than the one the upload actually lands under.
|
|
23
|
+
*/
|
|
24
|
+
function parseUploadMetadata(header) {
|
|
25
|
+
let artifactIdRaw;
|
|
26
|
+
let videoidRaw;
|
|
27
|
+
let projectidRaw;
|
|
28
|
+
let kind = 'video';
|
|
29
|
+
let relatedTo;
|
|
30
|
+
for (const pair of header.split(',')) {
|
|
31
|
+
const trimmed = pair.trim();
|
|
32
|
+
if (!trimmed)
|
|
33
|
+
continue;
|
|
34
|
+
const sep = trimmed.indexOf(' ');
|
|
35
|
+
if (sep < 0)
|
|
36
|
+
continue;
|
|
37
|
+
const key = trimmed.slice(0, sep);
|
|
38
|
+
const value = trimmed.slice(sep + 1).trim();
|
|
39
|
+
if (!value)
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const decoded = Buffer.from(value, 'base64').toString('utf8');
|
|
43
|
+
if (key === 'artifactId') {
|
|
44
|
+
artifactIdRaw ??= decoded;
|
|
45
|
+
}
|
|
46
|
+
else if (key === 'videoid') {
|
|
47
|
+
videoidRaw ??= decoded;
|
|
48
|
+
}
|
|
49
|
+
else if (key === 'projectid') {
|
|
50
|
+
projectidRaw ??= decoded;
|
|
51
|
+
}
|
|
52
|
+
else if (key === 'kind') {
|
|
53
|
+
const lower = decoded.trim().toLowerCase();
|
|
54
|
+
kind = lower === 'project' ? 'project' : lower === 'captions' ? 'captions' : 'video';
|
|
55
|
+
}
|
|
56
|
+
else if (key === 'relatedTo' && !relatedTo) {
|
|
57
|
+
relatedTo = isUuid(decoded) ? decoded : undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore malformed base64
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const candidate = (artifactIdRaw ?? videoidRaw ?? projectidRaw ?? '').trim();
|
|
65
|
+
const artifactId = isUuid(candidate) ? candidate : undefined;
|
|
66
|
+
return { artifactId, kind, relatedTo };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `@tus/server`'s `BaseHandler.getFileIdFromRequest` — the function that
|
|
70
|
+
* ultimately decides which upload a PATCH/HEAD/DELETE actually operates on —
|
|
71
|
+
* extracts its file id from the request URL's *last* `/`-delimited segment
|
|
72
|
+
* (`reExtractFileID = /([^/]+)\/?$/`), not from the first segment after
|
|
73
|
+
* `/upload/`. This MUST mirror that exact regex: a URL with extra path
|
|
74
|
+
* segments after the real id (Fastify's `/upload/*` route accepts them) would
|
|
75
|
+
* otherwise let `authorize()` see and approve one artifactId (whichever this
|
|
76
|
+
* function resolved) while `@tus/server` writes the request body against a
|
|
77
|
+
* *different* one (whichever it resolved) — an attacker holding a valid
|
|
78
|
+
* token for their own artifact could smuggle a second, victim artifactId as
|
|
79
|
+
* a trailing path segment and have their bytes land there instead, fully
|
|
80
|
+
* bypassing authorization for the artifact actually written to.
|
|
81
|
+
*/
|
|
82
|
+
const TUS_LAST_URL_SEGMENT = /([^/]+)\/?$/;
|
|
83
|
+
/**
|
|
84
|
+
* Decode the tus file id (base64url-encoded, shaped `<kind>/<artifactId><ext>`
|
|
85
|
+
* by `namingFunction` in `lib/pulsevaultTus.ts`) that `@tus/server` itself
|
|
86
|
+
* will resolve a PATCH/HEAD/DELETE request to, and recover the artifactId via
|
|
87
|
+
* the exact same parser `onUploadFinish` uses — so this can never drift from
|
|
88
|
+
* what `@tus/server` actually operates on. See `TUS_LAST_URL_SEGMENT` above
|
|
89
|
+
* for why this must match the *last* URL segment, not the first one after
|
|
90
|
+
* `/upload/`.
|
|
91
|
+
*/
|
|
92
|
+
function artifactIdFromTusUrl(url) {
|
|
93
|
+
const match = TUS_LAST_URL_SEGMENT.exec(url);
|
|
94
|
+
if (!match?.[1])
|
|
95
|
+
return undefined;
|
|
96
|
+
let lastSegment;
|
|
97
|
+
try {
|
|
98
|
+
lastSegment = decodeURIComponent(match[1]);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
let decoded;
|
|
104
|
+
try {
|
|
105
|
+
decoded = Buffer.from(lastSegment, 'base64url').toString('utf8');
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return artifactIdFromUploadId(decoded);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the artifact kind for an artifactId from storage. Duck-typed so it
|
|
114
|
+
* works with any adapter (those without `getKind` return `"video"`).
|
|
115
|
+
*/
|
|
116
|
+
async function resolveStorageKind(storage, artifactId) {
|
|
117
|
+
const candidate = storage.getKind;
|
|
118
|
+
if (typeof candidate !== 'function')
|
|
119
|
+
return 'video';
|
|
120
|
+
const result = await candidate(artifactId);
|
|
121
|
+
return result ?? 'video';
|
|
122
|
+
}
|
|
123
|
+
/** Resolve the `relatedTo` artifact for an artifactId from storage, if the adapter supports it. */
|
|
124
|
+
async function resolveStorageRelatedTo(storage, artifactId) {
|
|
125
|
+
const candidate = storage.getRelatedTo;
|
|
126
|
+
if (typeof candidate !== 'function')
|
|
127
|
+
return undefined;
|
|
128
|
+
const result = await candidate(artifactId);
|
|
129
|
+
return result ?? undefined;
|
|
130
|
+
}
|
|
131
|
+
function extractAuthzStatus(err) {
|
|
132
|
+
const e = err;
|
|
133
|
+
if (typeof e?.statusCode === 'number')
|
|
134
|
+
return e.statusCode;
|
|
135
|
+
if (typeof e?.status_code === 'number')
|
|
136
|
+
return e.status_code;
|
|
137
|
+
return 403;
|
|
138
|
+
}
|
|
139
|
+
function extractAuthzMessage(err) {
|
|
140
|
+
if (err instanceof Error && err.message)
|
|
141
|
+
return err.message;
|
|
142
|
+
return 'Forbidden';
|
|
143
|
+
}
|
|
144
|
+
function writeJson(res, statusCode, body) {
|
|
145
|
+
res.writeHead(statusCode, { 'content-type': 'application/json; charset=utf-8' });
|
|
146
|
+
res.end(JSON.stringify(body));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Every response from a pulsevault route carries the wire protocol version
|
|
150
|
+
* it implements, so a client can detect "this server is too old/new for me"
|
|
151
|
+
* without a dedicated round-trip. Called at the top of each granular handler
|
|
152
|
+
* (rather than only in the aggregate `handler`) so it's also applied when a
|
|
153
|
+
* host framework — e.g. the Fastify adapter, which hijacks the reply and
|
|
154
|
+
* calls these directly — bypasses the aggregate router entirely. Must run
|
|
155
|
+
* before any `writeHead`/`end` call below: `setHeader` values merge into
|
|
156
|
+
* whatever headers object a later `writeHead` call passes.
|
|
157
|
+
*/
|
|
158
|
+
function stampProtocolVersion(res) {
|
|
159
|
+
res.setHeader('Protocol-Version', String(PROTOCOL_VERSION));
|
|
160
|
+
}
|
|
161
|
+
/** Same augmentation as `augment.ts`'s `FastifyRequest.pulseVault`, applied to a raw request. */
|
|
162
|
+
function stashPulseVaultContext(req, ctx) {
|
|
163
|
+
req.pulseVault = ctx;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build the framework-agnostic core: the same authorize/validatePayload/
|
|
167
|
+
* onUploadComplete/onArtifactEvent orchestration, tus glue, capabilities
|
|
168
|
+
* payload, and artifact GET/DELETE logic the Fastify plugin uses, operating
|
|
169
|
+
* on raw `(req, res)` instead of Fastify's `request`/`reply` wrappers. Both
|
|
170
|
+
* the Fastify plugin and this factory ultimately call into the same
|
|
171
|
+
* `lib/pulsevaultTus.js` tus server, so behavior can't drift between them.
|
|
172
|
+
*/
|
|
173
|
+
export function createPulseVaultCore(options) {
|
|
174
|
+
validateBasePath(options.basePath, 'basePath');
|
|
175
|
+
validateMaxUploadSize(options.maxUploadSize);
|
|
176
|
+
validateUploadUnit(options.uploadUnit);
|
|
177
|
+
validateAllowedExtensions(options.allowedExtensions);
|
|
178
|
+
warnIfUsingDeprecatedProjectHooks(options);
|
|
179
|
+
const { storage, basePath, maxUploadSize, cache, authorize, onArtifactEvent } = options;
|
|
180
|
+
const stripBasePath = options.stripBasePath ?? true;
|
|
181
|
+
const uploadUnit = options.uploadUnit ?? 'beat';
|
|
182
|
+
const allowedExtensions = normalizeAllowedExtensions(options.allowedExtensions);
|
|
183
|
+
const validatePayload = composeValidatePayload(options.validatePayload, options.validateProjectPayload);
|
|
184
|
+
const onUploadComplete = composeOnUploadComplete(options.onUploadComplete, options.onProjectUploadComplete);
|
|
185
|
+
const logger = options.logger ?? consoleLogger;
|
|
186
|
+
const tusPath = `${basePath}/upload`;
|
|
187
|
+
const tusServer = createPulsevaultTusServer({
|
|
188
|
+
storage,
|
|
189
|
+
tusPath,
|
|
190
|
+
maxSize: maxUploadSize,
|
|
191
|
+
allowedExtensions,
|
|
192
|
+
validatePayload,
|
|
193
|
+
onUploadComplete,
|
|
194
|
+
onArtifactEvent,
|
|
195
|
+
logger,
|
|
196
|
+
});
|
|
197
|
+
/**
|
|
198
|
+
* Run the consumer's `authorize` hook (if any) for a TUS request. Returns
|
|
199
|
+
* `true` iff the request may proceed; on rejection, this function already
|
|
200
|
+
* wrote the response.
|
|
201
|
+
*/
|
|
202
|
+
const runAuthorize = async (req, res, phase) => {
|
|
203
|
+
let artifactId;
|
|
204
|
+
let kind = 'video';
|
|
205
|
+
let relatedTo;
|
|
206
|
+
if (phase === 'create') {
|
|
207
|
+
const meta = req.headers['upload-metadata'];
|
|
208
|
+
if (typeof meta === 'string') {
|
|
209
|
+
({ artifactId, kind, relatedTo } = parseUploadMetadata(meta));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
artifactId = artifactIdFromTusUrl(req.url ?? '');
|
|
214
|
+
if (artifactId) {
|
|
215
|
+
kind = await resolveStorageKind(storage, artifactId);
|
|
216
|
+
relatedTo = await resolveStorageRelatedTo(storage, artifactId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (artifactId) {
|
|
220
|
+
stashPulseVaultContext(req, { artifactId, kind, relatedTo });
|
|
221
|
+
}
|
|
222
|
+
if (!authorize) {
|
|
223
|
+
return { ok: true, artifactId, kind, relatedTo };
|
|
224
|
+
}
|
|
225
|
+
if (!artifactId && phase === 'patch') {
|
|
226
|
+
// PROTOCOL.md §5.2: failing to resolve the artifactId for an in-flight
|
|
227
|
+
// upload request is an authorization failure — reject, don't fall
|
|
228
|
+
// through to "no artifactId to check, so allow".
|
|
229
|
+
logger.info({ url: req.url, phase }, 'pulsevault authorize rejected: unresolvable artifactId');
|
|
230
|
+
writeJson(res, 403, pulseVaultError('Unable to resolve artifact for authorization'));
|
|
231
|
+
return { ok: false };
|
|
232
|
+
}
|
|
233
|
+
if (!artifactId) {
|
|
234
|
+
return { ok: true, artifactId, kind, relatedTo };
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await authorize(req, { phase, artifactId, kind, relatedTo });
|
|
238
|
+
return { ok: true, artifactId, kind, relatedTo };
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const statusCode = extractAuthzStatus(err);
|
|
242
|
+
const message = extractAuthzMessage(err);
|
|
243
|
+
logger.info({ err, artifactId, phase, statusCode }, 'pulsevault authorize rejected');
|
|
244
|
+
if (phase === 'create') {
|
|
245
|
+
await onArtifactEvent?.({ phase: 'authorize', artifactId, kind, reason: message });
|
|
246
|
+
}
|
|
247
|
+
writeJson(res, statusCode, pulseVaultError(message));
|
|
248
|
+
return { ok: false };
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const handleTus = async (req, res) => {
|
|
252
|
+
stampProtocolVersion(res);
|
|
253
|
+
const phase = req.method === 'POST' ? 'create' : 'patch';
|
|
254
|
+
const authz = await runAuthorize(req, res, phase);
|
|
255
|
+
if (!authz.ok)
|
|
256
|
+
return;
|
|
257
|
+
try {
|
|
258
|
+
await pulseVaultTusContext.run({ request: req, artifactId: authz.artifactId }, () => tusServer.handle(req, res));
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
logger.error({ err }, 'pulsevault tus handler failed');
|
|
262
|
+
if (res.headersSent || res.writableEnded) {
|
|
263
|
+
res.destroy();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
res.statusCode = 500;
|
|
267
|
+
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
268
|
+
res.end('Internal Server Error');
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const handleCapabilities = async (_req, res) => {
|
|
272
|
+
stampProtocolVersion(res);
|
|
273
|
+
writeJson(res, 200, {
|
|
274
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
275
|
+
minSupportedVersion: MIN_SUPPORTED_PROTOCOL_VERSION,
|
|
276
|
+
maxSupportedVersion: MAX_SUPPORTED_PROTOCOL_VERSION,
|
|
277
|
+
uploadUnit,
|
|
278
|
+
kinds: ['video', 'project', 'captions'],
|
|
279
|
+
allowedExtensions,
|
|
280
|
+
maxUploadSize,
|
|
281
|
+
checksum: { algorithms: ['sha256', 'sha1', 'md5'] },
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
/**
|
|
285
|
+
* Shared prelude for the artifact GET/DELETE handlers: resolve `kind`/
|
|
286
|
+
* `relatedTo` from storage, stash the request context, then run `authorize`
|
|
287
|
+
* (if configured) for the given phase. Returns `undefined` (having already
|
|
288
|
+
* written the response) when validation fails or `authorize` rejects.
|
|
289
|
+
*/
|
|
290
|
+
const prepareArtifactRequest = async (req, res, artifactId, phase, token) => {
|
|
291
|
+
if (!isUuid(artifactId)) {
|
|
292
|
+
writeJson(res, 400, pulseVaultError('`artifactId` must be a valid UUID'));
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const kind = await resolveStorageKind(storage, artifactId);
|
|
296
|
+
const relatedTo = await resolveStorageRelatedTo(storage, artifactId);
|
|
297
|
+
stashPulseVaultContext(req, { artifactId, kind, relatedTo });
|
|
298
|
+
if (!authorize)
|
|
299
|
+
return { kind, relatedTo };
|
|
300
|
+
try {
|
|
301
|
+
await authorize(req, { phase, artifactId, kind, relatedTo, token });
|
|
302
|
+
return { kind, relatedTo };
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
const statusCode = extractAuthzStatus(err);
|
|
306
|
+
const message = extractAuthzMessage(err);
|
|
307
|
+
logger.info({ err, artifactId, phase, statusCode }, 'pulsevault authorize rejected');
|
|
308
|
+
await onArtifactEvent?.({ phase: 'authorize', artifactId, kind, reason: message });
|
|
309
|
+
writeJson(res, statusCode, pulseVaultError(message));
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const handleArtifactDelete = async (req, res, artifactId) => {
|
|
314
|
+
stampProtocolVersion(res);
|
|
315
|
+
const prepared = await prepareArtifactRequest(req, res, artifactId, 'delete');
|
|
316
|
+
if (!prepared)
|
|
317
|
+
return;
|
|
318
|
+
if (typeof storage.remove !== 'function') {
|
|
319
|
+
writeJson(res, 501, pulseVaultError('Storage adapter does not support delete'));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const removed = await storage.remove(artifactId);
|
|
323
|
+
if (!removed) {
|
|
324
|
+
writeJson(res, 404, pulseVaultError('Artifact not found'));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
res.writeHead(204);
|
|
328
|
+
res.end();
|
|
329
|
+
};
|
|
330
|
+
const handleArtifactGet = async (req, res, artifactId, token) => {
|
|
331
|
+
stampProtocolVersion(res);
|
|
332
|
+
const prepared = await prepareArtifactRequest(req, res, artifactId, 'resolve', token);
|
|
333
|
+
if (!prepared)
|
|
334
|
+
return;
|
|
335
|
+
const resolved = await storage.resolve(artifactId);
|
|
336
|
+
if (!resolved) {
|
|
337
|
+
writeJson(res, 404, pulseVaultError('Artifact not found'));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (resolved.kind === 'redirect') {
|
|
341
|
+
res.writeHead(resolved.statusCode ?? 302, { Location: resolved.url });
|
|
342
|
+
res.end();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const result = await send(req, resolved.filename, { root: resolved.root, ...cache });
|
|
346
|
+
if (result.type === 'error') {
|
|
347
|
+
writeJson(res, result.statusCode, pulseVaultError(result.metadata.error.message));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const headers = { ...result.headers };
|
|
351
|
+
// If the storage adapter provided an explicit content type (e.g. for
|
|
352
|
+
// non-standard extensions like `.pulse`), override what @fastify/send
|
|
353
|
+
// would otherwise infer from the filename.
|
|
354
|
+
if (resolved.contentType) {
|
|
355
|
+
headers['content-type'] = resolved.contentType;
|
|
356
|
+
}
|
|
357
|
+
res.writeHead(result.statusCode, headers);
|
|
358
|
+
result.stream.pipe(res);
|
|
359
|
+
};
|
|
360
|
+
const handler = async (req, res, next) => {
|
|
361
|
+
// (Protocol-Version is stamped by each granular handler below, so it's
|
|
362
|
+
// applied consistently whether reached through this router or called
|
|
363
|
+
// directly by a host framework's own routing.)
|
|
364
|
+
const notFound = () => {
|
|
365
|
+
if (next) {
|
|
366
|
+
next();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
|
|
370
|
+
res.end('Not Found');
|
|
371
|
+
};
|
|
372
|
+
const url = new URL(req.url ?? '/', 'http://internal');
|
|
373
|
+
let pathname = url.pathname;
|
|
374
|
+
if (stripBasePath && basePath !== '') {
|
|
375
|
+
if (pathname === basePath) {
|
|
376
|
+
pathname = '';
|
|
377
|
+
}
|
|
378
|
+
else if (pathname.startsWith(`${basePath}/`)) {
|
|
379
|
+
pathname = pathname.slice(basePath.length);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
notFound();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (pathname === '')
|
|
387
|
+
pathname = '/';
|
|
388
|
+
if (pathname === '/upload' || pathname.startsWith('/upload/')) {
|
|
389
|
+
await handleTus(req, res);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (pathname === '/capabilities' && req.method === 'GET') {
|
|
393
|
+
await handleCapabilities(req, res);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const artifactMatch = pathname.match(/^\/artifacts\/([^/]+)$/);
|
|
397
|
+
if (artifactMatch?.[1]) {
|
|
398
|
+
const artifactId = artifactMatch[1];
|
|
399
|
+
if (req.method === 'GET') {
|
|
400
|
+
await handleArtifactGet(req, res, artifactId, url.searchParams.get('token') ?? undefined);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (req.method === 'DELETE') {
|
|
404
|
+
await handleArtifactDelete(req, res, artifactId);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
notFound();
|
|
409
|
+
};
|
|
410
|
+
return {
|
|
411
|
+
handler,
|
|
412
|
+
shutdown: async () => {
|
|
413
|
+
await storage.shutdown?.();
|
|
414
|
+
},
|
|
415
|
+
handleTus,
|
|
416
|
+
handleCapabilities,
|
|
417
|
+
handleArtifactGet,
|
|
418
|
+
handleArtifactDelete,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// Re-exported so a non-Fastify consumer never needs to import from both `.`
|
|
422
|
+
// and `./core` for a normal setup — these are already framework-agnostic
|
|
423
|
+
// and identical to what the `.` (Fastify) entry point re-exports.
|
|
424
|
+
export { createLocalStorage } from './storage/local.js';
|
|
425
|
+
export { createS3Storage } from './storage/s3.js';
|
|
426
|
+
export { sniffMp4, createMp4Sniffer, createS3Mp4Sniffer } from './lib/magic.js';
|
|
427
|
+
export { buildUploadLink } from './lib/deeplinks.js';
|
|
428
|
+
export { issueCapabilityToken, verifyCapabilityToken, createCapabilityAuthorize, } from './lib/capability-token.js';
|
|
429
|
+
export { createChecksumValidator, createS3ChecksumValidator, parseChecksumMetadata, } from './lib/checksum.js';
|
|
430
|
+
export {} from './lib/request.js';
|
|
431
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PulseVaultRequest } from './request.js';
|
|
2
|
+
import type { UploadKind } from '../storage/types.js';
|
|
3
|
+
export type PulseVaultAuthorizePhase = 'create' | 'patch' | 'resolve' | 'delete';
|
|
4
|
+
export type PulseVaultAuthorizeContext = {
|
|
5
|
+
phase: PulseVaultAuthorizePhase;
|
|
6
|
+
artifactId: string;
|
|
7
|
+
/** Artifact kind: `"video"`, `"project"`, or `"captions"`. Always present. */
|
|
8
|
+
kind: UploadKind;
|
|
9
|
+
/** Bearer / query-string token forwarded from the watch URL, if present. Only populated during the `"resolve"` phase. */
|
|
10
|
+
token?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The session-anchor artifact this one declared via `Upload-Metadata.relatedTo`,
|
|
13
|
+
* if any. Lets `createCapabilityAuthorize` authorize an artifact against a
|
|
14
|
+
* token scoped to the session it belongs to rather than its own id.
|
|
15
|
+
*/
|
|
16
|
+
relatedTo?: string;
|
|
17
|
+
};
|
|
18
|
+
export type PulseVaultAuthorize = (request: PulseVaultRequest, ctx: PulseVaultAuthorizeContext) => void | Promise<void>;
|
|
19
|
+
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/lib/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjF,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,wBAAwB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,IAAI,EAAE,UAAU,CAAC;IACjB,yHAAyH;IACzH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,0BAA0B,KAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/lib/authorize.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { PulseVaultAuthorize } from './authorize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Claims signed into a capability token. `kid` lets a secret be rotated with
|
|
4
|
+
* an overlap window (old + new key both verify) instead of instantly
|
|
5
|
+
* invalidating every outstanding token; `iat` closes a clock-skew gap where
|
|
6
|
+
* signing only `exp` would let a slow server clock accept an already-expired
|
|
7
|
+
* token; `issuer` binds the token to the issuing deployment's identity so a
|
|
8
|
+
* secret accidentally shared between two independent orgs can't be replayed
|
|
9
|
+
* across them.
|
|
10
|
+
*/
|
|
11
|
+
export type CapabilityTokenClaims = {
|
|
12
|
+
/** The artifact (or session-anchor artifact — see `relatedTo`) this token authorizes. */
|
|
13
|
+
artifactId: string;
|
|
14
|
+
/** Issued-at, seconds since epoch. */
|
|
15
|
+
iat: number;
|
|
16
|
+
/** Expiry, seconds since epoch. */
|
|
17
|
+
exp: number;
|
|
18
|
+
/** Key id, looked up via `lookupSecret` at verify time. */
|
|
19
|
+
kid: string;
|
|
20
|
+
/** Issuing deployment's identity, e.g. `"https://vault.acme-hospital.org"`. */
|
|
21
|
+
issuer: string;
|
|
22
|
+
};
|
|
23
|
+
export type IssueCapabilityTokenOptions = {
|
|
24
|
+
/** Key id this token is signed under. Required so rotation has an explicit overlap window. */
|
|
25
|
+
keyId: string;
|
|
26
|
+
/** Issuing deployment's identity. Required so cross-issuer replay can be rejected at verify time. */
|
|
27
|
+
issuer: string;
|
|
28
|
+
/** Token lifetime in seconds. Defaults to 1800 (30 minutes). */
|
|
29
|
+
expirySeconds?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Mint a stateless, HMAC-signed capability token scoped to one artifact (or
|
|
33
|
+
* session-anchor artifact). No server-side session table — verification is a
|
|
34
|
+
* pure function of `(token, secret)`, so the issuing deployment can rotate
|
|
35
|
+
* keys, change its TTL policy, or revoke at the artifact level entirely on
|
|
36
|
+
* its own schedule. See `createCapabilityAuthorize` to use this as the
|
|
37
|
+
* plugin's `authorize` hook directly.
|
|
38
|
+
*/
|
|
39
|
+
export declare function issueCapabilityToken(artifactId: string, secret: string, opts: IssueCapabilityTokenOptions): string;
|
|
40
|
+
/** Look up the signing secret for a given key id. Return `null`/`undefined` for an unrecognized `kid`. */
|
|
41
|
+
export type LookupSecret = (keyId: string) => string | null | undefined;
|
|
42
|
+
export type VerifyCapabilityTokenOptions = {
|
|
43
|
+
/** Must match the token's `issuer` claim exactly. */
|
|
44
|
+
issuer: string;
|
|
45
|
+
/** Clock-skew tolerance in seconds applied to both `iat` and `exp` checks. Defaults to 30. */
|
|
46
|
+
clockToleranceSeconds?: number;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Verify a token minted by `issueCapabilityToken`. Returns the authorized
|
|
50
|
+
* `artifactId` on success, or `null` for any failure (malformed token,
|
|
51
|
+
* unknown `kid`, bad signature, expired, issued too far in the future, or
|
|
52
|
+
* issuer mismatch) — deliberately collapsed to one failure shape so callers
|
|
53
|
+
* can't accidentally branch on *why* a token failed and leak which part was
|
|
54
|
+
* wrong to an attacker.
|
|
55
|
+
*/
|
|
56
|
+
export declare function verifyCapabilityToken(token: string, lookupSecret: LookupSecret, opts: VerifyCapabilityTokenOptions): {
|
|
57
|
+
artifactId: string;
|
|
58
|
+
} | null;
|
|
59
|
+
/**
|
|
60
|
+
* Ready-made `authorize` hook backed by `verifyCapabilityToken`. Pass this
|
|
61
|
+
* directly as the plugin's `authorize` option for a secure-by-default setup
|
|
62
|
+
* with no custom auth code:
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* await app.register(pulseVault, {
|
|
66
|
+
* // ...
|
|
67
|
+
* authorize: createCapabilityAuthorize((kid) => keys[kid] ?? null, {
|
|
68
|
+
* issuer: "https://vault.acme-hospital.org",
|
|
69
|
+
* }),
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Authorizes the request if the token's `artifactId` matches either the
|
|
74
|
+
* artifact being acted on, or that artifact's `relatedTo` session anchor —
|
|
75
|
+
* so one token issued for a video also covers its captions and (under
|
|
76
|
+
* `uploadUnit: "beat"`) every beat and the pulse manifest uploaded in the
|
|
77
|
+
* same session, without minting a token per artifact.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createCapabilityAuthorize(lookupSecret: LookupSecret, opts: VerifyCapabilityTokenOptions): PulseVaultAuthorize;
|
|
80
|
+
//# sourceMappingURL=capability-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-token.d.ts","sourceRoot":"","sources":["../../src/lib/capability-token.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAA8B,MAAM,gBAAgB,CAAC;AAEtF;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,yFAAyF;IACzF,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAqBF,MAAM,MAAM,2BAA2B,GAAG;IACxC,8FAA8F;IAC9F,KAAK,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,2BAA2B,GAChC,MAAM,CAWR;AAED,0GAA0G;AAC1G,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,4BAA4B,GAAG;IACzC,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,4BAA4B,GACjC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkC/B;AAcD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,4BAA4B,GACjC,mBAAmB,CAcrB"}
|