Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions berghain.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ type LevelConfig struct {
Duration time.Duration
Type ValidationType

// Captcha configuration, required for the turnstile, hcaptcha and
// recaptcha validation types.
// Captcha configuration, required for the turnstile, hcaptcha,
// recaptcha and cap validation types.
CaptchaSitekey string
CaptchaSecret string
// CaptchaVerifyURL overrides the provider siteverify endpoint,
Expand Down
4 changes: 3 additions & 1 deletion cmd/spop/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ func (c LevelConfig) AsLevelConfig() *berghain.LevelConfig {
lc.Type = berghain.ValidationTypeHCaptcha
case "recaptcha":
lc.Type = berghain.ValidationTypeReCaptcha
case "cap":
lc.Type = berghain.ValidationTypeCap
default:
Fatal("unknown validation type", "validator", c.Type)
}

switch lc.Type {
case berghain.ValidationTypeTurnstile, berghain.ValidationTypeHCaptcha, berghain.ValidationTypeReCaptcha:
case berghain.ValidationTypeTurnstile, berghain.ValidationTypeHCaptcha, berghain.ValidationTypeReCaptcha, berghain.ValidationTypeCap:
if c.Sitekey == "" || c.Secret == "" {
Fatal("captcha types require a sitekey and a secret", "validator", c.Type)
}
Expand Down
9 changes: 8 additions & 1 deletion cmd/spop/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ frontend:
- duration: 10s
type: pow
countdown: 0
# captcha levels (turnstile, hcaptcha, recaptcha) verify the widget
# captcha levels (turnstile, hcaptcha, recaptcha, cap) verify the widget
# token against the provider, so the agent needs outbound HTTPS access
- duration: 12h
type: turnstile
sitekey: 1x00000000000000000000AA # dummy sitekey, always passes
secret: 1x0000000000000000000000000000000AA # dummy secret, always passes
- duration: 240s # nb: this must be long enough to cover the time to captcha completion
type: cap
sitekey: b125d3f13a
secret: sk-xxx
verify_url: https://captcha.example.com/b125d3f13a/siteverify # cap uses paths scoped to the sitekey
skip_hostname_check: true # TODO: why?
countdown: 1
3 changes: 3 additions & 0 deletions validator_captcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func (captchaValidator) isValid(b *Berghain, req *ValidatorRequest, _ *Validator
verifyURL = "https://api.hcaptcha.com/siteverify"
case ValidationTypeReCaptcha:
verifyURL = "https://www.google.com/recaptcha/api/siteverify"
case ValidationTypeCap:
// not very useful to guess a default here
return fmt.Errorf("%w: undefined verify_url", errCaptchaUnavailable)
}
}

Expand Down
3 changes: 2 additions & 1 deletion validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
ValidationTypeTurnstile
ValidationTypeHCaptcha
ValidationTypeReCaptcha
ValidationTypeCap
)

type ValidatorResponse struct {
Expand Down Expand Up @@ -82,7 +83,7 @@ func (v ValidationType) RunValidator(b *Berghain, req *ValidatorRequest, resp *V
return validatorNone(b, req, resp)
case ValidationTypePOW:
return validatorPOW(b, req, resp)
case ValidationTypeTurnstile, ValidationTypeHCaptcha, ValidationTypeReCaptcha:
case ValidationTypeTurnstile, ValidationTypeHCaptcha, ValidationTypeReCaptcha, ValidationTypeCap:
return validatorCaptcha(b, req, resp)
default:
return errors.New("unknown validation type")
Expand Down
43 changes: 43 additions & 0 deletions web/src/challange/challanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export const captchaProviders = Object.freeze({
// when the script was loaded async.
useReady: true,
}),
6: Object.freeze({
name: "Cap",
script: "https://test.home.lysergic.dev/cap/cap.min.js", // TODO: make this configurable
global: "cap",
useReady: true,
}),
});

const captchaOnloadCallback = "__berghainCaptchaLoaded";
Expand Down Expand Up @@ -118,6 +124,41 @@ function loadProviderScript(provider, environment){
*/
async function loadCaptchaApi(provider, environment){
let api;

if (provider.name === "Cap"){
environment.CAP_CUSTOM_WASM_URL = ["/cap/cap_wasm_bg.wasm"]; // TODO: make this configurable

environment.cap = {
ready: (callback) => {
if (typeof callback === "function"){
callback();
}
},
render: (container, options) => {
const widgetElement = environment.document.createElement("cap-widget");
container.appendChild(widgetElement);

const capInstance = new environment.Cap({
apiEndpoint: `/${options.sitekey}`,
}, widgetElement);

capInstance.addEventListener("solve", (event) => {
if (options && typeof options.callback === "function"){
options.callback(event.detail.token);
}
});

capInstance.addEventListener("error", () => {
if (options && typeof options["error-callback"] === "function"){
options["error-callback"]();
}
});

return capInstance;
},
};
}

try {
await loadProviderScript(provider, environment);
api = environment[provider.global];
Expand Down Expand Up @@ -188,6 +229,8 @@ export function getChallengeSolver(challengeType){
case 4:
case 5:
return [`Waiting for ${captchaProviders[challengeType].name}...`, challengeCaptcha];
case 6:
return ["Please solve the challenge...", challengeCaptcha];
default:
throw new Error(`Unknown challenge type: ${challengeType}`);
}
Expand Down
2 changes: 1 addition & 1 deletion web/test/challanges.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function scriptEnvironment(onScript){
}

test("describes every captcha challenge type", () => {
assert.deepEqual(Object.keys(captchaProviders), ["3", "4", "5"]);
assert.deepEqual(Object.keys(captchaProviders), ["3", "4", "5", "6"]);

for (const provider of Object.values(captchaProviders)){
assert.match(provider.script, /^https:\/\//);
Expand Down
Loading