From bdae28a8251f641c397e8218231e8c4166a6ad20 Mon Sep 17 00:00:00 2001 From: Devshh <96d5d7b5-774d-482c-9da6-50ec8b5cf6c2@anonaddy.me> Date: Tue, 24 Mar 2020 18:06:20 +0400 Subject: [PATCH] Added image tags and update readme --- README.md | 5 +++ dev/mat.json | 47 +---------------------------- src/R.js | 6 ++++ src/client/client.js | 42 +++++++++++++++++--------- src/client/helpers/reloadSession.js | 2 +- src/client/helpers/verifyImage.js | 17 ++++++++--- src/models/IDBSession.js | 1 - src/models/ImageObject.js | 5 --- src/models/UserSession.js | 38 ++++++++++++++++++----- src/routes/api/index.js | 6 ++-- src/routes/api/reloadSession.js | 21 ++++++------- src/routes/api/verifyImage.js | 29 +++++++++++++----- 12 files changed, 115 insertions(+), 104 deletions(-) delete mode 100644 src/models/ImageObject.js diff --git a/README.md b/README.md index 02a3e97..407a203 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,11 @@ It has much the same API and has an additional benefit of helping [OpenStreetCam # Donate If you use uCaptcha in production, or want to see the project grow, consider funding the project to help pay for hosting and bandwidth costs. + + XMR: `45uYweaLaasTFKdEHK9qADRZMatRE3U9vKFwT1kpaig26GnruZm1t21ipVjsC1KLeeL7sG3m8bHfhcce4tQJLDKNCFBLPar` + + BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn` @@ -16,6 +20,7 @@ BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn` - [ ] Think of a way to detect if user's selection is correct answer - [ ] Create tests - [ ] Configure Rollup to create 2 packages 1x esm 1x cjs +- [ ] Improve the UI ## Medium term (> 1 month) - [ ] Increase security and accuracy of predictions diff --git a/dev/mat.json b/dev/mat.json index 54176bb..9e26dfe 100644 --- a/dev/mat.json +++ b/dev/mat.json @@ -1,46 +1 @@ -{ - "B4mXt9gEOp1r": { - "mat": [ - 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.4, - 0.6, - 0.6, - 0.6 - ], - "nums": 1, - "obj": 0 - }, - "U63lqPRdPinG": { - "mat": [ - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.30000000000000004, - 0.7, - 0.7, - 0.7, - 0.30000000000000004, - 0.7, - 0.7, - 0.7 - ], - "nums": 2, - "obj": 0 - } -} \ No newline at end of file +{} \ No newline at end of file diff --git a/src/R.js b/src/R.js index e9191dc..bc4c431 100644 --- a/src/R.js +++ b/src/R.js @@ -11,3 +11,9 @@ export const PROJECT_ROOT = process.cwd(); export const IMAGES_FOLDER = path.join(PROJECT_ROOT, 'public', 'images'); export const MAX_SESSION_TIME = 60 * 30; + +export const TAGS = [ + null, + 'vehicle', + 'house', +] diff --git a/src/client/client.js b/src/client/client.js index 109094f..f5e7edb 100644 --- a/src/client/client.js +++ b/src/client/client.js @@ -1,7 +1,7 @@ import reloadSession from './helpers/reloadSession.js'; import getImage from './helpers/getImage.js'; import verifyImage from './helpers/verifyImage.js'; -import {createElement} from './util/document.js'; +import {createElement as ce, querySelector as qs} from './util/document.js'; /** * Create a uCaptcha box @@ -10,6 +10,9 @@ import {createElement} from './util/document.js'; */ function uCaptchaBox(websiteKey) { const styles = ` + #ucaptcha-container { + font-family: Arial; + } #ucaptcha-next { border: none; background-color: royalblue; @@ -21,6 +24,10 @@ function uCaptchaBox(websiteKey) { #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; @@ -33,11 +40,11 @@ function uCaptchaBox(websiteKey) { border: 10px solid white; } `; - const styleTag = createElement('style'); + const styleTag = ce('style'); styleTag.innerHTML = styles; document.head.appendChild(styleTag); - const checkbox = createElement('div', { + const checkbox = ce('div', { style: 'height:25px;display:inline-block', }); checkbox.style.cursor = 'pointer'; @@ -45,29 +52,34 @@ function uCaptchaBox(websiteKey) { checkbox.style.border = '2px solid #888'; checkbox.style.width = '25px'; - const captchaBox = createElement('div'); + const captchaBox = ce('div'); captchaBox.appendChild(checkbox); - const imageContainer = createElement('div', {id: 'ucaptcha-container'}); + const captchaContainer = ce('div', {id: 'ucaptcha-container'}); - const image = createElement('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'}); - imageContainer.appendChild(image); + const imageTagTitle = ce('h3', {id: 'ucaptcha-caption'}); + captchaContainer.appendChild(imageTagTitle); - const btn = createElement('button', {id: 'ucaptcha-next'}); + const image = ce('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'}); + captchaContainer.appendChild(image); + + const btn = ce('button', {id: 'ucaptcha-next'}); btn.textContent = 'Next'; - captchaBox.appendChild(imageContainer); + captchaBox.appendChild(captchaContainer); captchaBox.appendChild(btn); checkbox.onclick = async function() { /** @type {import('../shared/models/UserSession.js').UserSession} */ const session = await reloadSession(websiteKey); - const imageGrid = createElement('table', {id: 'ucaptcha-grid'}); + qs('#ucaptcha-caption').innerHTML = `Select all squares with ${session.getImageTag()}s`; + + const imageGrid = ce('table', {id: 'ucaptcha-grid'}); for (let i = 0; i < 4; i++) { - const tr = createElement('tr'); + const tr = ce('tr'); for (let ii = 0; ii < 4; ii++) { - const td = createElement('td', + const td = ce('td', {'style': 'cursor:pointer'}); td.addEventListener('click', (e)=>{ e.target.classList.toggle('selected'); @@ -82,7 +94,7 @@ function uCaptchaBox(websiteKey) { getImage(session, captchaBox); }); - imageContainer.appendChild(imageGrid); + captchaContainer.appendChild(imageGrid); getImage(session, captchaBox); @@ -98,8 +110,8 @@ function uCaptchaBox(websiteKey) { * @param {string} selector */ export function create(websiteKey, selector) { - // const iframe = createElement("iframe"); + // const iframe = ce("iframe"); // iframe.setAttribute("src", "https://localhost:444/?k="+websiteKey) - document.querySelector(selector).appendChild(uCaptchaBox(websiteKey)); + qs(selector).appendChild(uCaptchaBox(websiteKey)); } diff --git a/src/client/helpers/reloadSession.js b/src/client/helpers/reloadSession.js index a727d55..6a52835 100644 --- a/src/client/helpers/reloadSession.js +++ b/src/client/helpers/reloadSession.js @@ -5,7 +5,7 @@ import UserSession from '../../models/UserSession.js'; * @param {string} websiteKey * @return {Promise} */ -export default function initializeSession(websiteKey) { +export default function reloadSession(websiteKey) { return new Promise((resolve, reject)=>{ request(`/api/reload?k=${websiteKey}`) .then((resp)=>{ diff --git a/src/client/helpers/verifyImage.js b/src/client/helpers/verifyImage.js index b334e99..08d5893 100644 --- a/src/client/helpers/verifyImage.js +++ b/src/client/helpers/verifyImage.js @@ -1,7 +1,9 @@ import request from '../util/request.js'; +import UserSession from '../../models/UserSession.js'; +import {querySelector as qs} from '../util/document.js'; /** - * @typedef { import('../../models/UserSession.js').UserSession } UserSession + * @typedef {import('../../models/UserSession.js')} UserSession */ @@ -10,7 +12,8 @@ import request from '../util/request.js'; * @param {UserSession} session * @param {HTMLElement} captchaGrid */ -export async function verfiyImage(session, captchaGrid) { +export default async function verifyImage(session, captchaGrid) { + captchaGrid.classList.add('blurred'); const tds = captchaGrid.querySelectorAll('td'); const selectedTds = []; @@ -29,11 +32,15 @@ export async function verfiyImage(session, captchaGrid) { 'mat': mat, }; - await request('/api/verify', {_method: 'POST', _body: body}); + const resp = await request('/api/verify', {_method: 'POST', _body: body}); + + session.deserialize(resp); + qs('#ucaptcha-caption') + .innerHTML = `Select all squares with ${session.getImageTag()}s`; + + captchaGrid.classList.remove('blurred'); selectedTds.map((td)=>{ td.classList.remove('selected'); }); } - -export default verfiyImage; diff --git a/src/models/IDBSession.js b/src/models/IDBSession.js index aa2a9e2..38db360 100644 --- a/src/models/IDBSession.js +++ b/src/models/IDBSession.js @@ -7,4 +7,3 @@ * @property {number} obj Image object ID * @property {number} score How likely is the user to be a human? */ -export const IDBSession = {}; diff --git a/src/models/ImageObject.js b/src/models/ImageObject.js deleted file mode 100644 index 3ca8836..0000000 --- a/src/models/ImageObject.js +++ /dev/null @@ -1,5 +0,0 @@ -export default [ - null, - 'Vehicle', - 'House', -]; diff --git a/src/models/UserSession.js b/src/models/UserSession.js index 218e22b..08eed5f 100644 --- a/src/models/UserSession.js +++ b/src/models/UserSession.js @@ -8,6 +8,9 @@ export default function UserSession() { /** @private @type {string | undefined} */ this._websiteKey = undefined; + /** @private @type {string | undefined} */ + this._imageTag = undefined; + /** * Get session identifier @@ -43,6 +46,23 @@ export default function UserSession() { }; + /** + * Get image tag as text + * @return {string} + */ + this.getImageTag = function() { + return this._imageTag; + }; + + /** + * Set image tag as text + * @param {string} imageObject + */ + this.setImageTag = function(imageObject) { + this._imageTag = imageObject; + }; + + /** * Serialize the session into a JSON object * @return {Array} @@ -50,14 +70,12 @@ export default function UserSession() { this.serialize = function() { const sessionId = this.getSessionId(); const websiteKey = this.getWebsiteKey(); - - if (!sessionId || !websiteKey) { - throw Error(); - } + const imageTag = this.getImageTag(); return [ sessionId, websiteKey, + imageTag, ]; }; @@ -65,9 +83,13 @@ export default function UserSession() { * Deserialize session data from JSON object * @param {Array} payload */ - this.deserialize = function([_sessionId, _websiteKey]) { - console.log(_sessionId, _websiteKey); - this.setSessionId(_sessionId); - this.setWebsiteKey(_websiteKey); + this.deserialize = function([ + sessionId, + websiteKey, + imageTag, + ]) { + this.setSessionId(sessionId); + this.setWebsiteKey(websiteKey); + this.setImageTag(imageTag); }; } diff --git a/src/routes/api/index.js b/src/routes/api/index.js index d6680a3..8305748 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -49,10 +49,8 @@ router.post('/verify', async (req, res)=>{ const mat = req.body.mat; console.log(req.body); - await verifyImage(sessionId, mat); - sendJson(res, { - ok: 1, - }); + const session = await verifyImage(sessionId, mat); + sendJson(res, session.serialize()); }); export default router; diff --git a/src/routes/api/reloadSession.js b/src/routes/api/reloadSession.js index 46111d4..c89cd96 100644 --- a/src/routes/api/reloadSession.js +++ b/src/routes/api/reloadSession.js @@ -5,11 +5,11 @@ import pickRandomFile from './pickRandomFile.js'; import UserSession from '../../models/UserSession.js'; import {randomBytes} from '../../helpers/utils.js'; import {client} from '../../helpers/idb.js'; -import {MAX_SESSION_TIME} from '../../R.js'; +import {MAX_SESSION_TIME, TAGS} from '../../R.js'; -/** - * @typedef {import('../../models/IDBSession.js').IDBSession} IDBSession - */ +// /** +// * @typedef {import('../../models/IDBSession.js')} IDBSession +// */ /** @@ -20,28 +20,27 @@ import {MAX_SESSION_TIME} from '../../R.js'; export default async function initializeSession(websiteKey, cookies) { let randomSessionId; - const session = new UserSession(); if (cookies[websiteKey]) { randomSessionId = cookies[websiteKey]; - - session.setSessionId(randomSessionId); - session.setWebsiteKey(websiteKey); - - return session; } else { randomSessionId = randomBytes(8); } const image = await pickRandomFile(); + const imageTagId = 1; // TODO: Change later to be dynamic based on `image` + const session = new UserSession(); + session.setSessionId(randomSessionId); session.setWebsiteKey(websiteKey); + session.setImageTag(TAGS[imageTagId]); - /** @type {IDBSession} */ + /** @type {import('../../models/IDBSession.js')} */ const idbPayload = { sessionId: session.getSessionId(), websiteKey: session.getWebsiteKey(), image, + obj: imageTagId, score: 0.5, }; diff --git a/src/routes/api/verifyImage.js b/src/routes/api/verifyImage.js index 1e510a2..f589762 100644 --- a/src/routes/api/verifyImage.js +++ b/src/routes/api/verifyImage.js @@ -1,9 +1,13 @@ import fs from 'fs'; import path from 'path'; import {client} from '../../helpers/idb.js'; -import {PROJECT_ROOT, MAX_SESSION_TIME} from '../../R.js'; +import {PROJECT_ROOT, MAX_SESSION_TIME, TAGS} from '../../R.js'; import {argmaxThresh} from '../../helpers/utils.js'; import pickRandomFile from './pickRandomFile.js'; +import UserSession from '../../models/UserSession.js'; + + +/** @typedef {import('../../models/IDBSession.js')} IDBSession */ /** * @typedef {Object} FileMat @@ -80,13 +84,12 @@ function updateMat(imageMat, userMat, image) { */ export default function(sessionId, userMat) { return new Promise((resolve, reject)=>{ - client.get(sessionId, (err, result)=>{ - console.log(sessionId); + client.get(sessionId, (err, resp)=>{ if (err) return reject(err); - if (!result) return reject(new Error('Session expired')); + if (!resp) return reject(new Error('Session expired')); - /** @type {import('../../models/IDBSession.js').IDBSession} */ - result = JSON.parse(result); + /** @type {IDBSession} */ + const result = JSON.parse(resp); const imageMat = getMat(result.image); const trueArgmax = argmaxThresh(imageMat.mat, 0.6).join(','); @@ -109,13 +112,23 @@ export default function(sessionId, userMat) { } pickRandomFile().then((image)=>{ + // TODO: Make me dynamic + const newImageTag = 1; const update = JSON.stringify( - Object.assign(result, {image: image})); + Object.assign(result, { + image: image, + obj: newImageTag, + })); client.setex(sessionId, MAX_SESSION_TIME, update, (err)=>{ if (err) return reject(err); - resolve(); + const session = new UserSession(); + session.setSessionId(sessionId); + session.setWebsiteKey(result.websiteKey); + session.setImageTag(TAGS[newImageTag]); + + resolve(session); }); }); });