/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import {Config} from '~/Config';
import {FLUXER_USER_AGENT} from '~/Constants';
import type {ICaptchaService, VerifyCaptchaParams} from '~/infrastructure/ICaptchaService';
import {Logger} from '~/Logger';
interface HCaptchaVerifyResponse {
success: boolean;
challenge_ts?: string;
hostname?: string;
credit?: boolean;
'error-codes'?: Array;
score?: number;
score_reason?: Array;
}
export class CaptchaService implements ICaptchaService {
private readonly secretKey: string;
private readonly verifyUrl = 'https://api.hcaptcha.com/siteverify';
constructor() {
if (!Config.captcha.hcaptcha?.secretKey) {
throw new Error('HCAPTCHA_SECRET_KEY is required when CAPTCHA_ENABLED is true');
}
this.secretKey = Config.captcha.hcaptcha.secretKey;
}
async verify({token, remoteIp}: VerifyCaptchaParams): Promise {
try {
const params = new URLSearchParams();
params.append('secret', this.secretKey);
params.append('response', token);
if (remoteIp) {
params.append('remoteip', remoteIp);
}
const response = await fetch(this.verifyUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': FLUXER_USER_AGENT,
},
body: params.toString(),
});
if (!response.ok) {
Logger.error({status: response.status}, 'hCaptcha verify request failed');
return false;
}
const data = (await response.json()) as HCaptchaVerifyResponse;
if (!data.success) {
Logger.warn({errorCodes: data['error-codes']}, 'hCaptcha verification failed');
return false;
}
Logger.debug({hostname: data.hostname}, 'hCaptcha verification successful');
return true;
} catch (error) {
Logger.error({error}, 'Error verifying hCaptcha token');
return false;
}
}
}