mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(api): scope forwarded client ip trust per caller (#3198)
This commit is contained in:
1 parent
2ac164d5b8
commit
0aeff01c2d
15 files changed
+352
-30
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {APIConfig, BlueskyOAuthConfig} from '@app/api/config/APIConfig';
|
||||
import type {APIConfig, BlueskyOAuthConfig, TrustedCallerConfig} from '@app/api/config/APIConfig';
|
||||
import {DonationRateLimitConfigs} from '@app/api/rate_limit_configs/DonationRateLimitConfig';
|
||||
import {parseIpBanEntry} from '@app/api/utils/IpRangeUtils';
|
||||
import type {WorkerTaskName} from '@app/api/worker/WorkerLaneConfig';
|
||||
import type {MasterConfig} from '@fluxer/config/src/MasterConfig';
|
||||
@@ -99,6 +100,52 @@ function mapApnsApps(
|
||||
});
|
||||
}
|
||||
|
||||
const TRUSTED_CALLER_MIN_KEY_LENGTH = 32;
|
||||
|
||||
function parseTrustedCaller(entry: unknown, index: number): TrustedCallerConfig {
|
||||
const label = `FLUXER_API_TRUSTED_CALLERS entry ${index + 1}`;
|
||||
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
||||
throw new Error(`${label} must be a JSON object`);
|
||||
}
|
||||
const name = Reflect.get(entry, 'name');
|
||||
if (typeof name !== 'string' || name.trim().length === 0) {
|
||||
throw new Error(`${label} must have a name`);
|
||||
}
|
||||
const key = Reflect.get(entry, 'key');
|
||||
if (typeof key !== 'string' || key.trim().length < TRUSTED_CALLER_MIN_KEY_LENGTH) {
|
||||
throw new Error(`${label} key must be at least ${TRUSTED_CALLER_MIN_KEY_LENGTH} characters`);
|
||||
}
|
||||
const buckets = Reflect.get(entry, 'buckets');
|
||||
if (
|
||||
!Array.isArray(buckets) ||
|
||||
buckets.length === 0 ||
|
||||
!buckets.every((bucket) => typeof bucket === 'string' && bucket.trim().length > 0)
|
||||
) {
|
||||
throw new Error(`${label} buckets must be a non-empty list of bucket names`);
|
||||
}
|
||||
return {
|
||||
name: name.trim(),
|
||||
key: key.trim(),
|
||||
buckets: buckets.map((bucket: string) => bucket.trim()),
|
||||
};
|
||||
}
|
||||
|
||||
function buildTrustedCallers(master: MasterConfig): Array<TrustedCallerConfig> {
|
||||
const trustedCallers = (master.services.api.trusted_callers ?? []).map(parseTrustedCaller);
|
||||
const donationProxyKey = (master.services.api.donation_proxy_key ?? '').trim();
|
||||
if (donationProxyKey.length > 0 && donationProxyKey.length < TRUSTED_CALLER_MIN_KEY_LENGTH) {
|
||||
throw new Error(`FLUXER_API_DONATION_PROXY_KEY must be at least ${TRUSTED_CALLER_MIN_KEY_LENGTH} characters`);
|
||||
}
|
||||
if (donationProxyKey.length > 0) {
|
||||
trustedCallers.push({
|
||||
name: 'donation',
|
||||
key: donationProxyKey,
|
||||
buckets: Object.values(DonationRateLimitConfigs).map((routeConfig) => routeConfig.bucket),
|
||||
});
|
||||
}
|
||||
return trustedCallers;
|
||||
}
|
||||
|
||||
export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
||||
if (!master.internal) {
|
||||
throw new Error('internal configuration is required for the API');
|
||||
@@ -118,10 +165,7 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
||||
if (Buffer.from(uploadRelaySecretBase64, 'base64').length < 32) {
|
||||
throw new Error('FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64 must decode to at least 32 bytes');
|
||||
}
|
||||
const donationProxyKey = (master.services.api.donation_proxy_key ?? '').trim();
|
||||
if (donationProxyKey.length > 0 && donationProxyKey.length < 32) {
|
||||
throw new Error('FLUXER_API_DONATION_PROXY_KEY must be at least 32 characters');
|
||||
}
|
||||
const trustedCallers = buildTrustedCallers(master);
|
||||
if (!s3Config) {
|
||||
throw new Error('S3 configuration is required for the API');
|
||||
}
|
||||
@@ -227,7 +271,7 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
||||
},
|
||||
internal: {
|
||||
gatewayRpcAuthToken: master.services.gateway.rpc_auth_token ?? '',
|
||||
donationProxyKey,
|
||||
trustedCallers,
|
||||
},
|
||||
hosts: {
|
||||
marketing: extractHostname(master.endpoints.marketing),
|
||||
|
||||
Reference in new issue
Block a user