From be0776b9ea258faa10b772904b9544fcc7e0d1b9 Mon Sep 17 00:00:00 2001 From: Ashelyn Dawn Date: Wed, 3 Jun 2020 22:10:34 -0600 Subject: [PATCH] Email verification --- .env.sample | 8 ++- api/email.js | 56 +++++++++++++++++++ api/index.js | 1 + api/middleware/ensureUser.js | 9 +++ api/users.js | 17 ++++++ db/mappings/user.js | 8 +-- db/models/session.js | 12 ++-- db/models/user.js | 74 ++++++++++++++++++++++++ db/sql/1-tables.sql | 14 ++--- db/sql/2-views.sql | 7 --- db/sql/3-functions.sql | 67 ++++++++++++++++++++-- db/util.js | 8 +-- next.config.js | 3 +- package-lock.json | 45 ++++++++++++++- package.json | 2 + pages/_app.js | 2 +- pages/account/email/confirm.js | 96 ++++++++++++++++++++++++++++++++ pages/account/email/invalid.js | 32 +++++++++++ pages/account/index.js | 12 +++- utils/redirectGetInitialProps.js | 14 +++++ 20 files changed, 445 insertions(+), 42 deletions(-) create mode 100644 api/email.js create mode 100644 api/middleware/ensureUser.js create mode 100644 pages/account/email/confirm.js create mode 100644 pages/account/email/invalid.js create mode 100644 utils/redirectGetInitialProps.js diff --git a/.env.sample b/.env.sample index 9b27950..d43acdc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,12 +1,16 @@ +# DB Config DB_HOST= DB_USER= DB_NAME= DB_PASS= +# Application Config PW_SALTROUNDS=10 COOKIE_SECRET= -EASYPOST_API_KEY= +EXTERNAL_URL=http://localhost:3000 +# Api Keys +EASYPOST_API_KEY= STRIPE_PUBLIC_KEY= STRIPE_PRIVATE_KEY= -EXTERNAL_URL=http://localhost:3000 +SENDGRID_KEY= diff --git a/api/email.js b/api/email.js new file mode 100644 index 0000000..249336b --- /dev/null +++ b/api/email.js @@ -0,0 +1,56 @@ +const router = module.exports = require('express-promise-router')() +const ensureUser = require('./middleware/ensureUser') +const db = require('../db') + +const sendgrid = require('@sendgrid/mail') +sendgrid.setApiKey(process.env.SENDGRID_KEY) + +router.get('/links', ensureUser, async (req, res) => { + const links = await db.user.getOpenEmailLinks(req.user.uuid) + + res.json(links.map(stripLink)) +}) + +router.post('/', ensureUser, async (req, res) => { + if(req.user.time_email_confirmed) + return res.status(400).json({errors: [{ + param: 'email', + msg: 'Email address already verified' + }]}) + + const confirmUrl = await db.user.createLoginLink(req.user.uuid) + + const msg = { + to: req.user.email, + from: {email: 'registration@email.societyofsocks.us', name: 'Society of Socks'}, + templateId: 'd-33407f1dd1b14b7b84dd779511039c95', + dynamic_template_data: { + confirmUrl: confirmUrl + } + }; + + await sendgrid.send(msg); + + res.json({sent: true}) +}) + +router.get('/confirm/:uuid', ensureUser, async (req, res) => { + if(!req.query || !req.query.key) + return res.redirect('/account/email/invalid') + + const validLink = await db.user.verifyLoginLink(req.params.uuid, req.query.key) + if(!validLink) + return res.redirect('/account/email/invalid') + + await db.user.markLoginLinkUsed(validLink.uuid) + await db.user.markEmailVerified(validLink.user_uuid) + + return res.redirect('/account') +}) + +function stripLink(link) { + return { + ...link, + login_hash: undefined + } +} diff --git a/api/index.js b/api/index.js index cce4b4d..3733a79 100644 --- a/api/index.js +++ b/api/index.js @@ -24,6 +24,7 @@ router.use('/images/', require('./images')) router.use('/categories/', require('./categories')) router.use('/orders/', require('./orders')) router.use('/shipments/', require('./shipments')) +router.use('/email/', require('./email')) router.use((req, res, next)=>{ const err = new Error('Not found') diff --git a/api/middleware/ensureUser.js b/api/middleware/ensureUser.js new file mode 100644 index 0000000..16f8c6e --- /dev/null +++ b/api/middleware/ensureUser.js @@ -0,0 +1,9 @@ +module.exports = async (req, res) => { + if(!req.user) { + const err = new Error('Unauthorized') + err.status = 401 + throw err; + } + + return 'next' +} diff --git a/api/users.js b/api/users.js index a4541b2..e03150e 100644 --- a/api/users.js +++ b/api/users.js @@ -2,6 +2,9 @@ const router = require('express-promise-router')() const parseJSON = require('body-parser').json() const db = require('../db') +const sendgrid = require('@sendgrid/mail') +sendgrid.setApiKey(process.env.SENDGRID_KEY) + const validate = require('./middleware/validators') const registerValidation = [ @@ -32,6 +35,20 @@ router.post('/', parseJSON, registerValidation, async (req, res) => { await db.session.create(req, user) + // Send login email TODO: Abstract this so api/email and this route use the same function + const confirmUrl = await db.user.createLoginLink(user.uuid) + + const msg = { + to: user.email, + from: {email: 'registration@email.societyofsocks.us', name: 'Society of Socks'}, + templateId: 'd-33407f1dd1b14b7b84dd779511039c95', + dynamic_template_data: { + confirmUrl: confirmUrl + } + }; + + await sendgrid.send(msg); + res.json(user) }) diff --git a/db/mappings/user.js b/db/mappings/user.js index 9d44044..49f0a0a 100644 --- a/db/mappings/user.js +++ b/db/mappings/user.js @@ -22,16 +22,17 @@ module.exports = [ 'is_admin' ], collections: [ - {name: 'login_links', mapId: 'loginLinkMap', columnPrefix: 'login_link_'}, {name: 'sessions', mapId: 'sessionMap', columnPrefix: 'session_'} ] },{ - mapId: 'loginLinkMap', + mapId: 'emailLinkMap', idProperty: 'uuid', properties: [ 'time_created', 'timeout_length', - 'login_hash' + 'login_hash', + 'time_used', + 'user_uuid' ] },{ mapId: 'sessionMap', @@ -45,7 +46,6 @@ module.exports = [ 'referer' ], associations: [ - {name: 'originating_link', mapId: 'loginLinkMap', columnPrefix: 'login_link_'}, {name: 'user', mapId: 'userMap', columnPrefix: 'session_user_'}, {name: 'cart', mapId: 'cartMap', columnPrefix: 'cart_'} ] diff --git a/db/models/session.js b/db/models/session.js index 4c710d2..6eee2e0 100644 --- a/db/models/session.js +++ b/db/models/session.js @@ -5,15 +5,14 @@ const mappings = require('../mappings') const session = module.exports = {} -session.create = async (req, _user, login_link) => { +session.create = async (req, _user) => { const user = _user ? _user : req.user const session = await createSessionInternal( user ? user.uuid : null, req.ip, req.get('User Agent') || "", - req.get('Referrer') || "", - login_link ? login_link.uuid : null + req.get('Referrer') || "" ) req.session.uuid = session.uuid @@ -22,16 +21,15 @@ session.create = async (req, _user, login_link) => { return session } -const createSessionInternal = async (user_uuid, ip_address, user_agent, referer, origin_link_uuid) => { +const createSessionInternal = async (user_uuid, ip_address, user_agent, referer) => { const query = { - text: 'select * from sos.login_user_session($1, $2, $3, $4, $5, $6)', + text: 'select * from sos.login_user_session($1, $2, $3, $4, $5)', values: [ user_uuid, '2 hours', ip_address, user_agent, - referer, - origin_link_uuid + referer ] } diff --git a/db/models/user.js b/db/models/user.js index 13c61d4..8b284e3 100644 --- a/db/models/user.js +++ b/db/models/user.js @@ -2,7 +2,9 @@ const pg = require('../pg') const joinjs = require('join-js').default; const debug = require('debug')('sos:db:user') const mappings = require('../mappings') +const dbUtil = require('../util') +const uuid = require('uuid') const bcrypt = require('bcrypt') const session = require('./session') @@ -70,3 +72,75 @@ user.login = async (email, password) => { return _user } + +user.getOpenEmailLinks = (user_uuid) => + dbUtil.executeFunction({ + name: 'get_open_email_links_for_user', + params: [ + user_uuid + ], + returnType: 'emailLink', + tablePrefix: 'email_link_', + single: false + }) + +user.createLoginLink = async (user_uuid) => { + const linkCode = uuid.v4() + + const hash = await bcrypt.hash(linkCode, saltRounds) + + const link_record = await dbUtil.executeFunction({ + name: 'create_login_link', + params: [ + user_uuid, + '2 hours', + hash + ], + returnType: 'emailLink', + tablePrefix: 'email_link_', + single: true + }) + + return `${process.env.EXTERNAL_URL}/api/email/confirm/${link_record.uuid}?key=${linkCode}` +} + +user.verifyLoginLink = async (link_uuid, key) => { + const link_record = await dbUtil.executeQuery({ + query: { + text: 'select * from sos.email_link where email_link_uuid = $1', + values: [link_uuid] + }, + returnType: 'emailLink', + tablePrefix: 'email_link_', + single: true + }) + + if(!link_record){ + // Avoid early exit timing difference + await bcrypt.hash(key, saltRounds) + return null + } + + const valid = await bcrypt.compare(key, link_record.login_hash) + + if(!valid) return null + + return link_record +} + +user.markLoginLinkUsed = link_uuid => + dbUtil.executeFunction({ + name: 'set_link_used', + params: [link_uuid], + returnType: 'emailLink', + tablePrefix: 'email_link_', + single: true + }) + +user.markEmailVerified = user_uuid => + dbUtil.executeFunction({ + name: 'set_user_email_verified', + params: [user_uuid], + returnType: 'user', + single: true + }) diff --git a/db/sql/1-tables.sql b/db/sql/1-tables.sql index 2337ca6..84a9a12 100644 --- a/db/sql/1-tables.sql +++ b/db/sql/1-tables.sql @@ -14,12 +14,13 @@ create table sos."user" ( user_is_admin bool not null default false ); -create table sos."login_link" ( - login_link_uuid uuid primary key default uuid_generate_v4(), - login_link_user_uuid uuid not null references sos."user" (user_uuid), - login_link_time_created timestamptz not null default now(), - login_link_timeout_length interval not null, - login_link_login_hash varchar(60) not null +create table sos."email_link" ( + email_link_uuid uuid primary key default uuid_generate_v4(), + email_link_user_uuid uuid not null references sos."user" (user_uuid), + email_link_time_created timestamptz not null default now(), + email_link_timeout_length interval not null, + email_link_login_hash varchar(60) not null, + email_link_time_used timestamptz ); create table sos."cart" ( @@ -36,7 +37,6 @@ create table sos."session" ( session_user_agent varchar(500) not null, session_referer varchar(500) not null, session_user_uuid uuid references sos."user" (user_uuid), - session_originating_link uuid references sos."login_link" (login_link_uuid), session_cart uuid references sos."cart" (cart_uuid) ); diff --git a/db/sql/2-views.sql b/db/sql/2-views.sql index c7d9040..aeda29c 100644 --- a/db/sql/2-views.sql +++ b/db/sql/2-views.sql @@ -42,18 +42,11 @@ create or replace view sos.v_session as "session_user".user_time_registered as session_user_time_registered, "session_user".user_time_email_confirmed as session_user_time_email_confirmed, "session_user".user_is_admin as session_user_is_admin, - "login_link".*, v_cart.* from sos."session" left join sos."user" "session_user" on "session".session_user_uuid = "session_user".user_uuid - left join sos."login_link" on "session".session_originating_link = "login_link".login_link_uuid left join sos.v_cart on v_cart.cart_uuid = "session".session_cart; -create or replace view sos.v_login_link as - select - * - from sos."login_link" - left join sos."user" on "login_link".login_link_user_uuid = "user".user_uuid; create or replace view sos.v_category as select diff --git a/db/sql/3-functions.sql b/db/sql/3-functions.sql index 043b286..15058d5 100644 --- a/db/sql/3-functions.sql +++ b/db/sql/3-functions.sql @@ -41,7 +41,7 @@ begin return query select * from sos.validate_session(_session_uuid); end; $function$; -create or replace function sos.login_user_session(_user_uuid uuid, _timeout_length interval, _ip_addr varchar(50), _user_agent varchar(500), _referer varchar(500), _link uuid) +create or replace function sos.login_user_session(_user_uuid uuid, _timeout_length interval, _ip_addr varchar(50), _user_agent varchar(500), _referer varchar(500)) returns setof sos.v_session language plpgsql as $function$ @@ -53,15 +53,13 @@ begin session_timeout_length, session_ip_address, session_user_agent, - session_referer, - session_originating_link + session_referer ) values ( _user_uuid, _timeout_length, _ip_addr, _user_agent, - _referer, - _link + _referer ) returning session_uuid into _session_uuid; return query select * from sos.validate_session(_session_uuid); @@ -1064,3 +1062,62 @@ begin return query select * from sos.v_order where order_uuid = _order_uuid; end; $function$; + +create or replace function sos.get_open_email_links_for_user(_user_uuid uuid) + returns setof sos."email_link" + language plpgsql +as $function$ +begin + return query select * from sos."email_link" + where email_link_user_uuid = _user_uuid + and email_link_time_used is null + and email_link_time_created + email_link_timeout_length > now(); +end; $function$; + +create or replace function sos.create_login_link(_user_uuid uuid, _timeout_length interval, _link_hash text) + returns setof sos."email_link" + language plpgsql +as $function$ +declare + _link_uuid uuid; +begin + insert into sos."email_link" ( + email_link_user_uuid, + email_link_timeout_length, + email_link_login_hash + ) values ( + _user_uuid, + _timeout_length, + _link_hash + ) returning email_link_uuid into _link_uuid; + + return query select * from sos."email_link" where email_link_uuid = _link_uuid; +end; $function$; + +create or replace function sos.set_link_used(_link_uuid uuid) + returns setof sos."email_link" + language plpgsql +as $function$ +begin + update sos."email_link" set + email_link_time_used = now() + where email_link_uuid = _link_uuid; + + return query select * from sos."email_link" where email_link_uuid = _link_uuid; +end; $function$; + +create or replace function sos.set_user_email_verified(_user_uuid uuid) + returns setof sos."user" + language plpgsql +as $function$ +begin + update sos."user" set ( + user_email_confirmed, + user_time_email_confirmed + ) = ( + true, + now() + ) where user_uuid = _user_uuid; + + return query select * from sos."user" where user_uuid = _user_uuid; +end; $function$; diff --git a/db/util.js b/db/util.js index 28daed1..d0812b4 100644 --- a/db/util.js +++ b/db/util.js @@ -7,19 +7,19 @@ const util = module.exports = {}; const validateFunctionName = name => /^[a-z_]+$/.test(name) const getParamString = (length) => Array.from({length}, (_,i)=>i+1).map(i => '$' + i).join(', ') -util.executeQuery = async function({query, returnType, single = false}){ +util.executeQuery = async function({query, returnType, tablePrefix, single = false}){ debug(query) const {rows} = await pg.query(query) - const mappedObjs = joinjs.map(rows, mappings, returnType + 'Map', returnType + '_') + const mappedObjs = joinjs.map(rows, mappings, returnType + 'Map', tablePrefix || (returnType + '_')) if(single) return mappedObjs[0] return mappedObjs } -util.executeFunction = async function({name, params, returnType, single}) { +util.executeFunction = async function({name, params, returnType, single, tablePrefix}) { if(!validateFunctionName(name)) throw new Error("Invalid function name: " + name); const query = { @@ -27,5 +27,5 @@ util.executeFunction = async function({name, params, returnType, single}) { values: params } - return util.executeQuery({query, returnType, single}) + return util.executeQuery({query, returnType, single, tablePrefix}) } diff --git a/next.config.js b/next.config.js index 43a907b..74d5a4f 100644 --- a/next.config.js +++ b/next.config.js @@ -23,7 +23,8 @@ class CustomResolver { 'hooks', 'components', 'images', - 'pages' + 'pages', + 'utils' ] // If it's not the right pattern, or it's outside our list of directories diff --git a/package-lock.json b/package-lock.json index 929f4ca..4402a7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1273,6 +1273,33 @@ "resolved": "https://registry.npmjs.org/@rmwc/types/-/types-6.0.5.tgz", "integrity": "sha512-G4NZxakwsTMFxxpEWX90MOlZtVNzCwooLUg7FH7Oh5OJCnKWGmXK8ZMF59vvIEn7zCiEpVroKhOxNpMw9TIUTw==" }, + "@sendgrid/client": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.1.1.tgz", + "integrity": "sha512-V2BmOO81wHNmbTDwTJ07Olb9dWrj1G19xK4crwds68b9R0w05aOWDddZTvpn9mZnHwIJYqcZcBJuhdHDejuSHg==", + "requires": { + "@sendgrid/helpers": "^7.0.1", + "axios": "^0.19.2" + } + }, + "@sendgrid/helpers": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.0.1.tgz", + "integrity": "sha512-i/zsissq1upgdywtuJKysaplJJZC24GdtEKiJC1IRlXvBHzIjH4eU+rqUFO8h+hGji3UMURGgMFuLUXTUYvZ9w==", + "requires": { + "chalk": "^2.0.1", + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.1.1.tgz", + "integrity": "sha512-VXdJ9J6vBNMw+wMIGFRvms6EmV6pvoRHMWoLJGweHlsZDnvmK3rWUnnNaS3OdDQ3A8B5bMv2WKsEnHsMZ6iDUg==", + "requires": { + "@sendgrid/client": "^7.1.1", + "@sendgrid/helpers": "^7.0.1" + } + }, "@stripe/stripe-js": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.4.0.tgz", @@ -3121,6 +3148,11 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -8495,9 +8527,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", + "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==" }, "validate-npm-package-license": { "version": "3.0.4", @@ -9278,6 +9310,13 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } } }, "webpack-merge": { diff --git a/package.json b/package.json index 4584c61..9089159 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@easypost/api": "^3.8.1", "@rmwc/button": "^6.0.14", "@rmwc/icon": "^6.0.12", + "@sendgrid/mail": "^7.1.1", "@stripe/stripe-js": "^1.4.0", "axios": "^0.19.2", "babel-plugin-inline-react-svg": "^1.1.1", @@ -41,6 +42,7 @@ "sharp": "^0.24.1", "stripe": "^8.44.0", "use-measure": "^0.3.0", + "uuid": "^8.1.0", "validator": "^12.2.0" } } diff --git a/pages/_app.js b/pages/_app.js index 1b72cb6..1dab1ef 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -44,7 +44,7 @@ Layout.getInitialProps = async ({Component, ctx}) => { let pageProps = {}; if(Component.getInitialProps) - pageProps = await Component.getInitialProps({ctx}) + pageProps = await Component.getInitialProps({ctx, user}) return {pageProps, user, cart} } diff --git a/pages/account/email/confirm.js b/pages/account/email/confirm.js new file mode 100644 index 0000000..be1b19c --- /dev/null +++ b/pages/account/email/confirm.js @@ -0,0 +1,96 @@ +import {DateTime} from 'luxon' +import Link from 'next/link' +import {Button, FormController} from '~/components/form' + +import useUser from '~/hooks/useUser' +import redirect from '~/utils/redirectGetInitialProps' + +ConfirmEmail.getInitialProps = async ({ctx, user}) => { + const {axios} = ctx + const {data: links} = await axios.get('/api/email/links') + + if(!user) + return redirect(ctx, 302, '/login') + + if(user.email_confirmed) + return redirect(ctx, 302, '/account') + + return {links} +} + +export default function ConfirmEmail({links}) { + const user = useUser() + const lastLink = getLastLinkTime(links) + + return ( + <> +

