Converted the account system to an auth system
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
const { accounts } = require('../database/models');
|
||||
|
||||
//auth/account
|
||||
const route = async (req, res) => {
|
||||
const account = await accounts.findOne({
|
||||
where: {
|
||||
username: req.user.username
|
||||
}
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
res.status(401).send('Unknown account');
|
||||
}
|
||||
|
||||
//respond with the private-facing data
|
||||
res.status(200).json({
|
||||
contact: await account.contact
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = route;
|
||||
@@ -0,0 +1,52 @@
|
||||
//libraries
|
||||
const utils = require('util');
|
||||
const bcrypt = require('bcryptjs');
|
||||
var cron = require('node-cron');
|
||||
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
const { accounts } = require('../database/models');
|
||||
|
||||
//auth/deletion
|
||||
const route = async (req, res) => {
|
||||
const account = await accounts.findOne({
|
||||
where: {
|
||||
username: req.user.username
|
||||
}
|
||||
});
|
||||
|
||||
//compare the user's password
|
||||
const compare = utils.promisify(bcrypt.compare);
|
||||
const match = await compare(req.body.password, account.hash);
|
||||
|
||||
if (!match) {
|
||||
return res.status(401).send('incorrect password');
|
||||
}
|
||||
|
||||
//set the deletion time (2 days from now)
|
||||
const interval = new Date(new Date().setDate(new Date().getDate() + 2)); //wow
|
||||
await accounts.update({
|
||||
deletion: interval
|
||||
},
|
||||
{
|
||||
where: {
|
||||
username: req.user.username
|
||||
}
|
||||
});
|
||||
|
||||
//finally
|
||||
return res.status(200).end();
|
||||
};
|
||||
|
||||
//actually delete the accounts
|
||||
cron.schedule('0 * * * *', async () => {
|
||||
await accounts.destroy({
|
||||
where: {
|
||||
deletion: {
|
||||
[Op.lt]: Sequelize.fn('NOW')
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = route;
|
||||
+18
-4
@@ -1,10 +1,24 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
//the routes
|
||||
//TODO: import the routes here
|
||||
//middleware
|
||||
const authToken = require('../utilities/token-auth');
|
||||
|
||||
//basic route management
|
||||
//TODO: define the routes here
|
||||
//signup -> validate -> login all without a token
|
||||
router.post('/signup', require('./signup'));
|
||||
router.get('/validation', require('./validation'));
|
||||
router.post('/login', require('./login'));
|
||||
|
||||
//refresh token
|
||||
router.post('/token', require('./token'));
|
||||
|
||||
//middleware
|
||||
router.use(authToken);
|
||||
|
||||
//basic account management (needs a token)
|
||||
router.delete('/logout', require('./logout'));
|
||||
router.get('/account', require('./account'));
|
||||
router.patch('/update', require('./update'));
|
||||
router.delete('/deletion', require('./deletion'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//libraries
|
||||
const utils = require('util');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const { accounts } = require('../database/models');
|
||||
const generate = require('../utilities/token-generate');
|
||||
|
||||
//utilities
|
||||
const validateEmail = require('../utilities/validate-email');
|
||||
|
||||
//auth/login
|
||||
const route = async (req, res) => {
|
||||
//validate the given details
|
||||
const validateErr = await validateDetails(req.body);
|
||||
if (validateErr) {
|
||||
return res.status(401).send(validateErr);
|
||||
}
|
||||
|
||||
//get the existing account
|
||||
const account = await accounts.findOne({
|
||||
where: {
|
||||
email: req.body.email
|
||||
}
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
return res.status(401).send('incorrect email or password');
|
||||
}
|
||||
|
||||
//compare passwords
|
||||
const compare = utils.promisify(bcrypt.compare);
|
||||
const match = await compare(req.body.password, account.hash);
|
||||
|
||||
if (!match) {
|
||||
return res.status(401).send('incorrect email or password');
|
||||
}
|
||||
|
||||
//cancel deletion if any
|
||||
await accounts.update({ deletion: null }, {
|
||||
where: {
|
||||
id: account.id
|
||||
}
|
||||
});
|
||||
|
||||
//generate the JWT
|
||||
const tokens = generate(account.username, account.privilege);
|
||||
|
||||
//finally
|
||||
res.status(200).json(tokens);
|
||||
};
|
||||
|
||||
const validateDetails = async (body) => {
|
||||
//basic formatting (with an exception for the default admin account)
|
||||
if (!validateEmail(body.email) && body.email != `admin@${process.env.WEB_ADDRESS}`) {
|
||||
return 'invalid email';
|
||||
}
|
||||
//TODO: restore default admin account
|
||||
|
||||
//check for existing (banned)
|
||||
//TODO: restore banning
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = route;
|
||||
@@ -0,0 +1,10 @@
|
||||
const destroy = require('../utilities/token-destroy');
|
||||
|
||||
//auth/logout
|
||||
const route = (req, res) => {
|
||||
destroy(req.body.token);
|
||||
|
||||
return res.status(200).end();
|
||||
};
|
||||
|
||||
module.exports = route;
|
||||
@@ -0,0 +1,144 @@
|
||||
//libraries
|
||||
const bcrypt = require('bcryptjs');
|
||||
const nodemailer = require('nodemailer');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
const { accounts, pendingSignups } = require('../database/models');
|
||||
|
||||
|
||||
//utilities
|
||||
const uuid = require('../utilities/uuid');
|
||||
const validateEmail = require('../utilities/validate-email');
|
||||
const validateUsername = require('../utilities/validate-username');
|
||||
|
||||
//auth/signup
|
||||
const route = async (req, res) => {
|
||||
//validate the given details
|
||||
const validateErr = await validateDetails(req.body);
|
||||
if (validateErr) {
|
||||
return res.status(401).send(validateErr);
|
||||
}
|
||||
|
||||
//generate the password hash
|
||||
const hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
|
||||
|
||||
//generate the validation field
|
||||
const token = uuid();
|
||||
|
||||
//register signup
|
||||
const signupErr = await registerPendingSignup(req.body, hash, token);
|
||||
if (signupErr) {
|
||||
return res.status(500).send(signupErr);
|
||||
}
|
||||
|
||||
//send the validation email
|
||||
const emailErr = await sendValidationEmail(req.body.email, req.body.username, token);
|
||||
if (emailErr) {
|
||||
return res.status(500).send(emailErr);
|
||||
}
|
||||
|
||||
//finally
|
||||
res.status(200).send("Validation email sent!");
|
||||
return null;
|
||||
}
|
||||
|
||||
const validateDetails = async (body) => {
|
||||
//basic formatting
|
||||
if (!validateEmail(body.email)) {
|
||||
return 'invalid email';
|
||||
}
|
||||
|
||||
if (!validateUsername(body.username)) {
|
||||
return 'invalid username';
|
||||
}
|
||||
|
||||
//check for existing (banned)
|
||||
//TODO: re-add banned email checks
|
||||
|
||||
//check for existing email
|
||||
const emailRecord = await accounts.findOne({
|
||||
where: {
|
||||
email: body.email
|
||||
}
|
||||
});
|
||||
|
||||
if (emailRecord) {
|
||||
return 'email already exists';
|
||||
}
|
||||
|
||||
//check for existing username
|
||||
const usernameRecord = await accounts.findOne({
|
||||
where: {
|
||||
username: body.username
|
||||
}
|
||||
});
|
||||
|
||||
if (usernameRecord) {
|
||||
return 'username already exists';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const registerPendingSignup = async (body, hash, token) => {
|
||||
const record = await pendingSignups.upsert({
|
||||
email: body.email,
|
||||
username: body.username,
|
||||
hash: hash,
|
||||
contact: body.contact,
|
||||
token: token
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const sendValidationEmail = async (email, username, token) => {
|
||||
const addr = `${process.env.WEB_PROTOCOL}://${process.env.WEB_ADDRESS}/auth/validation?username=${username}&token=${token}`;
|
||||
const msg = `Hello ${username}!
|
||||
|
||||
Please visit the following link to validate your account: ${addr}
|
||||
|
||||
You can contact us directly at our physical mailing address here: ${process.env.MAIL_PHYSICAL}
|
||||
`;
|
||||
|
||||
let transporter, info;
|
||||
|
||||
//what exactly is a transport?
|
||||
try {
|
||||
transporter = nodemailer.createTransport({
|
||||
host: process.env.MAIL_SMTP,
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.MAIL_USERNAME,
|
||||
pass: process.env.MAIL_PASSWORD
|
||||
},
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
return `failed to create a mail transport: ${e}`;
|
||||
}
|
||||
|
||||
// send mail with defined transport object
|
||||
try {
|
||||
info = await transporter.sendMail({
|
||||
from: `signup@${process.env.WEB_ADDRESS}`, //WARNING: google overwrites this
|
||||
to: email,
|
||||
subject: 'Email Validation',
|
||||
text: msg
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
return `failed to send validation mail: ${e}`;
|
||||
}
|
||||
|
||||
if (info.accepted[0] != email) {
|
||||
return 'validation email failed to send';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = route;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const refresh = require('../utilities/token-refresh');
|
||||
|
||||
//auth/token
|
||||
module.exports = async (req, res) => {
|
||||
const refreshToken = req.body.token;
|
||||
|
||||
return refresh(refreshToken, (err, tokens) => {
|
||||
if (err) {
|
||||
return res.status(err).end();
|
||||
}
|
||||
|
||||
return res.status(200).send(tokens);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { accounts } = require('../database/models');
|
||||
|
||||
//auth/update
|
||||
const route = async (req, res) => {
|
||||
//generate the password hash
|
||||
let hash;
|
||||
|
||||
if (req.body.password) {
|
||||
hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
|
||||
}
|
||||
|
||||
//update the account
|
||||
await accounts.update({
|
||||
contact: req.body.contact,
|
||||
hash: hash
|
||||
}, {
|
||||
where: {
|
||||
username: req.user.username
|
||||
}
|
||||
});
|
||||
|
||||
//respond with an OK
|
||||
res.status(200).end();
|
||||
};
|
||||
|
||||
module.exports = route;
|
||||
@@ -0,0 +1,40 @@
|
||||
const { pendingSignups, accounts } = require('../database/models');
|
||||
|
||||
//auth/validation
|
||||
const route = async (req, res) => {
|
||||
//get the existing pending signup
|
||||
const info = await pendingSignups.findOne({
|
||||
where: {
|
||||
username: req.query.username
|
||||
}
|
||||
});
|
||||
|
||||
//check the given info
|
||||
if (!info) {
|
||||
return res.status(401).send('validation failed');
|
||||
}
|
||||
|
||||
if (info.token != req.query.token) {
|
||||
return res.status(401).send('tokens do not match');
|
||||
}
|
||||
|
||||
//move data to the accounts table
|
||||
accounts.create({
|
||||
email: info.email,
|
||||
username: info.username,
|
||||
hash: info.hash,
|
||||
contact: info.contact
|
||||
});
|
||||
|
||||
//delete the pending signup
|
||||
pendingSignups.destroy({
|
||||
where: {
|
||||
username: req.query.username
|
||||
}
|
||||
});
|
||||
|
||||
//finally
|
||||
res.status(200).send('Validation succeeded!');
|
||||
};
|
||||
|
||||
module.exports = route;
|
||||
Reference in New Issue
Block a user