mirror of
1
2
Fork 0

The captcha now completes! Also made improvements in UI

This commit is contained in:
Devshh 2020-04-02 10:08:09 +01:00
parent 20ab04f758
commit 23b611e093
19 changed files with 297 additions and 802 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@ node_modules
dist
.env
*.tsbuildinfo
public/images
public/images
dev/mat.json

24
demo/index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>uCaptcha Test</title>
<script src="../dist/client/bundle.js"></script>
</head>
<body>
<form method="POST" action="/">
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<div class="captcha-box"></div>
<input type="submit" value="Submit Form" />
</form>
<script>
ucaptcha.create("hUYUhuytryftyi6787yYtg67", ".captcha-box", function(token) {
alert(token);
});
</script>
</body>
</html>

View File

@ -1,684 +0,0 @@
{
"OxXrEtvErYzN": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.4,
0.4,
0.4,
0.6,
0.6,
0.6,
0.6,
0.6
],
"nums": 1,
"obj": 0
},
"zKsAXcOCNd9U": {
"mat": [
0.30000000000000004,
0.5,
0.30000000000000004,
0.7,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004
],
"nums": 2,
"obj": 0
},
"AyxF5UB7GyPC": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4
],
"nums": 1,
"obj": 0
},
"AD8Vjgv2zCjf": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.6,
0.6,
0.6
],
"nums": 1,
"obj": 0
},
"VEhAvRf5SlqP": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.4,
0.4,
0.4,
0.6,
0.4,
0.4,
0.6,
0.6
],
"nums": 1,
"obj": 0
},
"REemIAxKWPOr": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.30000000000000004,
0.30000000000000004,
0.5,
0.5
],
"nums": 2,
"obj": 0
},
"ilzrt3XTmEGI": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.30000000000000004,
0.30000000000000004,
0.5,
0.7
],
"nums": 2,
"obj": 0
},
"sq8XFCoHMsjh": {
"mat": [
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.4,
0.20000000000000004,
0.20000000000000004,
0.4,
0.6
],
"nums": 3,
"obj": 0
},
"Rkb9KvzIK8i6": {
"mat": [
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
0.20000000000000004,
2.7755575615628914e-17,
0.20000000000000004,
0.20000000000000004,
0.4
],
"nums": 5,
"obj": 0
},
"zAQrZUdBtAul": {
"mat": [
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
0.20000000000000004,
2.7755575615628914e-17,
2.7755575615628914e-17,
0.20000000000000004,
0.4
],
"nums": 5,
"obj": 0
},
"s0LDtVUcozVA": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0.10000000000000003,
0,
0,
0.10000000000000003,
0.30000000000000004
],
"nums": 6,
"obj": 0
},
"Ern8By1vuGjV": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0.10000000000000003
],
"nums": 8,
"obj": 0
},
"hn1uGROIKjnM": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0.10000000000000003
],
"nums": 8,
"obj": 0
},
"4q7BkDtACkDx": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2.7755575615628914e-17
],
"nums": 9,
"obj": 0
},
"WlEm9ZGsDOlN": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"nums": 10,
"obj": 0
},
"4d6y7SDP9r9c": {
"mat": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"nums": 11,
"obj": 0
},
"AkGcL0NHOVGy": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.5,
0.5,
0.5
],
"nums": 2,
"obj": 0
},
"GHI69eo7oMwY": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.5,
0.7,
0.7,
0.7,
0.7
],
"nums": 2,
"obj": 0
},
"MkvjgP0uimhW": {
"mat": [
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.4,
0.20000000000000004,
0.20000000000000004,
0.4,
0.4,
0.20000000000000004,
0.20000000000000004,
0.6,
0.6,
0.6,
0.6,
0.7999999999999999,
0.7999999999999999
],
"nums": 3,
"obj": 0
},
"gFuaeUebn0lE": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.5,
0.5,
0.30000000000000004,
0.5,
0.5,
0.5
],
"nums": 2,
"obj": 0
},
"xplUg1qXcCDV": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.5,
0.5,
0.5,
0.7,
0.7,
0.5
],
"nums": 2,
"obj": 0
},
"KNRZDFuT4X45": {
"mat": [
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.4,
0.4,
0.4,
0.4,
0.6,
0.7999999999999999,
0.6
],
"nums": 3,
"obj": 0
},
"G68Efl2ZE0U1": {
"mat": [
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.10000000000000003,
0.30000000000000004,
0.5,
0.5,
0.30000000000000004,
0.5,
0.8999999999999999,
0.7
],
"nums": 4,
"obj": 0
},
"JOAT6FV7VKKi": {
"mat": [
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
2.7755575615628914e-17,
0.20000000000000004,
0.4,
0.4,
0.20000000000000004,
0.4,
0.7999999999999999,
0.6
],
"nums": 5,
"obj": 0
},
"B25hZGGvUjZw": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4
],
"nums": 1,
"obj": 0
},
"mX0MuHDZJFVx": {
"mat": [
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004,
0.20000000000000004
],
"nums": 3,
"obj": 0
},
"GuOSUS3pciGR": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.6,
0.4,
0.6,
0.6,
0.6
],
"nums": 1,
"obj": 0
},
"OaeOLRd0m1Nu": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.5
],
"nums": 2,
"obj": 0
},
"jvceqG1K3aGV": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004
],
"nums": 2,
"obj": 0
},
"0iMT2yXLK0vj": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4
],
"nums": 1,
"obj": 0
},
"wDZ7DtjZN3xx": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4
],
"nums": 1,
"obj": 0
}
}

