diff --git a/example/combined/api/recaptcha.js b/example/combined/api/recaptcha.js new file mode 100644 index 0000000..02a5576 --- /dev/null +++ b/example/combined/api/recaptcha.js @@ -0,0 +1,60 @@ +import { useBody } from 'h3' +import { $fetch } from 'ohmyfetch/node' + +/** + * It is highly recommended to use enviroment variables instead of hardcoded secrets. + */ +const SECRET_KEYS = { + '2': '6LecsLIaAAAAABd-yNkMXt_rf7GjWaxVJDlWryYy', // v2 secret key + '3': '6LfembIaAAAAACcZlTsRvwf62fuCGXfR7e2HIj8S' // v3 secret key +} + +/** + * This is an example that demonstrates how verifying reCAPTCHA on the server side works. + * Do not use this middleware in your production. + */ +export default async (req, res) => { + res.setHeader('Content-Type', 'application/json') + try { + const { token, v } = await useBody(req) + + if (!SECRET_KEYS[v]) { + res.end(JSON.stringify({ + success: false, + message: 'Invalid version' + })) + return + } + + if (!token) { + res.end(JSON.stringify({ + success: false, + message: 'Invalid token' + })) + return + } + const response = await $fetch( + `https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEYS[v]}&response=${token}` + ) + + if (response.success) { + res.end(JSON.stringify({ + success: true, + message: 'Token verifyed', + response: response + })) + } else { + res.end(JSON.stringify({ + success: false, + message: 'Invalid token', + response: response + })) + } + } catch (e) { + console.log('ReCaptcha error:', e) + res.end(JSON.stringify({ + success: false, + message: 'Internal error' + })) + } +} diff --git a/example/combined/nuxt.config.js b/example/combined/nuxt.config.js new file mode 100644 index 0000000..928f863 --- /dev/null +++ b/example/combined/nuxt.config.js @@ -0,0 +1,24 @@ +const { resolve } = require('path') + +module.exports = { + buildDir: resolve(__dirname, '.nuxt'), + + modules: [ + ['../../lib/module', { + hideBadge: true, + siteKey: [ + '6LecsLIaAAAAAEeBOiX7b4rSwMDNL9zhIXlPNEB1', // v2 site key + '6LfembIaAAAAACPEdfjUpSmmYqMyJZn-ZU0aFUvb' // v3 site key + ] + }] + ], + + serverMiddleware: [ + { path: '/api/check-token', handler: '~/api/recaptcha' } + ], + + srcDir: __dirname, + + render: { resourceHints: false }, + rootDir: resolve(__dirname, '..') +} diff --git a/example/combined/pages/about.vue b/example/combined/pages/about.vue new file mode 100644 index 0000000..044392d --- /dev/null +++ b/example/combined/pages/about.vue @@ -0,0 +1,8 @@ + diff --git a/example/combined/pages/index.vue b/example/combined/pages/index.vue new file mode 100644 index 0000000..ac0d5a7 --- /dev/null +++ b/example/combined/pages/index.vue @@ -0,0 +1,119 @@ + + + diff --git a/lib/plugin.js b/lib/plugin.js index 8eba35d..57247fd 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -9,7 +9,9 @@ class ReCaptcha { throw new Error('ReCaptcha error: No key provided') } - if (!version) { + if (!version && !Array.isArray(siteKey)) { + throw new Error('ReCaptcha error: siteKey must be array when version not provided') + } else if (!version && typeof(siteKey) == 'string') { throw new Error('ReCaptcha error: No version provided') } @@ -26,6 +28,18 @@ class ReCaptcha { this.size = size } + get siteKeyV2() { + if (this.version === 2) return this.siteKey; + else if (Array.isArray(this.siteKey)) return this.siteKey[0]; + else return null; + } + + get siteKeyV3() { + if (this.version === 3) return this.siteKey; + else if (Array.isArray(this.siteKey)) return this.siteKey[1]; + else return null; + } + destroy () { if (this._ready) { this._ready = false @@ -43,11 +57,13 @@ class ReCaptcha { if (head.contains(style)) { head.removeChild(style) } - + const badge = document.querySelector('.grecaptcha-badge') if (badge) { badge.remove() } + + delete window.grecaptcha; } } @@ -57,7 +73,7 @@ class ReCaptcha { if ('grecaptcha' in window) { return window.grecaptcha.execute( - this.siteKey, + this.siteKeyV3, { action } ) } @@ -117,7 +133,7 @@ class ReCaptcha { script.setAttribute('defer', '') const params = [] - if (this.version === 3) { params.push('render=' + this.siteKey) } + if (this.siteKeyV3) { params.push('render=' + this.siteKeyV3) } if (this.language) { params.push('hl=' + this.language) } script.setAttribute('src', API_URL + '?' + params.join('&')) @@ -127,12 +143,7 @@ class ReCaptcha { this._ready = new Promise((resolve, reject) => { script.addEventListener('load', () => { - if (this.version === 3 && this.hideBadge) { - style.innerHTML = '.grecaptcha-badge { display: none }' - document.head.appendChild(style) - } else if(this.version === 2 && this.hideBadge) { - // display: none DISABLES the spam checking! - // ref: https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge + if (this.hideBadge) { style.innerHTML = '.grecaptcha-badge { visibility: hidden; }' document.head.appendChild(style) } @@ -156,14 +167,27 @@ class ReCaptcha { return this._eventBus.on(event, callback) } + off (event, callback) { + return this._eventBus.off(event, callback) + } + reset (widgetId) { - if (this.version === 2 || typeof widgetId !== 'undefined') { + if (this.siteKeyV2 || typeof widgetId !== 'undefined') { window.grecaptcha.reset(widgetId) } } - render (reference, { sitekey, theme }) { - return window.grecaptcha.render(reference.$el || reference, { sitekey, theme }) + render (reference, options) { + return window.grecaptcha.render(reference.$el || reference, Object.assign({ + "sitekey": this.siteKeyV2, + "size": this.size + }, + options, + { + "callback": "recaptchaSuccessCallback", + "expired-callback": "recaptchaExpiredCallback", + "error-callback": "recaptchaErrorCallback", + })); } } diff --git a/lib/recaptcha.vue b/lib/recaptcha.vue index fc0f7f3..754c3ba 100644 --- a/lib/recaptcha.vue +++ b/lib/recaptcha.vue @@ -1,16 +1,5 @@