widget static resources are now built locally

This commit is contained in:
realaravinth
2021-07-07 00:28:32 +05:30
parent 5d55971f19
commit bfebca6e0e
36 changed files with 2086 additions and 168 deletions

View File

@@ -5,7 +5,5 @@
href="<.= &*crate::VERIFICATIN_WIDGET_CSS .>"
/>
<script src="<.= &*crate::VERIFICATIN_WIDGET_JS .>"></script>
<script src="<.= &*crate::WIDGET_ROUTES.js .>"></script>
<script src="<.= &*crate::WIDGET_ROUTES.wasm .>"></script>
</body>
</html>

View File

@@ -0,0 +1,152 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
/** mcaptcha checkbox ID **/
export const btnId = 'widget__verification-checkbox';
/** get sitekey */
export const sitekey = () => {
let sitekey;
return (() => {
if (sitekey === null || sitekey === undefined) {
sitekey = new URL(window.location.href).searchParams.get('sitekey');
if (sitekey === null || sitekey === undefined) {
throw new Error(`Define sitekey in query parameter`);
}
}
return sitekey;
})();
};
/** mCaptcha API routes */
export const ROUTES = (() => {
const getConfig = '/api/v1/pow/config';
const verififyPoW = '/api/v1/pow/verify';
return {
/** get URL to fetch PoW configuration */
getConfig,
/** get URL to verify PoW*/
verififyPoW,
};
})();
/** get mCaptcha verifify checkbox button */
export const btn = () => {
let btn;
return (() => {
if (btn === null || btn === undefined) {
btn = <HTMLInputElement>document.getElementById(btnId);
if (btn === null || btn === undefined) {
throw new Error(`mCaptcha button not found)`);
}
}
return btn;
})();
};
export const messageText = () => {
let beforeClass = 'widget__verification-text--before';
let duringClass = 'widget__verification-text--during';
let errorClass = 'widget__verification-text--error';
let afterClass = 'widget__verification-text--after';
let before: HTMLElement;
let after: HTMLElement;
let during: HTMLElement;
let error: HTMLElement;
/** runner fn to display HTMLElement **/
const showMsg = (e: HTMLElement) => (e.style.display = 'block');
/** runner fn to hide HTMLElement **/
const hideMsg = (e: HTMLElement) => (e.style.display = 'none');
/** lazy init and get before elementt **/
const getBefore = () => {
if (before === null || before === undefined) {
before = <HTMLElement>document.querySelector(`.${beforeClass}`);
if (before === null || before === undefined) {
throw new Error(`before element not found)`);
}
return before;
}
};
/** lazy init and get after elementt **/
const getAfter = () => {
if (after === null || after === undefined) {
after = <HTMLSpanElement>document.querySelector(`.${afterClass}`);
if (after === null || after === undefined) {
throw new Error(`after element not found)`);
}
}
return after;
};
/** lazy init and get error elementt **/
const getError = () => {
if (error === null || error === undefined) {
error = <HTMLSpanElement>document.querySelector(`.${errorClass}`);
if (error === null || error === undefined) {
throw new Error(`before error not found)`);
}
}
return error;
};
/** lazy init and get during elementt **/
const getDuring = () => {
if (during === null || during === undefined) {
during = <HTMLSpanElement>document.querySelector(`.${duringClass}`);
if (during === null || during === undefined) {
throw new Error(`before during not found)`);
}
}
return during;
};
return {
/** display "before" message **/
before: () => {
showMsg(getBefore());
hideMsg(getAfter());
hideMsg(getDuring());
hideMsg(getError());
},
/** display "after" message **/
after: () => {
hideMsg(getBefore());
showMsg(getAfter());
hideMsg(getDuring());
hideMsg(getError());
},
/** display "during" message **/
during: () => {
hideMsg(getBefore());
hideMsg(getAfter());
showMsg(getDuring());
hideMsg(getError());
},
/** display "error" message **/
error: () => {
hideMsg(getBefore());
hideMsg(getAfter());
hideMsg(getDuring());
showMsg(getError());
},
};
};
export const inputId = 'mcaptcha-response';

View File

@@ -0,0 +1,48 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import genJsonPayload from './utils/genJsonPayload';
import * as CONST from './const';
type GetConfigPayload = {
key: string;
};
export type PoWConfig = {
string: string;
difficulty_factor: number;
salt: string;
};
/**
* fetch proof-of-work configuration
* @returns {PoWConfig} pow config
* */
export const fetchPoWConfig = async () => {
try {
const payload: GetConfigPayload = {
key: CONST.sitekey(),
};
const res = await fetch(CONST.ROUTES.getConfig, genJsonPayload(payload));
if (res.ok) {
const config: PoWConfig = await res.json();
return config;
} else {
const err = await res.json();
throw new Error(err);
}
} catch (err) {
throw err;
}
};
export default fetchPoWConfig;

View File

