Add auth service + move string values to constant folders + Add error messages to constants + setup a folder for auth controller + modify schema + fix comments + modify validator
This commit is contained in:
0
.vscode/settings.json
vendored
0
.vscode/settings.json
vendored
@@ -1,14 +1,8 @@
|
||||
module.exports = {
|
||||
defaultRoutes: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
||||
authEndpoints: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
||||
baseTableUrl: '/api/tables',
|
||||
universalAccessEndpoints: ['/api/auth/change-password'],
|
||||
fields: {
|
||||
_users: {
|
||||
SALT: 'salt',
|
||||
IS_SUPERUSER: 'is_superuser',
|
||||
HASHED_PASSWORD: 'hashed_password',
|
||||
},
|
||||
},
|
||||
|
||||
DEFAULT_PAGE_LIMIT: 10,
|
||||
DEFAULT_PAGE_INDEX: 0,
|
||||
PASSWORD: {
|
||||
@@ -17,6 +11,13 @@ module.exports = {
|
||||
},
|
||||
|
||||
httpVerbs: {
|
||||
POST: 'POST',
|
||||
GET: 'GET',
|
||||
PUT: 'PUT',
|
||||
DELETE: 'DELETE',
|
||||
},
|
||||
|
||||
httpMethodDefinitions: {
|
||||
POST: 'CREATE',
|
||||
GET: 'READ',
|
||||
PUT: 'UPDATE',
|
||||
|
||||
5
src/constants/auth.js
Normal file
5
src/constants/auth.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
SALT_ROUNDS: 10,
|
||||
ACCESS_TOKEN_SUBJECT: 'accessToken',
|
||||
REFRESH_TOKEN_SUBJECT: 'refreshToken',
|
||||
};
|
||||
@@ -1,5 +1,13 @@
|
||||
const dbConstants = require('./tables');
|
||||
const apiConstants = require('./api');
|
||||
const constantRoles = require('./roles');
|
||||
const responseMessages = require('./messages');
|
||||
const authConstants = require('./auth');
|
||||
|
||||
module.exports = { dbConstants, apiConstants, constantRoles };
|
||||
module.exports = {
|
||||
dbConstants,
|
||||
apiConstants,
|
||||
constantRoles,
|
||||
responseMessages,
|
||||
authConstants,
|
||||
};
|
||||
|
||||
41
src/constants/messages.js
Normal file
41
src/constants/messages.js
Normal file
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
successMessage: {
|
||||
SUCCESS: 'Success',
|
||||
ROW_INSERTED: 'Row Inserted',
|
||||
PASSWORD_UPDATE_SUCCESS: 'Password updated successfully',
|
||||
USER_UPDATE_SUCCESS: 'User updated successfully',
|
||||
INITIAL_USER_CREATED_SUCCESS: 'Initial user created successfully',
|
||||
},
|
||||
|
||||
errorMessage: {
|
||||
USERNAME_TAKEN_ERROR: 'This username is taken',
|
||||
WEAK_PASSWORD_ERROR: 'This password is weak, please use another password',
|
||||
DEFAULT_ROLE_NOT_CREATED_ERROR:
|
||||
'Please restart soul so a default role can be created',
|
||||
INVALID_USERNAME_PASSWORD_ERROR: 'Invalid username or password',
|
||||
INVALID_REFRESH_TOKEN_ERROR: 'Invalid refresh token',
|
||||
INVALID_ACCESS_TOKEN_ERROR: 'Invalid access token',
|
||||
USER_NOT_FOUND_ERROR: 'User not found',
|
||||
INVALID_CURRENT_PASSWORD_ERROR: 'Invalid current password',
|
||||
NOT_AUTHORIZED_ERROR: 'Not authorized',
|
||||
PERMISSION_NOT_DEFINED_ERROR: 'Permission not defined for this role',
|
||||
ROLE_NOT_FOUND_ERROR: 'Role not found for this user',
|
||||
AUTH_SET_TO_FALSE_ERROR:
|
||||
'You can not access this endpoint while AUTH is set to false',
|
||||
RESERVED_TABLE_NAME_ERROR:
|
||||
'The table name is reserved. Please choose a different name for the table.',
|
||||
SERVER_ERROR: 'Server error',
|
||||
|
||||
INITIAL_USER_USERNAME_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users username either from the CLI with the --iuu or from the environment variable using the INITIAL_USER_USERNAME flag',
|
||||
INITIAL_USER_PASSWORD_NOT_PASSED_ERROR:
|
||||
'Error: You should pass the initial users password either from the CLI with the --iup or from the environment variable using the INITIAL_USER_PASSWORD flag',
|
||||
|
||||
USERNAME_REQUIRED_ERROR: 'username is required',
|
||||
PASSWORD_REQUIRED_ERROR: 'password is required',
|
||||
},
|
||||
|
||||
infoMessage: {
|
||||
INITIAL_USER_ALREADY_CREATED: 'Initial user is already created',
|
||||
},
|
||||
};
|
||||
@@ -1,13 +1,47 @@
|
||||
const USERS_TABLE = '_users';
|
||||
const ROLES_TABLE = '_roles';
|
||||
const USERS_ROLES_TABLE = '_users_roles';
|
||||
const ROLES_PERMISSIONS_TABLE = '_roles_permissions';
|
||||
|
||||
module.exports = {
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
|
||||
reservedTableNames: [
|
||||
'_users',
|
||||
'_roles',
|
||||
'_roles_permissions',
|
||||
'_users_roles',
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
],
|
||||
|
||||
USER_TABLE: '_users',
|
||||
ROLE_TABLE: '_roles',
|
||||
USERS_ROLES_TABLE: '_users_roles',
|
||||
ROLE_PERMISSIONS_TABLE: '_roles_permissions',
|
||||
constraints: {
|
||||
UNIQUE_USERS_ROLE: 'unique_users_role',
|
||||
UNIQUE_ROLES_TABLE: 'unique_ROLES_TABLE',
|
||||
},
|
||||
|
||||
tableFields: {
|
||||
ID: 'id',
|
||||
|
||||
// _role fields
|
||||
ROLE_NAME: 'name',
|
||||
|
||||
// _user fields
|
||||
USERNAME: 'username',
|
||||
HASHED_PASSWORD: 'hashed_password',
|
||||
SALT: 'salt',
|
||||
IS_SUPERUSER: 'is_superuser',
|
||||
|
||||
// _roles_permissions fields
|
||||
ROLE_ID: 'role_id',
|
||||
TABLE_NAME: 'table_name',
|
||||
CREATE: 'create',
|
||||
READ: 'read',
|
||||
UPDATE: 'update',
|
||||
DELETE: 'delete',
|
||||
|
||||
// _users_roles fields
|
||||
USER_ID: 'user_id',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,729 +0,0 @@
|
||||
const { tableService, rowService } = require('../services');
|
||||
const { constantRoles, apiConstants, dbConstants } = require('../constants');
|
||||
const schema = require('../db/schema');
|
||||
const config = require('../config');
|
||||
const {
|
||||
hashPassword,
|
||||
checkPasswordStrength,
|
||||
comparePasswords,
|
||||
generateToken,
|
||||
decodeToken,
|
||||
toBoolean,
|
||||
} = require('../utils');
|
||||
|
||||
const { USER_TABLE, ROLE_TABLE, USERS_ROLES_TABLE, ROLE_PERMISSIONS_TABLE } =
|
||||
dbConstants;
|
||||
|
||||
const createDefaultTables = async () => {
|
||||
let roleId;
|
||||
|
||||
// check if the default tables are already created
|
||||
const roleTable = tableService.checkTableExists(ROLE_TABLE);
|
||||
const usersTable = tableService.checkTableExists(USER_TABLE);
|
||||
const rolesPermissionTable = tableService.checkTableExists(
|
||||
ROLE_PERMISSIONS_TABLE,
|
||||
);
|
||||
const usersRolesTable = tableService.checkTableExists(USERS_ROLES_TABLE);
|
||||
|
||||
// create _users table
|
||||
if (!usersTable) {
|
||||
tableService.createTable(USER_TABLE, schema.userSchema);
|
||||
}
|
||||
|
||||
// create _users_roles table
|
||||
if (!usersRolesTable) {
|
||||
tableService.createTable(
|
||||
USERS_ROLES_TABLE,
|
||||
|
||||
schema.usersRoleSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: 'unique_users_role',
|
||||
fields: ['user_id', 'role_id'],
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// create _roles table
|
||||
if (!roleTable) {
|
||||
tableService.createTable(ROLE_TABLE, schema.roleSchema);
|
||||
|
||||
// create a default role in the _roles table
|
||||
const role = rowService.save({
|
||||
tableName: ROLE_TABLE,
|
||||
fields: { name: constantRoles.DEFAULT_ROLE },
|
||||
});
|
||||
roleId = role.lastInsertRowid;
|
||||
}
|
||||
|
||||
// create _roles_permissions table
|
||||
if (!rolesPermissionTable && roleId) {
|
||||
tableService.createTable(
|
||||
ROLE_PERMISSIONS_TABLE,
|
||||
schema.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: ROLE_PERMISSIONS_TABLE,
|
||||
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: USER_TABLE,
|
||||
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: USER_TABLE,
|
||||
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) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Register User'
|
||||
#swagger.description = 'Endpoint to signup'
|
||||
|
||||
#swagger.parameters['username'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: { $ref: '#/definitions/UserRegistrationRequestBody' }
|
||||
}
|
||||
*/
|
||||
|
||||
const { username, password } = req.body.fields;
|
||||
|
||||
try {
|
||||
if (!username) {
|
||||
return res.status(400).send({ message: 'username is required' });
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return res.status(400).send({ message: 'password is required' });
|
||||
}
|
||||
|
||||
// check if the username is taken
|
||||
let user = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username],
|
||||
});
|
||||
|
||||
if (user.length > 0) {
|
||||
return res.status(409).send({ message: 'This username is taken' });
|
||||
|
||||
/*
|
||||
#swagger.responses[409] = {
|
||||
description: 'Username taken error',
|
||||
schema: {
|
||||
$ref: '#/definitions/UsernameTakenErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// 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',
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/WeakPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(password, 10);
|
||||
|
||||
// create the user
|
||||
const newUser = rowService.save({
|
||||
tableName: USER_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
salt,
|
||||
hashed_password: hashedPassword,
|
||||
is_superuser: 'false',
|
||||
},
|
||||
});
|
||||
|
||||
// find the default role from the DB
|
||||
let defaultRole = rowService.get({
|
||||
tableName: ROLE_TABLE,
|
||||
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',
|
||||
});
|
||||
/*
|
||||
#swagger.responses[500] = {
|
||||
description: 'Server error',
|
||||
schema: {
|
||||
$ref: '#/definitions/DefaultRoleNotCreatedErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// create a role for the user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id },
|
||||
});
|
||||
|
||||
res.status(201).send({ message: 'Row Inserted' });
|
||||
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Row inserted',
|
||||
schema: {
|
||||
$ref: '#/definitions/InsertRowSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
const obtainAccessToken = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Obtain Access Token'
|
||||
#swagger.description = 'Endpoint to generate access and refresh tokens'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: { $ref: '#/definitions/ObtainAccessTokenRequestBody' }
|
||||
}
|
||||
*/
|
||||
|
||||
// extract payload
|
||||
const { username, password } = req.body.fields;
|
||||
|
||||
try {
|
||||
// check if the username exists in the Db
|
||||
const users = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
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' });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid username or password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidCredentialErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
let userRoles, permissions, roleIds;
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
userRoles = rowService.get({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
whereString: 'WHERE user_id=?',
|
||||
whereStringValues: [user.id],
|
||||
});
|
||||
|
||||
if (userRoles <= 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.send({ message: 'Role not found for this user' });
|
||||
}
|
||||
|
||||
roleIds = userRoles.map((role) => role.role_id);
|
||||
|
||||
// get the permission of the role
|
||||
permissions = rowService.get({
|
||||
tableName: ROLE_PERMISSIONS_TABLE,
|
||||
whereString: `WHERE role_id IN (${roleIds.map(() => '?')})`,
|
||||
whereStringValues: [...roleIds],
|
||||
});
|
||||
}
|
||||
|
||||
const payload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
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 } });
|
||||
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Access token and Refresh token generated',
|
||||
schema: {
|
||||
$ref: '#/definitions/ObtainAccessTokenSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return res.status(500).json({
|
||||
message: error.message,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccessToken = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Refresh Access Token'
|
||||
#swagger.description = 'Endpoint to refresh access and refresh tokens'
|
||||
*/
|
||||
|
||||
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: USER_TABLE,
|
||||
whereString: 'WHERE id=?',
|
||||
whereStringValues: [payload.userId],
|
||||
});
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: `User with userId = ${payload.userId} not found` });
|
||||
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
$ref: '#/definitions/UserNotFoundErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
let userRoles, permissions, roleIds;
|
||||
const user = users[0];
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
userRoles = rowService.get({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
whereString: 'WHERE user_id=?',
|
||||
whereStringValues: [user.id],
|
||||
});
|
||||
|
||||
roleIds = userRoles.map((role) => role.role_id);
|
||||
|
||||
// get the permission of the role
|
||||
permissions = rowService.get({
|
||||
tableName: ROLE_PERMISSIONS_TABLE,
|
||||
whereString: `WHERE role_id IN (${roleIds.map(() => '?')})`,
|
||||
whereStringValues: [...roleIds],
|
||||
});
|
||||
}
|
||||
|
||||
const newPayload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
permissions,
|
||||
};
|
||||
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: 'accessToken', ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime,
|
||||
);
|
||||
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: 'refreshToken', ...newPayload },
|
||||
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(200).send({ message: 'Success', data: { userId: user.id } });
|
||||
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Access token refreshed',
|
||||
schema: {
|
||||
$ref: '#/definitions/RefreshAccessTokenSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(403).send({ message: 'Invalid refresh token' });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid refresh token error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidRefreshTokenErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Change Password'
|
||||
#swagger.description = 'Endpoint to change a password'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: {
|
||||
$ref: '#/definitions/ChangePasswordRequestBody'
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const userInfo = req.user;
|
||||
const { currentPassword, newPassword } = req.body.fields;
|
||||
|
||||
try {
|
||||
// get the user from the Db
|
||||
const users = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
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' });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// 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',
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/WeakPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(newPassword, 10);
|
||||
|
||||
user.salt = salt;
|
||||
user.hashed_password = hashedPassword;
|
||||
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USER_TABLE,
|
||||
lookupField: `id`,
|
||||
fieldsString: `hashed_password = '${hashedPassword}', salt = '${salt}'`,
|
||||
pks: `${user.id}`,
|
||||
});
|
||||
|
||||
res.status(200).send({
|
||||
message: 'Password updated successfully',
|
||||
data: { id: user.id, username: user.username },
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/ChangePasswordSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
const createInitialUser = async () => {
|
||||
// extract some fields from the environment variables or from the CLI
|
||||
const { initialUserUsername: username, initialUserPassword: password } =
|
||||
config;
|
||||
|
||||
try {
|
||||
// check if there is are users in the DB
|
||||
const users = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
whereString: '',
|
||||
whereStringValues: [],
|
||||
});
|
||||
|
||||
if (users.length <= 0) {
|
||||
// check if initial users username is passed from the env or CLI
|
||||
if (!username) {
|
||||
console.error(
|
||||
'Error: You should pass the initial users username either from the CLI with the --iuu or from the environment variable using the INITIAL_USER_USERNAME flag',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if initial users password is passed from the env or CLI
|
||||
if (!password) {
|
||||
console.error(
|
||||
'Error: You should pass the initial users password either from the CLI with the --iup or from the environment variable using the INITIAL_USER_PASSWORD flag',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if the usernmae is taken
|
||||
const users = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username],
|
||||
});
|
||||
|
||||
if (users.length > 0) {
|
||||
console.error(
|
||||
'Error: The username you passed for the initial user is already taken, please use another username',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if the password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
console.error(
|
||||
'Error: The password you passed for the initial user is weak, please use another password',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(password, 10);
|
||||
|
||||
// create the initial user
|
||||
const { lastInsertRowid: userId } = rowService.save({
|
||||
tableName: USER_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
is_superuser: 'false',
|
||||
},
|
||||
});
|
||||
|
||||
// get the default role from the DB
|
||||
const roles = rowService.get({
|
||||
tableName: ROLE_TABLE,
|
||||
whereString: 'WHERE name=?',
|
||||
whereStringValues: [constantRoles.DEFAULT_ROLE],
|
||||
});
|
||||
|
||||
if (roles.length <= 0) {
|
||||
console.log(
|
||||
'Default role not found, please restart soul so a default role can be created',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const defaultRoleId = roles[0].id;
|
||||
|
||||
// create a _users_role for the initial user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: userId, role_id: defaultRoleId },
|
||||
});
|
||||
|
||||
console.log('Initial user created');
|
||||
} else {
|
||||
console.log('Initial user is already created');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const isUsernameTaken = (username) => {
|
||||
let user = rowService.get({
|
||||
tableName: USER_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username],
|
||||
});
|
||||
|
||||
return user.length > 0;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createDefaultTables,
|
||||
updateSuperuser,
|
||||
registerUser,
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
changePassword,
|
||||
createInitialUser,
|
||||
isUsernameTaken,
|
||||
};
|
||||
@@ -182,30 +182,6 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('PUT /tables/_users/rows/:id should throw a 409 error if the username is taken', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.put('/api/tables/_users/rows/1')
|
||||
.set('Cookie', [`accessToken=${accessToken}`])
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username, //A user with user1.username is already created in the first test suite
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toEqual(409);
|
||||
expect(res.body.message).toEqual('This username is already taken');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('DELETE /tables/_users/rows/:id should remove a user', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', isSuperuser: true },
|
||||
|
||||
16
src/controllers/auth/common.js
Normal file
16
src/controllers/auth/common.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { rowService } = require('../../services');
|
||||
const { dbConstants } = require('../../constants');
|
||||
|
||||
const { USERS_TABLE } = dbConstants;
|
||||
|
||||
const isUsernameTaken = (username) => {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: 'WHERE username=?',
|
||||
whereStringValues: [username],
|
||||
});
|
||||
|
||||
return users.length > 0;
|
||||
};
|
||||
|
||||
module.exports = { isUsernameTaken };
|
||||
5
src/controllers/auth/index.js
Normal file
5
src/controllers/auth/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const users = require('./user');
|
||||
const token = require('./token');
|
||||
const tables = require('./tables');
|
||||
|
||||
module.exports = { ...users, ...token, ...tables };
|
||||
96
src/controllers/auth/tables.js
Normal file
96
src/controllers/auth/tables.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const { tableService, rowService } = require('../../services');
|
||||
const { constantRoles, dbConstants } = require('../../constants');
|
||||
const schema = require('../../db/schema');
|
||||
|
||||
const {
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
constraints,
|
||||
tableFields,
|
||||
} = dbConstants;
|
||||
|
||||
const createDefaultTables = async () => {
|
||||
let roleId;
|
||||
|
||||
// check if the default tables are already created
|
||||
const roleTable = tableService.checkTableExists(ROLES_TABLE);
|
||||
const usersTable = tableService.checkTableExists(USERS_TABLE);
|
||||
const rolesPermissionTable = tableService.checkTableExists(
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
);
|
||||
const usersRolesTable = tableService.checkTableExists(USERS_ROLES_TABLE);
|
||||
|
||||
// create _users table
|
||||
if (!usersTable) {
|
||||
tableService.createTable(USERS_TABLE, schema.userSchema);
|
||||
}
|
||||
|
||||
// create _users_roles table
|
||||
if (!usersRolesTable) {
|
||||
tableService.createTable(
|
||||
USERS_ROLES_TABLE,
|
||||
|
||||
schema.usersRoleSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_USERS_ROLE,
|
||||
fields: [tableFields.USER_ID, tableFields.USER_ID],
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// create _roles table
|
||||
if (!roleTable) {
|
||||
tableService.createTable(ROLES_TABLE, schema.roleSchema);
|
||||
|
||||
// create a default role in the _roles table
|
||||
const role = rowService.save({
|
||||
tableName: ROLES_TABLE,
|
||||
fields: { name: constantRoles.DEFAULT_ROLE },
|
||||
});
|
||||
roleId = role.lastInsertRowid;
|
||||
}
|
||||
|
||||
// create _roles_permissions table
|
||||
if (!rolesPermissionTable && roleId) {
|
||||
tableService.createTable(
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
schema.rolePermissionSchema,
|
||||
{
|
||||
multipleUniqueConstraints: {
|
||||
name: constraints.UNIQUE_ROLES_TABLE,
|
||||
fields: [tableFields.ROLE_ID, tableFields.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_TABLE,
|
||||
fields: permissions,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createDefaultTables,
|
||||
};
|
||||
243
src/controllers/auth/token.js
Normal file
243
src/controllers/auth/token.js
Normal file
@@ -0,0 +1,243 @@
|
||||
const { authService } = require('../../services');
|
||||
const { responseMessages, authConstants } = require('../../constants');
|
||||
const config = require('../../config');
|
||||
const {
|
||||
comparePasswords,
|
||||
generateToken,
|
||||
decodeToken,
|
||||
toBoolean,
|
||||
} = require('../../utils');
|
||||
|
||||
const { successMessage, errorMessage } = responseMessages;
|
||||
|
||||
const obtainAccessToken = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Obtain Access Token'
|
||||
#swagger.description = 'Endpoint to generate access and refresh tokens'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: { $ref: '#/definitions/ObtainAccessTokenRequestBody' }
|
||||
}
|
||||
*/
|
||||
|
||||
// extract payload
|
||||
const { username, password } = req.body.fields;
|
||||
|
||||
try {
|
||||
// check if the username exists in the Db
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_USERNAME_PASSWORD_ERROR });
|
||||
}
|
||||
|
||||
// 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: errorMessage.INVALID_USERNAME_PASSWORD_ERROR });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid username or password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidCredentialErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
let permissions, roleIds;
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id,
|
||||
res,
|
||||
});
|
||||
|
||||
permissions = roleData.permissions;
|
||||
roleIds = roleData.roleIds;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
permissions,
|
||||
};
|
||||
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime,
|
||||
);
|
||||
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...payload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime,
|
||||
);
|
||||
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions);
|
||||
res.cookie(
|
||||
authConstants.REFRESH_TOKEN_SUBJECT,
|
||||
refreshToken,
|
||||
cookieOptions,
|
||||
);
|
||||
|
||||
res
|
||||
.status(201)
|
||||
.send({ message: successMessage.SUCCESS, data: { userId: user.id } });
|
||||
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Access token and Refresh token generated',
|
||||
schema: {
|
||||
$ref: '#/definitions/ObtainAccessTokenSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return res.status(500).json({
|
||||
message: errorMessage.SERVER_ERROR,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccessToken = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Refresh Access Token'
|
||||
#swagger.description = 'Endpoint to refresh access and refresh tokens'
|
||||
*/
|
||||
|
||||
try {
|
||||
// extract the payload from the token and verify it
|
||||
const payload = await decodeToken(
|
||||
req.cookies.refreshToken,
|
||||
config.tokenSecret,
|
||||
);
|
||||
|
||||
// find the user
|
||||
const users = authService.getUsersById({ userId: payload.userId });
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.USER_NOT_FOUND_ERROR });
|
||||
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
$ref: '#/definitions/UserNotFoundErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
let permissions, roleIds;
|
||||
const user = users[0];
|
||||
|
||||
// if the user is not a superuser get the role and its permission from the DB
|
||||
if (!toBoolean(user.is_superuser)) {
|
||||
const roleData = getUsersRoleAndPermission({
|
||||
userId: user.id,
|
||||
res,
|
||||
});
|
||||
|
||||
permissions = roleData.permissions;
|
||||
roleIds = roleData.roleIds;
|
||||
}
|
||||
|
||||
const newPayload = {
|
||||
username: user.username,
|
||||
userId: user.id,
|
||||
isSuperuser: user.is_superuser,
|
||||
roleIds,
|
||||
permissions,
|
||||
};
|
||||
|
||||
// generate an access token
|
||||
const accessToken = await generateToken(
|
||||
{ subject: authConstants.ACCESS_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.accessTokenExpirationTime,
|
||||
);
|
||||
|
||||
// generate a refresh token
|
||||
const refreshToken = await generateToken(
|
||||
{ subject: authConstants.REFRESH_TOKEN_SUBJECT, ...newPayload },
|
||||
config.tokenSecret,
|
||||
config.refreshTokenExpirationTime,
|
||||
);
|
||||
|
||||
// set the token in the cookie
|
||||
let cookieOptions = { httpOnly: true, secure: false, Path: '/' };
|
||||
res.cookie(authConstants.ACCESS_TOKEN_SUBJECT, accessToken, cookieOptions);
|
||||
res.cookie(
|
||||
authConstants.REFRESH_TOKEN_SUBJECT,
|
||||
refreshToken,
|
||||
cookieOptions,
|
||||
);
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.send({ message: successMessage.SUCCESS, data: { userId: user.id } });
|
||||
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Access token refreshed',
|
||||
schema: {
|
||||
$ref: '#/definitions/RefreshAccessTokenSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(403).send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'Invalid refresh token error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidRefreshTokenErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
const getUsersRoleAndPermission = ({ userId, res }) => {
|
||||
const userRoles = authService.getUserRoleByUserId({ userId });
|
||||
|
||||
if (userRoles <= 0) {
|
||||
res.status(401).send({ message: errorMessage.ROLE_NOT_FOUND_ERROR });
|
||||
throw new Error(errorMessage.ROLE_NOT_FOUND_ERROR);
|
||||
}
|
||||
|
||||
const roleIds = userRoles.map((role) => role.role_id);
|
||||
|
||||
// get the permission of the role
|
||||
const permissions = authService.getPermissionByRoleIds({ roleIds });
|
||||
|
||||
return { userRoles, roleIds, permissions };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
};
|
||||
394
src/controllers/auth/user.js
Normal file
394
src/controllers/auth/user.js
Normal file
@@ -0,0 +1,394 @@
|
||||
const { rowService, authService } = require('../../services');
|
||||
const {
|
||||
apiConstants,
|
||||
dbConstants,
|
||||
responseMessages,
|
||||
authConstants,
|
||||
} = require('../../constants');
|
||||
const config = require('../../config');
|
||||
const {
|
||||
hashPassword,
|
||||
checkPasswordStrength,
|
||||
comparePasswords,
|
||||
} = require('../../utils');
|
||||
|
||||
const { USERS_TABLE, USERS_ROLES_TABLE, tableFields } = dbConstants;
|
||||
|
||||
const { SALT_ROUNDS } = authConstants;
|
||||
|
||||
const { successMessage, errorMessage, infoMessage } = responseMessages;
|
||||
|
||||
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 = authService.getRoleByUserId({ id });
|
||||
|
||||
// abort if the id is invalid
|
||||
if (users.length === 0) {
|
||||
console.log(errorMessage.USER_NOT_FOUND_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if the is_superuser field is passed
|
||||
if (is_superuser !== undefined) {
|
||||
fieldsString = `${tableFields.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(errorMessage.WEAK_PASSWORD_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(
|
||||
password,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
newHashedPassword = hashedPassword;
|
||||
newSalt = salt;
|
||||
|
||||
fieldsString = `${fieldsString ? fieldsString + ', ' : ''} ${
|
||||
tableFields.HASHED_PASSWORD
|
||||
} = '${newHashedPassword}', ${tableFields.SALT} = '${newSalt}'`;
|
||||
}
|
||||
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString,
|
||||
pks: `${id}`,
|
||||
});
|
||||
|
||||
console.log(successMessage.USER_UPDATE_SUCCESS);
|
||||
process.exit(1);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const registerUser = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Register User'
|
||||
#swagger.description = 'Endpoint to signup'
|
||||
|
||||
#swagger.parameters['username'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: { $ref: '#/definitions/UserRegistrationRequestBody' }
|
||||
}
|
||||
*/
|
||||
|
||||
const { username, password } = req.body.fields;
|
||||
|
||||
try {
|
||||
if (!username) {
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: errorMessage.USERNAME_REQUIRED_ERROR });
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: errorMessage.PASSWORD_REQUIRED_ERROR });
|
||||
}
|
||||
|
||||
// check if the username is taken
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
|
||||
if (users.length > 0) {
|
||||
return res
|
||||
.status(409)
|
||||
.send({ message: errorMessage.USERNAME_TAKEN_ERROR });
|
||||
|
||||
/*
|
||||
#swagger.responses[409] = {
|
||||
description: 'Username taken error',
|
||||
schema: {
|
||||
$ref: '#/definitions/UsernameTakenErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// check if the password is weak
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR,
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/WeakPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(password, SALT_ROUNDS);
|
||||
|
||||
// create the user
|
||||
const newUser = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
salt,
|
||||
hashed_password: hashedPassword,
|
||||
is_superuser: 'false',
|
||||
},
|
||||
});
|
||||
|
||||
// find the default role from the DB
|
||||
const defaultRole = authService.getDefaultRole();
|
||||
|
||||
if (defaultRole.length <= 0) {
|
||||
return res.status(500).send({
|
||||
message: errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR,
|
||||
});
|
||||
/*
|
||||
#swagger.responses[500] = {
|
||||
description: 'Server error',
|
||||
schema: {
|
||||
$ref: '#/definitions/DefaultRoleNotCreatedErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// create a role for the user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: newUser.lastInsertRowid, role_id: defaultRole[0].id },
|
||||
});
|
||||
|
||||
res.status(201).send({ message: successMessage.ROW_INSERTED });
|
||||
|
||||
/*
|
||||
#swagger.responses[201] = {
|
||||
description: 'Row inserted',
|
||||
schema: {
|
||||
$ref: '#/definitions/InsertRowSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR });
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async (req, res) => {
|
||||
/*
|
||||
#swagger.tags = ['Auth']
|
||||
#swagger.summary = 'Change Password'
|
||||
#swagger.description = 'Endpoint to change a password'
|
||||
|
||||
#swagger.parameters['body'] = {
|
||||
in: 'body',
|
||||
required: true,
|
||||
type: 'object',
|
||||
schema: {
|
||||
$ref: '#/definitions/ChangePasswordRequestBody'
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const userInfo = req.user;
|
||||
const { currentPassword, newPassword } = req.body.fields;
|
||||
|
||||
try {
|
||||
// get the user from the Db
|
||||
const users = authService.getUsersById({ userId: userInfo.userId });
|
||||
|
||||
if (users.length <= 0) {
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.USER_NOT_FOUND_ERROR });
|
||||
}
|
||||
|
||||
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: errorMessage.INVALID_CURRENT_PASSWORD_ERROR });
|
||||
/*
|
||||
#swagger.responses[401] = {
|
||||
description: 'User not found error',
|
||||
schema: {
|
||||
$ref: '#/definitions/InvalidPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// check if the new password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(newPassword),
|
||||
)
|
||||
) {
|
||||
return res.status(400).send({
|
||||
message: errorMessage.WEAK_PASSWORD_ERROR,
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[400] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/WeakPasswordErrorResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { salt, hashedPassword } = await hashPassword(
|
||||
newPassword,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
|
||||
user.salt = salt;
|
||||
user.hashed_password = hashedPassword;
|
||||
|
||||
// update the user
|
||||
rowService.update({
|
||||
tableName: USERS_TABLE,
|
||||
lookupField: tableFields.ID,
|
||||
fieldsString: `${tableFields.HASHED_PASSWORD} = '${hashedPassword}', ${tableFields.SALT} = '${salt}'`,
|
||||
pks: `${user.id}`,
|
||||
});
|
||||
|
||||
res.status(200).send({
|
||||
message: successMessage.PASSWORD_UPDATE_SUCCESS,
|
||||
data: { id: user.id, username: user.username },
|
||||
});
|
||||
|
||||
/*
|
||||
#swagger.responses[200] = {
|
||||
description: 'Weak password error',
|
||||
schema: {
|
||||
$ref: '#/definitions/ChangePasswordSuccessResponse'
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: errorMessage.SERVER_ERROR });
|
||||
}
|
||||
};
|
||||
|
||||
const createInitialUser = async () => {
|
||||
// extract some fields from the environment variables or from the CLI
|
||||
const { initialUserUsername: username, initialUserPassword: password } =
|
||||
config;
|
||||
|
||||
try {
|
||||
// check if there are users in the DB
|
||||
const users = authService.getAllUsers();
|
||||
|
||||
if (users.length <= 0) {
|
||||
// check if initial users username is passed from the env or CLI
|
||||
if (!username) {
|
||||
console.error(errorMessage.INITIAL_USER_USERNAME_NOT_PASSED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if initial users password is passed from the env or CLI
|
||||
if (!password) {
|
||||
console.error(errorMessage.INITIAL_USER_PASSWORD_NOT_PASSED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if the usernmae is taken
|
||||
const users = authService.getUsersByUsername({ username });
|
||||
|
||||
if (users.length > 0) {
|
||||
console.error(errorMessage.USERNAME_TAKEN_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// check if the password is strong
|
||||
if (
|
||||
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||
checkPasswordStrength(password),
|
||||
)
|
||||
) {
|
||||
console.error(errorMessage.WEAK_PASSWORD_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const { hashedPassword, salt } = await hashPassword(
|
||||
password,
|
||||
SALT_ROUNDS,
|
||||
);
|
||||
|
||||
// create the initial user
|
||||
const { lastInsertRowid: userId } = rowService.save({
|
||||
tableName: USERS_TABLE,
|
||||
fields: {
|
||||
username,
|
||||
hashed_password: hashedPassword,
|
||||
salt,
|
||||
is_superuser: 'false',
|
||||
},
|
||||
});
|
||||
|
||||
// get the default role from the DB
|
||||
const roles = authService.getDefaultRole();
|
||||
|
||||
if (roles.length <= 0) {
|
||||
console.log(errorMessage.DEFAULT_ROLE_NOT_CREATED_ERROR);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const defaultRoleId = roles[0].id;
|
||||
|
||||
// create a _users_role for the initial user
|
||||
rowService.save({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
fields: { user_id: userId, role_id: defaultRoleId },
|
||||
});
|
||||
|
||||
console.log(successMessage.INITIAL_USER_CREATED_SUCCESS);
|
||||
} else {
|
||||
console.log(infoMessage.INITIAL_USER_ALREADY_CREATED);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
updateSuperuser,
|
||||
registerUser,
|
||||
changePassword,
|
||||
createInitialUser,
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
const { dbConstants } = require('../constants');
|
||||
|
||||
const { tableFields, ROLES_TABLE, USERS_TABLE } = dbConstants;
|
||||
|
||||
module.exports = {
|
||||
roleSchema: [
|
||||
{
|
||||
name: 'name',
|
||||
name: tableFields.ROLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -11,21 +15,21 @@ module.exports = {
|
||||
|
||||
userSchema: [
|
||||
{
|
||||
name: 'username',
|
||||
name: tableFields.USERNAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: 'hashed_password',
|
||||
name: tableFields.HASHED_PASSWORD,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: true,
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: 'salt',
|
||||
name: tableFields.SALT,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -33,7 +37,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
{
|
||||
name: 'is_superuser',
|
||||
name: tableFields.IS_SUPERUSER,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -43,16 +47,16 @@ module.exports = {
|
||||
|
||||
rolePermissionSchema: [
|
||||
{
|
||||
name: 'role_id',
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: '_roles', column: 'id' },
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'table_name',
|
||||
name: tableFields.TABLE_NAME,
|
||||
type: 'TEXT',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -60,7 +64,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
{
|
||||
name: 'create',
|
||||
name: tableFields.CREATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -68,7 +72,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
{
|
||||
name: 'read',
|
||||
name: tableFields.READ,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -76,7 +80,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
{
|
||||
name: 'update',
|
||||
name: tableFields.UPDATE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -84,7 +88,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
{
|
||||
name: 'delete',
|
||||
name: tableFields.DELETE,
|
||||
type: 'BOOLEAN',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
@@ -94,21 +98,21 @@ module.exports = {
|
||||
|
||||
usersRoleSchema: [
|
||||
{
|
||||
name: 'user_id',
|
||||
name: tableFields.USER_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: '_users', column: 'id' },
|
||||
foreignKey: { table: USERS_TABLE, column: tableFields.ID },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'role_id',
|
||||
name: tableFields.ROLE_ID,
|
||||
type: 'NUMERIC',
|
||||
primaryKey: false,
|
||||
notNull: true,
|
||||
unique: false,
|
||||
foreignKey: { table: '_roles', column: 'id' },
|
||||
foreignKey: { table: ROLES_TABLE, column: tableFields.ID },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
const config = require('../config');
|
||||
const { registerUser, isUsernameTaken } = require('../controllers/auth');
|
||||
const { apiConstants, dbConstants } = require('../constants/');
|
||||
const { registerUser } = require('../controllers/auth');
|
||||
const {
|
||||
apiConstants,
|
||||
dbConstants,
|
||||
responseMessages,
|
||||
} = require('../constants/');
|
||||
const { removeFields } = require('../utils');
|
||||
|
||||
const { SALT, HASHED_PASSWORD, IS_SUPERUSER } = apiConstants.fields._users;
|
||||
const { reservedTableNames } = dbConstants;
|
||||
const { httpVerbs } = apiConstants;
|
||||
const { reservedTableNames, USERS_TABLE, tableFields } = dbConstants;
|
||||
const { errorMessage } = responseMessages;
|
||||
|
||||
const processRowRequest = async (req, res, next) => {
|
||||
const resource = req.params.name;
|
||||
const { method, body } = req;
|
||||
const fields = body.fields;
|
||||
const { method } = req;
|
||||
|
||||
// If the user sends a request when auth is set to false, throw an error
|
||||
if (apiConstants.defaultRoutes.includes(resource) && !config.auth) {
|
||||
// If the user sends a request to the auth tables while AUTH is set to false, throw an error
|
||||
if (apiConstants.authEndpoints.includes(resource) && !config.auth) {
|
||||
return res.status(403).send({
|
||||
message: 'You can not access this endpoint while AUTH is set to false',
|
||||
message: errorMessage.AUTH_SET_TO_FALSE_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
// Redirect this request to the registerUser controller => POST /api/tables/_users/rows
|
||||
if (resource === '_users' && method === 'POST') {
|
||||
if (resource === USERS_TABLE && method === httpVerbs.POST) {
|
||||
return registerUser(req, res);
|
||||
}
|
||||
|
||||
// Remove some fields for this request and check the username field => PUT /api/tables/_users/rows
|
||||
if (resource === '_users' && method === 'PUT') {
|
||||
// check if the username is taken
|
||||
if (fields.username) {
|
||||
if (isUsernameTaken(fields.username)) {
|
||||
return res
|
||||
.status(409)
|
||||
.send({ message: 'This username is already taken' });
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === USERS_TABLE && method === httpVerbs.PUT) {
|
||||
/**
|
||||
* remove some user fields from the request like (is_superuser, hashed_password, salt).
|
||||
* NOTE: password can be updated via the /change-password API and superuser status can be only updated from the CLI
|
||||
*/
|
||||
removeFields([req.body.fields], [SALT, IS_SUPERUSER, HASHED_PASSWORD]);
|
||||
removeFields(
|
||||
[req.body.fields],
|
||||
[tableFields.SALT, tableFields.IS_SUPERUSER, tableFields.HASHED_PASSWORD],
|
||||
);
|
||||
}
|
||||
|
||||
next();
|
||||
@@ -51,8 +49,8 @@ const processRowResponse = async (req, res, next) => {
|
||||
const payload = req.response.payload;
|
||||
|
||||
// Remove some fields from the response
|
||||
if (resource === '_users') {
|
||||
removeFields(payload.data, [SALT, HASHED_PASSWORD]);
|
||||
if (resource === USERS_TABLE) {
|
||||
removeFields(payload.data, [tableFields.SALT, tableFields.HASHED_PASSWORD]);
|
||||
}
|
||||
|
||||
res.status(status).send(payload);
|
||||
@@ -63,10 +61,10 @@ const processTableRequest = async (req, res, next) => {
|
||||
const { method, body, baseUrl } = req;
|
||||
|
||||
// if the user tries to create a table with the reserved table names throw an error. Request => POST /api/tables
|
||||
if (baseUrl === apiConstants.baseTableUrl && method === 'POST') {
|
||||
if (baseUrl === apiConstants.baseTableUrl && method === httpVerbs.POST) {
|
||||
if (reservedTableNames.includes(body.name)) {
|
||||
return res.status(409).send({
|
||||
message: `The table name is reserved. Please choose a different name for the table. Table name: ${body.name}.`,
|
||||
message: errorMessage.RESERVED_TABLE_NAME_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
const config = require('../config');
|
||||
const { decodeToken, toBoolean } = require('../utils/index');
|
||||
const { apiConstants } = require('../constants');
|
||||
const { apiConstants, responseMessages } = require('../constants');
|
||||
|
||||
const isAuthenticated = async (req, res, next) => {
|
||||
const { errorMessage } = responseMessages;
|
||||
|
||||
const hasAccess = async (req, res, next) => {
|
||||
let payload;
|
||||
const { name: tableName } = req.params;
|
||||
const verb = req.method;
|
||||
@@ -18,7 +20,9 @@ const isAuthenticated = async (req, res, next) => {
|
||||
);
|
||||
req.user = payload;
|
||||
} catch (error) {
|
||||
return res.status(403).send({ message: 'Invalid access token' });
|
||||
return res
|
||||
.status(401)
|
||||
.send({ message: errorMessage.INVALID_ACCESS_TOKEN_ERROR });
|
||||
}
|
||||
|
||||
// if the user is a super_user, allow access on the resource
|
||||
@@ -33,7 +37,9 @@ const isAuthenticated = async (req, res, next) => {
|
||||
|
||||
// if table_name is not passed from the router throw unauthorized error
|
||||
if (!tableName) {
|
||||
return res.status(403).send({ message: 'Not authorized' });
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.NOT_AUTHORIZED_ERROR });
|
||||
}
|
||||
|
||||
// if the user is not a super user, check the users permission on the resource
|
||||
@@ -44,14 +50,15 @@ const isAuthenticated = async (req, res, next) => {
|
||||
if (permissions.length <= 0) {
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: 'Permission not defined for this role' });
|
||||
.send({ message: errorMessage.PERMISSION_NOT_DEFINED_ERROR });
|
||||
}
|
||||
|
||||
// If the user has permission on the table in at least in one of the roles then allow access on the table
|
||||
let hasPermission = false;
|
||||
|
||||
permissions.some((resource) => {
|
||||
const httpMethod = apiConstants.httpVerbs[verb].toLowerCase();
|
||||
const httpMethod =
|
||||
apiConstants.httpMethodDefinitions[verb].toLowerCase();
|
||||
|
||||
if (toBoolean(resource[httpMethod])) {
|
||||
hasPermission = true;
|
||||
@@ -62,7 +69,9 @@ const isAuthenticated = async (req, res, next) => {
|
||||
if (hasPermission) {
|
||||
next();
|
||||
} else {
|
||||
return res.status(403).send({ message: 'Not authorized' });
|
||||
return res
|
||||
.status(403)
|
||||
.send({ message: errorMessage.NOT_AUTHORIZED_ERROR });
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
@@ -73,4 +82,4 @@ const isAuthenticated = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { isAuthenticated };
|
||||
module.exports = { hasAccess };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const validator = (schema) => (req, res, next) => {
|
||||
const { body, params, query } = req;
|
||||
const data = { body, params, query };
|
||||
const { body, params, query, cookies } = req;
|
||||
const data = { body, params, query, cookies };
|
||||
|
||||
const { value, error } = schema.validate(data);
|
||||
|
||||
@@ -13,6 +13,7 @@ const validator = (schema) => (req, res, next) => {
|
||||
req.body = value.body;
|
||||
req.params = value.params;
|
||||
req.query = value.query;
|
||||
req.cookies = value.cookies;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const express = require('express');
|
||||
const controllers = require('../controllers/auth');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const schema = require('../schemas/auth');
|
||||
const { isAuthenticated } = require('../middlewares/auth');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -21,8 +21,8 @@ router.get(
|
||||
|
||||
router.put(
|
||||
'/change-password',
|
||||
hasAccess,
|
||||
validator(schema.changePassword),
|
||||
isAuthenticated,
|
||||
controllers.changePassword,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ const controllers = require('../controllers/rows');
|
||||
const { broadcast } = require('../middlewares/broadcast');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const { processRowRequest, processRowResponse } = require('../middlewares/api');
|
||||
const { isAuthenticated } = require('../middlewares/auth');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
const schema = require('../schemas/rows');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/:name/rows',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.listTableRows),
|
||||
processRowRequest,
|
||||
controllers.listTableRows,
|
||||
@@ -19,7 +19,7 @@ router.get(
|
||||
);
|
||||
router.post(
|
||||
'/:name/rows',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.insertRowInTable),
|
||||
processRowRequest,
|
||||
controllers.insertRowInTable,
|
||||
@@ -27,14 +27,14 @@ router.post(
|
||||
);
|
||||
router.get(
|
||||
'/:name/rows/:pks',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.getRowInTableByPK),
|
||||
controllers.getRowInTableByPK,
|
||||
processRowResponse,
|
||||
);
|
||||
router.put(
|
||||
'/:name/rows/:pks',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.updateRowInTableByPK),
|
||||
processRowRequest,
|
||||
controllers.updateRowInTableByPK,
|
||||
@@ -42,7 +42,7 @@ router.put(
|
||||
);
|
||||
router.delete(
|
||||
'/:name/rows/:pks',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.deleteRowInTableByPK),
|
||||
controllers.deleteRowInTableByPK,
|
||||
broadcast,
|
||||
|
||||
@@ -3,14 +3,14 @@ const express = require('express');
|
||||
const controllers = require('../controllers/tables');
|
||||
const { validator } = require('../middlewares/validation');
|
||||
const schema = require('../schemas/tables');
|
||||
const { isAuthenticated } = require('../middlewares/auth');
|
||||
const { hasAccess } = require('../middlewares/auth');
|
||||
const { processTableRequest } = require('../middlewares/api');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.listTables),
|
||||
controllers.listTables,
|
||||
);
|
||||
@@ -18,21 +18,21 @@ router.get(
|
||||
router.post(
|
||||
'/',
|
||||
processTableRequest,
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.createTable),
|
||||
controllers.createTable,
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:name',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.getTableSchema),
|
||||
controllers.getTableSchema,
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:name',
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
validator(schema.deleteTable),
|
||||
controllers.deleteTable,
|
||||
);
|
||||
|
||||
@@ -10,12 +10,21 @@ const obtainAccessToken = Joi.object({
|
||||
password: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const refreshAccessToken = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}).required(),
|
||||
});
|
||||
|
||||
const changePassword = Joi.object({
|
||||
@@ -28,10 +37,31 @@ const changePassword = Joi.object({
|
||||
newPassword: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional(),
|
||||
}).required(),
|
||||
});
|
||||
|
||||
const registerUser = Joi.object({
|
||||
query: Joi.object().required(),
|
||||
params: Joi.object({}).required(),
|
||||
body: Joi.object({
|
||||
fields: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
accessToken: Joi.string().required(),
|
||||
refreshToken: Joi.string().optional(),
|
||||
}).required(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
obtainAccessToken,
|
||||
refreshAccessToken,
|
||||
changePassword,
|
||||
registerUser,
|
||||
};
|
||||
|
||||
@@ -12,10 +12,15 @@ const transaction = Joi.object({
|
||||
}),
|
||||
Joi.object({
|
||||
query: Joi.string().required(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.required(),
|
||||
}).required(),
|
||||
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -14,6 +14,10 @@ const listTableRows = Joi.object({
|
||||
name: Joi.string(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const insertRowInTable = Joi.object({
|
||||
@@ -28,6 +32,10 @@ const insertRowInTable = Joi.object({
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const getRowInTableByPK = Joi.object({
|
||||
@@ -48,6 +56,10 @@ const getRowInTableByPK = Joi.object({
|
||||
pks: Joi.string().required(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const updateRowInTableByPK = Joi.object({
|
||||
@@ -68,6 +80,10 @@ const updateRowInTableByPK = Joi.object({
|
||||
body: Joi.object({
|
||||
fields: Joi.object().required(),
|
||||
}).required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const deleteRowInTableByPK = Joi.object({
|
||||
@@ -86,6 +102,10 @@ const deleteRowInTableByPK = Joi.object({
|
||||
pks: Joi.string().required(),
|
||||
}).required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -7,6 +7,10 @@ const listTables = Joi.object({
|
||||
}).required(),
|
||||
params: Joi.object().required(),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const createTable = Joi.object({
|
||||
@@ -38,7 +42,7 @@ const createTable = Joi.object({
|
||||
'BLOB',
|
||||
'BOOLEAN',
|
||||
'DATE',
|
||||
'DATETIME'
|
||||
'DATETIME',
|
||||
)
|
||||
.insensitive()
|
||||
.required(),
|
||||
@@ -67,10 +71,14 @@ const createTable = Joi.object({
|
||||
.default('RESTRICT'),
|
||||
}),
|
||||
index: Joi.boolean(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.required(),
|
||||
}),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const getTableSchema = Joi.object({
|
||||
@@ -83,6 +91,10 @@ const getTableSchema = Joi.object({
|
||||
.required(),
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const deleteTable = Joi.object({
|
||||
@@ -95,6 +107,10 @@ const deleteTable = Joi.object({
|
||||
.required(),
|
||||
}),
|
||||
body: Joi.object().required(),
|
||||
cookies: Joi.object({
|
||||
refreshToken: Joi.string().optional(),
|
||||
accessToken: Joi.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
78
src/services/authService.js
Normal file
78
src/services/authService.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const db = require('../db');
|
||||
const rowService = require('./rowService')(db);
|
||||
|
||||
const { constantRoles, dbConstants } = require('../constants');
|
||||
|
||||
const {
|
||||
USERS_TABLE,
|
||||
ROLES_TABLE,
|
||||
USERS_ROLES_TABLE,
|
||||
ROLES_PERMISSIONS_TABLE,
|
||||
tableFields,
|
||||
} = dbConstants;
|
||||
|
||||
module.exports = () => {
|
||||
return {
|
||||
getUsersByUsername({ username }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.USERNAME} =?`,
|
||||
whereStringValues: [username],
|
||||
});
|
||||
|
||||
return users;
|
||||
},
|
||||
|
||||
getUsersById({ userId }) {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ID}=?`,
|
||||
whereStringValues: [userId],
|
||||
});
|
||||
|
||||
return users;
|
||||
},
|
||||
|
||||
getAllUsers() {
|
||||
const users = rowService.get({
|
||||
tableName: USERS_TABLE,
|
||||
whereString: '',
|
||||
whereStringValues: [],
|
||||
});
|
||||
|
||||
return users;
|
||||
},
|
||||
|
||||
getPermissionByRoleIds({ roleIds }) {
|
||||
const permissions = rowService.get({
|
||||
tableName: ROLES_PERMISSIONS_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_ID} IN (${roleIds.map(
|
||||
() => '?',
|
||||
)})`,
|
||||
whereStringValues: [...roleIds],
|
||||
});
|
||||
|
||||
return permissions;
|
||||
},
|
||||
|
||||
getUserRoleByUserId({ userId }) {
|
||||
const userRoles = rowService.get({
|
||||
tableName: USERS_ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.USER_ID} =?`,
|
||||
whereStringValues: [userId],
|
||||
});
|
||||
|
||||
return userRoles;
|
||||
},
|
||||
|
||||
getDefaultRole() {
|
||||
const defaultRole = rowService.get({
|
||||
tableName: ROLES_TABLE,
|
||||
whereString: `WHERE ${tableFields.ROLE_NAME}=?`,
|
||||
whereStringValues: [constantRoles.DEFAULT_ROLE],
|
||||
});
|
||||
|
||||
return defaultRole;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -2,5 +2,6 @@ const db = require('../db');
|
||||
|
||||
const rowService = require('./rowService')(db);
|
||||
const tableService = require('./tableService')(db);
|
||||
const authService = require('./authService')(db);
|
||||
|
||||
module.exports = { rowService, tableService };
|
||||
module.exports = { rowService, tableService, authService };
|
||||
|
||||
@@ -278,9 +278,6 @@
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -323,9 +320,6 @@
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,9 +448,6 @@
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -508,111 +499,39 @@
|
||||
},
|
||||
"/api/auth/token/obtain": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Obtain Access Token",
|
||||
"description": "Endpoint to generate access and refresh tokens",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ObtainAccessTokenRequestBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Access token and Refresh token generated",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ObtainAccessTokenSuccessResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid username or password error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/InvalidCredentialErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/auth/token/refresh": {
|
||||
"get": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Refresh Access Token",
|
||||
"description": "Endpoint to refresh access and refresh tokens",
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Access token refreshed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RefreshAccessTokenSuccessResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid refresh token error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/InvalidRefreshTokenErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/auth/change-password": {
|
||||
"put": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Change Password",
|
||||
"description": "Endpoint to change a password",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ChangePasswordRequestBody"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Weak password error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ChangePasswordSuccessResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Weak password error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/WeakPasswordErrorResponse"
|
||||
}
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "User not found error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/InvalidPasswordErrorResponse"
|
||||
}
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user