이미지 업로드와 WebPush 응답의 자원 경계를 제한한다

This commit is contained in:
Hide_D committed 2026-10-09 07:09:23 +09:00
1 parent c5716cf144
commit 5771701007
18 files changed
+879 -183

No files matched your search

@@ -1,4 +1,4 @@
import { createHash, createHmac, randomUUID } from 'node:crypto';
import { performSignedImageUpload } from '@sammo-ts/common/images/imageUpload';
export interface ContentImageUploadResult {
publicUrl: string;
@@ -21,32 +21,16 @@ export class RemoteContentImageStore implements ContentImageUploadStore {
if (!/^[a-f0-9]{32}\.(?:avif|webp|jpg|png|gif)$/.test(input.filename)) {
throw new Error('Invalid content image filename.');
}
const pathname = `/v1/uploads/content/core2026/${input.filename}`;
const expires = String(Math.floor(this.now() / 1000) + 60);
const requestId = randomUUID();
const digest = createHash('sha256').update(input.body).digest('hex');
const signature = createHmac('sha256', this.secret)
.update(`${expires}.${requestId}.${pathname}.${input.contentType}.${digest}`)
.digest('hex');
const response = await this.fetchImpl(`${this.baseUrl.replace(/\/$/, '')}${pathname}`, {
method: 'PUT',
headers: {
'content-type': input.contentType,
'x-image-client': 'core2026',
'x-image-expires': expires,
'x-image-request-id': requestId,
'x-image-signature': signature,
await performSignedImageUpload(
{
...input,
kind: 'content',
baseUrl: this.baseUrl,
secret: this.secret,
},
body: new Uint8Array(input.body),
});
if (!response.ok) {
throw new Error(`Image repository upload failed with HTTP ${response.status}.`);
}
const expectedPath = `uploads/core2026/${input.filename}`;
const payload: unknown = await response.json();
if (!payload || typeof payload !== 'object' || !('path' in payload) || payload.path !== expectedPath) {
throw new Error('Image repository returned an unexpected content path.');
}
this.fetchImpl,
this.now
);
return { publicUrl: `${this.publicBaseUrl.replace(/\/$/, '')}/${input.filename}` };
}
}