mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(captcha): make ALTCHA the only captcha (#3035)
This commit is contained in:
1 parent
d433a039b5
commit
4f968bbc47
209 files changed
+4097
-8692
No files matched your search
@@ -39,6 +39,7 @@ const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
|
||||
const APP_PUBLIC_CONFIG_KEY = 'app_public_config';
|
||||
const INSTANCE_POLICY_CONFIG_KEY = 'instance_policy_config';
|
||||
const INSTANCE_INTEGRATIONS_CONFIG_KEY = 'instance_integrations_config';
|
||||
const LEGACY_ALTCHA_CAPTCHA_CONFIG_KEY = 'altcha_captcha_config';
|
||||
const REGISTRATION_CONFIG_KEY = 'registration_config';
|
||||
const REGISTRATION_URLS_KEY = 'registration_urls';
|
||||
const REGISTRATION_PENDING_APPROVALS_KEY = 'registration_pending_approvals';
|
||||
@@ -150,36 +151,54 @@ describe('InstanceConfigRepository', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reports the effective captcha provider as none while the selected pair is incomplete', async () => {
|
||||
const executor = new CountingInMemoryCassandraQueryExecutor();
|
||||
setCassandraQueryExecutorForTesting(executor);
|
||||
const kvProvider = new MockKVProvider();
|
||||
const repository = createRepository(kvProvider);
|
||||
it('turns the captcha on at the default difficulty when no row is stored', async () => {
|
||||
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
|
||||
const repository = createRepository(new MockKVProvider());
|
||||
|
||||
await repository.setInstanceIntegrationsConfig({
|
||||
captcha: {
|
||||
provider: 'turnstile',
|
||||
hcaptcha_site_key: 'hcaptcha-site-key',
|
||||
hcaptcha_secret_key: 'hcaptcha-secret-key',
|
||||
},
|
||||
});
|
||||
await expect(repository.getCaptchaConfig()).resolves.toEqual({enabled: true, cost: 5000, max_counter: 1000});
|
||||
});
|
||||
|
||||
await expect(repository.getEffectiveCaptchaConfig()).resolves.toMatchObject({
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
});
|
||||
it('ignores a legacy altcha captcha row that turned the experiment off', async () => {
|
||||
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
|
||||
const repository = createRepository(new MockKVProvider());
|
||||
|
||||
await repository.setInstanceIntegrationsConfig({
|
||||
captcha: {
|
||||
turnstile_site_key: 'turnstile-site-key',
|
||||
turnstile_secret_key: 'turnstile-secret-key',
|
||||
},
|
||||
});
|
||||
await repository.setConfig(
|
||||
LEGACY_ALTCHA_CAPTCHA_CONFIG_KEY,
|
||||
JSON.stringify({enabled: false, config_version: 4, cost: 5000, max_counter: 10000}),
|
||||
);
|
||||
|
||||
await expect(repository.getEffectiveCaptchaConfig()).resolves.toMatchObject({
|
||||
enabled: true,
|
||||
provider: 'turnstile',
|
||||
});
|
||||
await expect(repository.getCaptchaConfig()).resolves.toEqual({enabled: true, cost: 5000, max_counter: 1000});
|
||||
});
|
||||
|
||||
it('merges a partial captcha update onto the stored config', async () => {
|
||||
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
|
||||
const repository = createRepository(new MockKVProvider());
|
||||
|
||||
await repository.updateCaptchaConfig({cost: 2000});
|
||||
await repository.updateCaptchaConfig({enabled: false});
|
||||
|
||||
await expect(repository.getCaptchaConfig()).resolves.toEqual({enabled: false, cost: 2000, max_counter: 1000});
|
||||
});
|
||||
|
||||
it('drops a legacy captcha integration, secrets included, on the next integrations write', async () => {
|
||||
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
|
||||
const repository = createRepository(new MockKVProvider());
|
||||
|
||||
await repository.setConfig(
|
||||
INSTANCE_INTEGRATIONS_CONFIG_KEY,
|
||||
JSON.stringify({
|
||||
captcha: {provider: 'legacy-provider', site_key: 'legacy-site-key', secret_key: 'legacy-secret-key'},
|
||||
youtube: {api_key: 'youtube-key'},
|
||||
}),
|
||||
);
|
||||
expect(await repository.getInstanceIntegrationsConfig()).not.toHaveProperty('captcha');
|
||||
|
||||
await repository.setInstanceIntegrationsConfig({gif: {klipy_api_key: 'klipy-key'}});
|
||||
|
||||
const stored = JSON.parse((await repository.getConfig(INSTANCE_INTEGRATIONS_CONFIG_KEY)) ?? '{}');
|
||||
expect(stored).not.toHaveProperty('captcha');
|
||||
expect(stored.youtube.api_key).toBe('youtube-key');
|
||||
expect(stored.gif.klipy_api_key).toBe('klipy-key');
|
||||
});
|
||||
|
||||
it('keeps the stored setup state when a branding field is invalid', async () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,8 +14,6 @@ import {afterEach, describe, expect, it} from 'vitest';
|
||||
|
||||
interface DiscoveryCaptcha {
|
||||
provider: string;
|
||||
hcaptcha_site_key: string | null;
|
||||
turnstile_site_key: string | null;
|
||||
}
|
||||
|
||||
describe('InstanceController discovery captcha', () => {
|
||||
@@ -62,40 +60,15 @@ describe('InstanceController discovery captcha', () => {
|
||||
return ((await response.json()) as {captcha: DiscoveryCaptcha}).captcha;
|
||||
}
|
||||
|
||||
it('advertises no provider and no site key while the selected pair is incomplete', async () => {
|
||||
const repository = createRepository();
|
||||
await repository.setInstanceIntegrationsConfig({
|
||||
captcha: {
|
||||
provider: 'turnstile',
|
||||
hcaptcha_site_key: 'hcaptcha-site-key',
|
||||
hcaptcha_secret_key: 'hcaptcha-secret-key',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(readCaptcha(repository)).resolves.toEqual({
|
||||
provider: 'none',
|
||||
hcaptcha_site_key: null,
|
||||
turnstile_site_key: null,
|
||||
});
|
||||
it('advertises altcha by default', async () => {
|
||||
await expect(readCaptcha(createRepository())).resolves.toEqual({provider: 'altcha'});
|
||||
});
|
||||
|
||||
it('advertises only the site key that matches the named provider', async () => {
|
||||
it('advertises no provider once an admin turns the captcha off', async () => {
|
||||
const repository = createRepository();
|
||||
await repository.setInstanceIntegrationsConfig({
|
||||
captcha: {
|
||||
provider: 'turnstile',
|
||||
hcaptcha_site_key: 'hcaptcha-site-key',
|
||||
hcaptcha_secret_key: 'hcaptcha-secret-key',
|
||||
turnstile_site_key: 'turnstile-site-key',
|
||||
turnstile_secret_key: 'turnstile-secret-key',
|
||||
},
|
||||
});
|
||||
await repository.updateCaptchaConfig({enabled: false});
|
||||
|
||||
await expect(readCaptcha(repository)).resolves.toEqual({
|
||||
provider: 'turnstile',
|
||||
hcaptcha_site_key: null,
|
||||
turnstile_site_key: 'turnstile-site-key',
|
||||
});
|
||||
await expect(readCaptcha(repository)).resolves.toEqual({provider: 'none'});
|
||||
});
|
||||
|
||||
it('publishes the domain migration kill switch and anonymous rollout without the targeting lists', async () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
isDiscoveryNotModified,
|
||||
nextDiscoveryValidators,
|
||||
} from '@app/api/instance/DiscoveryValidators';
|
||||
import type {InstanceCaptchaEffectiveConfig} from '@app/api/instance/InstanceConfigRepository';
|
||||
import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware';
|
||||
import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
||||
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
||||
@@ -17,6 +16,7 @@ import type {HonoEnv} from '@app/api/types/HonoEnv';
|
||||
import {API_CODE_VERSION} from '@fluxer/constants/src/AppConstants';
|
||||
import {buildDiscoveryResponse, type DiscoveryStaticInput} from '@fluxer/instance_bootstrap/src/BuildDiscovery';
|
||||
import type {InstanceAppPublic} from '@fluxer/instance_bootstrap/src/Types';
|
||||
import type {CaptchaConfig} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
|
||||
import {toDomainMigrationDiscovery} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas';
|
||||
import {WellKnownFluxerResponse} from '@fluxer/schema/src/domains/instance/InstanceSchemas';
|
||||
import type {Hono} from 'hono';
|
||||
@@ -27,7 +27,7 @@ function buildDiscoveryStaticInput(
|
||||
gifService: GifService | undefined,
|
||||
appPublic: InstanceAppPublic,
|
||||
runtime: {
|
||||
captcha: InstanceCaptchaEffectiveConfig;
|
||||
captcha: CaptchaConfig;
|
||||
emailEnabled: boolean;
|
||||
},
|
||||
): DiscoveryStaticInput {
|
||||
@@ -61,9 +61,7 @@ function buildDiscoveryStaticInput(
|
||||
webapp: Config.endpoints.webApp,
|
||||
},
|
||||
captcha: {
|
||||
provider: runtime.captcha.provider,
|
||||
hcaptcha_site_key: runtime.captcha.provider === 'hcaptcha' ? runtime.captcha.hcaptcha_site_key : null,
|
||||
turnstile_site_key: runtime.captcha.provider === 'turnstile' ? runtime.captcha.turnstile_site_key : null,
|
||||
provider: runtime.captcha.enabled ? 'altcha' : 'none',
|
||||
},
|
||||
features: {
|
||||
voice_enabled: Config.voice.enabled,
|
||||
@@ -111,7 +109,7 @@ export function InstanceController(app: Hono<HonoEnv>) {
|
||||
instanceConfigRepository.getInstanceCommunityPublicConfig(),
|
||||
instanceConfigRepository.getResolvedServicesConfig(),
|
||||
instanceConfigRepository.getAppPublicConfig(),
|
||||
instanceConfigRepository.getEffectiveCaptchaConfig(),
|
||||
instanceConfigRepository.getCaptchaConfig(),
|
||||
instanceConfigRepository.getEffectiveEmailConfig(),
|
||||
instanceConfigRepository.getDomainMigrationConfig(),
|
||||
]);
|
||||
|
||||
Reference in new issue
Block a user