mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
refactor(api): emit moderation events and apply account actions (#3031)
This commit is contained in:
1 parent
c488906131
commit
364084c819
385 files changed
+8994
-22221
No files matched your search
@@ -194,9 +194,6 @@ function defaultConfig(): MasterConfig {
|
||||
from_name: 'Fluxer',
|
||||
app_base_url: '',
|
||||
},
|
||||
sms: {
|
||||
enabled: false,
|
||||
},
|
||||
captcha: {
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
@@ -251,12 +248,9 @@ function defaultConfig(): MasterConfig {
|
||||
},
|
||||
},
|
||||
blocklist_feeds: {},
|
||||
tor_exit_list: {},
|
||||
breached_password_check: {},
|
||||
risk_integration: {
|
||||
enabled: false,
|
||||
ipinfo_api_key: '',
|
||||
account_policy_dsl: undefined,
|
||||
ipinfo: {
|
||||
api_key: '',
|
||||
},
|
||||
push: {
|
||||
apns: {
|
||||
@@ -277,23 +271,6 @@ function defaultConfig(): MasterConfig {
|
||||
setup: {
|
||||
configured: false,
|
||||
},
|
||||
abuse_policy: {
|
||||
inbound_phone_country_codes: [],
|
||||
phone_flagging: {
|
||||
enabled: true,
|
||||
exempt_country_codes: [],
|
||||
},
|
||||
phone_verification: {
|
||||
inbound_required_prefixes: [],
|
||||
},
|
||||
direct_contact_spam: {
|
||||
enabled: false,
|
||||
country_codes: [],
|
||||
distinct_target_threshold: 25,
|
||||
target_window_ms: 2 * 60 * 60 * 1000,
|
||||
action: 'flag_spammer',
|
||||
},
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
relax_registration_rate_limits: false,
|
||||
@@ -590,11 +567,6 @@ function normalizeConfig(config: MasterConfig): MasterConfig {
|
||||
assertOneOf(config.integrations.captcha.provider, ['hcaptcha', 'turnstile', 'none'], 'FLUXER_CAPTCHA_PROVIDER');
|
||||
assertOneOf(config.integrations.search.engine, ['elasticsearch', 'meilisearch'], 'FLUXER_SEARCH_ENGINE');
|
||||
assertOneOf(config.integrations.cache_purge.adapter, CACHE_PURGE_ADAPTER_NAMES, 'FLUXER_CACHE_PURGE_ADAPTER');
|
||||
assertOneOf(
|
||||
config.instance.abuse_policy.direct_contact_spam.action,
|
||||
['flag_spammer', 'suppress_delivery'],
|
||||
'FLUXER_ABUSE_DIRECT_CONTACT_SPAM_ACTION',
|
||||
);
|
||||
validatePostgresConfig(config);
|
||||
validateCaptchaConfig(config);
|
||||
validateApiWorkerConfig(config);
|
||||
@@ -656,7 +628,7 @@ function applyPublicPort(config: MasterConfig, endpoints: DerivedEndpoints): Mas
|
||||
}
|
||||
const {bluesky, passkeys} = config.auth;
|
||||
const {branding} = config.instance;
|
||||
const {email, sms, voice} = config.integrations;
|
||||
const {email, voice} = config.integrations;
|
||||
return {
|
||||
...config,
|
||||
domain: {
|
||||
@@ -696,7 +668,6 @@ function applyPublicPort(config: MasterConfig, endpoints: DerivedEndpoints): Mas
|
||||
integrations: {
|
||||
...config.integrations,
|
||||
email: {...email, app_base_url: normalize(email.app_base_url)},
|
||||
sms: {...sms, inbound_webhook_public_url: normalizeOptional(sms.inbound_webhook_public_url)},
|
||||
voice: {...voice, url: normalize(voice.url)},
|
||||
},
|
||||
instance: {
|
||||
|
||||
@@ -207,15 +207,6 @@ export interface MasterConfig {
|
||||
secure: boolean;
|
||||
};
|
||||
};
|
||||
sms: {
|
||||
enabled: boolean;
|
||||
account_sid?: string;
|
||||
auth_token?: string;
|
||||
verify_service_sid?: string;
|
||||
inbound_challenge_number?: string;
|
||||
inbound_webhook_auth_token?: string;
|
||||
inbound_webhook_public_url?: string;
|
||||
};
|
||||
captcha: {
|
||||
enabled: boolean;
|
||||
provider: 'hcaptcha' | 'turnstile' | 'none';
|
||||
@@ -288,16 +279,11 @@ export interface MasterConfig {
|
||||
blocklist_feeds: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
tor_exit_list: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
breached_password_check: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
risk_integration: {
|
||||
enabled: boolean;
|
||||
ipinfo_api_key: string;
|
||||
account_policy_dsl?: unknown;
|
||||
ipinfo: {
|
||||
api_key: string;
|
||||
};
|
||||
push: {
|
||||
apns: {
|
||||
@@ -340,23 +326,6 @@ export interface MasterConfig {
|
||||
setup: {
|
||||
configured: boolean;
|
||||
};
|
||||
abuse_policy: {
|
||||
inbound_phone_country_codes: Array<string>;
|
||||
phone_flagging: {
|
||||
enabled: boolean;
|
||||
exempt_country_codes: Array<string>;
|
||||
};
|
||||
phone_verification: {
|
||||
inbound_required_prefixes: Array<string>;
|
||||
};
|
||||
direct_contact_spam: {
|
||||
enabled: boolean;
|
||||
country_codes: Array<string>;
|
||||
distinct_target_threshold: number;
|
||||
target_window_ms: number;
|
||||
action: 'flag_spammer' | 'suppress_delivery';
|
||||
};
|
||||
};
|
||||
};
|
||||
dev: {
|
||||
relax_registration_rate_limits: boolean;
|
||||
|
||||
@@ -299,7 +299,6 @@ describe('ConfigLoader', () => {
|
||||
FLUXER_POSTGRES_PREPARED_STATEMENTS: 'false',
|
||||
FLUXER_API_WORKER_MODE: 'single_task',
|
||||
FLUXER_API_WORKER_TASK: 'processStripeWebhook',
|
||||
FLUXER_ACCOUNT_POLICY_DSL: '{"version":1,"id":"env_policy","rules":[]}',
|
||||
FLUXER_LIVEKIT_ENABLED: 'true',
|
||||
FLUXER_LIVEKIT_DEFAULT_REGION:
|
||||
'{"id":"local","name":"Local","emoji":"LC","latitude":59.3293,"longitude":18.0686}',
|
||||
@@ -316,11 +315,6 @@ describe('ConfigLoader', () => {
|
||||
expect(config.database.postgres.prepared_statements).toBe(false);
|
||||
expect(config.services.api.worker?.mode).toBe('single_task');
|
||||
expect(config.services.api.worker?.task).toBe('processStripeWebhook');
|
||||
expect(config.integrations.risk_integration.account_policy_dsl).toEqual({
|
||||
version: 1,
|
||||
id: 'env_policy',
|
||||
rules: [],
|
||||
});
|
||||
expect(config.integrations.voice.default_region?.id).toBe('local');
|
||||
});
|
||||
|
||||
@@ -475,7 +469,7 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_POSTGRES_SSL must be true');
|
||||
});
|
||||
|
||||
test('parses self-host branding, setup, abuse policy, and search engine environment variables', async () => {
|
||||
test('parses self-host branding, setup, and search engine environment variables', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_SEARCH_ENGINE: 'meilisearch',
|
||||
FLUXER_SEARCH_URL: 'http://meilisearch:7700',
|
||||
@@ -491,15 +485,6 @@ describe('ConfigLoader', () => {
|
||||
FLUXER_APP_STATUS_PAGE_URL: 'https://status.example',
|
||||
FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL: 'https://status.example/history',
|
||||
FLUXER_INSTANCE_SETUP_CONFIGURED: 'true',
|
||||
FLUXER_ABUSE_INBOUND_PHONE_COUNTRY_CODES: 'AA,BB',
|
||||
FLUXER_ABUSE_PHONE_FLAGGING_ENABLED: 'false',
|
||||
FLUXER_ABUSE_PHONE_FLAGGING_EXEMPT_COUNTRY_CODES: 'CC,DD',
|
||||
FLUXER_ABUSE_PHONE_INBOUND_REQUIRED_PREFIXES: '+101,+202',
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_ENABLED: 'true',
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_COUNTRY_CODES: 'AA,BB',
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_DISTINCT_TARGET_THRESHOLD: '9',
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_TARGET_WINDOW_MS: '12345',
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_ACTION: 'suppress_delivery',
|
||||
});
|
||||
|
||||
const config = await loadConfig();
|
||||
@@ -520,23 +505,6 @@ describe('ConfigLoader', () => {
|
||||
status_page_incident_history_url: 'https://status.example/history',
|
||||
});
|
||||
expect(config.instance.setup.configured).toBe(true);
|
||||
expect(config.instance.abuse_policy).toEqual({
|
||||
inbound_phone_country_codes: ['AA', 'BB'],
|
||||
phone_flagging: {
|
||||
enabled: false,
|
||||
exempt_country_codes: ['CC', 'DD'],
|
||||
},
|
||||
phone_verification: {
|
||||
inbound_required_prefixes: ['+101', '+202'],
|
||||
},
|
||||
direct_contact_spam: {
|
||||
enabled: true,
|
||||
country_codes: ['AA', 'BB'],
|
||||
distinct_target_threshold: 9,
|
||||
target_window_ms: 12345,
|
||||
action: 'suppress_delivery',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects an enabled captcha with no keys for the selected provider', async () => {
|
||||
@@ -677,22 +645,27 @@ describe('ConfigLoader', () => {
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.tor_exit_list.enabled).toBeUndefined();
|
||||
expect(config.integrations.breached_password_check.enabled).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads the optional outbound lookup switches from the environment', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_TOR_EXIT_LIST_ENABLED: 'true',
|
||||
FLUXER_BREACHED_PASSWORD_CHECK_ENABLED: 'false',
|
||||
});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.tor_exit_list.enabled).toBe(true);
|
||||
expect(config.integrations.breached_password_check.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('reads the IPinfo key from its current name before the previous one', async () => {
|
||||
stubMinimalEnv({FLUXER_RISK_IPINFO_API_KEY: 'previous'});
|
||||
expect((await loadConfig()).integrations.ipinfo.api_key).toBe('previous');
|
||||
resetConfig();
|
||||
stubMinimalEnv({FLUXER_IPINFO_API_KEY: 'current', FLUXER_RISK_IPINFO_API_KEY: 'previous'});
|
||||
expect((await loadConfig()).integrations.ipinfo.api_key).toBe('current');
|
||||
});
|
||||
|
||||
test('leaves Bluesky login off with no legal URLs by default', async () => {
|
||||
stubMinimalEnv();
|
||||
|
||||
@@ -823,7 +796,6 @@ describe('ConfigLoader', () => {
|
||||
FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT: 'http://localhost/media',
|
||||
FLUXER_S3_PUBLIC_ENDPOINT: 'http://localhost/s3',
|
||||
FLUXER_EMAIL_APP_BASE_URL: 'http://localhost',
|
||||
FLUXER_SMS_INBOUND_WEBHOOK_PUBLIC_URL: 'http://localhost/webhooks/sms',
|
||||
FLUXER_AUTH_BLUESKY_CLIENT_URI: 'http://localhost',
|
||||
FLUXER_AUTH_BLUESKY_TOS_URI: 'http://localhost/terms',
|
||||
FLUXER_APP_ICON_URL: 'http://localhost/icon.png',
|
||||
@@ -835,7 +807,6 @@ describe('ConfigLoader', () => {
|
||||
expect(config.services.gateway.media_proxy_endpoint).toBe('http://localhost:8088/media');
|
||||
expect(config.s3?.presigned_url_base).toBe('http://localhost:8088/s3');
|
||||
expect(config.integrations.email.app_base_url).toBe('http://localhost:8088');
|
||||
expect(config.integrations.sms.inbound_webhook_public_url).toBe('http://localhost:8088/webhooks/sms');
|
||||
expect(config.auth.bluesky.client_uri).toBe('http://localhost:8088');
|
||||
expect(config.auth.bluesky.tos_uri).toBe('http://localhost:8088/terms');
|
||||
expect(config.instance.branding.icon_url).toBe('http://localhost:8088/icon.png');
|
||||
|
||||
@@ -233,13 +233,6 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
||||
FLUXER_EMAIL_SMTP_USERNAME: {path: ['integrations', 'email', 'smtp', 'username']},
|
||||
FLUXER_EMAIL_SMTP_PASSWORD: {path: ['integrations', 'email', 'smtp', 'password']},
|
||||
FLUXER_EMAIL_SMTP_SECURE: {path: ['integrations', 'email', 'smtp', 'secure'], parse: parseBoolean},
|
||||
FLUXER_SMS_ENABLED: {path: ['integrations', 'sms', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_SMS_ACCOUNT_SID: {path: ['integrations', 'sms', 'account_sid']},
|
||||
FLUXER_SMS_AUTH_TOKEN: {path: ['integrations', 'sms', 'auth_token']},
|
||||
FLUXER_SMS_VERIFY_SERVICE_SID: {path: ['integrations', 'sms', 'verify_service_sid']},
|
||||
FLUXER_SMS_INBOUND_CHALLENGE_NUMBER: {path: ['integrations', 'sms', 'inbound_challenge_number']},
|
||||
FLUXER_SMS_INBOUND_WEBHOOK_AUTH_TOKEN: {path: ['integrations', 'sms', 'inbound_webhook_auth_token']},
|
||||
FLUXER_SMS_INBOUND_WEBHOOK_PUBLIC_URL: {path: ['integrations', 'sms', 'inbound_webhook_public_url']},
|
||||
FLUXER_CAPTCHA_ENABLED: {path: ['integrations', 'captcha', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_CAPTCHA_PROVIDER: {path: ['integrations', 'captcha', 'provider']},
|
||||
FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY: {path: ['integrations', 'captcha', 'hcaptcha', 'site_key']},
|
||||
@@ -326,17 +319,11 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
||||
parse: parseInteger,
|
||||
},
|
||||
FLUXER_BLOCKLIST_FEEDS_ENABLED: {path: ['integrations', 'blocklist_feeds', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_TOR_EXIT_LIST_ENABLED: {path: ['integrations', 'tor_exit_list', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_BREACHED_PASSWORD_CHECK_ENABLED: {
|
||||
path: ['integrations', 'breached_password_check', 'enabled'],
|
||||
parse: parseBoolean,
|
||||
},
|
||||
FLUXER_RISK_INTEGRATION_ENABLED: {path: ['integrations', 'risk_integration', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_RISK_IPINFO_API_KEY: {path: ['integrations', 'risk_integration', 'ipinfo_api_key']},
|
||||
FLUXER_ACCOUNT_POLICY_DSL: {
|
||||
path: ['integrations', 'risk_integration', 'account_policy_dsl'],
|
||||
parse: parseEnvValue,
|
||||
},
|
||||
FLUXER_IPINFO_API_KEY: {path: ['integrations', 'ipinfo', 'api_key']},
|
||||
FLUXER_PUSH_APNS_ENABLED: {path: ['integrations', 'push', 'apns', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_PUSH_APNS_TEAM_ID: {path: ['integrations', 'push', 'apns', 'team_id']},
|
||||
FLUXER_PUSH_APNS_KEY_ID: {path: ['integrations', 'push', 'apns', 'key_id']},
|
||||
@@ -366,41 +353,6 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
|
||||
FLUXER_APP_STATUS_PAGE_URL: {path: ['instance', 'branding', 'status_page_url']},
|
||||
FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL: {path: ['instance', 'branding', 'status_page_incident_history_url']},
|
||||
FLUXER_INSTANCE_SETUP_CONFIGURED: {path: ['instance', 'setup', 'configured'], parse: parseBoolean},
|
||||
FLUXER_ABUSE_INBOUND_PHONE_COUNTRY_CODES: {
|
||||
path: ['instance', 'abuse_policy', 'inbound_phone_country_codes'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_ABUSE_PHONE_FLAGGING_ENABLED: {
|
||||
path: ['instance', 'abuse_policy', 'phone_flagging', 'enabled'],
|
||||
parse: parseBoolean,
|
||||
},
|
||||
FLUXER_ABUSE_PHONE_FLAGGING_EXEMPT_COUNTRY_CODES: {
|
||||
path: ['instance', 'abuse_policy', 'phone_flagging', 'exempt_country_codes'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_ABUSE_PHONE_INBOUND_REQUIRED_PREFIXES: {
|
||||
path: ['instance', 'abuse_policy', 'phone_verification', 'inbound_required_prefixes'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_ENABLED: {
|
||||
path: ['instance', 'abuse_policy', 'direct_contact_spam', 'enabled'],
|
||||
parse: parseBoolean,
|
||||
},
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_COUNTRY_CODES: {
|
||||
path: ['instance', 'abuse_policy', 'direct_contact_spam', 'country_codes'],
|
||||
parse: parseCsv,
|
||||
},
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_DISTINCT_TARGET_THRESHOLD: {
|
||||
path: ['instance', 'abuse_policy', 'direct_contact_spam', 'distinct_target_threshold'],
|
||||
parse: parseInteger,
|
||||
},
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_TARGET_WINDOW_MS: {
|
||||
path: ['instance', 'abuse_policy', 'direct_contact_spam', 'target_window_ms'],
|
||||
parse: parseInteger,
|
||||
},
|
||||
FLUXER_ABUSE_DIRECT_CONTACT_SPAM_ACTION: {
|
||||
path: ['instance', 'abuse_policy', 'direct_contact_spam', 'action'],
|
||||
},
|
||||
FLUXER_DISCOVERY_ENABLED: {path: ['discovery', 'enabled'], parse: parseBoolean},
|
||||
FLUXER_DISCOVERY_MIN_MEMBER_COUNT: {path: ['discovery', 'min_member_count'], parse: parseInteger},
|
||||
FLUXER_DELETION_GRACE_PERIOD_HOURS: {path: ['deletion_grace_period_hours'], parse: parseInteger},
|
||||
@@ -537,6 +489,7 @@ export function setNestedValue(target: ConfigContainer, keys: Array<ConfigPathKe
|
||||
const NAMED_FLUXER_ENV_ALIASES: Record<string, string | undefined> = {
|
||||
FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT: 'FLUXER_MEDIA_PROXY_ENDPOINT',
|
||||
FLUXER_NATS_URL: 'FLUXER_NATS_CORE_URL',
|
||||
FLUXER_IPINFO_API_KEY: 'FLUXER_RISK_IPINFO_API_KEY',
|
||||
};
|
||||
|
||||
export function buildNamedFluxerEnvOverrides(env: NodeJS.ProcessEnv): ConfigObject {
|
||||
|
||||
Reference in new issue
Block a user