Confirm Email Address

+

+ In order to make use of account related features, we require you to + confirm your email address. +

+ + {lastLink ? ( + <> +

+ We last sent an email to {user.email} at: +

+ +

+ {lastLink.time_created.toFormat('LLLL dd, h:mm a')} +
+ This email will be valid until {lastLink.time_created.plus(lastLink.timeout_length).toFormat('LLLL dd, h:mm a')} +

+ +

+ If you haven't received it yet, please be patient. Emails can take several + minutes to be delivered, and depending on your email provider may also + be subject to additional scans or verification before it shows up in + your inbox. +

+ +

+ Also, be sure to check your spam or junk folders - registration email + like the one we sent can occasionally be caught in those. +

+ +

+ If you've waited a few minutes, and you're sure it won't arrive, you can + click the button below to send another one. If you still have issues, + please feel free to contact us. +

+ + window.location.reload()}> + + + + ) : ( + <> +

+ Click the button below to send a confirmation email. +

+ + window.location.reload()}> + + + + )} + + ) +} + +function getLastLinkTime(links) { + if(links.length < 1) + return null; + + let lastLink = links[0] + lastLink.time_created = DateTime.fromISO(lastLink.time_created) + + for(const current of links){ + current.time_created = DateTime.fromISO(current.time_created) + if(current.time_created.diff(lastLink.time_created).as('seconds') > 0) + lastLink = current; + } + + return lastLink +} diff --git a/pages/account/email/invalid.js b/pages/account/email/invalid.js new file mode 100644 index 0000000..2335bc3 --- /dev/null +++ b/pages/account/email/invalid.js @@ -0,0 +1,32 @@ +import Link from 'next/link' +import {Button, FormController} from '~/components/form' +import Router from 'next/router' + +import useUser from '~/hooks/useUser' + + +export default function InvalidEmail() { + const user = useUser() + + if(user.email_confirmed) + Router.push('/account') + + return ( + <> +

Invalid Confirmation Link

+

+ Sorry, but the email confirmation link you used was invalid or expired. +

+ +

+ If you've gotten this error before, + please contact us so we can help + resolve it. Otherwise, feel free to try sending the email again. +

+ + {window.location.href = '/account/email/confirm'}}> + + + + ) +} diff --git a/pages/account/index.js b/pages/account/index.js index f12202b..a99f360 100644 --- a/pages/account/index.js +++ b/pages/account/index.js @@ -4,7 +4,17 @@ import Router from 'next/router' import Table from '~/components/table' import useUser from '~/hooks/useUser' -AccountPage.getInitialProps = async function({ctx: {axios}}) { +import redirect from '~/utils/redirectGetInitialProps' + +AccountPage.getInitialProps = async function({ctx, user}) { + const {axios} = ctx; + + if(!user) + return redirect(ctx, 302, '/login') + + if(!user.email_confirmed) + return redirect(ctx, 302, '/account/email/confirm') + const {data} = await axios.get(`/api/orders`) return {orders: data.sort(sortOrders)} } diff --git a/utils/redirectGetInitialProps.js b/utils/redirectGetInitialProps.js new file mode 100644 index 0000000..c6d4c4c --- /dev/null +++ b/utils/redirectGetInitialProps.js @@ -0,0 +1,14 @@ +import Router from 'next/router' + +export default function (ctx, status, url) { + const {res} = ctx + + if(res) { + res.writeHead(status, {Location: url}) + res.end(); + } else if (window) { + Router.push(url) + } else { + console.error("Could not redirect for unknown reason") + } +}