Skip to content

Commit

Permalink
Add re-captcha element part of #23
Browse files Browse the repository at this point in the history
  • Loading branch information
akc42 committed Jul 31, 2020
1 parent 4036b8b commit f9d7b06
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 10 deletions.
167 changes: 167 additions & 0 deletions client/elements/re-captcha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
@licence
Copyright (c) 2020 Alan Chandler, all rights reserved
This file is part of Football-Mobile.
Football-Mobile is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Football-Mobile is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Football-Mobile. If not, see <http://www.gnu.org/licenses/>.
*/
import { LitElement, html } from '../libs/lit-element.js';
import api from '../modules/api.js';

let instance = 1;

const recaptcha = new Promise(function (resolve, reject) {
window.recaptchaElementCallback = function () {
resolve(window.grecaptcha); //grecaptcha is a global object that the script element I am about to add delivers
};

const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', 'grecaptchaLibrary');
script.setAttribute('defer', '');
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js?onload=recaptchaElementCallback&render=explicit');
head.appendChild(script);
});


/*
<re-captcha>
*/
class ReCaptcha extends LitElement {
static get styles() {
return [];
}
static get properties() {
return {
theme: {type: String},
invalid: {type: Boolean}, //failed to provide capture response
message: {type: String} //Error message to display when invalid
};
}
constructor() {
super();
this.theme = 'light';
this.invalid = false;
this.captureCompleted = false;
this.message = 'Captcha Not Completed';
this._setLocationOfBodyFloat = this._setLocationOfBodyFloat.bind(this);
this._scroll = this._scroll.bind(this);
this._captured = this._captured.bind(this);
this.isScrolling = false;
this.resizeObserver = new ResizeObserver(this._setLocationOfBodyFloat);

}
connectedCallback() {
super.connectedCallback();

//append an absolute div to the body in which to render our div.
this.float = document.createElement('div');
const captchaId = `captcha_${instance++}`;
this.float.setAttribute('id', this.captchaId)
this.float.style = 'position:absolute;';
document.body.appendChild(this.float);
recaptcha.then(grepcaptcha => grepcaptcha.render(this.captureId,{
'sitekey': global.recaptchaKey,
'theme': this.theme,
'callback': this._captured
}));
if (this.reserver !== undefined) {
this.resizeObserver.observe(this.reserve);
}
let parent = this;
while (parent = parent.parentNode) {
parent.addEventListener('scroll', this._scroll);
}

}
disconnectedCallback() {
super.disconnectedCallback();
this.float.remove(); //take our recapture element away
this.isScrolling = false;
this.captureCompleted = false;
this.resizeObserver.unobserve(this.reserve);
let parent = this;
while (parent = parent.parentNode) {
parent.removeEventListener('scroll', this._scroll);
}
}
update(changed) {
super.update(changed);
}
firstUpdated() {
this.reserve = this.shadowRoot.querySelector('#reserve');
this.resizeObserver.observe(this.reserve);

}
updated(changed) {
super.updated(changed);
}
render() {
return html`
<style>
#reserve {
width: 398px;
height: 98px;
}
</style>
<div class="error">${this.invalid? this.message: ' '}</div>
<!-- we reserve space in the dom for this -->
<div id="reserve"></div>
`;
}
validate() {
if (!this.captureCompleted) {
this.invalid = false;
}
return !this.invalid;
}
_captured(token) {
console.log('captured called with token', token);
//I don't want the client to see my secret key, so I am going to send the token to my server, and it can do the validation
api('session/recaptcha_verify',{token:token}).then(response => {
if (response.success) {
this.captureCompleted = true;
this.invalid = false;
} else {
this.invalid = true;
window.grecaptcha.reset(this.captchaId);
}
})
}
_scroll() {
if (this.isScrolling) return;
this.isScrolling = true;
requestAnimationFrame(() => {
this._setLocationOfBodyFloat();
this.isScrolling = false;
});

}
_setLocationOfBodyFloat() {
if (this.reserve !== undefined) {
const rect = this.reserve.getBoundingClientRect();
this.float.style.left = rect.left + 'px';
this.float.style.top = rect.top + 'px';
this.float.style.width = rect.width + 'px';
this.float.style.height = rect.height + 'px';
}
}
}
customElements.define('re-captcha', ReCaptcha);




