mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(media-proxy): sign attachment URLs and gate origins (#2830)
This commit is contained in:
@@ -144,6 +144,9 @@ function defaultConfig(): MasterConfig {
|
||||
token_ttl_secs: 900,
|
||||
keep_direct_countries: [],
|
||||
},
|
||||
attachment_urls: {
|
||||
secrets_base64: [],
|
||||
},
|
||||
},
|
||||
gateway: {
|
||||
port: 8771,
|
||||
@@ -354,6 +357,28 @@ function validateUploadRelaySecret(value: string, mode: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isCanonicalStandardBase64(value: string): boolean {
|
||||
if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(value)) {
|
||||
return false;
|
||||
}
|
||||
return Buffer.from(value, 'base64').toString('base64') === value;
|
||||
}
|
||||
|
||||
function validateAttachmentUrlSecrets(values: Array<string>): void {
|
||||
for (const value of values) {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isCanonicalStandardBase64(trimmed)) {
|
||||
throw new Error('FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64 entries must be standard base64');
|
||||
}
|
||||
if (Buffer.from(trimmed, 'base64').length < 32) {
|
||||
throw new Error('FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64 entries must decode to at least 32 bytes');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertBoolean(value: unknown, envName: string): asserts value is boolean {
|
||||
if (typeof value !== 'boolean') {
|
||||
throw new Error(`${envName} must be true or false`);
|
||||
@@ -575,6 +600,7 @@ function normalizeConfig(config: MasterConfig): MasterConfig {
|
||||
requireString(config.s3?.secret_access_key, 'FLUXER_S3_SECRET_ACCESS_KEY');
|
||||
requireString(config.services.media_proxy.secret_key, 'FLUXER_MEDIA_PROXY_SECRET_KEY');
|
||||
validateUploadRelaySecret(config.services.media_proxy.upload_relay.secret_base64, config.services.media_proxy.mode);
|
||||
validateAttachmentUrlSecrets(config.services.media_proxy.attachment_urls.secrets_base64);
|
||||
requireString(config.services.admin.secret_key_base, 'FLUXER_ADMIN_SECRET_KEY_BASE');
|
||||
requireString(config.services.admin.oauth_client_secret, 'FLUXER_ADMIN_OAUTH_CLIENT_SECRET');
|
||||
requireString(config.services.gateway.rpc_auth_token, 'FLUXER_GATEWAY_RPC_AUTH_TOKEN');
|
||||
|
||||
@@ -152,6 +152,9 @@ export interface MasterConfig {
|
||||
token_ttl_secs: number;
|
||||
keep_direct_countries: Array<string>;
|
||||
};
|
||||
attachment_urls: {
|
||||
secrets_base64: Array<string>;
|
||||
};
|
||||
};
|
||||
gateway: {
|
||||
port: number;
|
||||
|
||||
@@ -172,6 +172,10 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
||||
path: ['services', 'media_proxy', 'upload_relay', 'keep_direct_countries'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64: {
|
||||
path: ['services', 'media_proxy', 'attachment_urls', 'secrets_base64'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_ADMIN_PORT: {path: ['services', 'admin', 'port'], parse: parseInteger},
|
||||
FLUXER_ADMIN_BASE_PATH: {path: ['services', 'admin', 'base_path']},
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE: {path: ['services', 'admin', 'secret_key_base']},
|
||||
|
||||
@@ -54,3 +54,30 @@ export const ClientAttachmentReferenceRequest = ClientAttachmentBase.extend({
|
||||
});
|
||||
|
||||
export type ClientAttachmentReferenceRequest = z.infer<typeof ClientAttachmentReferenceRequest>;
|
||||
|
||||
export const RefreshAttachmentUrlsRequest = z.object({
|
||||
attachment_urls: z
|
||||
.array(z.string().max(2048))
|
||||
.min(1)
|
||||
.max(50)
|
||||
.describe('Attachment URLs to refresh (1-50 entries, each at most 2048 characters)'),
|
||||
});
|
||||
|
||||
export type RefreshAttachmentUrlsRequest = z.infer<typeof RefreshAttachmentUrlsRequest>;
|
||||
|
||||
export const RefreshedAttachmentUrl = z.object({
|
||||
original: z.string().describe('The requested URL, echoed back unchanged'),
|
||||
refreshed: z
|
||||
.string()
|
||||
.describe('The same URL carrying a fresh signature, or the original when it is not an attachment URL of ours'),
|
||||
});
|
||||
|
||||
export type RefreshedAttachmentUrl = z.infer<typeof RefreshedAttachmentUrl>;
|
||||
|
||||
export const RefreshAttachmentUrlsResponse = z.object({
|
||||
refreshed_urls: z
|
||||
.array(RefreshedAttachmentUrl)
|
||||
.describe('One entry per requested URL, in the order they were requested'),
|
||||
});
|
||||
|
||||
export type RefreshAttachmentUrlsResponse = z.infer<typeof RefreshAttachmentUrlsResponse>;
|
||||
|
||||
Reference in New Issue
Block a user