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

@@ -250,7 +250,6 @@ const RootComponent = React.forwardRef<HTMLDivElement, ModalProps>(
return [];
}
const inside: Array<Element> = [];
modalDocument.querySelectorAll('iframe[src*="hcaptcha"], .h-captcha').forEach((el) => inside.push(el));
const popoutsRoot = modalDocument.querySelector('[data-popouts-root]');
if (popoutsRoot) inside.push(popoutsRoot);
const mediaViewerPortalRoot = modalDocument.querySelector('[data-media-viewer-portal-root]');
@@ -156,12 +156,6 @@ const DEFAULT_INTEGRATION_DRAFT: ServiceIntegrationDraft = {
klipyApiKey: '',
youtubeMode: 'later',
youtubeApiKey: '',
captchaMode: 'later',
captchaProvider: 'hcaptcha',
hcaptchaSiteKey: '',
hcaptchaSecretKey: '',
turnstileSiteKey: '',
turnstileSecretKey: '',
emailMode: 'later',
emailEnabled: true,
emailFromEmail: '',
@@ -200,8 +194,6 @@ function wizardStepToIntegrationKind(step: WizardStep): IntegrationStepKind | nu
return 'gif';
case 'integration_youtube':
return 'youtube';
case 'integration_captcha':
return 'captcha';
case 'integration_email':
return 'email';
case 'integration_bluesky':
@@ -278,11 +270,6 @@ function isIntegrationStepValid(kind: IntegrationStepKind, draft: ServiceIntegra
return draft.klipyApiKey.trim().length > 0;
case 'youtube':
return draft.youtubeMode === 'later' || draft.youtubeApiKey.trim().length > 0;
case 'captcha':
if (draft.captchaMode === 'later') return true;
return draft.captchaProvider === 'hcaptcha'
? draft.hcaptchaSiteKey.trim().length > 0 && draft.hcaptchaSecretKey.trim().length > 0
: draft.turnstileSiteKey.trim().length > 0 && draft.turnstileSecretKey.trim().length > 0;
case 'email':
if (draft.emailMode === 'later' || !draft.emailEnabled) return true;
return (
@@ -313,13 +300,6 @@ function buildIntegrationsPatch(draft: ServiceIntegrationDraft) {
youtube?: {
api_key: string;
};
captcha?: {
provider: 'hcaptcha' | 'turnstile';
hcaptcha_site_key?: string;
hcaptcha_secret_key?: string;
turnstile_site_key?: string;
turnstile_secret_key?: string;
};
email?: {
enabled: boolean;
provider: 'smtp';
@@ -349,20 +329,6 @@ function buildIntegrationsPatch(draft: ServiceIntegrationDraft) {
if (draft.youtubeMode === 'configure') {
integrations.youtube = {api_key: draft.youtubeApiKey.trim()};
}
if (draft.captchaMode === 'configure') {
integrations.captcha =
draft.captchaProvider === 'hcaptcha'
? {
provider: 'hcaptcha',
hcaptcha_site_key: draft.hcaptchaSiteKey.trim(),
hcaptcha_secret_key: draft.hcaptchaSecretKey.trim(),
}
: {
provider: 'turnstile',
turnstile_site_key: draft.turnstileSiteKey.trim(),
turnstile_secret_key: draft.turnstileSecretKey.trim(),
};
}
if (draft.emailMode === 'configure') {
integrations.email = {
enabled: draft.emailEnabled,
@@ -580,7 +546,6 @@ export const SelfHostedSetupWizardGate = observer(() => {
});
setIntegrationDraft((current) => ({
...current,
captchaProvider: next.integrations.captcha.effective_provider === 'turnstile' ? 'turnstile' : 'hcaptcha',
emailEnabled: next.integrations.email.effective_enabled || next.integrations.email.enabled !== false,
emailFromEmail: next.integrations.email.from_email ?? current.emailFromEmail,
emailFromName: next.integrations.email.from_name ?? current.emailFromName,
@@ -14,7 +14,6 @@ export type WizardStep =
| 'media_expiry'
| 'integration_gif'
| 'integration_youtube'
| 'integration_captcha'
| 'integration_email'
| 'integration_bluesky'
| 'push_relay_consent'
@@ -34,7 +33,6 @@ export const CONFIGURE_STEPS: ReadonlyArray<WizardStep> = [
'media_expiry',
'integration_gif',
'integration_youtube',
'integration_captcha',
'integration_email',
'integration_bluesky',
'push_relay_consent',
@@ -915,21 +915,14 @@ export interface MediaExpiryDraft {
renewWindowDays: string;
}
export type IntegrationStepKind = 'gif' | 'youtube' | 'captcha' | 'email' | 'bluesky';
export type IntegrationStepKind = 'gif' | 'youtube' | 'email' | 'bluesky';
export type IntegrationSetupMode = 'later' | 'configure';
export type CaptchaProvider = 'hcaptcha' | 'turnstile';
export interface ServiceIntegrationDraft {
gifMode: IntegrationSetupMode;
klipyApiKey: string;
youtubeMode: IntegrationSetupMode;
youtubeApiKey: string;
captchaMode: IntegrationSetupMode;
captchaProvider: CaptchaProvider;
hcaptchaSiteKey: string;
hcaptchaSecretKey: string;
turnstileSiteKey: string;
turnstileSecretKey: string;
emailMode: IntegrationSetupMode;
emailEnabled: boolean;
emailFromEmail: string;
@@ -982,14 +975,6 @@ const YOUTUBE_SETUP_BODY_DESCRIPTOR = msg({
message: 'Add a YouTube Data API key to enrich YouTube links.',
comment: 'Setup wizard body for YouTube integration credentials.',
});
const CAPTCHA_SETUP_TITLE_DESCRIPTOR = msg({
message: 'Bot protection',
comment: 'Setup wizard title for CAPTCHA integration credentials.',
});
const CAPTCHA_SETUP_BODY_DESCRIPTOR = msg({
message: 'Choose hCaptcha or Cloudflare Turnstile for signup challenges.',
comment: 'Setup wizard body for CAPTCHA integration credentials.',
});
const EMAIL_SETUP_TITLE_DESCRIPTOR = msg({
message: 'Email delivery',
comment: 'Setup wizard title for SMTP integration credentials.',
@@ -1006,18 +991,7 @@ const BLUESKY_SETUP_BODY_DESCRIPTOR = msg({
message: 'Add Bluesky OAuth client metadata and a signing key.',
comment: 'Setup wizard body for Bluesky integration credentials.',
});
const PROVIDER_LABEL_DESCRIPTOR = msg({
message: 'Provider',
comment: 'Label for an integration provider choice.',
});
const HCAPTCHA_NAME_DESCRIPTOR = msg({message: 'hCaptcha', comment: 'hCaptcha provider option.'});
const TURNSTILE_NAME_DESCRIPTOR = msg({
message: 'Cloudflare Turnstile',
comment: 'Cloudflare Turnstile provider option.',
});
const API_KEY_LABEL_DESCRIPTOR = msg({message: 'API key', comment: 'Label for an integration API key input.'});
const SITE_KEY_LABEL_DESCRIPTOR = msg({message: 'Site key', comment: 'Label for a CAPTCHA site key input.'});
const SECRET_KEY_LABEL_DESCRIPTOR = msg({message: 'Secret key', comment: 'Label for a CAPTCHA secret key input.'});
const ENABLE_EMAIL_LABEL_DESCRIPTOR = msg({
message: 'Enable email delivery',
comment: 'Label for enabling SMTP email delivery in setup.',
@@ -1106,8 +1080,6 @@ function getIntegrationMode(draft: ServiceIntegrationDraft, kind: IntegrationSte
return draft.gifMode;
case 'youtube':
return draft.youtubeMode;
case 'captcha':
return draft.captchaMode;
case 'email':
return draft.emailMode;
case 'bluesky':
@@ -1121,8 +1093,6 @@ function getIntegrationCopy(kind: IntegrationStepKind): {title: MessageDescripto
return {title: GIF_SETUP_TITLE_DESCRIPTOR, body: GIF_SETUP_BODY_DESCRIPTOR};
case 'youtube':
return {title: YOUTUBE_SETUP_TITLE_DESCRIPTOR, body: YOUTUBE_SETUP_BODY_DESCRIPTOR};
case 'captcha':
return {title: CAPTCHA_SETUP_TITLE_DESCRIPTOR, body: CAPTCHA_SETUP_BODY_DESCRIPTOR};
case 'email':
return {title: EMAIL_SETUP_TITLE_DESCRIPTOR, body: EMAIL_SETUP_BODY_DESCRIPTOR};
case 'bluesky':
@@ -1302,9 +1272,6 @@ export const IntegrationStep = observer(
case 'youtube':
onDraftChange({youtubeMode: next});
break;
case 'captcha':
onDraftChange({captchaMode: next});
break;
case 'email':
onDraftChange({emailMode: next});
break;
@@ -1315,10 +1282,6 @@ export const IntegrationStep = observer(
},
[kind, onDraftChange],
);
const captchaOptions: ReadonlyArray<RadioOption<CaptchaProvider>> = [
{value: 'hcaptcha', name: i18n._(HCAPTCHA_NAME_DESCRIPTOR), desc: i18n._(CAPTCHA_SETUP_BODY_DESCRIPTOR)},
{value: 'turnstile', name: i18n._(TURNSTILE_NAME_DESCRIPTOR), desc: i18n._(CAPTCHA_SETUP_BODY_DESCRIPTOR)},
];
return (
<section className={styles.step} data-flx={`app.self-hosted-setup-wizard-gate.integration-${kind}-step`}>
<StepHeader
@@ -1354,48 +1317,6 @@ export const IntegrationStep = observer(
data-flx="app.setup.setup-wizard-steps.integration-step.input.draft-change--2"
/>
)}
{kind === 'captcha' && (
<>
<div className={styles.fieldLabel} data-flx="app.setup.setup-wizard-steps.integration-step.field-label">
{i18n._(PROVIDER_LABEL_DESCRIPTOR)}
</div>
<RadioGroup
options={captchaOptions}
value={draft.captchaProvider}
onChange={(value) => onDraftChange({captchaProvider: value})}
disabled={disabled}
aria-label={i18n._(PROVIDER_LABEL_DESCRIPTOR)}
data-flx="app.setup.setup-wizard-steps.integration-step.radio-group.draft-change"
/>
<Input
label={i18n._(SITE_KEY_LABEL_DESCRIPTOR)}
value={draft.captchaProvider === 'hcaptcha' ? draft.hcaptchaSiteKey : draft.turnstileSiteKey}
onChange={(event) =>
onDraftChange(
draft.captchaProvider === 'hcaptcha'
? {hcaptchaSiteKey: event.target.value}
: {turnstileSiteKey: event.target.value},
)
}
disabled={disabled}
data-flx="app.setup.setup-wizard-steps.integration-step.input.draft-change--3"
/>
<Input
label={i18n._(SECRET_KEY_LABEL_DESCRIPTOR)}
type="password"
value={draft.captchaProvider === 'hcaptcha' ? draft.hcaptchaSecretKey : draft.turnstileSecretKey}
onChange={(event) =>
onDraftChange(
draft.captchaProvider === 'hcaptcha'
? {hcaptchaSecretKey: event.target.value}
: {turnstileSecretKey: event.target.value},
)
}
disabled={disabled}
data-flx="app.setup.setup-wizard-steps.integration-step.input.draft-change.password"
/>
</>
)}
{kind === 'email' && (
<>
<Switch
@@ -12,7 +12,6 @@ import {Logger} from '@app/features/platform/utils/AppLogger';
import {MS_PER_HOUR, MS_PER_MINUTE} from '@fluxer/date_utils/src/DateConstants';
import type {
InstanceAppPublic,
InstanceCaptcha,
InstanceCommunity,
InstanceDiscoveryResponse,
InstanceEndpoints,
@@ -34,7 +33,6 @@ export interface InstanceConfig {
fetchedAt: number;
apiCodeVersion: number;
endpoints: InstanceEndpoints;
captcha: InstanceCaptcha;
features: InstanceFeatures;
registration: InstanceRegistration;
community: InstanceCommunity;
@@ -145,7 +143,6 @@ class InstanceConfigs {
fetchedAt: Date.now(),
apiCodeVersion: data.api_code_version,
endpoints: data.endpoints,
captcha: data.captcha,
features: data.features,
gif: {
provider: gifProviderInfo.name,
@@ -208,11 +205,6 @@ class InstanceConfigs {
gift: RuntimeConfig.giftEndpoint,
webapp: RuntimeConfig.webAppEndpoint,
},
captcha: {
provider: RuntimeConfig.captchaProvider,
hcaptcha_site_key: RuntimeConfig.hcaptchaSiteKey,
turnstile_site_key: RuntimeConfig.turnstileSiteKey,
},
features: RuntimeConfig.features,
gif: {
provider: RuntimeConfig.gifProvider,
@@ -12,7 +12,6 @@ import {http} from '@app/features/platform/transport/RestTransport';
import {API_CODE_VERSION} from '@fluxer/constants/src/AppConstants';
import type {
InstanceAppPublic,
InstanceCaptcha,
InstanceCommunity,
InstanceDiscoveryResponse,
InstanceFeatures,
@@ -28,7 +27,6 @@ import {makeAutoObservable, reaction, runInAction} from 'mobx';
export type {
GifProvider,
GifProviderInfo,
InstanceCaptcha,
InstanceCommunity,
InstanceDiscoveryResponse,
InstanceFeatures,
@@ -51,9 +49,6 @@ export interface RuntimeConfigSnapshot {
gifProvider: GifProvider;
gifProviderDisplayName: string;
gifAttributionRequired: boolean;
captchaProvider: 'hcaptcha' | 'turnstile' | 'none';
hcaptchaSiteKey: string | null;
turnstileSiteKey: string | null;
apiCodeVersion: number;
features: InstanceFeatures;
sso: InstanceSsoConfig | null;
@@ -295,9 +290,6 @@ class RuntimeConfig {
gifProvider: GifProvider = DEFAULT_GIF_PROVIDER_INFO.name;
gifProviderDisplayName: string = DEFAULT_GIF_PROVIDER_INFO.displayName;
gifAttributionRequired: boolean = DEFAULT_GIF_PROVIDER_INFO.attributionRequired;
captchaProvider: 'hcaptcha' | 'turnstile' | 'none' = 'none';
hcaptchaSiteKey: string | null = null;
turnstileSiteKey: string | null = null;
apiCodeVersion: number = API_CODE_VERSION;
features: InstanceFeatures = {...DEFAULT_INSTANCE_FEATURES};
sso: InstanceSsoConfig | null = null;
@@ -342,9 +334,6 @@ class RuntimeConfig {
gifProvider: this.gifProvider,
gifProviderDisplayName: this.gifProviderDisplayName,
gifAttributionRequired: this.gifAttributionRequired,
captchaProvider: this.captchaProvider,
hcaptchaSiteKey: this.hcaptchaSiteKey,
turnstileSiteKey: this.turnstileSiteKey,
apiCodeVersion: this.apiCodeVersion,
features: {...this.features},
sso: this.sso ? {...this.sso} : null,
@@ -448,9 +437,6 @@ class RuntimeConfig {
this.gifProvider = gifProviderInfo.name;
this.gifProviderDisplayName = gifProviderInfo.displayName;
this.gifAttributionRequired = gifProviderInfo.attributionRequired;
this.captchaProvider = instance.captcha.provider;
this.hcaptchaSiteKey = instance.captcha.hcaptcha_site_key;
this.turnstileSiteKey = instance.captcha.turnstile_site_key;
this.apiCodeVersion = instance.api_code_version;
this.features = {
...DEFAULT_INSTANCE_FEATURES,