sourceafRecaptcha::RecaptchaServer.fan

using afIoc::Inject
using afIocConfig::Config
using afDuvet::HtmlInjector
using web::WebClient
using util::JsonInStream

**
** To edit or view your reCAPTCHA account, visit `https://www.google.com/recaptcha/admin/`.
** 
const class RecaptchaServer {
    @Inject private const HtmlInjector  injector
    @Inject private const Log           log
    @Config { id="afRecaptcha.verifyUrl" }
            private const Uri           verifyUrl
    @Config { id="afRecaptcha.secretKey" }
            private const Str           secretKey
    @Config { id="afRecaptcha.enabled" }
            private const Bool          enabled
    
    new make(|This| f) { f(this) }
    
    Void setupCaptcha() {
        if (!enabled) return
        
        injector.injectScript.withScript(
        """function afRecaptchaOnLoadCallback() { 
            if (typeof afRecaptcha === "undefined") afRecaptcha = {};
            afRecaptcha.loaded = true;
            if (afRecaptcha.instance != null)
                afRecaptcha.instance.onLoad();
           }"""
        )
        injector.injectScript.fromExternalUrl(`https://www.google.com/recaptcha/api.js?render=explicit&onload=afRecaptchaOnLoadCallback`).async.defer
    }

    Bool verifyCaptcha(Str? response, Bool checked := true) {
        resp := null as Str:Obj?
        
        if (response?.trimToNull == null)
            return !checked ? false : throw Err("No reCAPTCHA given")

        if (enabled) {
            json := WebClient(verifyUrl).postForm([
                "secret"    : secretKey,
                "response"  : response ?: ""
            ]).resStr
            resp = JsonInStream(json.in).readJson
            
        } else {
            resp = ["success" : response != "<fail>"]
            if (response == "<error>")
                resp["error-codes"] = "afRecaptcha-test-error"
            log.info("Recaptcha Stub - " + (resp["success"] ? "Success!" : "Fail") + " - $response")
        }

        if (resp.containsKey("error-codes") && resp["error-codes"] != null)
            // error codes aren't a 500 from Google, but things like
            // Bad reCAPTCHA response - [timeout-or-duplicate]
            return !checked ? false : throw Err("Bad reCAPTCHA response - " + resp["error-codes"])
        
        success := resp["success"]
        if (!success && checked)
            throw Err("reCAPTRUE failed")
        return success
    }
}