/* SPDX-License-Identifier: GPL-3.0-or-later * * Part of Hacktcha, a free/libre front-end for reCAPTCHA for use with Haketilo. * * Copyright (C) 2022 Wojtek Kosior * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * As additional permission under GNU GPL version 3 section 7, you * may distribute forms of that code without the copy of the GNU * GPL normally required by section 4, provided you include this * license notice and, in case of non-source distribution, a URL * through which recipients can access the Corresponding Source. * If you modify file(s) with this exception, you may extend this * exception to your version of the file(s), but you are not * obligated to do so. If you do not wish to do so, delete this * exception statement from your version. * * As a special exception to the GPL, any HTML file which merely * makes function calls to this code, and for that purpose * includes it by reference shall be deemed a separate work for * copyright law purposes. If you modify this code, you may extend * this exception to your version of the code, but you are not * obligated to do so. If you do not wish to do so, delete this * exception statement from your version. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * I, Wojtek Kosior, thereby promise not to sue for violation of this file's * license. Although I request that you do not make use of this code in a * proprietary program, I am not going to enforce this in court. */ /* Load CORS bypass library first. */ "use strict"; var HCHA = {}; /* First, define some helper functions under the "HCHA" napesmace. */ /* * reCAPTCHA uses a less common variant of base64 which we produce by slightly * modifying the output of JavaScript's native base64 encoding routine. */ HCHA._rc_base64 = function(data) { return btoa(data) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", "."); } HCHA._get_rc_version = async function() { const url = "https://google.com/recaptcha/api.js"; const resp = await HKT.fetch(url); const text = await resp.text(); const reg = /https:\/\/www.gstatic.com\/recaptcha\/releases\/([^/]*)/; const match = reg.exec(text); if (!match) throw new Error(`URL '${url}' returned invalid response.`); return match[1]; } HCHA._make_anchor_frame_url = function(site_key, site_url, rc_version) { const url = new URL("https://google.com/recaptcha/api2/anchor"); url.search = new URLSearchParams({ "ar": "1", "k": site_key, "co": HCHA._rc_base64(site_url), "hl": "en", "v": rc_version, "size": "normal", "sa": "action" }); return url; } HCHA._style_contained_element = function(elem) { elem.style.width = "302px"; if (elem.tagName === "IFRAME") { elem.style.borderStyle = "none"; elem.style.height = "422px"; } else { elem.style.textAlign = "center"; elem.style.padding = "10px"; } } HCHA._spawn_frame = function(container, site_key, site_url, rc_version) { container.innerHTML = ''; HCHA._style_contained_element(container.firstElementChild); container.firstElementChild.src = HCHA._make_anchor_frame_url(site_key, site_url, rc_version); } /* This library's external API consists of the run() function defined below. */ HCHA.run = async function(container, site_key, site_url, start_immediately=false) { site_url ||= window.location.origin; const rc_version = await HCHA._get_rc_version(); if (!start_immediately) { container.innerHTML = "
"; HCHA._style_contained_element(container.firstElementChild); const button = container.querySelector("button"); await new Promise(cb => button.onclick = e => { e.preventDefault(); cb(); }); } HCHA._spawn_frame(container, site_key, site_url, rc_version); let event, resolve; while (true) { const prom = new Promise(cb => resolve = cb); window.addEventListener("message", resolve, {once: true}); event = await prom; if (event.origin !== "https://google.com") console.warn("Received message from unexepected origin!", event); else if (event.data === "recreate_frame") HCHA._spawn_frame(container, site_key, site_url, rc_version); else if (typeof event.data !== "string") console.error("Received token is not a string!", event); else break; } container.innerHTML = `

CAPTCHA completed successfully. The token shall be valid for 2 minutes.

` container.querySelector("button").onclick = () => container.innerHTML = ""; return event.data; }