Compare commits

..

2 Commits

Author SHA1 Message Date
Kayne Ruse 72b3babfd8 Reworking JWT authentication 2021-07-28 21:36:04 +10:00
Kayne Ruse c63e14ddf3 Patched some holes when poking with curl 2021-07-24 20:04:35 +10:00
18 changed files with 75 additions and 43 deletions
+8 -1
View File
@@ -19,9 +19,11 @@ Content-Type: application/json
"password": "helloworld" "password": "helloworld"
} }
//DOCS: Used for validating the email address above //DOCS: Used for validating the email address above
GET /auth/validation?username=example&token=12345678 GET /auth/validation?username=example&token=12345678
//DOCS: Login after validation //DOCS: Login after validation
POST /auth/login POST /auth/login
Content-Type: application/json Content-Type: application/json
@@ -37,7 +39,8 @@ Content-Type: application/json
"refreshToken": "fghij" "refreshToken": "fghij"
} }
//Replace an expired authToken pair with these values
//DOCS: Replace an expired authToken pair with these values
POST /auth/token POST /auth/token
Content-Type: application/json Content-Type: application/json
@@ -45,6 +48,7 @@ Content-Type: application/json
"token": "refreshToken" "token": "refreshToken"
} }
//DOCS: After this is called, the refresh route will no longer work //DOCS: After this is called, the refresh route will no longer work
DELETE /auth/logout DELETE /auth/logout
Authorization: Bearer accessToken Authorization: Bearer accessToken
@@ -53,6 +57,7 @@ Authorization: Bearer accessToken
"token": "refreshToken" "token": "refreshToken"
} }
//DOCS: Retreives the private account data, results vary //DOCS: Retreives the private account data, results vary
GET /auth/account GET /auth/account
Authorization: Bearer accessToken Authorization: Bearer accessToken
@@ -63,11 +68,13 @@ Authorization: Bearer accessToken
"refreshToken": "fghij" "refreshToken": "fghij"
} }
//DOCS: Update account data, input varies, but is always JSON //DOCS: Update account data, input varies, but is always JSON
PATCH /auth/account PATCH /auth/account
Content-Type: application/json Content-Type: application/json
Authorization: Bearer accessToken Authorization: Bearer accessToken
//DOCS: Sets the timer, account will be deleted after 2 days //DOCS: Sets the timer, account will be deleted after 2 days
DELETE /auth/account DELETE /auth/account
Authorization: Bearer accessToken Authorization: Bearer accessToken
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "auth-server", "name": "auth-server",
"version": "1.3.1", "version": "1.4.0",
"description": "An API centric auth server. Uses Sequelize and mariaDB by default.", "description": "An API centric auth server. Uses Sequelize and mariaDB by default.",
"main": "server/server.js", "main": "server/server.js",
"scripts": { "scripts": {
+1 -1
View File
@@ -27,7 +27,7 @@ const route = async (req, res) => {
//forcibly logout //forcibly logout
tokens.destroy({ tokens.destroy({
where: { where: {
username: req.body.username || '' email: req.body.email || ''
} }
}); });
+1 -1
View File
@@ -12,7 +12,7 @@ router.use(tokenAuth);
router.use(async (req, res, next) => { router.use(async (req, res, next) => {
const record = await accounts.findOne({ const record = await accounts.findOne({
where: { where: {
username: req.user.username || '' email: req.user.email || ''
} }
}); });
+11 -3
View File
@@ -9,18 +9,26 @@ const { accounts } = require('../database/models');
//auth/deletion //auth/deletion
const route = async (req, res) => { const route = async (req, res) => {
if (!req.body.password) {
return res.status(401).end('Missing password');
}
const account = await accounts.findOne({ const account = await accounts.findOne({
where: { where: {
index: req.user.index index: req.user.index || ''
} }
}); });
if (!account) {
return res.status(401).end('Missing account');
}
//compare the user's password //compare the user's password
const compare = utils.promisify(bcrypt.compare); const compare = utils.promisify(bcrypt.compare);
const match = await compare(req.body.password || '', account.hash); const match = await compare(req.body.password, account.hash);
if (!match) { if (!match) {
return res.status(401).send('incorrect password'); return res.status(401).send('Incorrect password');
} }
//set the deletion time (2 days from now) //set the deletion time (2 days from now)
+2 -2
View File
@@ -4,12 +4,12 @@ const { accounts } = require('../database/models');
const route = async (req, res) => { const route = async (req, res) => {
const account = await accounts.findOne({ const account = await accounts.findOne({
where: { where: {
index: req.user.index index: req.user.index || ''
} }
}); });
if (!account) { if (!account) {
return res.status(401).send('Unknown account'); return res.status(401).end('Unknown account');
} }
//respond with the private-facing data //respond with the private-facing data
+5 -5
View File
@@ -3,13 +3,13 @@ const { accounts } = require('../database/models');
//auth/update //auth/update
const route = async (req, res) => { const route = async (req, res) => {
//generate the password hash if (!req.body.password) {
let hash; return res.status(401).end('Missing password');
if (req.body.password) {
hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
} }
//generate the password hash
let hash = await bcrypt.hash(req.body.password, await bcrypt.genSalt(11));
//update the account //update the account
await accounts.update({ await accounts.update({
contact: req.body.contact, contact: req.body.contact,
+4 -2
View File
@@ -17,10 +17,13 @@ router.post('/token', require('./token'));
//middleware //middleware
router.use(tokenAuth); router.use(tokenAuth);
//logouts allowed when banned, still needs tokens
router.delete('/logout', require('./logout'));
router.use(async (req, res, next) => { router.use(async (req, res, next) => {
const record = await accounts.findOne({ const record = await accounts.findOne({
where: { where: {
username: req.user.username || '' email: req.user.email || ''
} }
}); });
@@ -36,7 +39,6 @@ router.use(async (req, res, next) => {
}); });
//basic account management (needs a token) //basic account management (needs a token)
router.delete('/logout', require('./logout'));
router.get('/account', require('./account-query')); router.get('/account', require('./account-query'));
router.patch('/account', require('./account-update')); router.patch('/account', require('./account-update'));
router.delete('/account', require('./account-delete')); router.delete('/account', require('./account-delete'));
+14 -9
View File
@@ -3,7 +3,7 @@ const utils = require('util');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const { accounts } = require('../database/models'); const { accounts } = require('../database/models');
const generate = require('../utilities/token-generate'); const tokenGenerate = require('../utilities/token-generate');
//utilities //utilities
const validateEmail = require('../utilities/validate-email'); const validateEmail = require('../utilities/validate-email');
@@ -13,7 +13,7 @@ const route = async (req, res) => {
//validate the given details //validate the given details
const validateErr = await validateDetails(req.body); const validateErr = await validateDetails(req.body);
if (validateErr) { if (validateErr) {
return res.status(401).send(validateErr); return res.status(401).end(validateErr);
} }
//get the existing account //get the existing account
@@ -48,20 +48,25 @@ const route = async (req, res) => {
} }
//generate the JWT //generate the JWT
const tokens = generate(account.index, account.username, account.type, account.admin, account.mod); const token = tokenGenerate(account.index, account.email, account.username, account.type, account.admin, account.mod);
//finally //finally
res.status(200).json(tokens); res.status(200).json(token);
}; };
const validateDetails = async (body) => { const validateDetails = async (body) => {
//basic formatting (with an exception for the default admin account) if (!body.email) {
if (!validateEmail(body.email) && body.email != `${process.env.ADMIN_DEFAULT_USERNAME}@${process.env.WEB_ADDRESS}`) { return 'Missing email';
return 'invalid email';
} }
//check for existing (banned) if (!body.password) {
//TODO: restore banning return 'Missing password';
}
//basic formatting (with an exception for the default admin account)
if (!validateEmail(body.email) && body.email != `${process.env.ADMIN_DEFAULT_USERNAME}@${process.env.WEB_ADDRESS}`) {
return 'Invalid email';
}
return null; return null;
} }
+2 -2
View File
@@ -1,8 +1,8 @@
const destroy = require('../utilities/token-destroy'); const tokenDestroy = require('../utilities/token-destroy');
//auth/logout //auth/logout
const route = (req, res) => { const route = (req, res) => {
destroy(req.body.token); tokenDestroy(req.body.token);
return res.status(200).end(); return res.status(200).end();
}; };
+14 -7
View File
@@ -6,7 +6,6 @@ const Op = Sequelize.Op;
const { accounts, pendingSignups } = require('../database/models'); const { accounts, pendingSignups } = require('../database/models');
//utilities //utilities
const uuid = require('../utilities/uuid'); const uuid = require('../utilities/uuid');
const validateEmail = require('../utilities/validate-email'); const validateEmail = require('../utilities/validate-email');
@@ -46,11 +45,11 @@ const route = async (req, res) => {
const validateDetails = async (body) => { const validateDetails = async (body) => {
//basic formatting //basic formatting
if (!validateEmail(body.email)) { if (!validateEmail(body.email)) {
return 'invalid email'; return 'Invalid email';
} }
if (!validateUsername(body.username)) { if (!validateUsername(body.username)) {
return 'invalid username'; return 'Invalid username';
} }
//check for existing (banned) //check for existing (banned)
@@ -64,23 +63,31 @@ const validateDetails = async (body) => {
}); });
if (emailRecord) { if (emailRecord) {
return 'email already exists'; return 'Email already exists';
}
if (!body.username) {
return 'Missing username';
} }
//check for existing username //check for existing username
const usernameRecord = await accounts.findOne({ const usernameRecord = await accounts.findOne({
where: { where: {
username: body.username || '' username: body.username
} }
}); });
if (usernameRecord) { if (usernameRecord) {
return 'username already exists'; return 'Username already exists';
} }
//validate password //validate password
if (!body.password) {
return 'Missing password';
}
if (body.password.length < 8) { if (body.password.length < 8) {
return 'password too short'; return 'Password too short';
} }
return null; return null;
+3 -3
View File
@@ -1,16 +1,16 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const refresh = require('../utilities/token-refresh'); const tokenRefresh = require('../utilities/token-refresh');
//auth/token //auth/token
module.exports = async (req, res) => { module.exports = async (req, res) => {
const refreshToken = req.body.token; const refreshToken = req.body.token;
return refresh(refreshToken, (err, tokens) => { return tokenRefresh(refreshToken, (err, token) => {
if (err) { if (err) {
return res.status(err).end(); return res.status(err).end();
} }
return res.status(200).send(tokens); return res.status(200).send(token);
}); });
}; };
+2 -2
View File
@@ -11,11 +11,11 @@ const route = async (req, res) => {
//check the given info //check the given info
if (!info) { if (!info) {
return res.status(401).send('validation failed'); return res.status(401).send('Validation failed');
} }
if (info.token != req.query.token) { if (info.token != req.query.token) {
return res.status(401).send('tokens do not match'); return res.status(401).send('Tokens do not match');
} }
//move data to the accounts table //move data to the accounts table
+1 -1
View File
@@ -3,5 +3,5 @@ const sequelize = require('..');
module.exports = sequelize.define('tokens', { module.exports = sequelize.define('tokens', {
token: 'varchar(320)', token: 'varchar(320)',
username: 'varchar(320)' //TODO: why username? email: 'varchar(320)'
}); });
+3 -2
View File
@@ -2,9 +2,10 @@ const jwt = require('jsonwebtoken');
const { tokens } = require('../database/models'); const { tokens } = require('../database/models');
//generates a JWT token based on the given arguments //generates a JWT token based on the given arguments
module.exports = (index, username, type, admin, mod) => { module.exports = (index, email, username, type, admin, mod) => {
const content = { const content = {
index, index,
email,
username, username,
type, type,
admin, admin,
@@ -14,7 +15,7 @@ module.exports = (index, username, type, admin, mod) => {
const accessToken = jwt.sign(content, process.env.SECRET_ACCESS, { expiresIn: '10m' }); const accessToken = jwt.sign(content, process.env.SECRET_ACCESS, { expiresIn: '10m' });
const refreshToken = jwt.sign(content, process.env.SECRET_REFRESH, { expiresIn: '30d' }); const refreshToken = jwt.sign(content, process.env.SECRET_REFRESH, { expiresIn: '30d' });
tokens.create({ token: refreshToken, username: username }); tokens.create({ token: refreshToken, email: email });
return { accessToken, refreshToken }; return { accessToken, refreshToken };
}; };
+1 -1
View File
@@ -24,7 +24,7 @@ module.exports = (token, callback) => {
return callback(403); return callback(403);
} }
const result = generate(user.index, user.username, user.type, user.admin, user.mod); const result = generate(user.index, user.email, user.username, user.type, user.admin, user.mod);
destroy(token); destroy(token);
+1
View File
@@ -0,0 +1 @@
ALTER TABLE `accounts` CHANGE `id` `index` INT( 11 ) NOT NULL AUTO_INCREMENT;
+1
View File
@@ -0,0 +1 @@
DROP TABLE tokens;