+++ /dev/null
-{
- "user": "contact@ndcode.org",
- "password": "XXXContact12",
- "host": "mail.ndcode.org",
- "tls": {"ciphers": "SSLv3"}
-}
+++ /dev/null
-{
- "user": "feedback@ndcode.org",
- "password": "XXXFeedback12",
- "host": "mail.ndcode.org",
- "tls": {"ciphers": "SSLv3"}
-}
"contact_to": "Nick Downing <nick@ndcode.org>",
"feedback_from": "NDCODE Feedback <feedback@ndcode.org>",
"feedback_to": "Nick Downing <nick@ndcode.org>",
+ "noreply_from": "NDCODE <noreply@ndcode.org>",
+ "noreply_signature": "NDCODE Team",
"copyright": "Integration Logic Pty Ltd trading as NDCODE and contributors"
}
--- /dev/null
+{
+ "host": "mail.ndcode.org",
+ "port": 587,
+ "secure": false,
+ "auth": {
+ "user": "contact@ndcode.org",
+ "pass": "XXXContact12"
+ },
+ "requireTLS": true
+}
--- /dev/null
+{
+ "host": "mail.ndcode.org",
+ "port": 587,
+ "secure": false,
+ "auth": {
+ "user": "feedback@ndcode.org",
+ "pass": "XXXFeedback12",
+ },
+ "requireTLS": true
+}
--- /dev/null
+{
+ "host": "mail.ndcode.org",
+ "port": 587,
+ "secure": false,
+ "auth": {
+ "user": "noreply@ndcode.org",
+ "pass": "XXXNoreply12"
+ },
+ "requireTLS": true
+}
let assert = require('assert')
let logjson = (await import('@ndcode/logjson')).default
-let EmailJSCache = require('@ndcode/emailjs_cache')
+let NodeMailerCache = require('@ndcode/nodemailer_cache')
let XDate = require('xdate')
let ZettairCache = require('@ndcode/zettair_cache')
super(resources, root, options, prev_site)
this.database = undefined
this.database_date = new XDate().toUTCString('yyyyMMdd')
- this.emailjs_cache = undefined
+ this.nodemailer_cache = undefined
this.zettair_cache = undefined
}
}
)
- assert(this.emailjs_cache === undefined)
- this.emailjs_cache = await this.resources.ref(
- 'emailjs_cache',
- async () => new EmailJSCache(true)
+ assert(this.nodemailer_cache === undefined)
+ this.nodemailer_cache = await this.resources.ref(
+ 'nodemailer_cache',
+ async () => new NodeMailerCache(true)
)
assert(this.zettair_cache === undefined)
assert(this.database !== undefined)
await this.resources.unref('database')
- assert(this.emailjs_cache !== undefined)
- await this.resources.unref('emailjs_cache')
+ assert(this.nodemailer_cache !== undefined)
+ await this.resources.unref('nodemailer_cache')
assert(this.zettair_cache !== undefined)
await this.resources.unref('zettair_cache')
this.database_date = new_database_date
}
- assert(this.emailjs_cache !== undefined)
- this.emailjs_cache.kick()
+ assert(this.nodemailer_cache !== undefined)
+ this.nodemailer_cache.kick()
assert(this.zettair_cache !== undefined)
this.zettair_cache.kick()
}
- // retrieves a particular email account (loaded into an emailjs object)
- get_emailjs(pathname) {
- return /*await*/ this.emailjs_cache.get(this.root + pathname)
+ // retrieves a particular email account (as a nodemailer transport)
+ get_nodemailer(pathname) {
+ return /*await*/ this.nodemailer_cache.get(this.root + pathname)
}
// retrieves a particular search index (node.js wrapper of a zettair object)
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('/_lib/page.jst')
- //let session = await _require('/session.jst')
-
- // initialize env.sessions, env.session_key, env.session
- //await session(env)
// initialize env.cart
//await cart(env)
div.scrollbar-fix {
div.container {
div.row.align-items-center.py-3 {
- div.col-sm-8 {
+ div.col-sm-7 {
_out.push(logo_large)
}
- div.'col-sm-4' {
- //div {
- // let signed_in =
- // Object.prototype.hasOwnProperty.call(env.session, 'account')
- // span#signed-in-status {
- // if (signed_in)
- // `Signed in as ${env.session.account}.`
- // else
- // 'Browsing as guest.'
- // }
- // ' '
- // if (signed_in)
- // a#sign-in(href="" hidden="") {'Sign in'}
- // else
- // a#sign-in(href="") {'Sign in'}
- // ' '
- // if (signed_in)
- // a#sign-out(href="") {'Sign out'}
- // else
- // a#sign-out(href="" hidden="") {'Sign out'}
- //}
- //p {}
+ div.'col-sm-5' {
+ div.'mb-1'.text-right {
+ span#signed-in-status {
+ if (env.signed_in_as !== null)
+ `Signed in as ${env.signed_in_as}.`
+ else
+ 'Browsing as guest.'
+ }
+ ' '
+ if (env.signed_in_as !== null)
+ a#sign-in(href="" hidden="") {'Sign in'}
+ else
+ a#sign-in(href="") {'Sign in'}
+ ' '
+ if (env.signed_in_as !== null)
+ a#sign-out(href="") {'Sign out'}
+ else
+ a#sign-out(href="" hidden="") {'Sign out'}
+ }
form/*.form-inline*/(action="/search/index.html") {
div.input-group {
let stream_buffers = require('stream-buffers')
-return async (env, api, func) => {
+return async (env, endpoint, func) => {
let Problem = await _require('/_lib/Problem.jst')
let result
)
env.request.pipe(write_stream)
let arguments = JSON.parse((await data).toString())
- console.log('api', api, 'arguments', JSON.stringify(arguments))
+ console.log('endpoint', endpoint, 'arguments', JSON.stringify(arguments))
result = await func(...arguments)
if (result === undefined)
result = null
- console.log('api', api, 'result', JSON.stringify(result))
+ console.log('endpoint', endpoint, 'result', JSON.stringify(result))
}
catch (error) {
let problem =
// title
'Internal server error',
// details
- error.message,
+ (error.stack || error.message).toString()
// status
500
)
- console.log('api', api, 'problem', problem.detail)
+ console.log('endpoint', endpoint, 'problem', problem.detail)
env.mime_type = 'application/problem+json; charset=utf-8'
env.site.serve(
-let XDate = require('xdate')
+let logjson = (await import('@ndcode/logjson')).default
let cookie = require('cookie')
let crypto = require('crypto')
+let XDate = require('xdate')
return async (env, transaction) => {
let cookies = cookie.parse(env.request.headers.cookie || '')
await transaction.get({})
).get('sessions', {})
- let session_key, session, expires = new XDate(now)
+ let session, expires = new XDate(now)
if (
Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
(
- session = await sessions.get(session_key = cookies.session_key)
+ session = await sessions.get(env.session_key = cookies.session_key)
) !== undefined &&
now < await session.get('expires', 0)
)
// first request for session, maybe a bot, retain session for only 1 day
expires.addDays(1)
do {
- session_key = crypto.randomBytes(16).toString('hex')
- } while (sessions.has(session_key))
+ env.session_key = crypto.randomBytes(16).toString('hex')
+ } while (sessions.has(env.session_key))
session = transaction.LazyObject()
- sessions.set(session_key, session)
+ sessions.set(env.session_key, session)
}
await session.set('expires', expires.getTime())
-
env.response.setHeader(
'Set-Cookie',
- `session_key=${session_key}; expires=${expires.toUTCString()}; path=/;`
+ `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/;`
)
- env.session_key = session_key
+ env.signed_in_as = await logjson.logjson_to_json(
+ await session.get('signed_in_as', null)
+ )
return session
}
"418": "No verification image in session",
"419": "Verification code mismatch",
"420": "Account already exists",
+ "421": "Account does not exist",
"500": "Internal server error",
"501": "Not implemented",
"502": "Bad gateway",
post_request(
// env
env,
- // api
+ // endpoint
'/api/sign_up/create_account',
// func
async (verification_code, details) => {
password: details.password.slice(0, 256),
contact_me: details.contact_me ? true : false
}
+ if (
+ verification_code.length < 6 ||
+ details.given_names.length === 0 ||
+ details.password.length < 8
+ )
+ throw new Problem(
+ 'Bad request',
+ 'Minimum length check failed',
+ 400
+ )
let transaction = await env.site.database.Transaction()
try {
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.`,
+ `Please call the "/api/verification_image.png" endpoint to create a verification image, in same session as the "/api/sign_up/create_account.json" call and less than one hour prior.`,
418
)
post_request(
// env
env,
- // api
+ // endpoint
'/api/sign_up/get_draft',
// func
async () => {
--- /dev/null
+let crypto = require('crypto')
+let logjson = (await import('@ndcode/logjson')).default
+let XDate = require('xdate')
+
+return async env => {
+ let nodemailer_noreply = await env.site.get_nodemailer(
+ '/_config/nodemailer_noreply.json'
+ )
+ let globals = await env.site.get_json('/_config/globals.json')
+ 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,
+ // endpoint
+ '/api/sign_up/send_verification_email',
+ // func
+ async email => {
+ // coerce and/or validate
+ email = email.slice(0, 256).toLowerCase()
+ if (email.length === 0)
+ throw new Problem(
+ 'Bad request',
+ 'Minimum length check failed',
+ 400
+ )
+
+ let transaction = await env.site.database.Transaction()
+ try {
+ // initialize env.session_key, set cookie in env.response
+ await session_cookie(env, transaction)
+
+ let account = await (
+ await (
+ await transaction.get({})
+ ).get('accounts', {})
+ ).get(email)
+ if (account === undefined)
+ throw new Problem(
+ 'Account does not exist',
+ `Please create the account for "${email}" before attempting to send a verification email.`
+ 421
+ )
+
+ let key = crypto.randomBytes(16).toString('hex')
+ let expires = new XDate()
+ expires.addDays(1)
+ account.set(
+ 'verify_email',
+ transaction.json_to_logjson({key, expires: expires.getTime()})
+ )
+
+ let given_names = await logjson.logjson_to_json(
+ await account.get('given_names', '')
+ )
+ let family_name = await logjson.logjson_to_json(
+ await account.get('family_name', '')
+ )
+ let name =
+ family_name.length ? `${given_names} ${family_name}` : given_names
+
+ await nodemailer_noreply.sendMail(
+ {
+ from: globals.noreply_from,
+ to: `${name} <${email}>`,
+ subject: 'Email address verification',
+ text: `Dear ${given_names},
+
+We have received a request to sign up using your email address.
+
+If this request is valid, please verify your email address by visiting the below link:
+${globals.site_url}/my_account/verify_email/index.html?email=${encodeURIComponent(email)}&key=${key}
+
+The link is valid for 24 hours.
+
+Thanks,
+${globals.noreply_signature}
+`
+ }
+ )
+
+ await transaction.commit()
+ }
+ catch (error) {
+ transaction.rollback()
+ throw error
+ }
+ }
+ )
+}
post_request(
// env
env,
- // api
+ // endpoint
'/api/sign_up/set_draft',
// func
async details => {
#!/bin/sh
rm -rf node_modules package-lock.json
-npm link @ndcode/emailjs_cache @ndcode/logjson @ndcode/zettair_cache
+npm link @ndcode/logjson @ndcode/nodemailer_cache @ndcode/zettair_cache
npm install
npm link
div.card-body {
p#step-2-message {'Please enter your details first.'}
- input.btn.btn-outline-secondary#step-2-back(type="button" value="Back") {}
+ input.btn.btn-outline-secondary.mr-3#step-2-back(type="button" value="Back") {}
+ input.btn.btn-outline-secondary#step-2-continue(type="button" value="Continue") {}
}
}
}
div.card-body {
p#step-3-message {'Please create your account first.'}
- input.btn.btn-outline-secondary#step-3-back(type="button" value="Back") {}
+ input.btn.btn-outline-secondary.mr-3#step-3-back(type="button" value="Back") {}
+ input.btn.btn-outline-secondary#step-3-resend-email(type="button" value="Resend email") {}
}
}
}
'/api/sign_up/set_draft.json',
...arguments
)
+ let sign_up_send_verification_email = async (...arguments) => api_call(
+ '/api/sign_up/send_verification_email.json',
+ ...arguments
+ )
- let details = () => {
+ let coerce_details = () => {
return {
email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
given_names: document.getElementById('given-names').value.slice(0, 256),
let draft_timeout_running = false
let draft_timeout_handler = async () => {
draft_timeout_running = false
- await sign_up_set_draft(details())
+ await sign_up_set_draft(coerce_details())
//console.log('draft', await sign_up_get_draft())
}
let draft_change_handler = () => {
}
}
+ let step_1 = async () => {
+ if (
+ !document.getElementById('given-names').reportValidity() ||
+ !document.getElementById('family-name').reportValidity() ||
+ !document.getElementById('email').reportValidity() ||
+ !document.getElementById('password').reportValidity() ||
+ !document.getElementById('verification-code').reportValidity()
+ ) {
+ $('#step-1-tick').hide()
+ $('#step-1-cross').show()
+ //$('#step-1-spinner').hide()
+ return false
+ }
+ $('#step-1-tick').show()
+ $('#step-1-cross').hide()
+ //$('#step-1-spinner').hide()
+ return true
+ }
+
+ let step_3_email = ''
+ let step_2 = async () => {
+ $('#step-2-tick').hide()
+ $('#step-2-cross').hide()
+ $('#step-2-spinner').show()
+ try {
+ let details = coerce_details()
+ await sign_up_create_account(
+ // verification_code
+ document.getElementById('verification-code').value.slice(0, 6).toLowerCase(),
+ // details
+ details
+ )
+ step_3_email = details.email
+ }
+ catch (e) {
+ $('#step-2-tick').hide()
+ $('#step-2-cross').show()
+ $('#step-2-spinner').hide()
+
+ document.getElementById('step-2-message').textContent = e.message
+ $('#step-2-collapse').collapse('show')
+ return false
+ }
+ $('#step-2-tick').show()
+ $('#step-2-cross').hide()
+ $('#step-2-spinner').hide()
+
+ document.getElementById('step-2-message').textContent = `Your account with email "${document.getElementById('email').value}" has been created.`
+ return true
+ }
+
+ let step_3 = async () => {
+ $('#step-3-tick').hide()
+ $('#step-3-cross').hide()
+ $('#step-3-spinner').show()
+ try {
+ await sign_up_send_verification_email(step_3_email)
+ }
+ catch (e) {
+ $('#step-3-tick').hide()
+ $('#step-3-cross').show()
+ $('#step-3-spinner').hide()
+
+ document.getElementById('step-3-message').textContent = e.message
+ $('#step-3-collapse').collapse('show')
+ return false
+ }
+ $('#step-3-tick').show()
+ $('#step-3-cross').hide()
+ $('#step-3-spinner').hide()
+
+ document.getElementById('step-3-message').textContent = 'Verification email has been sent. Please check your email for next steps.'
+ return true
+ }
+
document.addEventListener(
'DOMContentLoaded',
() => {
document.getElementById('step-1-continue').addEventListener(
'click',
async () => {
- if (
- !document.getElementById('given-names').reportValidity() ||
- !document.getElementById('family-name').reportValidity() ||
- !document.getElementById('email').reportValidity() ||
- !document.getElementById('password').reportValidity() ||
- !document.getElementById('verification-code').reportValidity()
- ) {
- $('#step-1-tick').hide()
- $('#step-1-cross').show()
- //$('#step-1-spinner').hide()
- return
- }
- $('#step-1-tick').show()
- $('#step-1-cross').hide()
- //$('#step-1-spinner').hide()
-
- $('#step-2-tick').hide()
- $('#step-2-cross').hide()
- $('#step-2-spinner').show()
- try {
- await sign_up_create_account(
- // verification_code
- document.getElementById('verification-code').value.slice(0, 6).toLowerCase(),
- // details
- details()
- )
- }
- catch (e) {
- $('#step-2-tick').hide()
- $('#step-2-cross').show()
- $('#step-2-spinner').hide()
-
- document.getElementById('step-2-message').textContent = e.message
- $('#step-2-collapse').collapse('show')
- return
- }
- $('#step-2-tick').show()
- $('#step-2-cross').hide()
- $('#step-2-spinner').hide()
-
- document.getElementById('step-2-message').textContent = `Your account with email "${document.getElementById('email').value}" has been created.`
- $('#step-2-collapse').collapse('show')
+ if (await step_1() && await step_2() && await step_3())
+ $('#step-3-collapse').collapse('show')
}
)
- $('#step-2-back').click(
+ document.getElementById('step-2-back').addEventListener(
+ 'click',
() => {$('#step-1-collapse').collapse('show')}
)
- $('#step-3-back').click(
+ document.getElementById('step-2-continue').addEventListener(
+ 'click',
+ async () => {
+ if (await step_3())
+ $('#step-3-collapse').collapse('show')
+ }
+ )
+
+ document.getElementById('step-3-back').addEventListener(
+ 'click',
() => {$('#step-2-collapse').collapse('show')}
)
+ document.getElementById('step-3-resend-email').addEventListener(
+ 'click',
+ async () => {
+ if (await step_3())
+ $('#step-3-collapse').collapse('show')
+ }
+ )
+
let image_seq = 1
$('#new-code').click(
() => {
"description": "Example website using JavaScript Template system",
"directories": {},
"dependencies": {
- "@ndcode/emailjs_cache": "^0.1.0",
"@ndcode/logjson": "^0.1.0",
+ "@ndcode/nodemailer_cache": "^0.1.0",
"@ndcode/zettair_cache": "^0.1.0",
"captchagen": "^1.2.0",
"cookie": "^0.3.1",