diff --git a/authentik/stages/captcha/api.py b/authentik/stages/captcha/api.py index 496342808..41079530c 100644 --- a/authentik/stages/captcha/api.py +++ b/authentik/stages/captcha/api.py @@ -12,7 +12,7 @@ class CaptchaStageSerializer(StageSerializer): class Meta: model = CaptchaStage - fields = StageSerializer.Meta.fields + ["public_key", "private_key"] + fields = StageSerializer.Meta.fields + ["public_key", "private_key", "js_url", "api_url"] extra_kwargs = {"private_key": {"write_only": True}} diff --git a/authentik/stages/captcha/migrations/0002_captchastage_api_url_captchastage_js_url_and_more.py b/authentik/stages/captcha/migrations/0002_captchastage_api_url_captchastage_js_url_and_more.py new file mode 100644 index 000000000..97d3d15c1 --- /dev/null +++ b/authentik/stages/captcha/migrations/0002_captchastage_api_url_captchastage_js_url_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.2 on 2022-10-20 19:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_stages_captcha", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="captchastage", + name="api_url", + field=models.TextField(default="https://www.recaptcha.net/recaptcha/api/siteverify"), + ), + migrations.AddField( + model_name="captchastage", + name="js_url", + field=models.TextField(default="https://www.recaptcha.net/recaptcha/api.js"), + ), + migrations.AlterField( + model_name="captchastage", + name="private_key", + field=models.TextField(help_text="Private key, acquired your captcha Provider."), + ), + migrations.AlterField( + model_name="captchastage", + name="public_key", + field=models.TextField(help_text="Public key, acquired your captcha Provider."), + ), + ] diff --git a/authentik/stages/captcha/models.py b/authentik/stages/captcha/models.py index 434f1f6a8..9703b5f07 100644 --- a/authentik/stages/captcha/models.py +++ b/authentik/stages/captcha/models.py @@ -11,12 +11,11 @@ from authentik.flows.models import Stage class CaptchaStage(Stage): """Verify the user is human using Google's reCaptcha.""" - public_key = models.TextField( - help_text=_("Public key, acquired from https://www.google.com/recaptcha/intro/v3.html") - ) - private_key = models.TextField( - help_text=_("Private key, acquired from https://www.google.com/recaptcha/intro/v3.html") - ) + public_key = models.TextField(help_text=_("Public key, acquired your captcha Provider.")) + private_key = models.TextField(help_text=_("Private key, acquired your captcha Provider.")) + + js_url = models.TextField(default="https://www.recaptcha.net/recaptcha/api.js") + api_url = models.TextField(default="https://www.recaptcha.net/recaptcha/api/siteverify") @property def serializer(self) -> type[BaseSerializer]: diff --git a/authentik/stages/captcha/stage.py b/authentik/stages/captcha/stage.py index 514955d81..4ea0738d6 100644 --- a/authentik/stages/captcha/stage.py +++ b/authentik/stages/captcha/stage.py @@ -20,6 +20,7 @@ class CaptchaChallenge(WithUserInfoChallenge): """Site public key""" site_key = CharField() + js_url = CharField(read_only=True) component = CharField(default="ak-stage-captcha") @@ -34,7 +35,7 @@ class CaptchaChallengeResponse(ChallengeResponse): stage: CaptchaStage = self.stage.executor.current_stage try: response = get_http_session().post( - "https://www.google.com/recaptcha/api/siteverify", + stage.api_url, headers={ "Content-type": "application/x-www-form-urlencoded", }, @@ -61,6 +62,7 @@ class CaptchaStageView(ChallengeStageView): def get_challenge(self, *args, **kwargs) -> Challenge: return CaptchaChallenge( data={ + "js_url": self.executor.current_stage.js_url, "type": ChallengeTypes.NATIVE.value, "site_key": self.executor.current_stage.public_key, } diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 9fd113f2b..0cbff2795 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-12 16:22+0000\n" +"POT-Creation-Date: 2022-12-15 15:01+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1555,21 +1555,19 @@ msgstr "" msgid "WebAuthn Devices" msgstr "" +#: authentik/stages/captcha/models.py:14 +msgid "Public key, acquired your captcha Provider." +msgstr "" + #: authentik/stages/captcha/models.py:15 -msgid "" -"Public key, acquired from https://www.google.com/recaptcha/intro/v3.html" +msgid "Private key, acquired your captcha Provider." msgstr "" -#: authentik/stages/captcha/models.py:18 -msgid "" -"Private key, acquired from https://www.google.com/recaptcha/intro/v3.html" -msgstr "" - -#: authentik/stages/captcha/models.py:39 +#: authentik/stages/captcha/models.py:38 msgid "Captcha Stage" msgstr "" -#: authentik/stages/captcha/models.py:40 +#: authentik/stages/captcha/models.py:39 msgid "Captcha Stages" msgstr "" @@ -1897,7 +1895,7 @@ msgstr "" msgid "No Pending user to login." msgstr "" -#: authentik/stages/user_login/stage.py:55 +#: authentik/stages/user_login/stage.py:58 msgid "Successfully logged in!" msgstr "" diff --git a/schema.yml b/schema.yml index 5c03d64cd..467a281fe 100644 --- a/schema.yml +++ b/schema.yml @@ -26175,7 +26175,11 @@ components: type: string site_key: type: string + js_url: + type: string + readOnly: true required: + - js_url - pending_user - pending_user_avatar - site_key @@ -26222,7 +26226,11 @@ components: $ref: '#/components/schemas/FlowSet' public_key: type: string - description: Public key, acquired from https://www.google.com/recaptcha/intro/v3.html + description: Public key, acquired your captcha Provider. + js_url: + type: string + api_url: + type: string required: - component - meta_model_name @@ -26245,12 +26253,18 @@ components: public_key: type: string minLength: 1 - description: Public key, acquired from https://www.google.com/recaptcha/intro/v3.html + description: Public key, acquired your captcha Provider. private_key: type: string writeOnly: true minLength: 1 - description: Private key, acquired from https://www.google.com/recaptcha/intro/v3.html + description: Private key, acquired your captcha Provider. + js_url: + type: string + minLength: 1 + api_url: + type: string + minLength: 1 required: - name - private_key @@ -33384,12 +33398,18 @@ components: public_key: type: string minLength: 1 - description: Public key, acquired from https://www.google.com/recaptcha/intro/v3.html + description: Public key, acquired your captcha Provider. private_key: type: string writeOnly: true minLength: 1 - description: Private key, acquired from https://www.google.com/recaptcha/intro/v3.html + description: Private key, acquired your captcha Provider. + js_url: + type: string + minLength: 1 + api_url: + type: string + minLength: 1 PatchedCertificateKeyPairRequest: type: object description: CertificateKeyPair Serializer diff --git a/web/src/admin/stages/captcha/CaptchaStageForm.ts b/web/src/admin/stages/captcha/CaptchaStageForm.ts index f161897d0..d9625aa21 100644 --- a/web/src/admin/stages/captcha/CaptchaStageForm.ts +++ b/web/src/admin/stages/captcha/CaptchaStageForm.ts @@ -43,7 +43,7 @@ export class CaptchaStageForm extends ModelForm { renderForm(): TemplateResult { return html`
- ${t`This stage checks the user's current session against the Google reCaptcha service.`} + ${t`This stage checks the user's current session against the Google reCaptcha (or compatible) service.`}
{ + + ${t`Advanced settings`} +
+ + +

+ ${t`URL to fetch JavaScript from, defaults to recaptcha. Can be replaced with any compatible alternative.`} +

+
+ + +

+ ${t`URL used to validate captcha response, defaults to recaptcha. Can be replaced with any compatible alternative.`} +

+
+
+
`; } } diff --git a/web/src/flow/stages/captcha/CaptchaStage.ts b/web/src/flow/stages/captcha/CaptchaStage.ts index 21637f08c..f4748b1f9 100644 --- a/web/src/flow/stages/captcha/CaptchaStage.ts +++ b/web/src/flow/stages/captcha/CaptchaStage.ts @@ -28,7 +28,7 @@ export class CaptchaStage extends BaseStage