From 723bcc3755d6daf38667d4f07b052be5777a1dd7 Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Fri, 14 Jan 2022 22:19:18 +1100 Subject: [PATCH] Move *.jst from / to /_lib to keep things tidy, implement /_lib/Problem.jst and /_lib/post_request.jst to contain repetitive code for API call implementation, for the client side move /api/sign_up.js.min to more general /js/api_call.js.min --- _lib/Problem.jst | 9 + breadcrumbs.jst => _lib/breadcrumbs.jst | 0 navbar.jst => _lib/navbar.jst | 2 +- page.jst => _lib/page.jst | 2 +- _lib/post_request.jst | 75 +++++++++ session_cookie.jst => _lib/session_cookie.jst | 0 api/errors.json | 8 +- api/sign_up.js.min | 28 ---- api/sign_up.json.jst | 155 ------------------ api/sign_up/create_account.json.jst | 69 ++++++++ api/verification_image.png.jst | 2 +- contact/index.html.jst | 4 +- index.html.jst | 4 +- js/api_call.js.min | 12 ++ jsdoc.dir.jst | 2 +- my_account/index.html.jst | 4 +- my_account/sign_up/index.html.jst | 28 +++- search/index.html.jst | 4 +- sphinx.dir.jst | 2 +- 19 files changed, 204 insertions(+), 206 deletions(-) create mode 100644 _lib/Problem.jst rename breadcrumbs.jst => _lib/breadcrumbs.jst (100%) rename navbar.jst => _lib/navbar.jst (99%) rename page.jst => _lib/page.jst (98%) create mode 100644 _lib/post_request.jst rename session_cookie.jst => _lib/session_cookie.jst (100%) delete mode 100644 api/sign_up.js.min delete mode 100644 api/sign_up.json.jst create mode 100644 api/sign_up/create_account.json.jst create mode 100644 js/api_call.js.min diff --git a/_lib/Problem.jst b/_lib/Problem.jst new file mode 100644 index 0000000..e403795 --- /dev/null +++ b/_lib/Problem.jst @@ -0,0 +1,9 @@ +class Problem { + constructor(title, detail, status) { + this.title = title + this.detail = detail + this.status = status + } +} + +return Problem diff --git a/breadcrumbs.jst b/_lib/breadcrumbs.jst similarity index 100% rename from breadcrumbs.jst rename to _lib/breadcrumbs.jst diff --git a/navbar.jst b/_lib/navbar.jst similarity index 99% rename from navbar.jst rename to _lib/navbar.jst index fea0c4a..8498bb0 100644 --- a/navbar.jst +++ b/_lib/navbar.jst @@ -8,7 +8,7 @@ return async (env, head, body, scripts) => { let icon_search_mono = await env.site.get_min_svg('/_svg/icon_search_mono.svg') let logo_large = await env.site.get_min_svg('/_svg/logo_large.svg') let menu = await env.site.get_menu('/_menu.json') - let page = await _require('/page.jst') + let page = await _require('/_lib/page.jst') //let session = await _require('/session.jst') // initialize env.sessions, env.session_key, env.session diff --git a/page.jst b/_lib/page.jst similarity index 98% rename from page.jst rename to _lib/page.jst index 7345f29..058ae05 100644 --- a/page.jst +++ b/_lib/page.jst @@ -5,7 +5,7 @@ let crypto = require('crypto') return async (env, head, body, scripts) => { let favicons = await env.site.get_min_html('/_favicon/favicons.html') let globals = await env.site.get_json('/_config/globals.json') - let session_cookie = await _require('/session_cookie.jst') + let session_cookie = await _require('/_lib/session_cookie.jst') // initialize env.session_key, set cookie in env.response let transaction = await env.site.database.Transaction() diff --git a/_lib/post_request.jst b/_lib/post_request.jst new file mode 100644 index 0000000..a20e797 --- /dev/null +++ b/_lib/post_request.jst @@ -0,0 +1,75 @@ +let stream_buffers = require('stream-buffers') + +return async (env, api, func) => { + let Problem = await _require('/_lib/Problem.jst') + + let result + try { + if (env.request.method !== 'POST') { + env.response.setHeader('Allow', 'POST') + throw new Problem( + 'Method not allowed', + `The endpoint "${env.parsed_url.path}" requires a POST request.`, + 405 + ) + } + + let write_stream = new stream_buffers.WritableStreamBuffer() + let data = new Promise( + (resolve, reject) => { + write_stream. + on('finish', () => {resolve(write_stream.getContents())}). + on('error', () => {reject()}) + } + ) + env.request.pipe(write_stream) + let arguments = JSON.parse((await data).toString()) + console.log('api', api, 'arguments', arguments) + + result = await func(...arguments) + if (result === undefined) + result = null + console.log('api', api, 'result', result) + } + catch (error) { + let problem = + error instanceof Problem ? + error : + new Problem( + // title + 'Internal server error', + // details + error.message, + // status + 500 + ) + console.log('api', api, 'problem', problem.detail) + + env.mime_type = 'application/problem+json; charset=utf-8' + env.site.serve( + env, + problem.status, + Buffer.from( + JSON.stringify( + { + title: problem.title, + detail: problem.detail, + status: problem.status + }, + null, + 2 + ) + '\n', + 'utf-8' + ), + 'post_request.jst' + ) + return + } + + env.site.serve( + env, + 200, + Buffer.from(JSON.stringify(result, null, 2) + '\n', 'utf-8'), + 'post_request.jst' + ) +} diff --git a/session_cookie.jst b/_lib/session_cookie.jst similarity index 100% rename from session_cookie.jst rename to _lib/session_cookie.jst diff --git a/api/errors.json b/api/errors.json index 28b7e37..fc1b415 100644 --- a/api/errors.json +++ b/api/errors.json @@ -18,5 +18,11 @@ "417": "Expectation failed", "418": "No verification image in session", "419": "Verification code mismatch", - "420": "Account already exists" + "420": "Account already exists", + "500": "Internal server error", + "501": "Not implemented", + "502": "Bad gateway", + "503": "Service unavailable", + "504": "Gateway timeout", + "505": "HTTP version not supported" } diff --git a/api/sign_up.js.min b/api/sign_up.js.min deleted file mode 100644 index e4140d4..0000000 --- a/api/sign_up.js.min +++ /dev/null @@ -1,28 +0,0 @@ -/*let*/ sign_up = async ( - email, - verification_code, - given_names, - family_name, - password, - contact_me -) => { - let response = await fetch( - '/api/sign_up.json', - { - method: 'POST', - body: JSON.stringify( - { - email, - verification_code, - given_names, - family_name, - password, - contact_me - } - ) - } - ) - if (!response.ok) - throw new Error((await response.json()).detail) - return /*await*/ response.json() -} diff --git a/api/sign_up.json.jst b/api/sign_up.json.jst deleted file mode 100644 index 12483f5..0000000 --- a/api/sign_up.json.jst +++ /dev/null @@ -1,155 +0,0 @@ -let stream_buffers = require('stream-buffers') -let XDate = require('xdate') - -return async env => { - let session_cookie = await _require('/session_cookie.jst') - - if (env.request.method !== 'POST') { - env.response.setHeader('Allow', 'POST') - env.mime_type = 'application/problem+json; charset=utf-8' - env.site.serve( - env, - 405, - Buffer.from( - JSON.stringify( - { - title: 'Method not allowed', - detail: `The endpoint "${env.parsed_url.path}" requires a POST request.`, - status: 405 - }, - null, - 2 - ) + '\n', - 'utf-8' - ), - 'sign_up.json.jst' - ) - return - } - - let write_stream = new stream_buffers.WritableStreamBuffer() - let data = new Promise( - (resolve, reject) => { - write_stream. - on('finish', () => {resolve(write_stream.getContents())}). - on('error', () => {reject()}) - } - ) - env.request.pipe(write_stream) - let query = JSON.parse((await data).toString()) - let email = query.email.toLowerCase() - console.log('sign up', email) - - // initialize env.session_key, set cookie in env.response - let transaction = await env.site.database.Transaction() - let session = await session_cookie(env, transaction) - - let captcha = await session.get('captcha') - if (captcha === undefined || XDate.now() >= captcha.get('expires')) { - transaction.rollback() - - env.mime_type = 'application/problem+json; charset=utf-8' - env.site.serve( - env, - 418, - Buffer.from( - JSON.stringify( - { - title: 'No verification image in session', - detail: `Please call the "/api/verification_image.png" endpoint to create a verification image, in same session as the "/api/sign_up.json" call and less than one hour prior.`, - status: 418 - }, - null, - 2 - ) + '\n', - 'utf-8' - ), - 'sign_up.json.jst' - ) - return - } - - - let verification_code = query.verification_code.toLowerCase() - let captcha_text = await captcha.get('text') - if (verification_code !== captcha_text) { - console.log(`verification code mismatch, \"${verification_code}\" should be \"${captcha_text}\"`) - transaction.rollback() - - env.mime_type = 'application/problem+json; charset=utf-8' - env.site.serve( - env, - 419, - Buffer.from( - JSON.stringify( - { - title: 'Verification code mismatch', - detail: `The provided verification code "${verification_code}" did not match the verification image.`, - status: 419 - }, - null, - 2 - ) + '\n', - 'utf-8' - ), - 'sign_up.json.jst' - ) - return - } - - let accounts = await ( - await transaction.get({}) - ).get('accounts', {}) - - if (accounts.has(email)) { - transaction.rollback() - - env.mime_type = 'application/problem+json; charset=utf-8' - env.site.serve( - env, - 420, - Buffer.from( - JSON.stringify( - { - title: 'Account already exists', - detail: `The email "${email}" already has an account registered.`, - status: 420 - }, - null, - 2 - ) + '\n', - 'utf-8' - ), - 'sign_up.json.jst' - ) - return - } - accounts.set( - email, - transaction.json_to_logjson( - { - given_names: query.given_names || '', - family_name: query.family_name || '', - password: query.password || '', - contact_me: query.contact_me || false, - email_verified: false - } - ) - ) - - await transaction.commit() - - env.site.serve( - env, - 200, - Buffer.from( - JSON.stringify( - null, - null, - 2 - ) + '\n', - 'utf-8' - ), - 'sign_up.json.jst' - ) -} diff --git a/api/sign_up/create_account.json.jst b/api/sign_up/create_account.json.jst new file mode 100644 index 0000000..ba00760 --- /dev/null +++ b/api/sign_up/create_account.json.jst @@ -0,0 +1,69 @@ +let XDate = require('xdate') + +return async env => { + let post_request = await _require('/_lib/post_request.jst') + let session_cookie = await _require('/_lib/session_cookie.jst') + let Problem = await _require('/_lib/Problem.jst') + + post_request( + // env + env, + // api + '/api/sign_up/create_account', + // func + async (verification_code, details) => { + // coerce and/or validate + verification_code = verification_code.slice(0, 6).toLowerCase() + details = { + email: details.email.slice(0, 256).toLowerCase(), + given_names: details.given_names.slice(0, 256), + family_name: details.family_name.slice(0, 256), + password: details.password.slice(0, 256), + contact_me: details.contact_me ? true : false + } + + let transaction = await env.site.database.Transaction() + try { + // initialize env.session_key, set cookie in env.response + let session = await session_cookie(env, transaction) + + let captcha = await session.get('captcha') + if (captcha === undefined || XDate.now() >= captcha.get('expires')) + throw new Problem( + 'No verification image in session', + `Please call the "/api/verification_image.png" endpoint to create a verification image, in same session as the "/api/sign_up.json" call and less than one hour prior.`, + 418 + ) + + let captcha_text = await captcha.get('text') + if (verification_code !== captcha_text) { + console.log(`verification code mismatch, \"${verification_code}\" should be \"${captcha_text}\"`) + + throw new Problem( + 'Verification code mismatch', + `The provided verification code "${verification_code}" did not match the verification image.`, + 419 + ) + } + + let accounts = await ( + await transaction.get({}) + ).get('accounts', {}) + + if (accounts.has(details.email)) + throw new Problem( + 'Account already exists', + `The email "${details.email}" already has an account registered.`, + 420 + ) + accounts.set(details.email, transaction.json_to_logjson(details)) + + await transaction.commit() + } + catch (error) { + transaction.rollback() + throw error + } + } + ) +} diff --git a/api/verification_image.png.jst b/api/verification_image.png.jst index db41565..7d0bfea 100644 --- a/api/verification_image.png.jst +++ b/api/verification_image.png.jst @@ -3,7 +3,7 @@ let util = require('util') let XDate = require('xdate') return async env => { - let session_cookie = await _require('/session_cookie.jst') + let session_cookie = await _require('/_lib/session_cookie.jst') let captcha = captchagen.create() captcha.generate() diff --git a/contact/index.html.jst b/contact/index.html.jst index 3b89d33..2216fc6 100644 --- a/contact/index.html.jst +++ b/contact/index.html.jst @@ -3,9 +3,9 @@ let stream_buffers = require('stream-buffers') let XDate = require('xdate') return async env => { - let breadcrumbs = await _require('/breadcrumbs.jst') + let breadcrumbs = await _require('/_lib/breadcrumbs.jst') let globals = await env.site.get_json('/_config/globals.json') - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') await navbar( env, diff --git a/index.html.jst b/index.html.jst index 118c236..c34c82d 100644 --- a/index.html.jst +++ b/index.html.jst @@ -1,7 +1,7 @@ return async env => { - let breadcrumbs = await _require('/breadcrumbs.jst') + let breadcrumbs = await _require('/_lib/breadcrumbs.jst') let icon_jst = await env.site.get_min_svg('/_svg/icon_jst.svg') - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') let icon_pitree = await env.site.get_min_svg('/_svg/icon_pitree.svg') await navbar( diff --git a/js/api_call.js.min b/js/api_call.js.min new file mode 100644 index 0000000..7ddced8 --- /dev/null +++ b/js/api_call.js.min @@ -0,0 +1,12 @@ +api_call = async (endpoint, ...arguments) => { + let response = await fetch( + endpoint, + { + method: 'POST', + body: JSON.stringify(arguments) + } + ) + if (!response.ok) + throw new Error((await response.json()).detail) + return /*await*/ response.json() +} diff --git a/jsdoc.dir.jst b/jsdoc.dir.jst index b22ef9f..35f8d86 100644 --- a/jsdoc.dir.jst +++ b/jsdoc.dir.jst @@ -20,7 +20,7 @@ let serve_html = async (env, pathname) => { } // insert title and body into our navbar - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') await navbar( env, // head diff --git a/my_account/index.html.jst b/my_account/index.html.jst index 42331ee..0e44a6c 100644 --- a/my_account/index.html.jst +++ b/my_account/index.html.jst @@ -3,9 +3,9 @@ let stream_buffers = require('stream-buffers') return async env => { //let account = await _require('/account.jst') - let breadcrumbs = await _require('/breadcrumbs.jst') + let breadcrumbs = await _require('/_lib/breadcrumbs.jst') let menu = await env.site.get_menu('/my_account/_menu.json') - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') await navbar( env, diff --git a/my_account/sign_up/index.html.jst b/my_account/sign_up/index.html.jst index 9bb63e7..8eaff34 100644 --- a/my_account/sign_up/index.html.jst +++ b/my_account/sign_up/index.html.jst @@ -1,8 +1,8 @@ return async env => { - let breadcrumbs = await _require('/breadcrumbs.jst') + let breadcrumbs = await _require('/_lib/breadcrumbs.jst') let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg') let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg') - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') await navbar( env, @@ -155,9 +155,14 @@ return async env => { }, // scripts async _out => { - script(src="/api/sign_up.js") {} + script(src="/js/api_call.js") {} script { + let sign_up_create_account = async (...arguments) => api_call( + '/api/sign_up/create_account.json', + ...arguments + ) + $(document).ready( () => { $('#step-1-continue').click( @@ -182,12 +187,17 @@ return async env => { $('#step-2-cross').hide() $('#step-2-spinner').show() try { - await sign_up( - document.getElementById('email').value, - document.getElementById('verification-code').value - document.getElementById('given-names').value, - document.getElementById('family-name').value, - document.getElementById('password').value + await sign_up_create_account( + // verification_code + document.getElementById('verification-code').value, + // details + { + email: document.getElementById('email').value, + given_names: document.getElementById('given-names').value, + family_name: document.getElementById('family-name').value, + password: document.getElementById('password').value, + contact_me: document.getElementById('contact-me').value + } ) } catch (e) { diff --git a/search/index.html.jst b/search/index.html.jst index e80bbfe..b43beb1 100644 --- a/search/index.html.jst +++ b/search/index.html.jst @@ -2,8 +2,8 @@ let assert = require('assert') let querystring = require('querystring') return async env => { - let breadcrumbs = await _require('/breadcrumbs.jst') - let navbar = await _require('/navbar.jst') + let breadcrumbs = await _require('/_lib/breadcrumbs.jst') + let navbar = await _require('/_lib/navbar.jst') let zet_site = await env.site.get_zettair('/_zet/site') let query = env.parsed_url.query.query diff --git a/sphinx.dir.jst b/sphinx.dir.jst index 39baefb..0856b4e 100644 --- a/sphinx.dir.jst +++ b/sphinx.dir.jst @@ -20,7 +20,7 @@ let serve_html = async (env, pathname) => { } // insert head before/after stylesheet and body into our navbar - let navbar = await _require('/navbar.jst') + let navbar = await _require('/_lib/navbar.jst') await navbar( env, // head -- 2.34.1