feat(auth): add user and role management
This commit is contained in:
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import Permission from '../../models/permission';
|
||||
import Role from '../../models/role';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
};
|
||||
|
||||
const createRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||
context.currentUser.can('create', 'Role');
|
||||
|
||||
const { name, description, permissions } = params.input;
|
||||
const key = kebabCase(name);
|
||||
|
||||
const existingRole = await Role.query().findOne({ key });
|
||||
|
||||
if (existingRole) {
|
||||
throw new Error('Role already exists!');
|
||||
}
|
||||
|
||||
return await Role.query().insertGraph({
|
||||
key,
|
||||
name,
|
||||
description,
|
||||
permissions,
|
||||
}, { relate: ['permissions'] }).returning('*');
|
||||
};
|
||||
|
||||
export default createRole;
|
@@ -1,15 +1,21 @@
|
||||
import User from '../../models/user';
|
||||
import Role from '../../models/role';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
fullName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
role: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const createUser = async (_parent: unknown, params: Params) => {
|
||||
const createUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||
context.currentUser.can('create', 'User');
|
||||
|
||||
const { fullName, email, password } = params.input;
|
||||
|
||||
const existingUser = await User.query().findOne({ email });
|
||||
@@ -18,14 +24,23 @@ const createUser = async (_parent: unknown, params: Params) => {
|
||||
throw new Error('User already exists!');
|
||||
}
|
||||
|
||||
const role = await Role.query().findOne({ key: 'user' });
|
||||
|
||||
const user = await User.query().insert({
|
||||
const userPayload: Partial<User> = {
|
||||
fullName,
|
||||
email,
|
||||
password,
|
||||
roleId: role.id,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
context.currentUser.can('update', 'Role');
|
||||
|
||||
userPayload.roleId = params.input.role.id;
|
||||
} catch {
|
||||
// void
|
||||
const role = await Role.query().findOne({ key: 'user' });
|
||||
userPayload.roleId = role.id;
|
||||
}
|
||||
|
||||
const user = await User.query().insert(userPayload);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
@@ -0,0 +1,22 @@
|
||||
import { Duration } from 'luxon';
|
||||
import Context from '../../types/express/context';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
|
||||
const deleteCurrentUser = async (_parent: unknown, params: never, context: Context) => {
|
||||
const id = context.currentUser.id;
|
||||
|
||||
await context.currentUser.$query().delete();
|
||||
|
||||
const jobName = `Delete user - ${id}`;
|
||||
const jobPayload = { id };
|
||||
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
|
||||
const jobOptions = {
|
||||
delay: millisecondsFor30Days
|
||||
};
|
||||
|
||||
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default deleteCurrentUser;
|
41
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
41
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import Role from '../../models/role';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
const deleteRole = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('delete', 'Role');
|
||||
|
||||
const role = await Role
|
||||
.query()
|
||||
.findById(params.input.id)
|
||||
.throwIfNotFound();
|
||||
|
||||
const count = await role
|
||||
.$relatedQuery('users')
|
||||
.resultSize();
|
||||
|
||||
if (count > 0) {
|
||||
throw new Error('All users must be migrated away from the role!');
|
||||
}
|
||||
|
||||
if (role.isAdmin) {
|
||||
throw new Error('Admin role cannot be deleted!');
|
||||
}
|
||||
|
||||
// delete permissions first
|
||||
await role.$relatedQuery('permissions').delete();
|
||||
await role.$query().delete();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default deleteRole;
|
@@ -1,11 +1,24 @@
|
||||
import Context from '../../types/express/context';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
import { Duration } from 'luxon';
|
||||
import Context from '../../types/express/context';
|
||||
import User from '../../models/user';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
|
||||
const deleteUser = async (_parent: unknown, params: never, context: Context) => {
|
||||
const id = context.currentUser.id;
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
await context.currentUser.$query().delete();
|
||||
const deleteUser = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('delete', 'User');
|
||||
|
||||
const id = params.input.id;
|
||||
|
||||
await User.query().deleteById(id);
|
||||
|
||||
const jobName = `Delete user - ${id}`;
|
||||
const jobPayload = { id };
|
||||
|
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import User from '../../models/user';
|
||||
import Role from '../../models/role';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
fullName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
|
||||
const registerUser = async (_parent: unknown, params: Params) => {
|
||||
const { fullName, email, password } = params.input;
|
||||
|
||||
const existingUser = await User.query().findOne({ email });
|
||||
|
||||
if (existingUser) {
|
||||
throw new Error('User already exists!');
|
||||
}
|
||||
|
||||
const role = await Role.query().findOne({ key: 'user' });
|
||||
|
||||
const user = await User.query().insert({
|
||||
fullName,
|
||||
email,
|
||||
password,
|
||||
roleId: role.id,
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default registerUser;
|
@@ -8,7 +8,7 @@ type Params = {
|
||||
};
|
||||
};
|
||||
|
||||
const updateUser = async (
|
||||
const updateCurrentUser = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
@@ -22,4 +22,4 @@ const updateUser = async (
|
||||
return user;
|
||||
};
|
||||
|
||||
export default updateUser;
|
||||
export default updateCurrentUser;
|
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import Context from '../../types/express/context';
|
||||
import Role from '../../models/role';
|
||||
import Permission from '../../models/permission';
|
||||
import permissionCatalog from '../../helpers/permission-catalog.ee';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
};
|
||||
|
||||
const updateRole = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Role');
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
permissions,
|
||||
} = params.input;
|
||||
|
||||
const role = await Role
|
||||
.query()
|
||||
.findById(id)
|
||||
.throwIfNotFound();
|
||||
|
||||
try {
|
||||
const updatedRole = await Role.transaction(async (trx) => {
|
||||
await role.$relatedQuery('permissions', trx).delete();
|
||||
|
||||
if (permissions?.length) {
|
||||
const sanitizedPermissions = permissions
|
||||
.filter((permission) => {
|
||||
const {
|
||||
action,
|
||||
subject,
|
||||
conditions,
|
||||
} = permission;
|
||||
|
||||
const relevantAction = permissionCatalog.actions.find(actionCatalogItem => actionCatalogItem.key === action);
|
||||
const validSubject = relevantAction.subjects.includes(subject);
|
||||
const validConditions = conditions.every(condition => {
|
||||
return !!permissionCatalog
|
||||
.conditions
|
||||
.find((conditionCatalogItem) => conditionCatalogItem.key === condition);
|
||||
})
|
||||
|
||||
return validSubject && validConditions;
|
||||
})
|
||||
.map((permission) => ({
|
||||
...permission,
|
||||
roleId: role.id,
|
||||
}));
|
||||
|
||||
await Permission.query().insert(sanitizedPermissions);
|
||||
}
|
||||
|
||||
await role
|
||||
.$query(trx)
|
||||
.patch(
|
||||
{
|
||||
name,
|
||||
description,
|
||||
}
|
||||
);
|
||||
|
||||
return await Role
|
||||
.query(trx)
|
||||
.leftJoinRelated({
|
||||
permissions: true
|
||||
})
|
||||
.withGraphFetched({
|
||||
permissions: true
|
||||
})
|
||||
.findById(id);
|
||||
});
|
||||
|
||||
return updatedRole;
|
||||
} catch (err) {
|
||||
throw new Error('The role could not be updated!');
|
||||
}
|
||||
};
|
||||
|
||||
export default updateRole;
|
44
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
44
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import Context from '../../types/express/context';
|
||||
import User from '../../models/user';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const updateUser = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'User');
|
||||
|
||||
const userPayload: Partial<User> = {
|
||||
email: params.input.email,
|
||||
fullName: params.input.fullName,
|
||||
};
|
||||
|
||||
try {
|
||||
context.currentUser.can('update', 'Role');
|
||||
|
||||
userPayload.roleId = params.input.role.id;
|
||||
} catch {
|
||||
// void
|
||||
}
|
||||
|
||||
const user = await User.query()
|
||||
.patchAndFetchById(
|
||||
params.input.id,
|
||||
userPayload,
|
||||
);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default updateUser;
|
Reference in New Issue
Block a user