View File

@ -10,7 +10,7 @@
},
"scripts": {
"start": "NODE_ENV=production; node src/start.js",
"server": "nodemon src/start.js --ignore dev/ --ignore public/ --ignore src/client/",
"server": "nodemon src/start.js --ignore dev/ --ignore public/ --ignore src/client/ --ignore dist/client/",
"rollup:dev": "NODE_ENV=development; rollup -c -w",
"rollup:build": "NODE_ENV=production; rollup -c",
"test": "node test/test-server.js"

View File

@ -1,8 +1,11 @@
import path from 'path';
import {fileURLToPath} from 'url';
export const __dirname = fileURLToPath(import.meta.url);
export const dirname = meta => fileURLToPath(import.meta.url);
export const PROJECT_ROOT = process.cwd();
export const TAGS = [
null,
'vehicle',

View File

@ -1,105 +1,77 @@
import reloadSession from './helpers/reloadSession.js';
import getImage from './helpers/getImage.js';
import verifyImage from './helpers/verifyImage.js';
import {createElement as ce, querySelector as qs} from './util/document.js';
import styles from './res/styles.js';
/**
* Create a uCaptcha box
* @param {string} websiteKey Website key
* @param {Function} [onVerified] Function that will be called on token received
* @return {HTMLElement} uCaptcha box
*/
function uCaptchaBox(websiteKey) {
const styles = `
#ucaptcha-container {
font-family: Arial;
}
#ucaptcha-next {
border: none;
background-color: royalblue;
color: #FFF;
padding: 10px 20px;
text-transform: uppercase;
border-radius: 3px;
}
#ucaptcha-grid {
width: 384px;
height: 384px;
transition: .2s ease;
}
#ucaptcha-grid.blurred {
background: rgba(255, 255, 255, 0.4);
}
#ucaptcha-grid td {
transition: .1s ease;
cursor: pointer;
padding: 10px;
}
function uCaptchaBox(websiteKey, onVerified) {
/** @type {import('../models/Session.js').Session} */
let session;
#ucaptcha-grid td.selected {
padding: 0;
border: 10px solid white;
}
`;
const styleTag = ce('style');
styleTag.innerHTML = styles;
document.head.appendChild(styleTag);
const checkbox = ce('div', {
style: 'height:25px;display:inline-block',
});
checkbox.style.cursor = 'pointer';
checkbox.style.borderRadius = '3px';
checkbox.style.border = '2px solid #888';
checkbox.style.width = '25px';
const captchaBox = ce('div', {id: 'ucaptcha'});
const captchaBox = ce('div');
const checkbox = ce('div', {id: 'uc-checkbox'});
captchaBox.appendChild(checkbox);
const captchaContainer = ce('div', {id: 'ucaptcha-container'});
const captchaContainer = ce('div', {id: 'uc-container', style: 'visibility: hidden'});
const imageTagTitle = ce('h3', {id: 'ucaptcha-caption'});
const imageTagTitle = ce('p', {id: 'uc-caption'});
captchaContainer.appendChild(imageTagTitle);
const image = ce('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'});
const image = ce('img', {id: 'uc-img', style: 'position:absolute;display:block;z-index:-999'});
captchaContainer.appendChild(image);
const btn = ce('button', {id: 'ucaptcha-next'});
btn.textContent = 'Next';
const imageGrid = ce('table', {id: 'uc-grid'});
for (let i = 0; i < 4; i++) {
const tr = ce('tr');
for (let ii = 0; ii < 4; ii++) {
const td = ce('td',
{'style': 'cursor:pointer'});
td.addEventListener('click', (e)=>{
e.target.classList.toggle('selected');
});
tr.appendChild(td);
}
imageGrid.appendChild(tr);
}
captchaContainer.appendChild(imageGrid);
captchaBox.appendChild(captchaContainer);
captchaBox.appendChild(btn);
const nextBtn = ce('button', {id: 'uc-next'});
nextBtn.textContent = 'Next';
nextBtn.addEventListener('click', async (e)=>{
await verifyImage(session, captchaBox, captchaContainer, onVerified);
});
captchaContainer.appendChild(nextBtn);
checkbox.onclick = async function() {
/** @type {import('../models/Session.js').Session} */
const session = await reloadSession(websiteKey);
const overlay = ce('div', {id: 'uc-overlay', style: 'display: none'});
document.body.appendChild(overlay);
qs('#ucaptcha-caption').innerHTML = `Select all squares with <b>${session['imageTag']}s</b>`;
document.body.appendChild(captchaContainer);
const imageGrid = ce('table', {id: 'ucaptcha-grid'});
for (let i = 0; i < 4; i++) {
const tr = ce('tr');
for (let ii = 0; ii < 4; ii++) {
const td = ce('td',
{'style': 'cursor:pointer'});
td.addEventListener('click', (e)=>{
e.target.classList.toggle('selected');
});
tr.appendChild(td);
}
imageGrid.appendChild(tr);
captchaBox.addEventListener('click', async ()=>{
if (!session) {
session = await reloadSession(websiteKey);
await getImage(session, captchaContainer);
qs('#uc-caption').innerHTML = `Select all squares with <b>${session['imageTag']}s</b>`;
}
btn.addEventListener('click', (e)=>{
verifyImage(session, captchaBox);
});
overlay.style.display = 'block';
captchaContainer.style.visibility = 'visible';
});
captchaContainer.appendChild(imageGrid);
getImage(session, captchaBox);
checkbox.setAttribute('style',
checkbox.getAttribute('style') + 'background-color:royalblue;');
};
return captchaBox;
}
@ -107,10 +79,11 @@ function uCaptchaBox(websiteKey) {
* Instantiate a uCaptcha box
* @param {string} websiteKey
* @param {string} selector
* @param {Function} [onVerified]
*/
export function create(websiteKey, selector) {
export function create(websiteKey, selector, onVerified) {
// const iframe = ce("iframe");
// iframe.setAttribute("src", "https://localhost:444/?k="+websiteKey)
qs(selector).appendChild(uCaptchaBox(websiteKey));
qs(selector).appendChild(uCaptchaBox(websiteKey, onVerified));
}

View File

@ -1,4 +1,5 @@
import request from '../util/request.js';
import {querySelector as qs} from '../util/document.js';
/**
* @typedef {import('../../models/Session.js').Session} Session
@ -7,16 +8,15 @@ import request from '../util/request.js';
/**
* Instantiate the uCaptcha loop
* @param {Session} session
* @param {HTMLElement} captchaBox
* @param {HTMLElement} captchaContainer
*/
export default function getImage(session, captchaBox) {
request(`/api/image?s=${session['sessionId']}`, {_responseType: 'blob'})
.then((blob)=>{
const imageUrl = URL.createObjectURL(blob);
console.log(imageUrl);
captchaBox
.querySelector('#ucaptcha-container')
.querySelector('#ucaptcha-img')
.setAttribute('src', imageUrl);
});
export default async function getImage(session, captchaContainer) {
const blob = await request(
`/api/image?s=${session['sessionId']}`, {_responseType: 'blob'});
const imageUrl = URL.createObjectURL(blob);
captchaContainer.querySelector('#uc-img')
.setAttribute('src', imageUrl);
return;
}

View File

@ -1,6 +1,7 @@
import request from '../util/request.js';
import getImage from './getImage.js';
import {querySelector as qs} from '../util/document.js';
import checkmark from '../res/checkmark.js';
/**
@ -11,9 +12,12 @@ import {querySelector as qs} from '../util/document.js';
* Verify the image
* @param {Session} session
* @param {HTMLElement} captchaBox
* @param {HTMLElement} captchaContainer
* @param {Function} [onVerified]
*/
export default async function verifyImage(session, captchaBox) {
const captchaGrid = captchaBox.querySelector('#ucaptcha-grid');
export default async function verifyImage(
session, captchaBox, captchaContainer, onVerified) {
const captchaGrid = captchaContainer.querySelector('#uc-grid');
captchaGrid.classList.add('blurred');
const tds = captchaGrid.querySelectorAll('td');
@ -37,10 +41,19 @@ export default async function verifyImage(session, captchaBox) {
/** @type {Session} */
const resp = await request('/api/verify', {_method: 'POST', _body: body});
qs('#ucaptcha-caption')
if (resp.token) {
// FIXME: Doesn't work
captchaBox.removeEventListener('click', async ()=>{});
captchaBox.querySelector('#uc-checkbox').innerHTML += checkmark;
captchaContainer.style.visibility = 'hidden';
onVerified(resp.token);
return;
}
qs('#uc-caption')
.innerHTML = `Select all squares with <b>${resp['imageTag']}s</b>`;
getImage(session, captchaBox);
await getImage(session, captchaContainer);
captchaGrid.classList.remove('blurred');

View File

@ -0,0 +1,8 @@
/* eslint-disable max-len */
export default
`
<svg version="1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
<polygon fill="#43A047" stroke="#43A047" stroke-width="2" points="40.6,12.1 17,35.7 7.4,26.1 4.6,29 17,41.3 43.4,14.9"/>
</svg>
`;

78
src/client/res/styles.js Normal file
View File

@ -0,0 +1,78 @@
// For syntax highlighting within VSCode
const styled = {
div(p) {
return p;
},
};
export default styled.div`
#ucaptcha {
position: relative;
display: flex;
align-items: center;
padding: 0 24px;
font-family: Arial;
border: 1px solid #AAA;
box-shadow: 0 0 4px 1px rgba(0,0,0,0.08);
background: #F8F8F8;
width: 250px;
height: 70px;
border-radius: 4px;
}
#uc-checkbox {
width: 25px;
height: 25px;
border: 2px solid #BBB;
background: #FFF;
border-radius: 3px;
cursor: pointer;
}
#ucaptcha:active #uc-checkbox {
background: #EEE;
}
#ucaptcha:hover #uc-checkbox {
background: #FCFCFC;
}
#uc-container {
top: 0;
position: fixed;
z-index: 999999;
background: #FFF;
padding: 12px;
transition: 1s ease all;
}
#uc-overlay {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
background: rgba(0,0,0,0.4);
z-index: 99999;
}
#uc-next {
border: none;
background-color: royalblue;
color: #FFF;
padding: 10px 20px;
text-transform: uppercase;
border-radius: 3px;
}
#uc-grid {
width: 384px;
height: 384px;
transition: .2s ease;
}
#uc-grid.blurred {
background: rgba(255, 255, 255, 0.4);
}
#uc-grid td {
transition: .1s ease;
cursor: pointer;
padding: 10px;
}
#uc-grid td.selected {
padding: 0;
border: 10px solid white;
}
`;

View File

@ -1,4 +1,5 @@
import fs from 'fs';
import {randomBytes} from './utils.js';
import path from 'path';
import {client} from '../idb.js';
import {PROJECT_ROOT, MAX_SESSION_TIME, TAGS} from '../R.js';
@ -6,7 +7,9 @@ import {argmaxThresh} from './utils.js';
import pickRandomFile from './pickRandomFile.js';
// Anything above this value is considered a true selection within image matrix
const TOLERANCE = 0.6;
const MAT_TOLERANCE = process.env.NODE_ENV === 'development' ? 999.0 : 0.6;
// A user with a score above this value is considered a human
const SCORE_TOLERANCE = 0.9;
/**
* @typedef {Object} FileMat
@ -95,8 +98,15 @@ export default async function verifyImage(sessionId, userMat) {
console.log('Result: ', result);
let {image, score, websiteKey} = result;
/** @type {import('../models/Session.js').Session)} */
const session = {
sessionId,
websiteKey,
};
const imageMat = getMat(image);
const trueArgmax = argmaxThresh(imageMat.mat, TOLERANCE).join(',');
const trueArgmax = argmaxThresh(imageMat.mat, MAT_TOLERANCE).join(',');
const userArgmax = argmaxThresh(userMat, 1).join(',');
updateMat(imageMat, userMat, image);
@ -115,6 +125,25 @@ export default async function verifyImage(sessionId, userMat) {
score += delta;
}
if (score >= SCORE_TOLERANCE) {
const nonce = randomBytes(24);
session.token = nonce;
const payload = JSON.stringify({
websiteKey,
});
await client.del(sessionId);
await client.setex(nonce, 120, payload);
return [
null,
session,
{
removeCookie: websiteKey,
},
];
}
const newImageFilename = await pickRandomFile();
console.log('New image', newImageFilename);
// TODO: Make imageTagId dynamic
@ -129,12 +158,7 @@ export default async function verifyImage(sessionId, userMat) {
await client.setex(sessionId, MAX_SESSION_TIME, update);
/** @type {import('../models/Session.js').Session)} */
const session = {
sessionId,
websiteKey,
imageTag: TAGS[newImageTagId],
};
session.imageTag = TAGS[newImageTagId];
return [
null,

View File

@ -0,0 +1,35 @@
import {client} from '../idb.js';
/**
* Verify token from ucaptcha response
* @param {string} secret
* @param {string} token
*/
export default async function verifyToken(secret, token) {
// TODO: We need authentication of the secret key!
const response = {
success: null,
key: null,
errors: [],
};
if (!secret || !token) {
response.errors.push('INVALID_REQUEST');
return response;
}
const resp = await client.get(token);
if (!resp) {
response.success = false;
return response;
}
await client.del(token);
const result = JSON.parse(resp);
response.success = true;
response.key = result.websiteKey;
return response;
}

View File

@ -3,13 +3,13 @@
* @property {string} sessionId User session ID
* @property {string} websiteKey Website ID
* @property {string} [imageTag] Human readable text image tag
* @property {string} [token] Token used for verification of session
*/
/**
* @typedef {Object} IDBSession User session model in IDB
* @property {string} sessionId User session ID
* @property {string} websiteKey Website ID
* @property {string} imageTag Human readable text image tag
* @property {string} image Image URL for challenge
* @property {number} obj Image object ID
* @property {number} score How likely is the user to be a human?

View File

@ -0,0 +1,23 @@
import express from 'express';
import verifyToken from '../../helpers/verifyToken.js';
const router = new express.Router();
/**
* @param {express.Request} req
* @param {express.Response} res
*/
async function tokenVerifyHandler(req, res) {
const {secret, token} = req.body;
const result = await verifyToken(secret, token);
if (result.errors.length) {
res.status(400).json(result);
return;
}
res.json(result);
}
export default router.post('/tokenverify', tokenVerifyHandler);

View File

@ -12,11 +12,17 @@ const verifyImageHandler = async (req, res)=>{
const sessionId = req.body.s;
const mat = req.body.mat;
const [error, session] = await verifyImage(sessionId, mat);
let [error, session, actions] = await verifyImage(sessionId, mat);
actions = actions || {};
if (error) {
sendJson(res, {
error,
});
return;
}
if (actions.removeCookie) {
res.clearCookie(actions.removeCookie);
}
sendJson(res, session);

View File

@ -23,6 +23,13 @@ if (process.env.NODE_ENV === 'development') {
import apiImage from './routes/api/image.js';
import apiVerify from './routes/api/verify.js';
import apiReload from './routes/api/reload.js';
app.use('/api', [apiImage, apiVerify, apiReload]);
import apiTokenVerify from './routes/api/tokenverify.js';
app.use('/api', [
apiImage,
apiVerify,
apiReload,
apiTokenVerify,
]);
export {app};

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>uCaptcha Test</title>
<script src="../dist/client/bundle.js"></script>
</head>
<body>
<div class="captcha-box"></div>
<script>
ucaptcha.create("hUYUhuytryftyi6787yYtg67", ".captcha-box")
</script>
</body>
</html>