@critical-path/core 0.30.0 → 0.31.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 +19 -0
- package/README.md +1 -1
- package/dist/engine/authorization.test.js +8 -4
- package/dist/engine/authorization.test.js.map +1 -1
- package/dist/engine/index.d.ts +17 -3
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +40 -13
- package/dist/engine/index.js.map +1 -1
- package/dist/schemas/index.d.ts +9 -11
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +3 -3
- package/dist/schemas/index.js.map +1 -1
- package/dist/storage/file-storage.d.ts +10 -0
- package/dist/storage/file-storage.d.ts.map +1 -1
- package/dist/storage/file-storage.js +40 -4
- package/dist/storage/file-storage.js.map +1 -1
- package/dist/storage/firebase-storage.d.ts.map +1 -1
- package/dist/storage/firebase-storage.js +10 -13
- package/dist/storage/firebase-storage.js.map +1 -1
- package/dist/storage/s3.d.ts +46 -18
- package/dist/storage/s3.d.ts.map +1 -1
- package/dist/storage/s3.js +59 -89
- package/dist/storage/s3.js.map +1 -1
- package/dist/storage/storage.test.js +88 -27
- package/dist/storage/storage.test.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/authorization.test.ts +13 -5
- package/src/engine/index.ts +43 -15
- package/src/schemas/index.ts +3 -3
- package/src/storage/file-storage.ts +44 -4
- package/src/storage/firebase-storage.ts +10 -13
- package/src/storage/s3.ts +98 -101
- package/src/storage/storage.test.ts +86 -28
|
@@ -29,38 +29,89 @@ describe('Storage Adapters', () => {
|
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
31
|
describe('S3StorageAdapter', () => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
// Stand-ins for @aws-sdk/client-s3 command classes: they just record their input
|
|
33
|
+
class PutObjectCommand {
|
|
34
|
+
input;
|
|
35
|
+
constructor(input) {
|
|
36
|
+
this.input = input;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class DeleteObjectCommand {
|
|
40
|
+
input;
|
|
41
|
+
constructor(input) {
|
|
42
|
+
this.input = input;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class GetObjectCommand {
|
|
46
|
+
input;
|
|
47
|
+
constructor(input) {
|
|
48
|
+
this.input = input;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const commands = { PutObjectCommand, DeleteObjectCommand, GetObjectCommand };
|
|
52
|
+
function setup(send = async () => ({})) {
|
|
53
|
+
const sent = [];
|
|
54
|
+
const presigned = [];
|
|
55
|
+
const adapter = new S3StorageAdapter({
|
|
41
56
|
bucket: 'my-project-bucket',
|
|
42
57
|
region: 'eu-west-1',
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
pathPrefix: 'docs'
|
|
58
|
+
client: { send: async (command) => { sent.push(command); return send(command); } },
|
|
59
|
+
commands,
|
|
60
|
+
presign: async (command, { expiresIn }) => {
|
|
61
|
+
presigned.push({ command, expiresIn });
|
|
62
|
+
return `https://signed.example/${command.input.Key}?X-Amz-Expires=${expiresIn}`;
|
|
63
|
+
}
|
|
50
64
|
});
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
return { adapter, sent, presigned };
|
|
66
|
+
}
|
|
67
|
+
it('uploads with a real command object and returns the object URL', async () => {
|
|
68
|
+
const { adapter, sent } = setup();
|
|
69
|
+
const res = await adapter.upload({ filename: 'spec.pdf', data: new Uint8Array([1, 2, 3, 4]), mimeType: 'application/pdf', pathPrefix: 'docs' });
|
|
70
|
+
expect(res.storageKey).toMatch(/^docs\/\d+_[0-9a-f]{12}_spec\.pdf$/);
|
|
71
|
+
expect(res.sizeBytes).toBe(4);
|
|
53
72
|
expect(res.url).toBe(`https://my-project-bucket.s3.eu-west-1.amazonaws.com/${res.storageKey}`);
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
expect(sent[0]).toBeInstanceOf(PutObjectCommand);
|
|
74
|
+
expect(sent[0].input).toMatchObject({ Bucket: 'my-project-bucket', Key: res.storageKey, ContentType: 'application/pdf' });
|
|
75
|
+
expect(sent[0].input.ContentDisposition).toBeUndefined();
|
|
76
|
+
expect(await adapter.delete(res.storageKey)).toBe(true);
|
|
77
|
+
expect(sent[1]).toBeInstanceOf(DeleteObjectCommand);
|
|
78
|
+
});
|
|
79
|
+
it('forces active content to download', async () => {
|
|
80
|
+
const { adapter, sent } = setup();
|
|
81
|
+
await adapter.upload({ filename: 'x.svg', data: '<svg/>', mimeType: 'image/svg+xml', encoding: 'utf-8' });
|
|
82
|
+
expect(sent[0].input.ContentDisposition).toBe('attachment; filename="x.svg"');
|
|
83
|
+
});
|
|
84
|
+
it('propagates S3 errors instead of reporting success', async () => {
|
|
85
|
+
const { adapter } = setup(async () => {
|
|
86
|
+
throw new Error('AccessDenied');
|
|
59
87
|
});
|
|
60
|
-
expect(
|
|
61
|
-
expect(
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
await expect(adapter.upload({ filename: 'a.txt', data: 'x' })).rejects.toThrow('AccessDenied');
|
|
89
|
+
await expect(adapter.delete('a.txt')).rejects.toThrow('AccessDenied');
|
|
90
|
+
});
|
|
91
|
+
it('refuses to upload or presign without the AWS pieces', async () => {
|
|
92
|
+
const bare = new S3StorageAdapter({ bucket: 'b' });
|
|
93
|
+
await expect(bare.upload({ filename: 'a.txt', data: 'x' })).rejects.toThrow(/configure "client"/);
|
|
94
|
+
await expect(bare.getPresignedUploadUrl({ storageKey: 'k' })).rejects.toThrow(/presign/);
|
|
95
|
+
expect(() => new S3StorageAdapter({ bucket: 'b', client: { send: async () => ({}) } })).toThrow(/commands/);
|
|
96
|
+
});
|
|
97
|
+
it('presigns uploads through the provided signer, covering the content type', async () => {
|
|
98
|
+
const { adapter, presigned } = setup();
|
|
99
|
+
const result = await adapter.getPresignedUploadUrl({ storageKey: 'projects/p1/file.zip', contentType: 'application/zip', expiresInSeconds: 60 });
|
|
100
|
+
expect(result.uploadUrl).toBe('https://signed.example/projects/p1/file.zip?X-Amz-Expires=60');
|
|
101
|
+
expect(result.headers).toEqual({ 'Content-Type': 'application/zip' });
|
|
102
|
+
expect(presigned[0].command).toBeInstanceOf(PutObjectCommand);
|
|
103
|
+
expect(presigned[0].command.input).toMatchObject({ Key: 'projects/p1/file.zip', ContentType: 'application/zip' });
|
|
104
|
+
});
|
|
105
|
+
it('serves signed download URLs for private buckets', async () => {
|
|
106
|
+
const presign = async (command, { expiresIn }) => `https://signed.example/get/${command.input.Key}?e=${expiresIn}`;
|
|
107
|
+
const adapter = new S3StorageAdapter({ bucket: 'b', client: { send: async () => ({}) }, commands, presign, signedDownloads: true });
|
|
108
|
+
expect(await adapter.getDownloadUrl('k')).toBe('https://signed.example/get/k?e=3600');
|
|
109
|
+
});
|
|
110
|
+
it('rejects path prefixes that escape their directory', async () => {
|
|
111
|
+
const { adapter } = setup();
|
|
112
|
+
for (const pathPrefix of ['../other', 'projects/../../x', '/abs', 'a//b', 'bad prefix']) {
|
|
113
|
+
await expect(adapter.upload({ filename: 'a.txt', data: 'x', pathPrefix })).rejects.toThrow(/Invalid storage path prefix/);
|
|
114
|
+
}
|
|
64
115
|
});
|
|
65
116
|
});
|
|
66
117
|
describe('FirebaseStorageAdapter & InMemoryFirebaseStorageMock', () => {
|
|
@@ -70,6 +121,7 @@ describe('Storage Adapters', () => {
|
|
|
70
121
|
bucket: mockStorage,
|
|
71
122
|
bucketName: 'my-app.appspot.com'
|
|
72
123
|
});
|
|
124
|
+
expect(() => new FirebaseStorageAdapter()).toThrow(/requires a "bucket"/);
|
|
73
125
|
const uploadResult = await adapter.upload({
|
|
74
126
|
filename: 'architecture.png',
|
|
75
127
|
data: 'png-data-mock',
|
|
@@ -171,5 +223,14 @@ describe('Storage Adapters', () => {
|
|
|
171
223
|
expect(reviewAttachments.length).toBe(0);
|
|
172
224
|
});
|
|
173
225
|
});
|
|
226
|
+
it('stores uploads made through an actor view under the project prefix', async () => {
|
|
227
|
+
const engine = new CriticalPathEngine({ fileStorage: new InMemoryFileStore() });
|
|
228
|
+
const project = await engine.createProject({ name: 'Uploads' });
|
|
229
|
+
const task = await engine.createTask({ projectId: project.id, title: 'T' });
|
|
230
|
+
const attachment = await engine.withActor({ userId: 'u1' }).uploadAttachmentFile({ taskId: task.id, filename: 'notes.txt', data: 'hi', encoding: 'utf-8' });
|
|
231
|
+
expect(attachment.storageKey?.startsWith(`projects/${project.id}/`)).toBe(true);
|
|
232
|
+
expect(attachment.uploaderId).toBe('u1');
|
|
233
|
+
await expect(engine.withActor({ userId: 'u1' }).uploadAttachmentFile({ filename: 'orphan.txt', data: 'x' })).rejects.toThrow(/must reference a project/);
|
|
234
|
+
});
|
|
174
235
|
});
|
|
175
236
|
//# sourceMappingURL=storage.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../src/storage/storage.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../src/storage/storage.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -114,12 +114,20 @@ describe('role-based authorization (createRolePolicy)', () => {
|
|
|
114
114
|
const project = await engine.createProject({ name: 'Files', members: [{ userId: 'up', role: 'contributor' }] });
|
|
115
115
|
const up = engine.withActor({ userId: 'up' });
|
|
116
116
|
|
|
117
|
-
const presigned = await up.getPresignedAttachmentUploadUrl({ projectId: project.id,
|
|
118
|
-
expect(presigned.storageKey).
|
|
119
|
-
await expect(up.getPresignedAttachmentUploadUrl({
|
|
117
|
+
const presigned = await up.getPresignedAttachmentUploadUrl({ projectId: project.id, filename: 'spec.pdf' });
|
|
118
|
+
expect(presigned.storageKey).toMatch(new RegExp(`^projects/${project.id}/\\d+_[0-9a-f]+_spec\\.pdf$`));
|
|
119
|
+
await expect(up.getPresignedAttachmentUploadUrl({ filename: 'x' } as any)).rejects.toThrow(/projectId is required/);
|
|
120
|
+
|
|
121
|
+
const outsider = engine.withActor({ userId: 'nobody' });
|
|
122
|
+
await expect(outsider.getPresignedAttachmentUploadUrl({ projectId: project.id, filename: 'x' })).rejects.toThrow(/not found/);
|
|
123
|
+
|
|
124
|
+
// Registering someone else's file (so that deleting the attachment deletes it) is rejected
|
|
125
|
+
await expect(
|
|
126
|
+
up.createAttachment({ projectId: project.id, filename: 'x', url: 'https://e.com/x', mimeType: 'text/plain', sizeBytes: 1, storageKey: 'projects/other/secret.pdf' })
|
|
127
|
+
).rejects.toThrow(/storageKey must be under/);
|
|
120
128
|
await expect(
|
|
121
|
-
up.
|
|
122
|
-
).
|
|
129
|
+
up.createAttachment({ projectId: project.id, filename: 'x', url: 'https://e.com/x', mimeType: 'text/plain', sizeBytes: 1, storageKey: presigned.storageKey })
|
|
130
|
+
).resolves.toMatchObject({ storageKey: presigned.storageKey });
|
|
123
131
|
});
|
|
124
132
|
|
|
125
133
|
it('leaves the base engine and views without a policy unrestricted', async () => {
|
package/src/engine/index.ts
CHANGED
|
@@ -88,6 +88,7 @@ import { validateCustomFieldValues, validateCustomFieldDefinitions } from '../do
|
|
|
88
88
|
import { detectDependencyCycle, CircularDependencyError } from '../domain/graph.js';
|
|
89
89
|
import { ValidationError, NotFoundError, ForbiddenError } from '../domain/errors.js';
|
|
90
90
|
import { WebhookDispatcher, assertWebhookUrl } from '../webhooks/dispatcher.js';
|
|
91
|
+
import { buildStorageKey } from '../storage/file-storage.js';
|
|
91
92
|
import { generateWebhookSecret } from '../webhooks/signature.js';
|
|
92
93
|
import type { DomainEvent } from '../domain/events.js';
|
|
93
94
|
import { calculateCPM, type CPMOptions } from '../domain/cpm.js';
|
|
@@ -1572,6 +1573,7 @@ export class CriticalPathEngine {
|
|
|
1572
1573
|
input: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt' | 'uploaderId'> & { uploaderId?: string }
|
|
1573
1574
|
): Promise<Attachment> {
|
|
1574
1575
|
await this.requireAttachmentCreate(input);
|
|
1576
|
+
if (this.actor) await this.assertStorageKeyBelongs(input);
|
|
1575
1577
|
const attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'> = this.actor
|
|
1576
1578
|
? { ...input, uploaderId: this.actor.userId, uploaderType: this.actor.actorType ?? 'user' }
|
|
1577
1579
|
: { ...input, uploaderId: input.uploaderId ?? 'system' };
|
|
@@ -1646,12 +1648,17 @@ export class CriticalPathEngine {
|
|
|
1646
1648
|
}
|
|
1647
1649
|
// Check before uploading so denied requests do not leave orphaned files behind.
|
|
1648
1650
|
await this.requireAttachmentCreate(input);
|
|
1651
|
+
// Files always live under their project's prefix; only trusted base-engine calls may choose another.
|
|
1652
|
+
const projectId = await this.projectIdOfAttachment(input);
|
|
1653
|
+
if (this.actor && !projectId) {
|
|
1654
|
+
throw new ValidationError('Attachments must reference a project, task or comment.');
|
|
1655
|
+
}
|
|
1649
1656
|
|
|
1650
1657
|
const uploadResult = await this.fileStorage.upload({
|
|
1651
1658
|
filename: input.filename,
|
|
1652
1659
|
data: input.data,
|
|
1653
1660
|
mimeType: input.mimeType,
|
|
1654
|
-
pathPrefix:
|
|
1661
|
+
pathPrefix: projectId && (this.actor || !input.pathPrefix) ? `projects/${projectId}` : input.pathPrefix
|
|
1655
1662
|
});
|
|
1656
1663
|
|
|
1657
1664
|
return this.createAttachment({
|
|
@@ -1703,25 +1710,46 @@ export class CriticalPathEngine {
|
|
|
1703
1710
|
throw new Error(`Cannot read attachment "${id}": no valid storageKey or URL found.`);
|
|
1704
1711
|
}
|
|
1705
1712
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1713
|
+
/**
|
|
1714
|
+
* Returns a URL the client can upload a file to directly (e.g. a signed S3 PUT). The engine
|
|
1715
|
+
* chooses the storage key under `projects/<projectId>/`; register the uploaded file afterwards
|
|
1716
|
+
* with `createAttachment({ projectId, storageKey, ... })` using the returned `storageKey`.
|
|
1717
|
+
*/
|
|
1718
|
+
async getPresignedAttachmentUploadUrl(options: {
|
|
1719
|
+
projectId: string;
|
|
1720
|
+
filename: string;
|
|
1721
|
+
contentType?: string;
|
|
1722
|
+
expiresInSeconds?: number;
|
|
1723
|
+
}): Promise<PresignedUploadResult> {
|
|
1709
1724
|
if (!this.fileStorage || !this.fileStorage.getPresignedUploadUrl) {
|
|
1710
1725
|
throw new Error('Presigned uploads are not supported by the configured FileStorageAdapter.');
|
|
1711
1726
|
}
|
|
1712
|
-
|
|
1727
|
+
if (!options.projectId) throw new ValidationError('projectId is required for presigned uploads.');
|
|
1713
1728
|
if (this.enforcing) {
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1729
|
+
await this.requireProjectAccess('attachment.create', options.projectId);
|
|
1730
|
+
} else if (!(await this.store.getProject(options.projectId))) {
|
|
1731
|
+
throw new NotFoundError(`Project "${options.projectId}" not found.`);
|
|
1732
|
+
}
|
|
1733
|
+
return this.fileStorage.getPresignedUploadUrl({
|
|
1734
|
+
storageKey: buildStorageKey(`projects/${options.projectId}`, options.filename),
|
|
1735
|
+
contentType: options.contentType,
|
|
1736
|
+
expiresInSeconds: options.expiresInSeconds
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/**
|
|
1741
|
+
* A caller-supplied `storageKey` must live under the attachment's project prefix, because
|
|
1742
|
+
* deleting the attachment deletes that file: otherwise anyone could register, then delete,
|
|
1743
|
+
* another project's files.
|
|
1744
|
+
*/
|
|
1745
|
+
private async assertStorageKeyBelongs(link: { projectId?: string; taskId?: string; commentId?: string; storageKey?: string }): Promise<void> {
|
|
1746
|
+
if (!link.storageKey) return;
|
|
1747
|
+
const projectId = await this.projectIdOfAttachment(link);
|
|
1748
|
+
const prefix = `projects/${projectId}/`;
|
|
1749
|
+
const segments = link.storageKey.split('/');
|
|
1750
|
+
if (!projectId || !link.storageKey.startsWith(prefix) || segments.some((s) => s === '..' || s === '.')) {
|
|
1751
|
+
throw new ValidationError(`storageKey must be under "${projectId ? prefix : 'projects/<projectId>/'}" for this attachment.`);
|
|
1723
1752
|
}
|
|
1724
|
-
return this.fileStorage.getPresignedUploadUrl(presign);
|
|
1725
1753
|
}
|
|
1726
1754
|
|
|
1727
1755
|
// --- Teams ---
|
package/src/schemas/index.ts
CHANGED
|
@@ -303,14 +303,14 @@ export const UploadAttachmentSchema = strictObject({
|
|
|
303
303
|
filename: nonEmpty,
|
|
304
304
|
data: z.string(),
|
|
305
305
|
mimeType: z.string().optional(),
|
|
306
|
-
pathPrefix: z.string().optional(),
|
|
307
306
|
encoding: z.enum(['base64', 'utf-8', 'binary']).optional()
|
|
308
307
|
});
|
|
309
308
|
|
|
310
309
|
export const PresignAttachmentSchema = strictObject({
|
|
311
|
-
/** Project the upload belongs to;
|
|
310
|
+
/** Project the upload belongs to; the file is stored under `projects/<projectId>/`. */
|
|
312
311
|
projectId: nonEmpty,
|
|
313
|
-
|
|
312
|
+
/** Original file name; the engine generates the storage key from it. */
|
|
313
|
+
filename: nonEmpty,
|
|
314
314
|
expiresInSeconds: z.number().int().positive().optional(),
|
|
315
315
|
contentType: z.string().optional()
|
|
316
316
|
});
|
|
@@ -15,6 +15,49 @@ export interface StoredFile {
|
|
|
15
15
|
createdAt: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Builds a storage key `<prefix>/<timestamp>_<random>_<filename>`. The prefix is validated (no
|
|
20
|
+
* `.`/`..` segments, no leading slash, safe characters only) so callers cannot write outside it.
|
|
21
|
+
*/
|
|
22
|
+
export function buildStorageKey(pathPrefix: string | undefined, filename: string): string {
|
|
23
|
+
let prefix = '';
|
|
24
|
+
if (pathPrefix) {
|
|
25
|
+
const trimmed = pathPrefix.replace(/\/+$/, '');
|
|
26
|
+
const segments = trimmed.split('/');
|
|
27
|
+
if (
|
|
28
|
+
trimmed.startsWith('/') ||
|
|
29
|
+
segments.some((segment) => segment === '' || segment === '.' || segment === '..' || !/^[A-Za-z0-9._-]+$/.test(segment))
|
|
30
|
+
) {
|
|
31
|
+
throw new Error(`Invalid storage path prefix "${pathPrefix}".`);
|
|
32
|
+
}
|
|
33
|
+
prefix = `${trimmed}/`;
|
|
34
|
+
}
|
|
35
|
+
const random = Array.from(crypto.getRandomValues(new Uint8Array(6)), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
36
|
+
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, '_').replace(/^\.+/, '_') || 'file';
|
|
37
|
+
return `${prefix}${Date.now()}_${random}_${safeName}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Content types a browser would execute or render as a page if opened from the storage host. */
|
|
41
|
+
const ACTIVE_CONTENT_TYPES = new Set([
|
|
42
|
+
'text/html',
|
|
43
|
+
'application/xhtml+xml',
|
|
44
|
+
'image/svg+xml',
|
|
45
|
+
'text/javascript',
|
|
46
|
+
'application/javascript',
|
|
47
|
+
'text/xml',
|
|
48
|
+
'application/xml'
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `Content-Disposition` for stored files: active content (HTML, SVG, scripts) is forced to
|
|
53
|
+
* download so a stored file cannot run script on the storage origin.
|
|
54
|
+
*/
|
|
55
|
+
export function contentDispositionFor(mimeType: string, filename: string): string | undefined {
|
|
56
|
+
const base = mimeType.split(';')[0].trim().toLowerCase();
|
|
57
|
+
if (!ACTIVE_CONTENT_TYPES.has(base)) return undefined;
|
|
58
|
+
return `attachment; filename="${filename.replace(/["\\\r\n]/g, '_')}"`;
|
|
59
|
+
}
|
|
60
|
+
|
|
18
61
|
export function normalizeUploadData(
|
|
19
62
|
data: Uint8Array | ArrayBuffer | Buffer | Blob | string | unknown,
|
|
20
63
|
encoding?: 'base64' | 'utf-8' | 'binary',
|
|
@@ -92,10 +135,7 @@ export class InMemoryFileStore implements FileStorageAdapter {
|
|
|
92
135
|
}
|
|
93
136
|
|
|
94
137
|
async upload(input: UploadFileInput): Promise<UploadFileResult> {
|
|
95
|
-
const
|
|
96
|
-
const randomId = Math.random().toString(36).substring(2, 9);
|
|
97
|
-
const sanitizedFilename = input.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
98
|
-
const storageKey = `${prefix}${Date.now()}_${randomId}_${sanitizedFilename}`;
|
|
138
|
+
const storageKey = buildStorageKey(input.pathPrefix, input.filename);
|
|
99
139
|
|
|
100
140
|
const buffer = normalizeUploadData(input.data, input.encoding, input.mimeType);
|
|
101
141
|
const mimeType = input.mimeType || 'application/octet-stream';
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
PresignedUrlOptions,
|
|
6
6
|
PresignedUploadResult
|
|
7
7
|
} from '../types/index.js';
|
|
8
|
-
import { normalizeUploadData } from './file-storage.js';
|
|
8
|
+
import { buildStorageKey, contentDispositionFor, normalizeUploadData } from './file-storage.js';
|
|
9
9
|
|
|
10
10
|
export interface FirebaseStorageBucketInterface {
|
|
11
11
|
name?: string;
|
|
@@ -90,7 +90,11 @@ export class FirebaseStorageAdapter implements FileStorageAdapter {
|
|
|
90
90
|
private publicUrlBase?: string;
|
|
91
91
|
|
|
92
92
|
constructor(config: FirebaseStorageConfig = {}) {
|
|
93
|
-
|
|
93
|
+
if (!config.bucket) {
|
|
94
|
+
// Never fall back to an in-memory mock silently: files would vanish on restart.
|
|
95
|
+
throw new Error('FirebaseStorageAdapter requires a "bucket" (e.g. getStorage().bucket()). Use InMemoryFirebaseStorageMock in tests.');
|
|
96
|
+
}
|
|
97
|
+
this.bucket = config.bucket;
|
|
94
98
|
this.bucketName = config.bucketName || this.bucket.name || 'default-bucket';
|
|
95
99
|
if (this.bucket && config.bucketName && this.bucket.name !== config.bucketName) {
|
|
96
100
|
this.bucket.name = config.bucketName;
|
|
@@ -107,10 +111,7 @@ export class FirebaseStorageAdapter implements FileStorageAdapter {
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
async upload(input: UploadFileInput): Promise<UploadFileResult> {
|
|
110
|
-
const
|
|
111
|
-
const randomId = Math.random().toString(36).substring(2, 9);
|
|
112
|
-
const sanitizedFilename = input.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
113
|
-
const storageKey = `${prefix}${Date.now()}_${randomId}_${sanitizedFilename}`;
|
|
114
|
+
const storageKey = buildStorageKey(input.pathPrefix, input.filename);
|
|
114
115
|
|
|
115
116
|
const bodyData = normalizeUploadData(input.data, input.encoding, input.mimeType);
|
|
116
117
|
const mimeType = input.mimeType || 'application/octet-stream';
|
|
@@ -124,6 +125,7 @@ export class FirebaseStorageAdapter implements FileStorageAdapter {
|
|
|
124
125
|
await fileRef.save(bodyData, {
|
|
125
126
|
metadata: {
|
|
126
127
|
contentType: mimeType,
|
|
128
|
+
...(contentDispositionFor(mimeType, input.filename) ? { contentDisposition: contentDispositionFor(mimeType, input.filename) } : {}),
|
|
127
129
|
metadata: {
|
|
128
130
|
firebaseStorageDownloadTokens: downloadToken
|
|
129
131
|
}
|
|
@@ -201,12 +203,7 @@ export class FirebaseStorageAdapter implements FileStorageAdapter {
|
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
uploadUrl: this.getPublicUrl(storageKey),
|
|
207
|
-
storageKey,
|
|
208
|
-
method: 'PUT',
|
|
209
|
-
headers: options.contentType ? { 'Content-Type': options.contentType } : {}
|
|
210
|
-
};
|
|
206
|
+
// A public URL is not an upload credential; refuse rather than hand out a URL that cannot work.
|
|
207
|
+
throw new Error('FirebaseStorageAdapter cannot presign uploads: the bucket does not support getSignedUrl.');
|
|
211
208
|
}
|
|
212
209
|
}
|