feat(captcha): make ALTCHA the only captcha (#3035)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-29 17:00:15 +02:00
1 parent d433a039b5
commit 4f968bbc47
209 files changed
+4097 -8692

No files matched your search

@@ -34,9 +34,10 @@ import {
type RegistrationUrlResponse,
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
import {
type AltchaCaptchaConfig,
AltchaCaptchaConfigSchema,
} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas';
type CaptchaConfig,
CaptchaConfigSchema,
type CaptchaConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
import {
type DomainMigrationConfig,
DomainMigrationConfigSchema,
@@ -64,8 +65,6 @@ import {
type InstanceAppPublic,
InstanceAppPublicSchema,
type InstanceBranding,
type InstanceCaptchaProvider,
InstanceCaptchaProviderSchema,
type InstanceCommunity,
type InstanceRegistration,
InstanceRegistrationSchema,
@@ -79,7 +78,7 @@ import {z} from 'zod';
const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config';
const PUSH_RELAY_CONFIG_KEY = 'push_service_delivery_config';
const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config';
const ALTCHA_CAPTCHA_CONFIG_KEY = 'altcha_captcha_config';
const CAPTCHA_CONFIG_KEY = 'captcha_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
const REGISTRATION_CONFIG_KEY = 'registration_config';
const REGISTRATION_URLS_KEY = 'registration_urls';
@@ -189,14 +188,6 @@ interface InstanceYoutubeIntegrationConfig {
api_key: string | null;
}
interface InstanceCaptchaIntegrationConfig {
provider: InstanceCaptchaProvider | null;
hcaptcha_site_key: string | null;
hcaptcha_secret_key: string | null;
turnstile_site_key: string | null;
turnstile_secret_key: string | null;
}
interface InstanceEmailSmtpIntegrationConfig {
host: string | null;
port: number | null;
@@ -232,7 +223,6 @@ interface InstanceBlueskyIntegrationConfig {
interface InstanceIntegrationsConfig {
gif: InstanceGifIntegrationConfig;
youtube: InstanceYoutubeIntegrationConfig;
captcha: InstanceCaptchaIntegrationConfig;
email: InstanceEmailIntegrationConfig;
bluesky: InstanceBlueskyIntegrationConfig;
}
@@ -243,15 +233,6 @@ interface InstanceGifEffectiveConfig {
available: boolean;
}
export interface InstanceCaptchaEffectiveConfig {
enabled: boolean;
provider: InstanceCaptchaProvider;
hcaptcha_site_key: string | null;
hcaptcha_secret_key: string | null;
turnstile_site_key: string | null;
turnstile_secret_key: string | null;
}
interface InstanceIntegrationsAdminConfig {
gif: {
klipy_api_key_set: boolean;
@@ -261,15 +242,6 @@ interface InstanceIntegrationsAdminConfig {
api_key_set: boolean;
effective_available: boolean;
};
captcha: {
provider: InstanceCaptchaProvider | null;
effective_provider: InstanceCaptchaProvider;
hcaptcha_site_key: string | null;
hcaptcha_secret_key_set: boolean;
turnstile_site_key: string | null;
turnstile_secret_key_set: boolean;
effective_enabled: boolean;
};
email: {
enabled: boolean | null;
effective_enabled: boolean;
@@ -336,7 +308,6 @@ interface InstanceMediaAdminConfig {
interface InstanceIntegrationsConfigPatch {
gif?: Partial<InstanceGifIntegrationConfig>;
youtube?: Partial<InstanceYoutubeIntegrationConfig>;
captcha?: Partial<InstanceCaptchaIntegrationConfig>;
email?: Partial<Omit<InstanceEmailIntegrationConfig, 'smtp'>> & {
smtp?: Partial<InstanceEmailSmtpIntegrationConfig>;
};
@@ -435,7 +406,7 @@ type StoredConfigSection =
| 'gateway rollout'
| 'push relay'
| 'domain migration'
| 'altcha captcha'
| 'captcha'
| 'experiment delivery'
| 'instance policy'
| 'integrations'
@@ -596,8 +567,8 @@ function parseStoredDomainMigrationConfig(raw: string | null): DomainMigrationCo
return parseStoredConfigOrDefault(DomainMigrationConfigSchema, raw, 'domain migration');
}
function parseStoredAltchaCaptchaConfig(raw: string | null): AltchaCaptchaConfig {
return parseStoredConfigOrDefault(AltchaCaptchaConfigSchema, raw, 'altcha captcha');
function parseStoredCaptchaConfig(raw: string | null): CaptchaConfig {
return parseStoredConfigOrDefault(CaptchaConfigSchema, raw, 'captcha');
}
function parseStoredExperimentDeliveryConfig(raw: string | null): ExperimentDeliveryConfig {
@@ -718,15 +689,6 @@ const StoredBlueskyKeysSchema = z
const StoredInstanceIntegrationsSchema = z.object({
gif: z.object({klipy_api_key: StoredIntegrationStringSchema}).prefault({}),
youtube: z.object({api_key: StoredIntegrationStringSchema}).prefault({}),
captcha: z
.object({
provider: InstanceCaptchaProviderSchema.nullable().default(null),
hcaptcha_site_key: StoredIntegrationStringSchema,
hcaptcha_secret_key: StoredIntegrationStringSchema,
turnstile_site_key: StoredIntegrationStringSchema,
turnstile_secret_key: StoredIntegrationStringSchema,
})
.prefault({}),
email: z
.object({
enabled: StoredNullableBooleanSchema,
@@ -1270,7 +1232,7 @@ export class InstanceConfigRepository {
);
parseStoredPushRelayConfig(snapshot.get(PUSH_RELAY_CONFIG_KEY) ?? null);
parseStoredDomainMigrationConfig(snapshot.get(DOMAIN_MIGRATION_CONFIG_KEY) ?? null);
parseStoredAltchaCaptchaConfig(snapshot.get(ALTCHA_CAPTCHA_CONFIG_KEY) ?? null);
parseStoredCaptchaConfig(snapshot.get(CAPTCHA_CONFIG_KEY) ?? null);
parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null);
parseStoredInstancePolicyConfig(snapshot.get(INSTANCE_POLICY_CONFIG_KEY) ?? null);
checkStoredConfig('registration', () =>
@@ -1384,20 +1346,14 @@ export class InstanceConfigRepository {
);
}
async getAltchaCaptchaConfig(): Promise<AltchaCaptchaConfig> {
const raw = await this.getConfig(ALTCHA_CAPTCHA_CONFIG_KEY);
return parseStoredAltchaCaptchaConfig(raw);
async getCaptchaConfig(): Promise<CaptchaConfig> {
const raw = await this.getConfig(CAPTCHA_CONFIG_KEY);
return parseStoredCaptchaConfig(raw);
}
async setAltchaCaptchaConfig(config: AltchaCaptchaConfig): Promise<void> {
await this.updateAltchaCaptchaConfig(() => config);
}
updateAltchaCaptchaConfig(
update: (current: AltchaCaptchaConfig) => AltchaCaptchaConfig,
): Promise<AltchaCaptchaConfig> {
return this.updateStoredConfig(ALTCHA_CAPTCHA_CONFIG_KEY, (raw) =>
validateStoredConfig(AltchaCaptchaConfigSchema, update(parseStoredAltchaCaptchaConfig(raw)), 'altcha captcha'),
updateCaptchaConfig(patch: CaptchaConfigUpdateRequest): Promise<CaptchaConfig> {
return this.updateStoredConfig(CAPTCHA_CONFIG_KEY, (raw) =>
validateStoredConfig(CaptchaConfigSchema, {...parseStoredCaptchaConfig(raw), ...patch}, 'captcha'),
);
}
@@ -1545,10 +1501,6 @@ export class InstanceConfigRepository {
...current.youtube,
...(config.youtube ?? {}),
},
captcha: {
...current.captcha,
...(config.captcha ?? {}),
},
email: {
...current.email,
...(config.email ?? {}),
@@ -1642,33 +1594,6 @@ export class InstanceConfigRepository {
return integrations.youtube.api_key ?? normalizeOptionalString(Config.youtube.apiKey);
}
async getEffectiveCaptchaConfig(): Promise<InstanceCaptchaEffectiveConfig> {
const integrations = await this.getInstanceIntegrationsConfig();
const provider = integrations.captcha.provider ?? (Config.captcha.enabled ? Config.captcha.provider : 'none');
const hcaptchaSiteKey =
integrations.captcha.hcaptcha_site_key ?? normalizeOptionalString(Config.captcha.hcaptcha?.siteKey);
const hcaptchaSecretKey =
integrations.captcha.hcaptcha_secret_key ?? normalizeOptionalString(Config.captcha.hcaptcha?.secretKey);
const turnstileSiteKey =
integrations.captcha.turnstile_site_key ?? normalizeOptionalString(Config.captcha.turnstile?.siteKey);
const turnstileSecretKey =
integrations.captcha.turnstile_secret_key ?? normalizeOptionalString(Config.captcha.turnstile?.secretKey);
const providerReady =
provider === 'hcaptcha'
? Boolean(hcaptchaSiteKey && hcaptchaSecretKey)
: provider === 'turnstile'
? Boolean(turnstileSiteKey && turnstileSecretKey)
: false;
return {
enabled: providerReady,
provider: providerReady ? provider : 'none',
hcaptcha_site_key: hcaptchaSiteKey,
hcaptcha_secret_key: hcaptchaSecretKey,
turnstile_site_key: turnstileSiteKey,
turnstile_secret_key: turnstileSecretKey,
};
}
async getEffectiveEmailConfig(): Promise<APIConfig['email']> {
const integrations = await this.getInstanceIntegrationsConfig();
const provider = integrations.email.provider ?? Config.email.provider;
@@ -1731,11 +1656,10 @@ export class InstanceConfigRepository {
}
async getInstanceIntegrationsAdminConfig(): Promise<InstanceIntegrationsAdminConfig> {
const [integrations, gif, youtubeApiKey, captcha, email, bluesky] = await Promise.all([
const [integrations, gif, youtubeApiKey, email, bluesky] = await Promise.all([
this.getInstanceIntegrationsConfig(),
this.getEffectiveGifConfig(),
this.getEffectiveYoutubeApiKey(),
this.getEffectiveCaptchaConfig(),
this.getEffectiveEmailConfig(),
this.getEffectiveBlueskyConfig(),
]);
@@ -1748,17 +1672,6 @@ export class InstanceConfigRepository {
api_key_set: secretIsSet(integrations.youtube.api_key) || secretIsSet(Config.youtube.apiKey),
effective_available: Boolean(youtubeApiKey),
},
captcha: {
provider: integrations.captcha.provider,
effective_provider: captcha.provider,
hcaptcha_site_key: captcha.hcaptcha_site_key,
hcaptcha_secret_key_set:
secretIsSet(integrations.captcha.hcaptcha_secret_key) || secretIsSet(Config.captcha.hcaptcha?.secretKey),
turnstile_site_key: captcha.turnstile_site_key,
turnstile_secret_key_set:
secretIsSet(integrations.captcha.turnstile_secret_key) || secretIsSet(Config.captcha.turnstile?.secretKey),
effective_enabled: captcha.enabled,
},
email: {
enabled: integrations.email.enabled,
effective_enabled: email.enabled,