8 changes: 4 additions & 4 deletions client/modules/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ let clientLogUid = 0;
let version = 'v0.0.0';
let copyrightYear = '2020';
let cookieName = '';
let cookieVisitName = '';
let webmaster = '';
let siteLogo = '/appimages/site-logo.png';
let verifyExpires = 12;
let minPassLen = 6;
let dwellTime = 2000;
let reCaptchaKey = '';
let lrid = 0;
let lcid = 0;
let luid = 0;
Expand Down Expand Up @@ -62,12 +62,12 @@ const global = {
version = conf.version;
copyrightYear = conf.copyrightYear;
cookieName = conf.cookieName;
cookieVisitName = conf.cookieVisitName;
webmaster = conf.webmaster;
siteLogo = conf.siteLogo;
verifyExpires = conf.verifyExpires;
minPassLen = conf.minPassLen;
dwellTime = conf.dwellTime;
reCaptchaKey = conf.reCaptchaKey;
lrid = conf.lrid;
lcid = conf.lcid;
luid = conf.luid;
Expand Down Expand Up @@ -117,8 +117,8 @@ const global = {
get cookieName () {
return cookieName;
},
get cookieVisitName () {
return cookieVisitName;
get reCaptchaKey () {
return reCaptchaKey;
},
get webmaster () {
return webmaster;
Expand Down
2 changes: 1 addition & 1 deletion server/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
config.clientLog = s.get('client_log');
config.clientLogUid = s.get('client_log_uid');
config.cookieName = s.get('cookie_name');
config.cookieVisitName = s.get('cookie_visit_name');
config.webmaster = s.get('webmaster');
config.siteLogo = s.get('site_logo');
config.verifyExpires = s.get('verify_expires');
config.minPassLen = s.get('min_pass_len');
config.dwellTime = s.get('dwell_time');
config.reCaptchaKey = s.get('recaptcha_key');

const row = last.get();
config.lcid = row.cid;
Expand Down
8 changes: 4 additions & 4 deletions server/db-init/database.sql
Original file line number Diff line number Diff line change
Expand Up @@ -393,20 +393,19 @@ INSERT INTO settings (name,value) VALUES('version',14); --version of this config

INSERT INTO settings (name,value) VALUES('client_log',''); --if none empty string should specify colon separated function areas client should log or 'all' for every thing.
INSERT INTO settings (name,value) VALUES('client_log_uid',0); --if non zero limit client logging to that uid.
INSERT INTO settings (name,value) VALUES('cookie_visit_name','FMVISIT'); --name used for a cookie to record a visit where the user logged on.

INSERT INTO settings (name,value) VALUES('main_menu_icon','menu'); --character from material icon font to use as the main menu.
INSERT INTO settings (name,value) VALUES('webmaster','[email protected]'); --site webmaster.
INSERT INTO settings (name,value) VALUES('site_logo','/appimages/site_logo.png'); --url of the site_logo image to be used on info pages and in mail
INSERT INTO settings (name,value) VALUES('min_pass_len', 6); --minimum password length
INSERT INTO settings (name,value) VALUES('dwell_time', 2000); --time to elapse before new urls get to be pushed to the history stack

INSERT INTO settings (name,value) VALUES('recaptcha_key',''); --stardard recaptcha key for the recapcha element
--values for server config
INSERT INTO settings (name,value) VALUES('cache_age',0);--cache age before invalid (in hours), 0 is infinite
INSERT INTO settings (name,value) VALUES('server_port', 2040); --port the api server should listen on.
INSERT INTO settings (name,value) VALUES('cookie_name', 'FOOTBALL'); --name used for our main cookie
INSERT INTO settings (name,value) VALUES('cookie_key', 'newCookieKey'); --key used to encrypt/decrypt cookie token
INSERT INTO settings (name,value) VALUES('cookie_expires', 720); --hours until expire for standard logged on token
INSERT INTO settings (name,value) VALUES('recaptch_secret',''); -- secret key or verification of recaptcha.
INSERT INTO settings (name,value) VALUES('verify_expires', 24); --hours until expire for verification tokens.
INSERT INTO settings (name,value) VALUES('rate_limit', 30); --minutes that must elapse by verification emails
INSERT INTO settings (name,value) VALUES('email_from', '[email protected]'); --email address that mail comes from (do not reply)
Expand Down Expand Up @@ -454,7 +453,8 @@ INSERT INTO styles (name,style) VALUES('pw-input-length','100px'); --input field
-- UPDATE settings SET value = '/images/signature.png;Joe Bloggs' WHERE name = 'mail_signature'; --NOTE, site specific images should be in a different directory
-- UPDATE settings SET value = '/images/site_logo.png' WHERE name = 'site_logo'; --As above.
-- UPDATE settings SET value = 'newCookieKey' WHERE name = 'cookie_key';

-- UPDATE settings SET value = 'recaptcha_key' WHERE name = 'recaptcha_key';
-- UPDATE settings SET value = 'recaptcha_secret_key' WHERE name = 'recaptcha_secret';
COMMIT;
VACUUM;
-- set it all up as Write Ahead Log for max performance and minimum contention with other users.
Expand Down
3 changes: 2 additions & 1 deletion server/db-init/upgrade_13.sql
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,18 @@ INSERT INTO settings (name,value) VALUES('version',14); --version of this config

INSERT INTO settings (name,value) VALUES('client_log',''); --if none empty string should specify colon separated function areas client should log or 'all' for every thing.
INSERT INTO settings (name,value) VALUES('client_log_uid',0); --if non zero limit client logging to that uid.
INSERT INTO settings (name,value) VALUES('cookie_visit_name','FMVISIT'); --name used for a cookie to record a visit where the user logged on.
INSERT INTO settings (name,value) VALUES('webmaster','[email protected]'); --site webmaster.
INSERT INTO settings (name,value) VALUES('site_logo','/appimages/site_logo.png'); --url of the site_logo image to be used on info pages and in mail
INSERT INTO settings (name,value) VALUES('min_pass_len', 6); --minimum password length
INSERT INTO settings (name,value) VALUES('dwell_time', 2000); --time to elapse before new urls get to be pushed to the history stack
INSERT INTO settings (name,value) VALUES('recaptcha_key',''); --standard recaptcha key for the recapcha element
--values for server config
INSERT INTO settings (name,value) VALUES('cache_age',0);--cache age before invalid (in hours), 0 is infinite
INSERT INTO settings (name,value) VALUES('server_port', 2040); --port the api server should listen on.
INSERT INTO settings (name,value) VALUES('cookie_name', 'MBBall'); --name used for our main cookie
INSERT INTO settings (name,value) VALUES('cookie_key', 'newCookieKey'); --key used to encrypt/decrypt cookie token
INSERT INTO settings (name,value) VALUES('cookie_expires', 720); --hours until expire for standard logged on token
INSERT INTO settings (name,value) VALUES('recaptch_secret',''); -- secret key or verification of recaptcha.
INSERT INTO settings (name,value) VALUES('verify_expires', 24); --hours until expire for verification tokens.
INSERT INTO settings (name,value) VALUES('rate_limit', 30); --minutes that must elapse by verification emails
INSERT INTO settings (name,value) VALUES('email_from', '[email protected]'); --email address that mail comes from (do not reply)
Expand Down
50 changes: 50 additions & 0 deletions server/session/recaptcha_verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
@licence
Copyright (c) 2020 Alan Chandler, all rights reserved
This file is part of Football Mobile.
Football Mobile is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Football Mobile is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Football Mobile. If not, see <http://www.gnu.org/licenses/>.
*/

(function() {
'use strict';

const debug = require('debug')('football:api:recaptcha');
const db = require('../utils/database');
const secret = db.prepare('SELECT value FROM settings WHERE name = ?').pluck().get('recaptcha_secret');
https = require('https');


module.exports = async function(params) {
debug('new request with token', params.token);
return new Promise((accept, reject) => {
https.request({`https://www.google.com/recaptcha/api/siteverify?secret=${secret}&response=${params.token}`,{
method: 'POST',
}, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
accept(JSON.parse(data));
});

}).on("error", (err) => {
reject(err);
});
});
};
})();

0 comments on commit f9d7b06

Please sign in to comment.