@@ -0,0 +1,68 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import prove from './prove';
import fetchPoWConfig from './fetchPoWConfig';
import sendWork from './sendWork';
import sendToParent from './sendToParent';
import * as CONST from './const';
import '../main.scss';
let LOCK = false;
/** add mcaptcha widget element to DOM */
export const registerVerificationEventHandler = () => {
const verificationContainer = <HTMLElement>(
document.querySelector('.widget__verification-container')
);
verificationContainer.style.display = 'flex';
CONST.btn().addEventListener('click', e => solveCaptchaRunner(e));
};
export const solveCaptchaRunner = async (e: Event) => {
if (LOCK) {
e.preventDefault();
return;
}
try {
LOCK = true;
if (CONST.btn().checked == false) {
CONST.messageText().before();
LOCK = false;
return;
}
e.preventDefault();
// steps:
// 1. show during
CONST.messageText().during();
// 1. get config
const config = await fetchPoWConfig();
// 2. prove work
const proof = await prove(config);
// 3. submit work
const token = await sendWork(proof);
// 4. send token
sendToParent(token);
// 5. mark checkbox checked
CONST.btn().checked = true;
CONST.messageText().after();
LOCK = false;
} catch (e) {
CONST.messageText().error();
console.error(e);
LOCK = false;
}
};
registerVerificationEventHandler();

View File

@@ -0,0 +1,55 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import {gen_pow} from '../../../browser/pkg/index';
import {PoWConfig} from './fetchPoWConfig';
import * as CONST from './const';
export type Work = {
result: string;
nonce: number;
string: string;
key: string;
};
type WasmWork = {
result: string;
nonce: number;
};
/**
* proove work
* @param {PoWConfig} config - the proof-of-work configuration using which
* work needs to be computed
* */
const prove = async (config: PoWConfig) => {
try {
const proofString = gen_pow(
config.salt,
config.string,
config.difficulty_factor,
);
const proof: WasmWork = JSON.parse(proofString);
const res: Work = {
key: CONST.sitekey(),
string: config.string,
nonce: proof.nonce,
result: proof.result,
};
return res;
} catch (err) {
throw err;
}
};
export default prove;

View File

@@ -0,0 +1,24 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import {Token} from './sendWork';
/**
* send pow validation token as message to parant of the iframe
* @param {Token} token: token received from mCaptcha service
* upon successful PoW validation
* */
export const sendToParent = (token: Token) => {
window.parent.postMessage(token, '*');
// TODO set origin. Make parent send origin as query parameter
// or as a message to iframe
};
export default sendToParent;

View File

@@ -0,0 +1,42 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import genJsonPayload from './utils/genJsonPayload';
import * as CONST from './const';
import {Work} from './prove';
export type Token = {
token: string;
};
export const sendWork = async (payload: Work) => {
try {
const res = await fetch(CONST.ROUTES.verififyPoW, genJsonPayload(payload));
if (res.ok) {
console.debug('work verified');
const token: Token = await res.json();
console.debug(`token ${token.token}`);
return token;
} else {
const err = await res.json();
console.error(`error: ${err.error}`);
throw new Error(err);
}
} catch (err) {
CONST.messageText().error();
console.error(err);
await new Promise(r => setTimeout(r, 1000));
window.location.reload();
throw err;
}
};
export default sendWork;

View File

@@ -0,0 +1,50 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import * as CONST from '../const';
import {getBaseHtml, sitekey, checkbox} from './setupTests';
import * as TESTElements from './setupTests';
it('const works', () => {
const body = document.querySelector('body');
const container = getBaseHtml();
body.appendChild(container);
expect(CONST.sitekey()).toBe(sitekey);
expect(CONST.btn()).toBe(checkbox);
// display after
CONST.messageText().after();
expect(TESTElements.afterMsg.style.display).toBe('block');
expect(TESTElements.beforeMsg.style.display).toBe('none');
expect(TESTElements.duringMsg.style.display).toBe('none');
expect(TESTElements.errorMsg.style.display).toBe('none');
// display before
CONST.messageText().before();
expect(TESTElements.afterMsg.style.display).toBe('none');
expect(TESTElements.beforeMsg.style.display).toBe('block');
expect(TESTElements.duringMsg.style.display).toBe('none');
expect(TESTElements.errorMsg.style.display).toBe('none');
// display during
CONST.messageText().during();
expect(TESTElements.afterMsg.style.display).toBe('none');
expect(TESTElements.beforeMsg.style.display).toBe('none');
expect(TESTElements.duringMsg.style.display).toBe('block');
expect(TESTElements.errorMsg.style.display).toBe('none');
// display error
CONST.messageText().error();
expect(TESTElements.afterMsg.style.display).toBe('none');
expect(TESTElements.beforeMsg.style.display).toBe('none');
expect(TESTElements.duringMsg.style.display).toBe('none');
expect(TESTElements.errorMsg.style.display).toBe('block');
});

View File

@@ -0,0 +1,40 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import * as CONST from '../const';
export const sitekey = 'imbatman';
export const checkbox = <HTMLInputElement>document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = CONST.btnId;
const getMessages = (state: string) => {
const msg = <HTMLElement>document.createElement('span');
msg.className = `widget__verification-text--${state}`;
return msg;
};
export const beforeMsg = getMessages('before');
export const afterMsg = getMessages('after');
export const duringMsg = getMessages('during');
export const errorMsg = getMessages('error');
/** get base HTML with empty mCaptcha container */
export const getBaseHtml = () => {
const form = <HTMLFormElement>document.createElement('form');
form.appendChild(checkbox);
form.appendChild(beforeMsg);
form.appendChild(duringMsg);
form.appendChild(afterMsg);
form.appendChild(errorMsg);
return form;
};

View File

@@ -0,0 +1,30 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import genJsonPayload from './genJsonPayload';
'use strict';
const payload = {
username: 'Jhon',
};
const value = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
};
it('getFromUrl workds', () => {
expect(genJsonPayload(payload)).toEqual(value);
});

View File

@@ -0,0 +1,23 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
const genJsonPayload = (payload: any) => {
const value = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
};
return value;
};
export default genJsonPayload;