Email verification
parent
59bca0bfc1
commit
be0776b9ea
@ -1,12 +1,16 @@
|
|||||||
|
# DB Config
|
||||||
DB_HOST=
|
DB_HOST=
|
||||||
DB_USER=
|
DB_USER=
|
||||||
DB_NAME=
|
DB_NAME=
|
||||||
DB_PASS=
|
DB_PASS=
|
||||||
|
|
||||||
|
# Application Config
|
||||||
PW_SALTROUNDS=10
|
PW_SALTROUNDS=10
|
||||||
COOKIE_SECRET=
|
COOKIE_SECRET=
|
||||||
EASYPOST_API_KEY=
|
EXTERNAL_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Api Keys
|
||||||
|
EASYPOST_API_KEY=
|
||||||
STRIPE_PUBLIC_KEY=
|
STRIPE_PUBLIC_KEY=
|
||||||
STRIPE_PRIVATE_KEY=
|
STRIPE_PRIVATE_KEY=
|
||||||
EXTERNAL_URL=http://localhost:3000
|
SENDGRID_KEY=
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = async (req, res) => {
|
||||||
|
if(!req.user) {
|
||||||
|
const err = new Error('Unauthorized')
|
||||||
|
err.status = 401
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'next'
|
||||||
|
}
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<h2>Confirm Email Address</h2>
|
||||||
|
<p>
|
||||||
|
In order to make use of account related features, we require you to
|
||||||
|
confirm your email address.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{lastLink ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
We last sent an email to <strong>{user.email}</strong> at:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style={{textAlign: 'center'}}>
|
||||||
|
{lastLink.time_created.toFormat('LLLL dd, h:mm a')}
|
||||||
|
<br/>
|
||||||
|
<em>This email will be valid until {lastLink.time_created.plus(lastLink.timeout_length).toFormat('LLLL dd, h:mm a')}</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Also, be sure to check your spam or junk folders - registration email
|
||||||
|
like the one we sent can occasionally be caught in those.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
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 <Link href="/contact"><a>contact us</a></Link>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormController url="/api/email" afterSubmit={() => window.location.reload()}>
|
||||||
|
<Button type="submit">Resend Email</Button>
|
||||||
|
</FormController>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Click the button below to send a confirmation email.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormController url="/api/email" afterSubmit={() => window.location.reload()}>
|
||||||
|
<Button type="submit">Resend Email</Button>
|
||||||
|
</FormController>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<h2>Invalid Confirmation Link</h2>
|
||||||
|
<p>
|
||||||
|
Sorry, but the email confirmation link you used was invalid or expired.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you've gotten this error before,
|
||||||
|
please <Link href="/contact"><a>contact us</a></Link> so we can help
|
||||||
|
resolve it. Otherwise, feel free to try sending the email again.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<FormController url="/api/email" afterSubmit={() => {window.location.href = '/account/email/confirm'}}>
|
||||||
|
<Button type="submit">Resend Email</Button>
|
||||||
|
</FormController>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue