mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
WIP Roles and permissions access control.
This commit is contained in:
@@ -7,7 +7,6 @@ const factory = knexFactory(knex);
|
||||
|
||||
factory.define('user', 'users', async () => {
|
||||
const hashedPassword = await hashPassword('admin');
|
||||
|
||||
return {
|
||||
first_name: faker.name.firstName(),
|
||||
last_name: faker.name.lastName(),
|
||||
@@ -32,7 +31,6 @@ factory.define('account_type', 'account_types', async () => ({
|
||||
|
||||
factory.define('account', 'accounts', async () => {
|
||||
const accountType = await factory.create('account_type');
|
||||
|
||||
return {
|
||||
name: faker.lorem.word(),
|
||||
account_type_id: accountType.id,
|
||||
@@ -59,7 +57,6 @@ factory.define('item_metadata', 'items_metadata', async () => {
|
||||
factory.define('item', 'items', async () => {
|
||||
const category = await factory.create('item_category');
|
||||
const account = await factory.create('account');
|
||||
|
||||
return {
|
||||
name: faker.lorem.word(),
|
||||
note: faker.lorem.paragraph(),
|
||||
@@ -73,7 +70,6 @@ factory.define('item', 'items', async () => {
|
||||
|
||||
factory.define('setting', 'settings', async () => {
|
||||
const user = await factory.create('user');
|
||||
|
||||
return {
|
||||
key: faker.lorem.slug(),
|
||||
user_id: user.id,
|
||||
@@ -83,4 +79,45 @@ factory.define('setting', 'settings', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('role', 'roles', async () => ({
|
||||
name: faker.lorem.word(),
|
||||
description: faker.lorem.words(),
|
||||
predefined: false,
|
||||
}));
|
||||
|
||||
factory.define('user_has_role', 'user_has_roles', async () => {
|
||||
const user = await factory.create('user');
|
||||
const role = await factory.create('role');
|
||||
|
||||
return {
|
||||
user_id: user.id,
|
||||
role_id: role.id,
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('permission', 'permissions', async () => {
|
||||
const permissions = ['create', 'edit', 'delete', 'view', 'owner'];
|
||||
const randomPermission = permissions[Math.floor(Math.random() * permissions.length)];
|
||||
|
||||
return {
|
||||
name: randomPermission,
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('role_has_permission', 'role_has_permissions', async () => {
|
||||
const permission = await factory.create('permission');
|
||||
const role = await factory.create('role');
|
||||
const resource = await factory.create('resource');
|
||||
|
||||
return {
|
||||
role_id: role.id,
|
||||
permission_id: permission.id,
|
||||
resource_id: resource.id,
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('resource', 'resources', () => ({
|
||||
name: faker.lorem.word(),
|
||||
}));
|
||||
|
||||
export default factory;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('permissions', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTable('permissions');
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('resources', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTable('resources');
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('users', (table) => {
|
||||
table.increments();
|
||||
table.string('first_name');
|
||||
@@ -15,6 +15,6 @@ exports.up = function(knex) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('users');
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('user_has_roles', (table) => {
|
||||
table.increments();
|
||||
table.integer('user_id').unsigned().references('id').inTable('users');
|
||||
table.integer('role_id').unsigned().references('id').inTable('roles');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('user_has_roles');
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('oauth_tokens', table => {
|
||||
return knex.schema.createTable('oauth_tokens', (table) => {
|
||||
table.increments();
|
||||
table.string('access_token');
|
||||
table.date('access_token_expires_on');
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('role_has_accounts', (table) => {
|
||||
table.increments();
|
||||
table.integer('role_id').unsigned().references('id').inTable('roles');
|
||||
table.integer('account_id').unsigned().references('id').inTable('accounts');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('role_has_accounts');
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('role_has_permissions', (table) => {
|
||||
table.increments();
|
||||
table.integer('role_id').unsigned().references('id').inTable('roles');
|
||||
table.integer('permission_id').unsigned().references('id').inTable('permissions');
|
||||
table.integer('resource_id').unsigned().references('id').inTable('resources');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('role_has_permissions');
|
||||
351
server/src/http/controllers/Roles.js
Normal file
351
server/src/http/controllers/Roles.js
Normal file
@@ -0,0 +1,351 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import express from 'express';
|
||||
import { check, validationResult } from 'express-validator';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Role from '@/models/Role';
|
||||
import Permission from '@/models/Permission';
|
||||
import Resource from '@/models/Resource';
|
||||
import knex from '@/database/knex';
|
||||
|
||||
const AccessControllSchema = [
|
||||
{
|
||||
resource: 'items',
|
||||
label: 'products_services',
|
||||
permissions: ['create', 'edit', 'delete', 'view'],
|
||||
fullAccess: true,
|
||||
ownControl: true,
|
||||
},
|
||||
];
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const getResourceSchema = (resource) => AccessControllSchema.find((schema) => {
|
||||
return schema.resource === resource;
|
||||
});
|
||||
|
||||
const getResourcePermissions = (resource) => {
|
||||
const foundResource = getResourceSchema(resource);
|
||||
return foundResource ? foundResource.permissions : [];
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
findNotFoundResources(resourcesSlugs) {
|
||||
const schemaResourcesSlugs = AccessControllSchema.map((s) => s.resource);
|
||||
return difference(resourcesSlugs, schemaResourcesSlugs);
|
||||
},
|
||||
|
||||
findNotFoundPermissions(permissions, resourceSlug) {
|
||||
const schemaPermissions = getResourcePermissions(resourceSlug);
|
||||
return difference(permissions, schemaPermissions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/:id',
|
||||
this.editRole.validation,
|
||||
asyncMiddleware(this.editRole.handler.bind(this)));
|
||||
|
||||
// router.post('/',
|
||||
// this.newRole.validation,
|
||||
// asyncMiddleware(this.newRole.handler));
|
||||
|
||||
router.delete('/:id',
|
||||
this.deleteRole.validation,
|
||||
asyncMiddleware(this.deleteRole.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new role.
|
||||
*/
|
||||
newRole: {
|
||||
validation: [
|
||||
check('name').exists().trim().escape(),
|
||||
check('description').optional().trim().escape(),
|
||||
check('permissions').isArray({ min: 0 }),
|
||||
check('permissions.*.resource_slug').exists().whitelist('^[a-z0-9]+(?:-[a-z0-9]+)*$'),
|
||||
check('permissions.*.permissions').isArray({ min: 1 }),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const { name, description, permissions } = req.body;
|
||||
|
||||
const resourcesSlugs = permissions.map((perm) => perm.resource_slug);
|
||||
const permissionsSlugs = [];
|
||||
|
||||
const resourcesNotFound = this.findNotFoundResources(resourcesSlugs);
|
||||
const errorReasons = [];
|
||||
const notFoundPermissions = [];
|
||||
|
||||
if (resourcesNotFound.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'RESOURCE_SLUG_NOT_FOUND',
|
||||
code: 100,
|
||||
resources: resourcesNotFound,
|
||||
});
|
||||
}
|
||||
permissions.forEach((perm) => {
|
||||
const abilities = perm.permissions.map((ability) => ability);
|
||||
|
||||
// Gets the not found permissions in the schema.
|
||||
const notFoundAbilities = this.findNotFoundPermissions(abilities, perm.resource_slug);
|
||||
|
||||
if (notFoundAbilities.length > 0) {
|
||||
notFoundPermissions.push({
|
||||
resource_slug: perm.resource_slug, permissions: notFoundAbilities,
|
||||
});
|
||||
} else {
|
||||
const perms = perm.permissions || [];
|
||||
perms.forEach((permission) => {
|
||||
if (perms.indexOf(permission) !== -1) {
|
||||
permissionsSlugs.push(permission);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (notFoundPermissions.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'PERMISSIONS_SLUG_NOT_FOUND',
|
||||
code: 200,
|
||||
permissions: notFoundPermissions,
|
||||
});
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.boom.badRequest(null, { errors: errorReasons });
|
||||
}
|
||||
// Permissions.
|
||||
const [resourcesCollection, permsCollection] = await Promise.all([
|
||||
Resource.query((query) => { query.whereIn('name', resourcesSlugs); }).fetchAll(),
|
||||
Permission.query((query) => { query.whereIn('name', permissionsSlugs); }).fetchAll(),
|
||||
]);
|
||||
|
||||
const notStoredResources = difference(
|
||||
resourcesSlugs, resourcesCollection.map((s) => s.name),
|
||||
);
|
||||
const notStoredPermissions = difference(
|
||||
permissionsSlugs, permsCollection.map((perm) => perm.slug),
|
||||
);
|
||||
|
||||
const insertThread = [];
|
||||
|
||||
if (notStoredResources.length > 0) {
|
||||
insertThread.push(knex('resources').insert([
|
||||
...notStoredResources.map((resource) => ({ name: resource })),
|
||||
]));
|
||||
}
|
||||
if (notStoredPermissions.length > 0) {
|
||||
insertThread.push(knex('permissions').insert([
|
||||
...notStoredPermissions.map((permission) => ({ name: permission })),
|
||||
]));
|
||||
}
|
||||
|
||||
await Promise.all(insertThread);
|
||||
|
||||
const [storedPermissions, storedResources] = await Promise.all([
|
||||
Permission.query((q) => { q.whereIn('name', permissionsSlugs); }).fetchAll(),
|
||||
Resource.query((q) => { q.whereIn('name', resourcesSlugs); }).fetchAll(),
|
||||
]);
|
||||
|
||||
const storedResourcesSet = new Map(storedResources.map((resource) => [
|
||||
resource.attributes.name, resource.attributes.id,
|
||||
]));
|
||||
const storedPermissionsSet = new Map(storedPermissions.map((perm) => [
|
||||
perm.attributes.name, perm.attributes.id,
|
||||
]));
|
||||
const role = Role.forge({ name, description });
|
||||
|
||||
await role.save();
|
||||
|
||||
const roleHasPerms = permissions.map((resource) => resource.permissions.map((perm) => ({
|
||||
role_id: role.id,
|
||||
resource_id: storedResourcesSet.get(resource.resource_slug),
|
||||
permission_id: storedPermissionsSet.get(perm),
|
||||
})));
|
||||
|
||||
if (roleHasPerms.length > 0) {
|
||||
await knex('role_has_permissions').insert(roleHasPerms[0]);
|
||||
}
|
||||
return res.status(200).send({ id: role.get('id') });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the give role.
|
||||
*/
|
||||
editRole: {
|
||||
validation: [
|
||||
check('name').exists().trim().escape(),
|
||||
check('description').optional().trim().escape(),
|
||||
check('permissions').isArray({ min: 0 }),
|
||||
check('permissions.*.resource_slug').exists().whitelist('^[a-z0-9]+(?:-[a-z0-9]+)*$'),
|
||||
check('permissions.*.permissions').isArray({ min: 1 }),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const role = await Role.where('id', id).fetch();
|
||||
|
||||
if (!role) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
const { permissions } = req.body;
|
||||
const errorReasons = [];
|
||||
const permissionsSlugs = [];
|
||||
const notFoundPermissions = [];
|
||||
|
||||
const resourcesSlugs = permissions.map((perm) => perm.resource_slug);
|
||||
const resourcesNotFound = this.findNotFoundResources(resourcesSlugs);
|
||||
|
||||
if (resourcesNotFound.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'RESOURCE_SLUG_NOT_FOUND',
|
||||
code: 100,
|
||||
resources: resourcesNotFound,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.forEach((perm) => {
|
||||
const abilities = perm.permissions.map((ability) => ability);
|
||||
// Gets the not found permissions in the schema.
|
||||
const notFoundAbilities = this.findNotFoundPermissions(abilities, perm.resource_slug);
|
||||
|
||||
if (notFoundAbilities.length > 0) {
|
||||
notFoundPermissions.push({
|
||||
resource_slug: perm.resource_slug, permissions: notFoundAbilities,
|
||||
});
|
||||
} else {
|
||||
const perms = perm.permissions || [];
|
||||
perms.forEach((permission) => {
|
||||
if (perms.indexOf(permission) !== -1) {
|
||||
permissionsSlugs.push(permission);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (notFoundPermissions.length > 0) {
|
||||
errorReasons.push({
|
||||
type: 'PERMISSIONS_SLUG_NOT_FOUND',
|
||||
code: 200,
|
||||
permissions: notFoundPermissions,
|
||||
});
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.boom.badRequest(null, { errors: errorReasons });
|
||||
}
|
||||
|
||||
// Permissions.
|
||||
const [resourcesCollection, permsCollection] = await Promise.all([
|
||||
Resource.query((query) => { query.whereIn('name', resourcesSlugs); }).fetchAll(),
|
||||
Permission.query((query) => { query.whereIn('name', permissionsSlugs); }).fetchAll(),
|
||||
]);
|
||||
|
||||
const notStoredResources = difference(
|
||||
resourcesSlugs, resourcesCollection.map((s) => s.name),
|
||||
);
|
||||
const notStoredPermissions = difference(
|
||||
permissionsSlugs, permsCollection.map((perm) => perm.slug),
|
||||
);
|
||||
const insertThread = [];
|
||||
|
||||
if (notStoredResources.length > 0) {
|
||||
insertThread.push(knex('resources').insert([
|
||||
...notStoredResources.map((resource) => ({ name: resource })),
|
||||
]));
|
||||
}
|
||||
if (notStoredPermissions.length > 0) {
|
||||
insertThread.push(knex('permissions').insert([
|
||||
...notStoredPermissions.map((permission) => ({ name: permission })),
|
||||
]));
|
||||
}
|
||||
|
||||
await Promise.all(insertThread);
|
||||
|
||||
const [storedPermissions, storedResources] = await Promise.all([
|
||||
Permission.query((q) => { q.whereIn('name', permissionsSlugs); }).fetchAll(),
|
||||
Resource.query((q) => { q.whereIn('name', resourcesSlugs); }).fetchAll(),
|
||||
]);
|
||||
|
||||
const storedResourcesSet = new Map(storedResources.map((resource) => [
|
||||
resource.attributes.name, resource.attributes.id,
|
||||
]));
|
||||
const storedPermissionsSet = new Map(storedPermissions.map((perm) => [
|
||||
perm.attributes.name, perm.attributes.id,
|
||||
]));
|
||||
|
||||
await role.save();
|
||||
|
||||
|
||||
const savedRoleHasPerms = await knex('role_has_permissions').where({
|
||||
role_id: role.id,
|
||||
});
|
||||
|
||||
console.log(savedRoleHasPerms);
|
||||
|
||||
// const roleHasPerms = permissions.map((resource) => resource.permissions.map((perm) => ({
|
||||
// role_id: role.id,
|
||||
// resource_id: storedResourcesSet.get(resource.resource_slug),
|
||||
// permission_id: storedPermissionsSet.get(perm),
|
||||
// })));
|
||||
|
||||
// if (roleHasPerms.length > 0) {
|
||||
// await knex('role_has_permissions').insert(roleHasPerms[0]);
|
||||
// }
|
||||
return res.status(200).send({ id: role.get('id') });
|
||||
},
|
||||
},
|
||||
|
||||
deleteRole: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const role = await Role.where('id', id).fetch();
|
||||
|
||||
if (!role) {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
if (role.attributes.predefined) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ROLE_PREDEFINED', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
await knex('role_has_permissions')
|
||||
.where('role_id', role.id).delete({ require: false });
|
||||
|
||||
await role.destroy();
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
getRole: {
|
||||
validation: [],
|
||||
handler(req, res) {
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
// import OAuth2 from '@/http/controllers/OAuth2';
|
||||
import Authentication from '@/http/controllers/Authentication';
|
||||
import Users from '@/http/controllers/Users';
|
||||
import Roles from '@/http/controllers/Roles';
|
||||
import Items from '@/http/controllers/Items';
|
||||
import ItemCategories from '@/http/controllers/ItemCategories';
|
||||
import Accounts from '@/http/controllers/Accounts';
|
||||
@@ -10,6 +11,7 @@ export default (app) => {
|
||||
// app.use('/api/oauth2', OAuth2.router());
|
||||
app.use('/api/auth', Authentication.router());
|
||||
app.use('/api/users', Users.router());
|
||||
app.use('/api/roles', Roles.router());
|
||||
app.use('/api/accounts', Accounts.router());
|
||||
app.use('/api/accountOpeningBalance', AccountOpeningBalance.router());
|
||||
app.use('/api/items', Items.router());
|
||||
|
||||
4
server/src/http/middleware/authorization.js
Normal file
4
server/src/http/middleware/authorization.js
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
const authorization = (req, res, next) => {
|
||||
const { user } = req;
|
||||
};
|
||||
@@ -20,7 +20,7 @@ const Account = bookshelf.Model.extend({
|
||||
|
||||
balances() {
|
||||
return this.hasMany('AccountBalance', 'accounnt_id');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default bookshelf.model('Account', Account);
|
||||
|
||||
@@ -12,9 +12,7 @@ const AccountBalance = bookshelf.Model.extend({
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
account() {
|
||||
return this.belongsTo('Account', 'account_id');
|
||||
},
|
||||
|
||||
25
server/src/models/Permission.js
Normal file
25
server/src/models/Permission.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const Permission = bookshelf.Model.extend({
|
||||
|
||||
/**
|
||||
* Table name of Role model.
|
||||
* @type {String}
|
||||
*/
|
||||
tableName: 'permissions',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
role() {
|
||||
return this.belongsTo('Role', 'role_id');
|
||||
},
|
||||
|
||||
resource() {
|
||||
return this.belongsTo('Resource', 'resource_id');
|
||||
},
|
||||
});
|
||||
|
||||
export default bookshelf.model('Permission', Permission);
|
||||
21
server/src/models/Resource.js
Normal file
21
server/src/models/Resource.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const Resource = bookshelf.Model.extend({
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
tableName: 'resources',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
permissions() {
|
||||
},
|
||||
|
||||
roles() {
|
||||
},
|
||||
});
|
||||
|
||||
export default bookshelf.model('Resource', Resource);
|
||||
@@ -20,11 +20,18 @@ const Role = bookshelf.Model.extend({
|
||||
return this.belongsToMany('Permission', 'role_has_permissions', 'role_id', 'permission_id');
|
||||
},
|
||||
|
||||
/**
|
||||
* Role may has many resources.
|
||||
*/
|
||||
resources() {
|
||||
return this.belongsToMany('Resource', 'role_has_permissions', 'role_id', 'resource_id');
|
||||
},
|
||||
|
||||
/**
|
||||
* Role model may has many users.
|
||||
*/
|
||||
users() {
|
||||
return this.belongsTo('User');
|
||||
return this.belongsToMany('User', 'user_has_roles');
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,13 @@ const User = bookshelf.Model.extend({
|
||||
verifyPassword(password) {
|
||||
return bcrypt.compareSync(password, this.get('password'));
|
||||
},
|
||||
|
||||
/**
|
||||
* User model may has many associated roles.
|
||||
*/
|
||||
roles() {
|
||||
return this.belongsToMany('Role', 'user_has_roles', 'user_id', 'role_id');
|
||||
},
|
||||
});
|
||||
|
||||
export default bookshelf.model('User', User);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { create, expect } from '~/testInit';
|
||||
import '@/models/Account';
|
||||
import AccountType from '@/models/AccountType';
|
||||
|
||||
describe.only('Model: AccountType', () => {
|
||||
describe('Model: AccountType', () => {
|
||||
it('Shoud account type model has many associated accounts.', async () => {
|
||||
const accountType = await create('account_type');
|
||||
await create('account', { account_type_id: accountType.id });
|
||||
|
||||
21
server/tests/models/Permission.test.js
Normal file
21
server/tests/models/Permission.test.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { create } from '~/testInit';
|
||||
import Resource from '@/models/Resource';
|
||||
import '@/models/Role';
|
||||
|
||||
describe('Model: Permission', () => {
|
||||
it('Permission model may has associated role.', async () => {
|
||||
const roleHasPermissions = await create('role_has_permission');
|
||||
const resourceModel = await Resource.where('id', roleHasPermissions.resource_id).fetch();
|
||||
const roleModel = await resourceModel.role().fetch();
|
||||
|
||||
console.log(roleModel);
|
||||
});
|
||||
|
||||
it('Permission model may has associated resource.', async () => {
|
||||
const roleHasPermissions = await create('role_has_permission');
|
||||
const resourceModel = await Resource.where('id', roleHasPermissions.resource_id).fetch();
|
||||
const permissionModel = await resourceModel.permission().fetch();
|
||||
|
||||
console.log(permissionModel);
|
||||
});
|
||||
});
|
||||
0
server/tests/models/Resource.test.js
Normal file
0
server/tests/models/Resource.test.js
Normal file
34
server/tests/models/Role.test.js
Normal file
34
server/tests/models/Role.test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { expect, create } from '~/testInit';
|
||||
import Role from '@/models/Role';
|
||||
import '@/models/Permission';
|
||||
import '@/models/Resource';
|
||||
|
||||
describe('Model: Role', () => {
|
||||
it('Role model may has many associated users', async () => {
|
||||
const userHasRole = await create('user_has_role');
|
||||
await create('user_has_role', { role_id: userHasRole.role_id });
|
||||
|
||||
const roleModel = await Role.where('id', userHasRole.role_id).fetch();
|
||||
const roleUsers = await roleModel.users().fetch();
|
||||
|
||||
expect(roleUsers).to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
it('Role model may has many associated permissions.', async () => {
|
||||
const roleHasPermissions = await create('role_has_permission');
|
||||
|
||||
const roleModel = await Role.where('id', roleHasPermissions.role_id).fetch();
|
||||
const rolePermissions = await roleModel.permissions().fetch();
|
||||
|
||||
expect(rolePermissions).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('Role model may has many associated resources that has some or all permissions.', async () => {
|
||||
const roleHasPermissions = await create('role_has_permission');
|
||||
|
||||
const roleModel = await Role.where('id', roleHasPermissions.role_id).fetch();
|
||||
const roleResources = await roleModel.resources().fetch();
|
||||
|
||||
expect(roleResources).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
15
server/tests/models/User.test.js
Normal file
15
server/tests/models/User.test.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { create, expect } from '~/testInit';
|
||||
import User from '@/models/User';
|
||||
import '@/models/Role';
|
||||
|
||||
describe('Model: User', () => {
|
||||
it('User model may has many associated roles.', async () => {
|
||||
const userHasRole = await create('user_has_role');
|
||||
await create('user_has_role', { user_id: userHasRole.user_id });
|
||||
|
||||
const userModel = await User.where('id', userHasRole.user_id).fetch();
|
||||
const userRoles = await userModel.roles().fetch();
|
||||
|
||||
expect(userRoles).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { request, expect } from '~/testInit';
|
||||
|
||||
describe.only('routes: `/accountOpeningBalance`', () => {
|
||||
describe('routes: `/accountOpeningBalance`', () => {
|
||||
describe('POST `/accountOpeningBalance`', () => {
|
||||
it('Should `accounts` be array type.', async () => {
|
||||
const res = await request().post('/api/accountOpeningBalance').send({
|
||||
@@ -28,7 +28,7 @@ describe.only('routes: `/accountOpeningBalance`', () => {
|
||||
expect(res.status).equals(422);
|
||||
});
|
||||
|
||||
it.only('Should `accounts.*.id` be exist in the storage.', async () => {
|
||||
it('Should `accounts.*.id` be exist in the storage.', async () => {
|
||||
const res = await request().post('/api/accountOpeningBalance').send({
|
||||
accounts: [
|
||||
{ id: 100, credit: 100, debit: 100 },
|
||||
|
||||
@@ -1,26 +1,276 @@
|
||||
import { request, expect, create } from '~/testInit';
|
||||
import knex from '@/database/knex';
|
||||
|
||||
describe('routes: `/roles/`', () => {
|
||||
describe.only('routes: `/roles/`', () => {
|
||||
describe('POST: `/roles/`', () => {
|
||||
it('Should name be required.', () => {
|
||||
it('Should `name` be required.', async () => {
|
||||
const res = await request().post('/api/roles').send();
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundNameParam = res.body.errors.find((err) => err.param === 'name');
|
||||
expect(!!foundNameParam).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions` be ', () => {
|
||||
it('Should `permissions` be array.', async () => {
|
||||
const res = await request().post('/api/roles').send({
|
||||
permissions: 'not_array',
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPermissionsPerm = res.body.errors.find((err) => err.param === 'permissions');
|
||||
expect(!!foundPermissionsPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions.resource_slug` be slug.', async () => {
|
||||
const res = await request().post('/api/roles').send({
|
||||
permissions: [{ slug: 'not slug' }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPerm = res.body.errors.find((err) => err.param === 'permissions[0].resource_slug');
|
||||
expect(!!foundPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions.permissions be array.`', async () => {
|
||||
const res = await request().post('/api/roles').send({
|
||||
permissions: [{ permissions: 'not_array' }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPerm = res.body.errors.find((err) => err.param === 'permissions[0].permissions');
|
||||
expect(!!foundPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should response bad request in case the resource slug was invalid.', async () => {
|
||||
const res = await request().post('/api/roles').send({
|
||||
name: 'name',
|
||||
permissions: [{ resource_slug: 'invalid', permissions: ['item'] }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(400);
|
||||
expect(res.body.errors).include.something.that.deep.equals({
|
||||
type: 'RESOURCE_SLUG_NOT_FOUND',
|
||||
code: 100,
|
||||
resources: ['invalid'],
|
||||
});
|
||||
});
|
||||
|
||||
it('Should response bad request in case the permission type was invalid.', async () => {
|
||||
const res = await request().post('/api/roles').send({
|
||||
name: 'name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['item'] }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(400);
|
||||
expect(res.body.errors).include.something.that.deep.equals({
|
||||
type: 'PERMISSIONS_SLUG_NOT_FOUND',
|
||||
code: 200,
|
||||
permissions: [{ resource_slug: 'items', permissions: ['item'] }],
|
||||
});
|
||||
});
|
||||
|
||||
it('Should save the submit resources in the storage in case was not exist.', async () => {
|
||||
await request().post('/api/roles').send({
|
||||
name: 'Role Name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['create'] }],
|
||||
});
|
||||
|
||||
const storedResources = await knex('resources');
|
||||
expect(storedResources).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('Should save the submit permissions in the storage in case was not exist.', async () => {
|
||||
await request().post('/api/roles').send({
|
||||
name: 'Role Name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['create'] }],
|
||||
});
|
||||
|
||||
const storedPermissions = await knex('permissions');
|
||||
expect(storedPermissions).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('Should save the submit role in the storage with associated resource and permissions.', async () => {
|
||||
await request().post('/api/roles').send({
|
||||
name: 'Role Name',
|
||||
description: 'Role description',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['create', 'view'] }],
|
||||
});
|
||||
|
||||
const storedRoles = await knex('roles');
|
||||
const storedResource = await knex('resources').where('name', 'items').first();
|
||||
const storedPermissions = await knex('permissions');
|
||||
const roleHasPermissions = await knex('role_has_permissions')
|
||||
.where('role_id', storedRoles[0].id);
|
||||
|
||||
expect(storedRoles).to.have.lengthOf(1);
|
||||
expect(storedRoles[0].name).equals('Role Name');
|
||||
expect(storedRoles[0].description).equals('Role description');
|
||||
|
||||
expect(roleHasPermissions).to.have.lengthOf(2);
|
||||
expect(roleHasPermissions[0].role_id).equals(storedRoles[0].id);
|
||||
expect(roleHasPermissions[0].permission_id).equals(storedPermissions[0].id);
|
||||
expect(roleHasPermissions[0].resource_id).equals(storedResource.id);
|
||||
});
|
||||
|
||||
it('Should response success with correct data format.', async () => {
|
||||
const res = await request().post('/api/roles').send();
|
||||
|
||||
expect(res.status).equals(422);
|
||||
});
|
||||
|
||||
it('Should save the given role details in the storage.', () => {
|
||||
it('Should save the given role details in the storage.', async () => {
|
||||
const res = await request().post('/api/roles').send();
|
||||
|
||||
expect(res.status).equals(422);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST: `/roles/:id`', () => {
|
||||
it('Should response not found in case role was not exist.', async () => {
|
||||
const res = await request().post('/api/roles/10').send({
|
||||
name: 'Role Name',
|
||||
description: 'Description',
|
||||
permissions: [
|
||||
{ resource_slug: 'items', permissions: ['create'] },
|
||||
],
|
||||
});
|
||||
|
||||
expect(res.status).equals(404);
|
||||
});
|
||||
|
||||
it('Should `name` be required.', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send();
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundNameParam = res.body.errors.find((err) => err.param === 'name');
|
||||
expect(!!foundNameParam).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions` be array.', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send({
|
||||
permissions: 'not_array',
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPermissionsPerm = res.body.errors.find((err) => err.param === 'permissions');
|
||||
expect(!!foundPermissionsPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions.resource_slug` be slug.', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send({
|
||||
permissions: [{ slug: 'not slug' }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPerm = res.body.errors.find((err) => err.param === 'permissions[0].resource_slug');
|
||||
expect(!!foundPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should `permissions.permissions be array.`', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send({
|
||||
permissions: [{ permissions: 'not_array' }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(422);
|
||||
expect(res.body.code).equals('validation_error');
|
||||
|
||||
const foundPerm = res.body.errors.find((err) => err.param === 'permissions[0].permissions');
|
||||
expect(!!foundPerm).equals(true);
|
||||
});
|
||||
|
||||
it('Should response bad request in case the resource slug was invalid.', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send({
|
||||
name: 'name',
|
||||
permissions: [{ resource_slug: 'invalid', permissions: ['item'] }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(400);
|
||||
expect(res.body.errors).include.something.that.deep.equals({
|
||||
type: 'RESOURCE_SLUG_NOT_FOUND',
|
||||
code: 100,
|
||||
resources: ['invalid'],
|
||||
});
|
||||
});
|
||||
|
||||
it('Should response bad request in case the permission type was invalid.', async () => {
|
||||
const role = await create('role');
|
||||
const res = await request().post(`/api/roles/${role.id}`).send({
|
||||
name: 'name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['item'] }],
|
||||
});
|
||||
|
||||
expect(res.status).equals(400);
|
||||
expect(res.body.errors).include.something.that.deep.equals({
|
||||
type: 'PERMISSIONS_SLUG_NOT_FOUND',
|
||||
code: 200,
|
||||
permissions: [{ resource_slug: 'items', permissions: ['item'] }],
|
||||
});
|
||||
});
|
||||
|
||||
it('Should save the submit resources in the storage in case was not exist.', async () => {
|
||||
const role = await create('role');
|
||||
await request().post(`/api/roles/${role.id}`).send({
|
||||
name: 'Role Name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['create'] }],
|
||||
});
|
||||
|
||||
const storedResources = await knex('resources');
|
||||
expect(storedResources).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it.only('Should save the submit permissions in the storage in case was not exist.', async () => {
|
||||
const role = await create('role');
|
||||
await request().post(`/api/roles/${role.id}`).send({
|
||||
name: 'Role Name',
|
||||
permissions: [{ resource_slug: 'items', permissions: ['create'] }],
|
||||
});
|
||||
|
||||
const storedPermissions = await knex('permissions');
|
||||
expect(storedPermissions).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE: `/roles/:id`', () => {
|
||||
it('Should not delete the predefined role.', () => {
|
||||
it('Should response not found in case the role was not exist.', async () => {
|
||||
const res = await request().delete('/api/roles/100').send();
|
||||
|
||||
expect(res.status).equals(404);
|
||||
});
|
||||
|
||||
it('Should not delete the predefined role.', async () => {
|
||||
const role = await create('role', { predefined: true });
|
||||
const res = await request().delete(`/api/roles/${role.id}`).send();
|
||||
|
||||
expect(res.status).equals(400);
|
||||
});
|
||||
|
||||
it('Should delete the given role and its relations with permissions and resources.', async () => {
|
||||
const role = await create('role');
|
||||
await create('role_has_permission', { role_id: role.id });
|
||||
|
||||
await request().delete(`/api/roles/${role.id}`).send();
|
||||
|
||||
const storedRole = await knex('roles').where('id', role.id).first();
|
||||
expect(storedRole).to.equals(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user