mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(server): improve blob sync (#15367)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Standardized `usePresignedURL` configuration for AWS S3 and Cloudflare R2 (including `enabled`, `urlPrefix`, and `signKey`). - Storage upload URL generation now supports both direct provider presigning and server-mediated proxying based on configuration. - **Bug Fixes** - Tightened upload and multipart validation (content type/length checks, header vs query consistency, and stricter expiration handling). - Improved fallback behavior when direct upload URL initialization fails. - **Tests** - Updated R2 storage proxy end-to-end coverage to match the new URL/token behavior. - **Documentation** - Refreshed self-hosted JSON schema guidance for upload URL settings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -27,6 +27,11 @@ export interface S3StorageConfig {
|
||||
expiresInSeconds?: number;
|
||||
signContentTypeForPut?: boolean;
|
||||
};
|
||||
usePresignedURL?: {
|
||||
enabled: boolean;
|
||||
urlPrefix?: string;
|
||||
signKey?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const R2_JURISDICTIONS = ['default', 'eu'] as const;
|
||||
@@ -34,11 +39,6 @@ export const R2_JURISDICTIONS = ['default', 'eu'] as const;
|
||||
export interface R2StorageConfig extends Omit<S3StorageConfig, 'endpoint'> {
|
||||
accountId: string;
|
||||
jurisdiction?: (typeof R2_JURISDICTIONS)[number];
|
||||
usePresignedURL?: {
|
||||
enabled: boolean;
|
||||
urlPrefix?: string;
|
||||
signKey?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type StorageProviderConfig = { bucket: string } & (
|
||||
@@ -115,6 +115,27 @@ const S3ConfigSchema: JSONSchema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
usePresignedURL: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Controls browser upload URLs. Disabled or unavailable modes fall back to server uploads.',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether browser upload URLs are enabled.',
|
||||
},
|
||||
urlPrefix: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional custom origin for browser upload URLs. Provider presigned URLs also use this origin when signKey is not configured.',
|
||||
},
|
||||
signKey: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional HMAC key for signed upload URLs. Without urlPrefix, upload URLs use the server origin.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -189,28 +210,6 @@ export const StorageJSONSchema: JSONSchema = {
|
||||
description:
|
||||
'Optional jurisdiction for the cloudflare r2 endpoint. Set to "eu" for EU buckets.',
|
||||
},
|
||||
usePresignedURL: {
|
||||
type: 'object' as const,
|
||||
description:
|
||||
'The presigned url config for the cloudflare r2 storage provider.',
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean' as const,
|
||||
description:
|
||||
'Whether to use presigned url for the cloudflare r2 storage provider.',
|
||||
},
|
||||
urlPrefix: {
|
||||
type: 'string' as const,
|
||||
description:
|
||||
'The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: "https://storage.example.com"\nExample rule: is_timed_hmac_valid_v0("your_secret", http.request.uri, 10800, http.request.timestamp.sec, 6)',
|
||||
},
|
||||
signKey: {
|
||||
type: 'string' as const,
|
||||
description:
|
||||
'The presigned key for the cloudflare r2 storage provider.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHmac } from 'node:crypto';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import type { Response } from 'express';
|
||||
@@ -68,3 +69,20 @@ export const SIGNED_URL_EXPIRED = 60 * 60; // 1 hour
|
||||
export const STORAGE_PROXY_ROOT = '/api/storage';
|
||||
export const PROXY_UPLOAD_PATH = `${STORAGE_PROXY_ROOT}/upload`;
|
||||
export const PROXY_MULTIPART_PATH = `${STORAGE_PROXY_ROOT}/multipart`;
|
||||
|
||||
export function createStorageUploadToken(
|
||||
path: string,
|
||||
fields: (string | number)[],
|
||||
expiresAt: number,
|
||||
signKey: string
|
||||
) {
|
||||
const canonical = JSON.stringify([
|
||||
'affine-storage-upload',
|
||||
1,
|
||||
'PUT',
|
||||
path,
|
||||
...fields,
|
||||
expiresAt,
|
||||
]);
|
||||
return createHmac('sha256', signKey).update(canonical).digest('base64url');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user