using web::WebClient
using util::JsonInStream
** (Service) -
** To edit or view your hCaptcha account, visit `https://dashboard.hcaptcha.com/`.
**
const class CaptchaServer {
const Log log := typeof.pod.log
** The endpoint URL that tokens are verified against.
const Uri verifyUrl := `https://hcaptcha.com/siteverify`
** The secretKey for the account.
const Str secretKey
** The siteKey to verify against.
const Str siteKey
** Disable for local dev.
const Bool enabled := true
new make(|This| f) { f(this) }
Uri captchaJsUrl() {
`https://js.hcaptcha.com/1/api.js?render=explicit&onload=afHcaptchaOnLoadCallback`
}
Str captchaJsFunc() {
"""function afHcaptchaOnLoadCallback() {
if (typeof afHcaptcha === "undefined") afHcaptcha = {};
afHcaptcha.loaded = true;
if (afHcaptcha.instance != null)
afHcaptcha.instance.onLoad();
}"""
}
** Inject hCaptcha scripts into the page.
Void injectJsCaptcha(Obj htmlInjector) {
if (!enabled) return
htmlInjector->injectScript->withScript(captchaJsFunc)
htmlInjector->injectScript->fromExternalUrl(captchaJsUrl)->async->defer
}
** If not enabled, pass '<fail>' to mimic a failure.
Bool verifyCaptcha(Str? response, Bool checked := true) {
resp := null as Str:Obj?
if (response?.trimToNull == null)
return !checked ? false : throw Err("No hCaptcha given")
if (enabled) {
json := WebClient(verifyUrl).postForm([
"secret" : secretKey,
"response" : response ?: "",
"sitekey" : siteKey,
]).resStr
resp = JsonInStream(json.in).readJson
} else {
resp = ["success" : response != "<fail>"]
if (response == "<error>")
resp["error-codes"] = "afHcaptcha-test-error"
log.info("hCaptcha Stub - " + (resp["success"] ? "Success!" : "Fail") + " - $response")
}
errorCodes := resp["error-codes"] as Str[]
if (errorCodes != null) {
if (errorCodes.size == 0 && (errorCodes.first == "invalid-or-already-seen-response" || errorCodes.first == "sitekey-secret-mismatch"))
{ } // ignore intermittent hCapture errors that the user can't do anything about
else
// generally error-codes mean I've done something wrong and the request / token is invalid
// e.g. Bad hCaptcha response - [missing-input-secret]
// https://docs.hcaptcha.com/#siteverify-error-codes-table
throw Err("Bad hCaptcha response - ${errorCodes}")
}
success := resp["success"]
if (!success && checked)
throw Err("hCaptcha failed")
return success
}
}