├── test └── module.test.js ├── renovate.json ├── .eslintignore ├── commitlint.config.js ├── .gitignore ├── husky.config.js ├── .eslintrc.js ├── babel.config.js ├── example ├── base │ ├── v2 │ │ ├── pages │ │ │ ├── about.vue │ │ │ └── index.vue │ │ ├── nuxt.config.js │ │ └── api │ │ │ └── recaptcha.js │ ├── both │ │ ├── nuxt.config.js │ │ └── pages │ │ │ └── index.vue │ └── v3 │ │ ├── nuxt.config.js │ │ └── pages │ │ └── index.vue └── enterprise │ ├── v2 │ ├── pages │ │ ├── about.vue │ │ └── index.vue │ ├── nuxt.config.js │ └── api │ │ └── recaptcha.js │ ├── v3 │ ├── nuxt.config.js │ └── pages │ │ └── index.vue │ └── both │ ├── nuxt.config.js │ └── pages │ └── index.vue ├── .editorconfig ├── jest.config.js ├── lib ├── module.js ├── recaptcha.vue └── plugin.js ├── .circleci └── config.yml ├── LICENSE ├── types └── index.d.ts ├── package.json ├── README.md └── CHANGELOG.md /test/module.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Common 2 | node_modules 3 | dist 4 | .nuxt 5 | coverage 6 | 7 | # Plugin 8 | lib/plugin.js 9 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', 4 | 'pre-commit': 'yarn lint', 5 | 'pre-push': 'yarn lint' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | extends: [ 8 | '@nuxtjs' 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', { 5 | targets: { 6 | esmodules: true 7 | } 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /example/base/v2/pages/about.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /example/enterprise/v2/pages/about.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | collectCoverage: true, 4 | collectCoverageFrom: [ 5 | 'lib/**/*.js', 6 | '!lib/plugin.js' 7 | ], 8 | moduleNameMapper: { 9 | '^~/(.*)$': '/lib/$1', 10 | '^~~$': '', 11 | '^@@$': '', 12 | '^@/(.*)$': '/lib/$1' 13 | }, 14 | transform: { 15 | '^.+\\.js$': 'babel-jest' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/base/both/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | hideBadge: true, 9 | siteKey: '6LeE3ZAUAAAAANVaDO60w4ZBK44khqO7OpsitZNY', 10 | 11 | version: 3 12 | }] 13 | ], 14 | 15 | srcDir: __dirname, 16 | 17 | render: { resourceHints: false }, 18 | rootDir: resolve(__dirname, '..') 19 | } 20 | -------------------------------------------------------------------------------- /example/base/v3/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | hideBadge: true, 9 | siteKey: '6LeE3ZAUAAAAANVaDO60w4ZBK44khqO7OpsitZNY', 10 | 11 | version: 3 12 | }] 13 | ], 14 | 15 | srcDir: __dirname, 16 | 17 | render: { resourceHints: false }, 18 | rootDir: resolve(__dirname, '..') 19 | } 20 | -------------------------------------------------------------------------------- /example/enterprise/v3/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | mode: 'enterprise', 9 | 10 | hideBadge: true, 11 | siteKey: process.env.RECAPTCHA_SITE_KEY, 12 | 13 | version: 3 14 | }] 15 | ], 16 | 17 | srcDir: __dirname, 18 | 19 | render: { resourceHints: false }, 20 | rootDir: resolve(__dirname, '..') 21 | } 22 | -------------------------------------------------------------------------------- /example/enterprise/both/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | mode: 'enterprise', 9 | 10 | hideBadge: true, 11 | siteKey: process.env.RECAPTCHA_SITE_KEY, 12 | 13 | version: 3 14 | }] 15 | ], 16 | 17 | srcDir: __dirname, 18 | 19 | render: { resourceHints: false }, 20 | rootDir: resolve(__dirname, '..') 21 | } 22 | -------------------------------------------------------------------------------- /lib/module.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { resolve } = require('path') 4 | 5 | module.exports = function (moduleOptions) { 6 | const options = { 7 | ...moduleOptions, 8 | ...this.options.recaptcha 9 | } 10 | 11 | this.addPlugin({ 12 | fileName: 'recaptcha.js', 13 | options, 14 | 15 | src: resolve(__dirname, 'plugin.js') 16 | }) 17 | 18 | this.addTemplate({ 19 | fileName: 'recaptcha.vue', 20 | src: resolve(__dirname, 'recaptcha.vue') 21 | }) 22 | } 23 | 24 | module.exports.meta = require('../package.json') 25 | -------------------------------------------------------------------------------- /example/base/v2/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | siteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 9 | size: 'invisible', 10 | hideBadge: false, 11 | version: 2 12 | }] 13 | ], 14 | 15 | serverMiddleware: [ 16 | { path: '/api/check-token', handler: '~/api/recaptcha' } 17 | ], 18 | 19 | srcDir: __dirname, 20 | 21 | render: { resourceHints: false }, 22 | rootDir: resolve(__dirname, '..') 23 | } 24 | -------------------------------------------------------------------------------- /example/enterprise/v2/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = { 4 | buildDir: resolve(__dirname, '.nuxt'), 5 | 6 | modules: [ 7 | ['../../../lib/module', { 8 | mode: 'enterprise', 9 | siteKey: process.env.RECAPTCHA_SITE_KEY, 10 | size: 'invisible', 11 | hideBadge: false, 12 | version: 2 13 | }] 14 | ], 15 | 16 | serverMiddleware: [ 17 | { path: '/api/check-token', handler: '~/api/recaptcha' } 18 | ], 19 | 20 | srcDir: __dirname, 21 | 22 | render: { resourceHints: false }, 23 | rootDir: resolve(__dirname, '..') 24 | } 25 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node 6 | steps: 7 | # Checkout repository 8 | - checkout 9 | 10 | # Restore cache 11 | - restore_cache: 12 | key: yarn-cache-{{ checksum "yarn.lock" }} 13 | 14 | # Install dependencies 15 | - run: 16 | name: Install Dependencies 17 | command: NODE_ENV=dev yarn 18 | 19 | # Keep cache 20 | - save_cache: 21 | key: yarn-cache-{{ checksum "yarn.lock" }} 22 | paths: 23 | - "node_modules" 24 | 25 | # Lint 26 | - run: 27 | name: Lint 28 | command: yarn lint 29 | 30 | # Test 31 | # - run: 32 | # name: Tests 33 | # command: yarn jest 34 | 35 | # Coverage 36 | - run: 37 | name: Coverage 38 | command: yarn codecov 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) mvrlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/base/v3/pages/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | -------------------------------------------------------------------------------- /example/enterprise/v3/pages/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | -------------------------------------------------------------------------------- /example/base/v2/api/recaptcha.js: -------------------------------------------------------------------------------- 1 | import { useBody } from 'h3' 2 | import { $fetch } from 'ohmyfetch/node' 3 | 4 | /** 5 | * It is highly recommended to use enviroment variables instead of hardcoded secrets. 6 | */ 7 | const SECRET_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' 8 | 9 | /** 10 | * This is an example that demonstrates how verifying reCAPTCHA on the server side works. 11 | * Do not use this middleware in your production. 12 | */ 13 | export default async (req, res) => { 14 | res.setHeader('Content-Type', 'application/json') 15 | try { 16 | const { token } = await useBody(req) 17 | 18 | if (!token) { 19 | res.end(JSON.stringify({ 20 | success: false, 21 | message: 'Invalid token' 22 | })) 23 | return 24 | } 25 | const response = await $fetch( 26 | `https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}` 27 | ) 28 | 29 | if (response.success) { 30 | res.end(JSON.stringify({ 31 | success: true, 32 | message: 'Token verifyed', 33 | response: response 34 | })) 35 | } else { 36 | res.end(JSON.stringify({ 37 | success: false, 38 | message: 'Invalid token', 39 | response: response 40 | })) 41 | } 42 | } catch (e) { 43 | console.log('ReCaptcha error:', e) 44 | res.end(JSON.stringify({ 45 | success: false, 46 | message: 'Internal error' 47 | })) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/enterprise/v2/api/recaptcha.js: -------------------------------------------------------------------------------- 1 | import { useBody } from 'h3' 2 | import { $fetch } from 'ohmyfetch/node' 3 | 4 | /** 5 | * It is highly recommended to use enviroment variables instead of hardcoded secrets. 6 | */ 7 | const SECRET_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' 8 | 9 | /** 10 | * This is an example that demonstrates how verifying reCAPTCHA on the server side works. 11 | * Do not use this middleware in your production. 12 | */ 13 | export default async (req, res) => { 14 | res.setHeader('Content-Type', 'application/json') 15 | try { 16 | const { token } = await useBody(req) 17 | 18 | if (!token) { 19 | res.end(JSON.stringify({ 20 | success: false, 21 | message: 'Invalid token' 22 | })) 23 | return 24 | } 25 | const response = await $fetch( 26 | `https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}` 27 | ) 28 | 29 | if (response.success) { 30 | res.end(JSON.stringify({ 31 | success: true, 32 | message: 'Token verifyed', 33 | response: response 34 | })) 35 | } else { 36 | res.end(JSON.stringify({ 37 | success: false, 38 | message: 'Invalid token', 39 | response: response 40 | })) 41 | } 42 | } catch (e) { 43 | console.log('ReCaptcha error:', e) 44 | res.end(JSON.stringify({ 45 | success: false, 46 | message: 'Internal error' 47 | })) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export interface ReCaptchaOptions { 4 | /** 5 | * Toggles badge element visibility (v3) 6 | */ 7 | hideBadge?: boolean 8 | 9 | /** 10 | * ReCaptcha language (v2) 11 | */ 12 | language?: string 13 | 14 | /** 15 | * ReCaptcha mode. 16 | */ 17 | mode?: 'base' | 'enterprise' 18 | 19 | /** 20 | * Site key to send requests 21 | */ 22 | siteKey: string 23 | 24 | /** 25 | * Size of the widget (v2) 26 | */ 27 | size?: 'compact' | 'normal' | 'invisible' 28 | 29 | /** 30 | * Version 31 | */ 32 | version: number 33 | } 34 | 35 | export interface ReCaptchaInstance { 36 | /** 37 | * Options 38 | */ 39 | options: ReCaptchaOptions 40 | 41 | /** 42 | * Destroy ReCaptcha 43 | */ 44 | destroy(): void 45 | 46 | /** 47 | * Returns a verify token (v3) 48 | * @param action 49 | */ 50 | execute(action: string): Promise 51 | 52 | /** 53 | * Returns a verify token (v2) 54 | */ 55 | getResponse(): Promise 56 | 57 | /** 58 | * Initialize ReCaptcha 59 | */ 60 | init(): Promise 61 | 62 | /** 63 | * Reset ReCaptcha (v2) 64 | */ 65 | reset(widgetId?: number): void 66 | 67 | /** 68 | * Render ReCaptcha (v2) 69 | */ 70 | render(reference: string, { siteKey, theme } : { siteKey: string, theme?: string }): number 71 | } 72 | 73 | declare module 'vue/types/vue' { 74 | interface Vue { 75 | $recaptcha: ReCaptchaInstance 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /example/base/both/pages/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /example/enterprise/both/pages/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /example/base/v2/pages/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 75 | -------------------------------------------------------------------------------- /example/enterprise/v2/pages/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 75 | -------------------------------------------------------------------------------- /lib/recaptcha.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/recaptcha", 3 | "version": "1.1.2", 4 | "description": "Simple and easy Google reCAPTCHA integration with Nuxt.js", 5 | "keywords": [ 6 | "nuxtjs", 7 | "nuxt", 8 | "recaptcha", 9 | "captcha", 10 | "grecaptcha" 11 | ], 12 | "license": "MIT", 13 | "contributors": [ 14 | { 15 | "name": "mvrlin " 16 | } 17 | ], 18 | "main": "lib/module.js", 19 | "types": "types/index.d.ts", 20 | "bugs": "https://github.com/nuxt-community/recaptcha-module/issues", 21 | "repository": "https://github.com/nuxt-community/recaptcha-module", 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "scripts": { 26 | "dev": "nuxt example/base/both", 27 | "dev:v3": "nuxt example/base/v3", 28 | "dev:v2": "nuxt example/base/v2", 29 | "dev:enterprise": "nuxt example/enterprise/both", 30 | "dev:enterprise:v3": "nuxt example/enterprise/v3", 31 | "dev:enterprise:v2": "nuxt example/enterprise/v2", 32 | "lint": "eslint lib test", 33 | "test": "yarn lint && jest", 34 | "release": "standard-version && git push --follow-tags && npm publish" 35 | }, 36 | "files": [ 37 | "lib", 38 | "types/*.d.ts" 39 | ], 40 | "dependencies": {}, 41 | "devDependencies": { 42 | "@babel/core": "latest", 43 | "@babel/preset-env": "latest", 44 | "@commitlint/cli": "latest", 45 | "@commitlint/config-conventional": "latest", 46 | "h3": "latest", 47 | "@nuxtjs/eslint-config": "latest", 48 | "babel-eslint": "latest", 49 | "babel-jest": "latest", 50 | "codecov": "latest", 51 | "eslint": "latest", 52 | "eslint-config-standard": "latest", 53 | "eslint-plugin-import": "latest", 54 | "eslint-plugin-jest": "latest", 55 | "eslint-plugin-node": "latest", 56 | "eslint-plugin-promise": "latest", 57 | "eslint-plugin-standard": "latest", 58 | "eslint-plugin-vue": "latest", 59 | "husky": "latest", 60 | "jest": "latest", 61 | "nuxt-edge": "latest", 62 | "ohmyfetch": "latest", 63 | "request": "latest", 64 | "request-promise-native": "latest", 65 | "standard-version": "latest" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import Vue from 'vue' 3 | 4 | const API_URL = 'https://www.recaptcha.net/recaptcha/api.js' 5 | 6 | class ReCaptcha { 7 | constructor ({ hideBadge, language, mode, siteKey, version, size }) { 8 | if (!siteKey) { 9 | throw new Error('ReCaptcha error: No key provided') 10 | } 11 | 12 | if (!version) { 13 | throw new Error('ReCaptcha error: No version provided') 14 | } 15 | 16 | this._elements = {} 17 | this._grecaptcha = null 18 | 19 | this._eventBus = null 20 | this._ready = false 21 | 22 | this.hideBadge = hideBadge 23 | this.language = language 24 | 25 | this.siteKey = siteKey 26 | this.version = version 27 | this.size = size 28 | 29 | this.mode = mode 30 | } 31 | 32 | destroy () { 33 | if (this._ready) { 34 | this._ready = false 35 | 36 | const { head } = document 37 | const { style } = this._elements 38 | 39 | const scripts = [...document.head.querySelectorAll('script')] 40 | .filter(script => script.src.includes('recaptcha')) 41 | 42 | if (scripts.length) { 43 | scripts.forEach(script => head.removeChild(script)) 44 | } 45 | 46 | if (head.contains(style)) { 47 | head.removeChild(style) 48 | } 49 | 50 | const badge = document.querySelector('.grecaptcha-badge') 51 | if (badge) { 52 | badge.remove() 53 | } 54 | } 55 | } 56 | 57 | async execute (action) { 58 | try { 59 | await this.init() 60 | 61 | if ('grecaptcha' in window) { 62 | return this._grecaptcha.execute( 63 | this.siteKey, 64 | { action } 65 | ) 66 | } 67 | } catch (error) { 68 | throw new Error(`ReCaptcha error: Failed to execute ${error}`) 69 | } 70 | } 71 | 72 | getResponse (widgetId) { 73 | return new Promise((resolve, reject) => { 74 | if ('grecaptcha' in window) { 75 | if(this.size == 'invisible'){ 76 | this._grecaptcha.execute(widgetId) 77 | 78 | window.recaptchaSuccessCallback = token => { 79 | this._eventBus.emit('recaptcha-success', token) 80 | resolve(token) 81 | } 82 | 83 | window.recaptchaErrorCallback = error => { 84 | this._eventBus.emit('recaptcha-error', error) 85 | reject(error) 86 | } 87 | } else { 88 | const response = this._grecaptcha.getResponse(widgetId) 89 | 90 | if (response) { 91 | this._eventBus.emit('recaptcha-success', response) 92 | resolve(response) 93 | } else { 94 | const errorMessage = 'Failed to execute' 95 | 96 | this._eventBus.emit('recaptcha-error', errorMessage) 97 | reject(errorMessage) 98 | } 99 | } 100 | 101 | } 102 | }) 103 | } 104 | 105 | init () { 106 | if (this._ready) { 107 | // make sure caller waits until recaptcha get ready 108 | return this._ready 109 | } 110 | 111 | this._eventBus = new EventEmitter() 112 | this._elements = { 113 | script: document.createElement('script'), 114 | style: document.createElement('style') 115 | } 116 | 117 | const { script, style } = this._elements 118 | 119 | script.setAttribute('async', '') 120 | script.setAttribute('defer', '') 121 | 122 | const params = [] 123 | if (this.version === 3) { params.push('render=' + this.siteKey) } 124 | if (this.language) { params.push('hl=' + this.language) } 125 | 126 | let scriptUrl = API_URL 127 | 128 | if (this.mode === 'enterprise') { 129 | scriptUrl = scriptUrl.replace('api.js', 'enterprise.js') 130 | params.push('render=' + this.siteKey) 131 | } 132 | 133 | script.setAttribute('src', scriptUrl + '?' + params.join('&')) 134 | 135 | window.recaptchaSuccessCallback = (token) => this._eventBus.emit('recaptcha-success', token) 136 | window.recaptchaExpiredCallback = () => this._eventBus.emit('recaptcha-expired') 137 | window.recaptchaErrorCallback = () => this._eventBus.emit('recaptcha-error', 'Failed to execute') 138 | 139 | this._ready = new Promise((resolve, reject) => { 140 | script.addEventListener('load', () => { 141 | if (this.version === 3 && this.hideBadge) { 142 | style.innerHTML = '.grecaptcha-badge { display: none }' 143 | document.head.appendChild(style) 144 | } else if(this.version === 2 && this.hideBadge) { 145 | // display: none DISABLES the spam checking! 146 | // ref: https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge 147 | style.innerHTML = '.grecaptcha-badge { visibility: hidden; }' 148 | document.head.appendChild(style) 149 | } 150 | 151 | this._grecaptcha = window.grecaptcha.enterprise || window.grecaptcha 152 | this._grecaptcha.ready(resolve) 153 | }) 154 | 155 | script.addEventListener('error', () => { 156 | document.head.removeChild(script) 157 | reject('ReCaptcha error: Failed to load script') 158 | this._ready = null; 159 | }) 160 | 161 | document.head.appendChild(script) 162 | }) 163 | 164 | return this._ready 165 | } 166 | 167 | on (event, callback) { 168 | return this._eventBus.on(event, callback) 169 | } 170 | 171 | reset (widgetId) { 172 | if (this.version === 2 || typeof widgetId !== 'undefined') { 173 | this._grecaptcha.reset(widgetId) 174 | } 175 | } 176 | 177 | render (reference, { sitekey, theme }) { 178 | return this._grecaptcha.render(reference.$el || reference, { sitekey, theme }) 179 | } 180 | } 181 | 182 | export default function (_, inject) { 183 | const { recaptcha = {} } = _.$config || {} 184 | const options = { 185 | ...<%= serialize(options) %>, 186 | ...recaptcha, 187 | } 188 | 189 | Vue.component('Recaptcha', () => import('./recaptcha.vue')) 190 | inject('recaptcha', new ReCaptcha(options)) 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google reCAPTCHA 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![Circle CI][circle-ci-src]][circle-ci-href] 6 | [![Codecov][codecov-src]][codecov-href] 7 | [![Standard JS][standard-js-src]][standard-js-href] 8 | 9 | > 🤖 Simple and easy Google reCAPTCHA integration with Nuxt.js 10 | 11 | [📖 **Release Notes**](./CHANGELOG.md) 12 | 13 | ## Setup 14 | 15 | 1. Add `@nuxtjs/recaptcha` dependency with `yarn` or `npm` into your project 16 | 2. Add `@nuxtjs/recaptcha` to `modules` section of `nuxt.config.js` 17 | 3. Configure it: 18 | 19 | ```js 20 | { 21 | modules: [ 22 | [ 23 | '@nuxtjs/recaptcha', { 24 | /* reCAPTCHA options */ 25 | } 26 | ], 27 | ] 28 | } 29 | ``` 30 | 31 | using top level options 32 | 33 | ```js 34 | { 35 | modules: [ 36 | '@nuxtjs/recaptcha', 37 | ], 38 | 39 | recaptcha: { 40 | /* reCAPTCHA options */ 41 | }, 42 | } 43 | ``` 44 | 45 | ## Configuration 46 | 47 | ```js 48 | { 49 | // ... 50 | recaptcha: { 51 | hideBadge: Boolean, // Hide badge element (v3 & v2 via size=invisible) 52 | language: String, // Recaptcha language (v2) 53 | mode: String, // Mode: 'base', 'enterprise' 54 | siteKey: String, // Site key for requests 55 | version: Number, // Version 56 | size: String // Size: 'compact', 'normal', 'invisible' (v2) 57 | }, 58 | // ... 59 | } 60 | ``` 61 | 62 | ## Runtime config 63 | 64 | ```js 65 | // nuxt.config.js 66 | export default { 67 | publicRuntimeConfig: { 68 | recaptcha: { 69 | /* reCAPTCHA options */ 70 | siteKey: process.env.RECAPTCHA_SITE_KEY // for example 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | ## Generate reCAPTCHA Site Keys 77 | 78 | You can generate keys for the `basic` mode [by registering a new site](https://www.google.com/recaptcha/admin/create). 79 | 80 | For the `enterprise` mode, [use the Google Cloud Console](https://console.cloud.google.com/security/recaptcha). 81 | 82 | ## Usage 83 | 84 | ### reCAPTCHA v2 85 | 86 | 1. Add `` component inside your form: 87 | 88 | ```vue 89 |
90 | 91 | 92 | 93 | 94 | 95 | ``` 96 | 97 | 2. Call `getResponse` inside form submit handler to get reCAPTCHA token: 98 | 99 | ```js 100 | async onSubmit() { 101 | try { 102 | const token = await this.$recaptcha.getResponse() 103 | console.log('ReCaptcha token:', token) 104 | 105 | // send token to server alongside your form data 106 | 107 | // at the end you need to reset recaptcha 108 | await this.$recaptcha.reset() 109 | } catch (error) { 110 | console.log('Login error:', error) 111 | } 112 | }, 113 | ``` 114 | See: 115 | - [v2 example (base)](https://github.com/nuxt-community/recaptcha-module/tree/master/example/base/v2) 116 | - [v2 example (enterprise)](https://github.com/nuxt-community/recaptcha-module/tree/master/example/enterprise/v2) 117 | 118 | ### reCAPTCHA v3 119 | 120 | 1. Call `init` function inside `mounted` hook of your page 121 | 122 | ```js 123 | async mounted() { 124 | try { 125 | await this.$recaptcha.init() 126 | } catch (e) { 127 | console.error(e); 128 | } 129 | } 130 | ``` 131 | 132 | 2. Call `execute` function form submit handler to get reCAPTCHA token: 133 | 134 | ```js 135 | async onSubmit() { 136 | try { 137 | const token = await this.$recaptcha.execute('login') 138 | console.log('ReCaptcha token:', token) 139 | 140 | // send token to server alongside your form data 141 | 142 | } catch (error) { 143 | console.log('Login error:', error) 144 | } 145 | } 146 | ``` 147 | 148 | 3. Call `destroy` function inside `beforeDestroy` hook of the page. (This will remove reCAPTCHA scripts, styles and badge from the page) 149 | 150 | ```js 151 | beforeDestroy() { 152 | this.$recaptcha.destroy() 153 | } 154 | ``` 155 | 156 | See: 157 | - [v3 example (base)](https://github.com/nuxt-community/recaptcha-module/tree/master/example/base/v3) 158 | - [v3 example (enterprise)](https://github.com/nuxt-community/recaptcha-module/tree/master/example/enterprise/v3) 159 | 160 | 161 | ### Server Side 162 | 163 | When you send `data + token` to the server, you should verify the token on the server side to make sure it does not requested from a bot. 164 | You can find out how to verify token on the server side by looking at the [server middleware](https://github.com/nuxt-community/recaptcha-module/blob/master/example/base/v2/api/recaptcha.js) inside v2 example. (The server side is same for both versions) 165 | 166 | 167 | ## Info Hiding Badges 168 | 169 | You're allowed to hide the badge (i.e. for v3 and v2 invisible), as long as you include the recaptcha branding in the user flow. 170 | 171 | For example: 172 | 173 | ```html 174 | This site is protected by reCAPTCHA and the Google 175 | Privacy Policy and 176 | Terms of Service apply. 177 | 178 | ``` 179 | 180 | ## Development 181 | 182 | 1. Clone this repository 183 | 2. Install dependencies using `yarn install` or `npm install` 184 | 3. Start development server using `npm run dev` 185 | 186 | ## License 187 | 188 | [MIT License](./LICENSE) 189 | 190 | Copyright (c) mvrlin 191 | 192 | 193 | [npm-version-src]: https://img.shields.io/npm/dt/@nuxtjs/recaptcha.svg?style=flat-square 194 | [npm-version-href]: https://npmjs.com/package/@nuxtjs/recaptcha 195 | [npm-downloads-src]: https://img.shields.io/npm/v/@nuxtjs/recaptcha/latest.svg?style=flat-square 196 | [npm-downloads-href]: https://npmjs.com/package/@nuxtjs/recaptcha 197 | [circle-ci-src]: https://img.shields.io/circleci/project/github/nuxt-community/recaptcha-module.svg?style=flat-square 198 | [circle-ci-href]: https://circleci.com/gh/nuxt-community/recaptcha-module 199 | [codecov-src]: https://img.shields.io/codecov/c/github/nuxt-community/recaptcha-module.svg?style=flat-square 200 | [codecov-href]: https://codecov.io/gh/@nuxtjs/recaptcha 201 | [standard-js-src]: https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square 202 | [standard-js-href]: https://standardjs.com 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [1.1.2](https://github.com/nuxt-community/recaptcha-module/compare/v1.1.1...v1.1.2) (2023-03-21) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **types:** add missing optional properties to ReCaptchaOptions interface ([#121](https://github.com/nuxt-community/recaptcha-module/issues/121)) ([9370bd2](https://github.com/nuxt-community/recaptcha-module/commit/9370bd2)) 12 | 13 | 14 | 15 | 16 | ## [1.1.1](https://github.com/nuxt-community/recaptcha-module/compare/v1.1.0...v1.1.1) (2022-05-24) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * check enterprise mode instead of version ([4a8bc52](https://github.com/nuxt-community/recaptcha-module/commit/4a8bc52)) 22 | 23 | 24 | 25 | 26 | # [1.1.0](https://github.com/nuxt-community/recaptcha-module/compare/v1.0.4...v1.1.0) (2022-05-21) 27 | 28 | 29 | ### Features 30 | 31 | * add enterprise mode ([17a7d5f](https://github.com/nuxt-community/recaptcha-module/commit/17a7d5f)) 32 | 33 | 34 | 35 | 36 | ## [1.0.4](https://github.com/nuxt-community/recaptcha-module/compare/v1.0.3...v1.0.4) (2021-03-21) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * dataSize default overriding actual config ([#85](https://github.com/nuxt-community/recaptcha-module/issues/85)) ([a25daf7](https://github.com/nuxt-community/recaptcha-module/commit/a25daf7)) 42 | 43 | 44 | 45 | 46 | ## [1.0.3](https://github.com/nuxt-community/recaptcha-module/compare/v1.0.2...v1.0.3) (2021-03-16) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * prioritized dynamic input vs config ([#82](https://github.com/nuxt-community/recaptcha-module/issues/82)) ([2d125a3](https://github.com/nuxt-community/recaptcha-module/commit/2d125a3)) 52 | 53 | 54 | 55 | 56 | ## [1.0.2](https://github.com/nuxt-community/recaptcha-module/compare/v1.0.1...v1.0.2) (2021-03-11) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * use module language in V3 ([#80](https://github.com/nuxt-community/recaptcha-module/issues/80)) ([3812045](https://github.com/nuxt-community/recaptcha-module/commit/3812045)) 62 | 63 | 64 | 65 | 66 | ## [1.0.1](https://github.com/nuxt-community/recaptcha-module/compare/v1.0.0...v1.0.1) (2021-02-17) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * reset for multi-widget ([#78](https://github.com/nuxt-community/recaptcha-module/issues/78)) ([4ff519b](https://github.com/nuxt-community/recaptcha-module/commit/4ff519b)) 72 | 73 | 74 | 75 | 76 | # [1.0.0](https://github.com/nuxt-community/recaptcha-module/compare/v0.6.2...v1.0.0) (2021-02-03) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * describe error within execute() ([#40](https://github.com/nuxt-community/recaptcha-module/issues/40)) ([769ae67](https://github.com/nuxt-community/recaptcha-module/commit/769ae67)) 82 | * remove badge on destroy ([#76](https://github.com/nuxt-community/recaptcha-module/issues/76)) ([c419df1](https://github.com/nuxt-community/recaptcha-module/commit/c419df1)) 83 | 84 | 85 | ### Features 86 | 87 | * runtime config ([#70](https://github.com/nuxt-community/recaptcha-module/issues/70)) ([e090317](https://github.com/nuxt-community/recaptcha-module/commit/e090317)) 88 | * **language:** enable google auto detection ([#72](https://github.com/nuxt-community/recaptcha-module/issues/72)) ([3cb13fc](https://github.com/nuxt-community/recaptcha-module/commit/3cb13fc)) 89 | * **multi-widget:** render and verify multiple v2 widgets ([#75](https://github.com/nuxt-community/recaptcha-module/issues/75)) ([d3e3908](https://github.com/nuxt-community/recaptcha-module/commit/d3e3908)) 90 | 91 | 92 | 93 | 94 | ## [0.6.2](https://github.com/nuxt-community/recaptcha-module/compare/v0.6.1...v0.6.2) (2020-01-09) 95 | 96 | 97 | 98 | 99 | ## [0.6.1](https://github.com/nuxt-community/recaptcha-module/compare/v0.6.0...v0.6.1) (2019-10-20) 100 | 101 | 102 | 103 | 104 | # [0.6.0](https://github.com/nuxt-community/recaptcha-module/compare/v0.5.3...v0.6.0) (2019-10-20) 105 | 106 | 107 | ### Features 108 | 109 | * **v2:** add reset ([129e159](https://github.com/nuxt-community/recaptcha-module/commit/129e159)) 110 | 111 | 112 | 113 | 114 | ## [0.5.3](https://github.com/nuxt-community/recaptcha-module/compare/v0.5.2...v0.5.3) (2019-07-19) 115 | 116 | 117 | 118 | 119 | ## [0.5.2](https://github.com/nuxt-community/recaptcha-module/compare/v0.5.1...v0.5.2) (2019-07-08) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * fix string template for old Nuxt versions ([6887d38](https://github.com/nuxt-community/recaptcha-module/commit/6887d38)) 125 | 126 | 127 | 128 | 129 | ## [0.5.1](https://github.com/nuxt-community/recaptcha-module/compare/v0.5.0...v0.5.1) (2019-06-28) 130 | 131 | 132 | 133 | 134 | # [0.5.0](https://github.com/nuxt-community/recaptcha-module/compare/v0.4.1...v0.5.0) (2019-06-13) 135 | 136 | 137 | ### Features 138 | 139 | * **v2:** add language support ([195773e](https://github.com/nuxt-community/recaptcha-module/commit/195773e)) 140 | * **v2:** use callbacks, add expired event ([6b658b1](https://github.com/nuxt-community/recaptcha-module/commit/6b658b1)) 141 | 142 | 143 | 144 | 145 | ## [0.4.1](https://github.com/nuxt-community/recaptcha-module/compare/v0.4.0...v0.4.1) (2019-05-02) 146 | 147 | 148 | 149 | 150 | # [0.4.0](https://github.com/nuxt-community/recaptcha-module/compare/v0.3.3...v0.4.0) (2019-05-02) 151 | 152 | 153 | ### Features 154 | 155 | * reinitialize recaptcha v2 on re-visit ([701343a](https://github.com/nuxt-community/recaptcha-module/commit/701343a)) 156 | 157 | 158 | 159 | 160 | ## [0.3.3](https://github.com/nuxt-community/recaptcha-module/compare/v0.3.2...v0.3.3) (2019-04-15) 161 | 162 | 163 | ### Bug Fixes 164 | 165 | * use built-in events instead of Vue ([53cbe1e](https://github.com/nuxt-community/recaptcha-module/commit/53cbe1e)) 166 | 167 | 168 | 169 | 170 | ## [0.3.2](https://github.com/nuxt-community/recaptcha-module/compare/v0.3.1...v0.3.2) (2019-04-15) 171 | 172 | 173 | 174 | 175 | ## [0.3.1](https://github.com/nuxt-community/recaptcha-module/compare/v0.3.0...v0.3.1) (2019-04-15) 176 | 177 | 178 | 179 | 180 | # [0.3.0](https://github.com/nuxt-community/recaptcha-module/compare/v0.2.2...v0.3.0) (2019-04-15) 181 | 182 | 183 | ### Features 184 | 185 | * add v2 event-bus ([366d4de](https://github.com/nuxt-community/recaptcha-module/commit/366d4de)) 186 | 187 | 188 | 189 | 190 | ## [0.2.2](https://github.com/nuxt-community/recaptcha-module/compare/v0.2.1...v0.2.2) (2019-02-26) 191 | 192 | 193 | ### Bug Fixes 194 | 195 | * **lib:** fix error is not defined ([f581488](https://github.com/nuxt-community/recaptcha-module/commit/f581488)) 196 | 197 | 198 | 199 | 200 | ## [0.2.1](https://github.com/nuxt-community/recaptcha-module/compare/v0.2.0...v0.2.1) (2019-02-21) 201 | 202 | 203 | 204 | 205 | # [0.2.0](https://github.com/nuxt-community/recaptcha/compare/v0.1.0...v0.2.0) (2019-02-21) 206 | 207 | 208 | ### Features 209 | 210 | * **example:** split example by versions ([7ae76fe](https://github.com/nuxt-community/recaptcha/commit/7ae76fe)) 211 | * **example, lib:** add recaptcha component ([fc69668](https://github.com/nuxt-community/recaptcha/commit/fc69668)) 212 | * **lib:** add v2 support ([07e8b62](https://github.com/nuxt-community/recaptcha/commit/07e8b62)) 213 | * **pages:** add v2 example ([ee4cadc](https://github.com/nuxt-community/recaptcha/commit/ee4cadc)) 214 | * **types:** add v2 types ([31e05c7](https://github.com/nuxt-community/recaptcha/commit/31e05c7)) 215 | 216 | 217 | 218 | 219 | # 0.1.0 (2019-02-13) 220 | 221 | 222 | ### Bug Fixes 223 | 224 | * **lib:** convert string to template literal ([18e653a](https://github.com/nuxt-community/recaptcha/commit/18e653a)) 225 | * **lib:** remove code repetition ([1e15cd0](https://github.com/nuxt-community/recaptcha/commit/1e15cd0)) 226 | * **lib:** return Promise instead of Boolean ([cdfe204](https://github.com/nuxt-community/recaptcha/commit/cdfe204)) 227 | * **lib:** throw Error instead of String ([09450fa](https://github.com/nuxt-community/recaptcha/commit/09450fa)) 228 | * **types:** fix execute Promise type ([a9833f3](https://github.com/nuxt-community/recaptcha/commit/a9833f3)) 229 | * recaptcha case in comment ([39f2b5b](https://github.com/nuxt-community/recaptcha/commit/39f2b5b)) 230 | * use this ([101f97d](https://github.com/nuxt-community/recaptcha/commit/101f97d)) 231 | * wait for ready ([5662681](https://github.com/nuxt-community/recaptcha/commit/5662681)) 232 | 233 | 234 | ### Features 235 | 236 | * **plugin:** add execute helpers ([7a72172](https://github.com/nuxt-community/recaptcha/commit/7a72172)) 237 | * allow chaining __ready ([cb10c20](https://github.com/nuxt-community/recaptcha/commit/cb10c20)) 238 | * lazy initialize $recaptcha on execute ([1e2e482](https://github.com/nuxt-community/recaptcha/commit/1e2e482)) 239 | --------------------------------------------------------------------------------