Skip to content

Commit 110332a

Browse files
committed
Add reCaptcha v3 handler
Migrate JavaScript to TypeScript
1 parent c07328a commit 110332a

File tree

8 files changed

+364
-205
lines changed

8 files changed

+364
-205
lines changed
Lines changed: 40 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,49 @@
11
{if $recaptchaLegacyMode|empty}
22
{include file='shared_captcha'}
33
{else}
4-
{if RECAPTCHA_PUBLICKEY && RECAPTCHA_PRIVATEKEY}
5-
{if $supportsAsyncCaptcha|isset && $supportsAsyncCaptcha && RECAPTCHA_PUBLICKEY_INVISIBLE && RECAPTCHA_PRIVATEKEY_INVISIBLE}
6-
{assign var="recaptchaBucketID" value=true|microtime|sha1}
7-
<dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
8-
<dt><label>{lang}wcf.recaptcha.title{/lang}</label></dt>
9-
<dd>
10-
<input type="hidden" name="recaptcha-type" value="invisible">
11-
<div id="recaptchaBucket{$recaptchaBucketID}"></div>
12-
<noscript>
13-
<div style="width: 302px; height: 473px;">
14-
<div style="width: 302px; height: 422px; position: relative;">
15-
<div style="width: 302px; height: 422px; position: relative;">
16-
<iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
17-
</div>
18-
<div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
19-
<textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
20-
</div>
21-
</div>
22-
</div>
23-
</noscript>
24-
{if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
25-
{if $errorType|is_array && $errorType[recaptchaString]|isset}
26-
{assign var='__errorType' value=$errorType[recaptchaString]}
27-
{else}
28-
{assign var='__errorType' value=$errorType}
29-
{/if}
30-
<small class="innerError">
31-
{if $__errorType == 'empty'}
32-
{lang}wcf.global.form.error.empty{/lang}
33-
{else}
34-
{lang}wcf.captcha.recaptchaInvisible.error.recaptchaString.{$__errorType}{/lang}
35-
{/if}
36-
</small>
37-
{/if}
38-
</dd>
39-
</dl>
40-
<script data-relocate="true">
41-
if (!WCF.recaptcha) {
42-
WCF.recaptcha = {
43-
queue: [],
44-
callbackCalled: false,
45-
mapping: { }
46-
};
47-
48-
// this needs to be in global scope
49-
function recaptchaCallback() {
50-
var bucketId;
51-
WCF.recaptcha.callbackCalled = true;
52-
53-
// clear queue
54-
while (config = WCF.recaptcha.queue.shift()) {
55-
(function (config) {
56-
var bucketId = config.bucket;
57-
58-
require(['Dom/Traverse', 'Dom/Util'], function (DomTraverse, DomUtil) {
59-
var bucket = elById(bucketId);
60-
61-
var promise = new Promise(function (resolve, reject) {
62-
WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}'] = grecaptcha.render(bucket, {
63-
sitekey: '{RECAPTCHA_PUBLICKEY_INVISIBLE|encodeJS}',
64-
size: 'invisible',
65-
badge: 'inline',
66-
callback: resolve,
67-
theme: document.documentElement.dataset.colorScheme === "dark" ? "dark" : "light"
68-
});
69-
});
70-
71-
if (config.ajaxCaptcha) {
72-
WCF.System.Captcha.addCallback(config.ajaxCaptcha, function() {
73-
grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
74-
return promise.then(function (token) {
75-
return {
76-
'g-recaptcha-response': token,
77-
'recaptcha-type': 'invisible'
78-
};
79-
});
80-
});
81-
}
82-
else {
83-
var form = DomTraverse.parentByTag(bucket, 'FORM');
84-
85-
var pressed = undefined;
86-
elBySelAll('input[type=submit]', form, function (button) {
87-
button.addEventListener('click', function (event) {
88-
pressed = button;
89-
});
90-
});
91-
92-
var listener = function (event) {
93-
event.preventDefault();
94-
promise.then(function (token) {
95-
form.removeEventListener('submit', listener);
96-
pressed.disabled = false;
97-
pressed.click();
98-
});
99-
grecaptcha.execute(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']);
100-
}
101-
form.addEventListener('submit', listener);
102-
}
103-
104-
});
105-
})(config);
106-
}
107-
}
108-
}
109-
110-
// add captcha to queue
111-
WCF.recaptcha.queue.push({
112-
bucket: 'recaptchaBucket{$recaptchaBucketID}'
113-
{if $ajaxCaptcha|isset && $ajaxCaptcha}
114-
, ajaxCaptcha: '{$captchaID}'
115-
{/if}
116-
});
117-
118-
// trigger callback immediately, if API already is available
119-
if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
120-
121-
// ensure recaptcha API is loaded at most once
122-
if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
123-
</script>
4+
{if RECAPTCHA_PUBLICKEY_V3 && RECAPTCHA_PRIVATEKEY_V3}
5+
{assign var="recaptchaType" value="v3"}
6+
{assign var="recaptchaPublicKey" value=RECAPTCHA_PUBLICKEY_V3}
7+
{elseif RECAPTCHA_PUBLICKEY && RECAPTCHA_PRIVATEKEY}
8+
{if RECAPTCHA_PUBLICKEY_INVISIBLE && RECAPTCHA_PRIVATEKEY_INVISIBLE}
9+
{assign var="recaptchaType" value="invisible"}
10+
{assign var="recaptchaPublicKey" value=RECAPTCHA_PUBLICKEY_INVISIBLE}
12411
{else}
125-
{assign var="recaptchaBucketID" value=true|microtime|sha1}
126-
<dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
127-
<dt><label>{lang}wcf.recaptcha.title{/lang}</label></dt>
128-
<dd>
129-
<input type="hidden" name="recaptcha-type" value="v2">
130-
<div id="recaptchaBucket{$recaptchaBucketID}"></div>
131-
<noscript>
132-
<div style="width: 302px; height: 473px;">
133-
<div style="width: 302px; height: 422px; position: relative;">
134-
<div style="width: 302px; height: 422px; position: relative;">
135-
<iframe src="https://www.google.com/recaptcha/api/fallback?k={RECAPTCHA_PUBLICKEY|encodeJS}" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
136-
</div>
137-
<div style="width: 300px; height: 60px; position: relative; border-style: none; bottom: 12px; left: 0; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
138-
<textarea name="g-recaptcha-response" class="g-recaptcha-response" style="width: 290px; height: 50px; border: 1px solid #c1c1c1; margin: 5px; padding: 0px; resize: none;"></textarea>
139-
</div>
140-
</div>
141-
</div>
142-
</noscript>
143-
{if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
144-
{if $errorType|is_array && $errorType[recaptchaString]|isset}
145-
{assign var='__errorType' value=$errorType[recaptchaString]}
12+
{assign var="recaptchaType" value="v2"}
13+
{assign var="recaptchaPublicKey" value=RECAPTCHA_PUBLICKEY}
14+
{/if}
15+
{/if}
16+
{if !$ajaxCaptcha|isset}
17+
{assign var="ajaxCaptcha" value=false}
18+
{/if}
19+
20+
{if $recaptchaType|isset && $recaptchaPublicKey|isset}
21+
{assign var="recaptchaBucketID" value=true|microtime|sha1}
22+
<dl class="{if $errorField|isset && $errorField == 'recaptchaString'}formError{/if}">
23+
<dt>{if $recaptchaType !== "v3"}<label>{lang}wcf.recaptcha.title{/lang}</label>{/if}</dt>
24+
<dd>
25+
<input type="hidden" name="recaptcha-type" value="{$recaptchaType}">
26+
<div id="recaptchaBucket{$recaptchaBucketID}"></div>
27+
{if (($errorType|isset && $errorType|is_array && $errorType[recaptchaString]|isset) || ($errorField|isset && $errorField == 'recaptchaString'))}
28+
{if $errorType|is_array && $errorType[recaptchaString]|isset}
29+
{assign var='__errorType' value=$errorType[recaptchaString]}
30+
{else}
31+
{assign var='__errorType' value=$errorType}
32+
{/if}
33+
<small class="innerError">
34+
{if $__errorType == 'empty'}
35+
{lang}wcf.global.form.error.empty{/lang}
14636
{else}
147-
{assign var='__errorType' value=$errorType}
37+
{lang}wcf.captcha.recaptcha{$recaptchaType|ucfirst}.error.recaptchaString.{$__errorType}{/lang}
14838
{/if}
149-
<small class="innerError">
150-
{if $__errorType == 'empty'}
151-
{lang}wcf.global.form.error.empty{/lang}
152-
{else}
153-
{lang}wcf.captcha.recaptchaV2.error.recaptchaString.{$__errorType}{/lang}
154-
{/if}
155-
</small>
156-
{/if}
157-
</dd>
158-
</dl>
159-
<script data-relocate="true">
160-
if (!WCF.recaptcha) {
161-
WCF.recaptcha = {
162-
queue: [],
163-
callbackCalled: false,
164-
mapping: { }
165-
};
166-
167-
// this needs to be in global scope
168-
function recaptchaCallback() {
169-
var bucket;
170-
WCF.recaptcha.callbackCalled = true;
171-
172-
// clear queue
173-
while (bucket = WCF.recaptcha.queue.shift()) {
174-
WCF.recaptcha.mapping[bucket] = grecaptcha.render(bucket, {
175-
'sitekey' : '{RECAPTCHA_PUBLICKEY|encodeJS}',
176-
theme: document.documentElement.dataset.colorScheme === "dark" ? "dark" : "light"
177-
});
178-
}
179-
}
180-
}
181-
182-
// add captcha to queue
183-
WCF.recaptcha.queue.push('recaptchaBucket{$recaptchaBucketID}');
184-
185-
// trigger callback immediately, if API already is available
186-
if (WCF.recaptcha.callbackCalled) setTimeout(recaptchaCallback, 1);
187-
188-
{if $ajaxCaptcha|isset && $ajaxCaptcha}
189-
WCF.System.Captcha.addCallback('{$captchaID}', function() {
190-
return {
191-
'g-recaptcha-response': grecaptcha.getResponse(WCF.recaptcha.mapping['recaptchaBucket{$recaptchaBucketID}']),
192-
'type': 'v2'
193-
};
39+
</small>
40+
{/if}
41+
</dd>
42+
</dl>
43+
<script data-relocate="true">
44+
require(['WoltLabSuite/Core/Component/Captcha/Recaptcha'], ({ Recaptcha }) => {
45+
new Recaptcha('{$recaptchaType}', '{$recaptchaPublicKey|encodeJS}', 'recaptchaBucket{$recaptchaBucketID}'{if $ajaxCaptcha}, '{$captchaID|encodeJS}'{/if});
19446
});
195-
{/if}
196-
197-
// ensure recaptcha API is loaded at most once
198-
if (!window.grecaptcha) $.getScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaCallback');
199-
</script>
200-
{/if}
47+
</script>
20148
{/if}
20249
{/if}

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@googlemaps/markerclusterer": "2.5.3",
2020
"@types/facebook-js-sdk": "^3.3.12",
2121
"@types/google.maps": "^3.58.1",
22+
"@types/grecaptcha": "^3.0.9",
2223
"@types/jquery": "^3.5.32",
2324
"@types/pica": "5.1.3",
2425
"@types/prismjs": "^1.26.5",

0 commit comments

Comments
 (0)