Files
soul-server/src/controllers/auth.js
2024-03-08 18:13:08 +03:00

415 lines
11 KiB
JavaScript

const { tableService, rowService } = require('../services');
const config = require('../config');
const {
hashPassword,
checkPasswordStrength,
comparePasswords,
generateToken,
decodeToken,
} = require('../utils');
const { dbTables, constantRoles, apiConstants } = require('../constants');
const createDefaultTables = async () => {
let roleId;
// check if the default tables are already created
const roleTable = tableService.checkTableExists('_roles');
const usersTable = tableService.checkTableExists('_users');
const rolesPermissionTable =
tableService.checkTableExists('_roles_permissions');
const usersRolesTable = tableService.checkTableExists('_users_roles');
// create _users table
if (!usersTable) {
// create the _users table
tableService.createTable('_users', dbTables.userSchema);
}
// create _users_roles table
if (!usersRolesTable) {
// create the _users_roles table
tableService.createTable('_users_roles', dbTables.usersRoleSchema);
}
// create _roles table
if (!roleTable) {
// create the _role table
tableService.createTable('_roles', dbTables.roleSchema);
// create a default role in the _roles table
const role = rowService.save({
tableName: '_roles',
fields: { name: constantRoles.DEFAULT_ROLE },
});
roleId = role.lastInsertRowid;
}
// create _roles_permissions table
if (!rolesPermissionTable && roleId) {
// create the _roles_permissions table
tableService.createTable(
'_roles_permissions',
dbTables.rolePermissionSchema,
{
multipleUniqueConstraints: {
name: 'unique_role_table',
fields: ['role_id', 'table_name'],
},
},
);
// fetch all DB tables
const tables = tableService.listTables();
// add permission for the default role (for each db table)
const permissions = [];
for (const table of tables) {
permissions.push({
role_id: roleId,
table_name: table.name,
create: 'false',
read: 'true',
update: 'false',
delete: 'false',
});
}
// store the permissions in the db
rowService.bulkWrite({
tableName: '_roles_permissions',
fields: permissions,
});
}
};
const updateSuperuser = async (fields) => {
const { id, password, is_superuser } = fields;
let newHashedPassword, newSalt;
let fieldsString = '';
try {
// find the user by using the id field
const users = rowService.get({
tableName: '_users',
whereString: 'WHERE id=?',
whereStringValues: [id],
});
// abort if the id is invalid
if (users.length === 0) {
console.log('The user id you passed does not exist in the database');
process.exit(1);
}
// check if the is_superuser field is passed
if (is_superuser !== undefined) {
fieldsString = `is_superuser = '${is_superuser}'`;
}
// if the password is sent from the CLI, update it
if (password) {
// check if the password is weak
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(password),
)
) {
console.log('Your password should be at least 8 charachters long');
process.exit(1);
}
//hash the password
const { hashedPassword, salt } = await hashPassword(password, 10);
newHashedPassword = hashedPassword;
newSalt = salt;
fieldsString = `${
fieldsString ? fieldsString + ', ' : ''
}hashed_password = '${newHashedPassword}', salt = '${newSalt}'`;
}
// update the user
rowService.update({
tableName: '_users',
lookupField: `id`,
fieldsString,
pks: `${id}`,
});
console.log(
'User updated successfully, you can now restart soul without the updateuser command',
);
process.exit(1);
} catch (error) {
console.log(error);
}
};
const registerUser = async (req, res) => {
const { username, password } = req.body.fields;
try {
// check if the username is taken
let user = rowService.get({
tableName: '_users',
whereString: 'WHERE username=?',
whereStringValues: [username],
});
if (user.length > 0) {
return res.status(409).send({ message: 'This username is taken' });
}
// check if the password is weak
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(password),
)
) {
return res.status(400).send({
message: 'This password is weak, please use another password',
});
}
// hash the password
const { salt, hashedPassword } = await hashPassword(password, 10);
// create the user
const newUser = rowService.save({
tableName: '_users',
fields: {
username,
salt,
hashed_password: hashedPassword,
is_superuser: 'false',
},
});
// find the default role from the DB
let defaultRole = rowService.get({
tableName: '_roles',
whereString: 'WHERE name=?',
whereStringValues: [constantRoles.DEFAULT_ROLE],
});
if (defaultRole.length <= 0) {
return res.status(500).send({
message: 'Please restart soul so a default role can be created',
});
}
// create a role for the user
rowService.save({
tableName: '_users_roles',
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id },
});
res.status(201).send({ message: 'Row Inserted' });
} catch (error) {
console.log(error);
res.status(500).send({ message: error.message });
}
};
const obtainAccessToken = async (req, res) => {
// extract payload
const { username, password } = req.body.fields;
try {
// check if the username exists in the Db
const users = rowService.get({
tableName: '_users',
whereString: 'WHERE username=?',
whereStringValues: [username],
});
if (users.length <= 0) {
return res.status(401).send({ message: 'Invalid username or password' });
}
// check if the password is valid
const user = users[0];
const isMatch = await comparePasswords(password, user.hashed_password);
if (!isMatch) {
return res.status(401).send({ message: 'Invalid username or password' });
}
// get the users role from the DB
const usersRole = rowService.get({
tableName: '_users_roles',
whereString: 'WHERE user_id=?',
whereStringValues: [user.id],
});
const roleId = usersRole[0].role_id;
// get the permission of the role
const permissions = rowService.get({
tableName: '_roles_permissions',
whereString: 'WHERE role_id=?',
whereStringValues: [roleId],
});
const payload = {
username: user.username,
userId: user.id,
isSuperuser: user.is_superuser,
roleId,
permissions,
};
// generate an access token
const accessToken = await generateToken(
{ subject: 'accessToken', ...payload },
config.tokenSecret,
config.accessTokenExpirationTime,
);
// generate a refresh token
const refreshToken = await generateToken(
{ subject: 'refreshToken', ...payload },
config.tokenSecret,
config.refreshTokenExpirationTime,
);
// set the token in the cookie
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
res.cookie('accessToken', accessToken, cookieOptions);
res.cookie('refreshToken', refreshToken, cookieOptions);
res.status(201).send({ message: 'Success', data: { userId: user.id } });
} catch (error) {
console.log(error);
return res.status(500).json({
message: error.message,
error: error,
});
}
};
const refreshAccessToken = async (req, res) => {
try {
// extract the payload from the token and verify it
const payload = await decodeToken(
req.cookies.refreshToken,
config.tokenSecret,
);
// find the user
const users = rowService.get({
tableName: '_users',
whereString: 'WHERE id=?',
whereStringValues: [payload.userId],
});
if (users.length <= 0) {
return res
.status(401)
.send({ message: `User with userId = ${payload.userId} not found` });
}
const user = users[0];
const newPaylod = {
username: payload.username,
userId: payload.userId,
roleId: payload.roleId,
};
// generate an access token
const accessToken = await generateToken(
{ subject: 'accessToken', ...newPaylod },
config.tokenSecret,
config.accessTokenExpirationTime,
);
// generate a refresh token
const refreshToken = await generateToken(
{ subject: 'refreshToken', ...newPaylod },
config.tokenSecret,
config.refreshTokenExpirationTime,
);
// set the token in the cookie
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
res.cookie('accessToken', accessToken, cookieOptions);
res.cookie('refreshToken', refreshToken, cookieOptions);
res.status(201).send({ message: 'Success', data: { userId: user.id } });
} catch (error) {
res.status(401).send({ message: 'Invalid refresh token' });
}
};
const changePassword = async (req, res) => {
const userInfo = req.user;
const { currentPassword, newPassword } = req.body.fields;
try {
// get the user from the Db
const users = rowService.get({
tableName: '_users',
whereString: 'WHERE id=?',
whereStringValues: [userInfo.userId],
});
if (users.length <= 0) {
return res.status(401).send({ message: 'User not found' });
}
const user = users[0];
// check if the users current password is valid
const isMatch = await comparePasswords(
currentPassword,
user.hashed_password,
);
if (!isMatch) {
return res.status(401).send({ message: 'Invalid current password' });
}
// check if the new password is strong
if (
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
checkPasswordStrength(newPassword),
)
) {
return res.status(400).send({
message: 'This password is weak, please use another password',
});
}
// hash the password
const { salt, hashedPassword } = await hashPassword(newPassword, 10);
user.salt = salt;
user.hashed_password = hashedPassword;
// update the user
rowService.update({
tableName: '_users',
lookupField: `id`,
fieldsString: `hashed_password = '${hashedPassword}', salt = '${salt}'`,
pks: `${user.id}`,
});
res.status(201).send({
message: 'Password updated successfully',
data: { id: user.id, username: user.username },
});
} catch (error) {
res.status(500).send({ message: error.message });
}
};
module.exports = {
createDefaultTables,
updateSuperuser,
registerUser,
obtainAccessToken,
refreshAccessToken,
changePassword,
};