@vendure/core
Version:
A modern, headless ecommerce framework
373 lines • 17.4 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RoleService = void 0;
const common_1 = require("@nestjs/common");
const generated_types_1 = require("@vendure/common/lib/generated-types");
const shared_constants_1 = require("@vendure/common/lib/shared-constants");
const unique_1 = require("@vendure/common/lib/unique");
const request_context_1 = require("../../api/common/request-context");
const request_context_cache_service_1 = require("../../cache/request-context-cache.service");
const constants_1 = require("../../common/constants");
const errors_1 = require("../../common/error/errors");
const instrument_decorator_1 = require("../../common/instrument-decorator");
const utils_1 = require("../../common/utils");
const config_service_1 = require("../../config/config.service");
const transactional_connection_1 = require("../../connection/transactional-connection");
const channel_entity_1 = require("../../entity/channel/channel.entity");
const role_entity_1 = require("../../entity/role/role.entity");
const user_entity_1 = require("../../entity/user/user.entity");
const event_bus_1 = require("../../event-bus");
const role_event_1 = require("../../event-bus/events/role-event");
const list_query_builder_1 = require("../helpers/list-query-builder/list-query-builder");
const get_user_channels_permissions_1 = require("../helpers/utils/get-user-channels-permissions");
const patch_entity_1 = require("../helpers/utils/patch-entity");
const channel_service_1 = require("./channel.service");
/**
* @description
* Contains methods relating to {@link Role} entities.
*
* @docsCategory services
*/
let RoleService = class RoleService {
constructor(connection, channelService, listQueryBuilder, configService, eventBus, requestContextCache) {
this.connection = connection;
this.channelService = channelService;
this.listQueryBuilder = listQueryBuilder;
this.configService = configService;
this.eventBus = eventBus;
this.requestContextCache = requestContextCache;
}
async initRoles() {
await this.ensureSuperAdminRoleExists();
await this.ensureCustomerRoleExists();
await this.ensureRolesHaveValidPermissions();
}
findAll(ctx, options, relations) {
return this.listQueryBuilder
.build(role_entity_1.Role, options, { relations: (0, unique_1.unique)([...(relations !== null && relations !== void 0 ? relations : []), 'channels']), ctx })
.getManyAndCount()
.then(async ([items, totalItems]) => {
const visibleRoles = [];
for (const item of items) {
const canRead = await this.activeUserCanReadRole(ctx, item);
if (canRead) {
visibleRoles.push(item);
}
}
return {
items: visibleRoles,
totalItems,
};
});
}
findOne(ctx, roleId, relations) {
return this.connection
.getRepository(ctx, role_entity_1.Role)
.findOne({
where: { id: roleId },
relations: (0, unique_1.unique)([...(relations !== null && relations !== void 0 ? relations : []), 'channels']),
})
.then(async (result) => {
if (result && (await this.activeUserCanReadRole(ctx, result))) {
return result;
}
});
}
getChannelsForRole(ctx, roleId) {
return this.findOne(ctx, roleId).then(role => (role ? role.channels : []));
}
/**
* @description
* Returns the special SuperAdmin Role, which always exists in Vendure.
*/
getSuperAdminRole(ctx) {
return this.getRoleByCode(ctx, shared_constants_1.SUPER_ADMIN_ROLE_CODE).then(role => {
if (!role) {
throw new errors_1.InternalServerError('error.super-admin-role-not-found');
}
return role;
});
}
/**
* @description
* Returns the special Customer Role, which always exists in Vendure.
*/
getCustomerRole(ctx) {
return this.getRoleByCode(ctx, shared_constants_1.CUSTOMER_ROLE_CODE).then(role => {
if (!role) {
throw new errors_1.InternalServerError('error.customer-role-not-found');
}
return role;
});
}
/**
* @description
* Returns all the valid Permission values
*/
getAllPermissions() {
return Object.values(generated_types_1.Permission);
}
/**
* @description
* Returns true if the User has the specified permission on that Channel
*/
async userHasPermissionOnChannel(ctx, channelId, permission) {
return this.userHasAnyPermissionsOnChannel(ctx, channelId, [permission]);
}
/**
* @description
* Returns true if the User has any of the specified permissions on that Channel
*/
async userHasAnyPermissionsOnChannel(ctx, channelId, permissions) {
const permissionsOnChannel = await this.getActiveUserPermissionsOnChannel(ctx, channelId);
for (const permission of permissions) {
if (permissionsOnChannel.includes(permission)) {
return true;
}
}
return false;
}
async activeUserCanReadRole(ctx, role) {
const permissionsRequired = (0, get_user_channels_permissions_1.getChannelPermissions)([role]);
for (const channelPermissions of permissionsRequired) {
const activeUserHasRequiredPermissions = await this.userHasAllPermissionsOnChannel(ctx, channelPermissions.id, channelPermissions.permissions);
if (!activeUserHasRequiredPermissions) {
return false;
}
}
return true;
}
/**
* @description
* Returns true if the User has all the specified permissions on that Channel
*/
async userHasAllPermissionsOnChannel(ctx, channelId, permissions) {
const permissionsOnChannel = await this.getActiveUserPermissionsOnChannel(ctx, channelId);
for (const permission of permissions) {
if (!permissionsOnChannel.includes(permission)) {
return false;
}
}
return true;
}
async getActiveUserPermissionsOnChannel(ctx, channelId) {
const { activeUserId } = ctx;
if (activeUserId == null) {
return [];
}
// For apps with many channels, this is a performance bottleneck as it will be called
// for each channel in certain code paths such as the GetActiveAdministrator query in the
// admin ui. Caching the result prevents unbounded quadratic slowdown.
const userChannels = await this.requestContextCache.get(ctx, `RoleService.getActiveUserPermissionsOnChannel.user(${activeUserId})`, async () => {
const user = await this.connection.getEntityOrThrow(ctx, user_entity_1.User, activeUserId, {
relations: ['roles', 'roles.channels'],
});
return (0, get_user_channels_permissions_1.getUserChannelsPermissions)(user);
});
const channel = userChannels.find(c => (0, utils_1.idsAreEqual)(c.id, channelId));
if (!channel) {
return [];
}
return channel.permissions;
}
async create(ctx, input) {
this.checkPermissionsAreValid(input.permissions);
let targetChannels = [];
if (input.channelIds) {
targetChannels = await this.getPermittedChannels(ctx, input.channelIds);
}
else {
targetChannels = [ctx.channel];
}
await this.checkActiveUserHasSufficientPermissions(ctx, targetChannels, input.permissions);
const role = await this.createRoleForChannels(ctx, input, targetChannels);
await this.eventBus.publish(new role_event_1.RoleEvent(ctx, role, 'created', input));
return role;
}
async update(ctx, input) {
this.checkPermissionsAreValid(input.permissions);
const role = await this.findOne(ctx, input.id);
if (!role) {
throw new errors_1.EntityNotFoundError('Role', input.id);
}
if (role.code === shared_constants_1.SUPER_ADMIN_ROLE_CODE || role.code === shared_constants_1.CUSTOMER_ROLE_CODE) {
throw new errors_1.InternalServerError('error.cannot-modify-role', { roleCode: role.code });
}
const targetChannels = input.channelIds
? await this.getPermittedChannels(ctx, input.channelIds)
: undefined;
if (input.permissions) {
await this.checkActiveUserHasSufficientPermissions(ctx, targetChannels !== null && targetChannels !== void 0 ? targetChannels : role.channels, input.permissions);
}
(0, patch_entity_1.patchEntity)(role, {
code: input.code,
description: input.description,
permissions: input.permissions
? (0, unique_1.unique)([generated_types_1.Permission.Authenticated, ...input.permissions])
: undefined,
});
if (targetChannels) {
role.channels = targetChannels;
}
await this.connection.getRepository(ctx, role_entity_1.Role).save(role, { reload: false });
const updatedRole = await (0, utils_1.assertFound)(this.findOne(ctx, role.id));
await this.eventBus.publish(new role_event_1.RoleEvent(ctx, updatedRole, 'updated', input));
return updatedRole;
}
async delete(ctx, id) {
const role = await this.findOne(ctx, id);
if (!role) {
throw new errors_1.EntityNotFoundError('Role', id);
}
if (role.code === shared_constants_1.SUPER_ADMIN_ROLE_CODE || role.code === shared_constants_1.CUSTOMER_ROLE_CODE) {
throw new errors_1.InternalServerError('error.cannot-delete-role', { roleCode: role.code });
}
const deletedRole = new role_entity_1.Role(role);
await this.connection.getRepository(ctx, role_entity_1.Role).remove(role);
await this.eventBus.publish(new role_event_1.RoleEvent(ctx, deletedRole, 'deleted', id));
return {
result: generated_types_1.DeletionResult.DELETED,
};
}
async assignRoleToChannel(ctx, roleId, channelId) {
await this.channelService.assignToChannels(ctx, role_entity_1.Role, roleId, [channelId]);
}
async getPermittedChannels(ctx, channelIds) {
let permittedChannels = [];
for (const channelId of channelIds) {
const channel = await this.connection.getEntityOrThrow(ctx, channel_entity_1.Channel, channelId);
const hasPermission = await this.userHasPermissionOnChannel(ctx, channelId, generated_types_1.Permission.CreateAdministrator);
if (!hasPermission) {
throw new errors_1.ForbiddenError();
}
permittedChannels = [...permittedChannels, channel];
}
return permittedChannels;
}
checkPermissionsAreValid(permissions) {
if (!permissions) {
return;
}
const allAssignablePermissions = this.getAllAssignablePermissions();
for (const permission of permissions) {
if (!allAssignablePermissions.includes(permission) || permission === generated_types_1.Permission.SuperAdmin) {
throw new errors_1.UserInputError('error.permission-invalid', { permission });
}
}
}
/**
* @description
* Checks that the active User has sufficient Permissions on the target Channels to create
* a Role with the given Permissions. The rule is that an Administrator may only grant
* Permissions that they themselves already possess.
*/
async checkActiveUserHasSufficientPermissions(ctx, targetChannels, permissions) {
const permissionsRequired = (0, get_user_channels_permissions_1.getChannelPermissions)([
new role_entity_1.Role({
permissions: (0, unique_1.unique)([generated_types_1.Permission.Authenticated, ...permissions]),
channels: targetChannels,
}),
]);
for (const channelPermissions of permissionsRequired) {
const activeUserHasRequiredPermissions = await this.userHasAllPermissionsOnChannel(ctx, channelPermissions.id, channelPermissions.permissions);
if (!activeUserHasRequiredPermissions) {
throw new errors_1.UserInputError('error.active-user-does-not-have-sufficient-permissions');
}
}
}
getRoleByCode(ctx, code) {
const repository = ctx
? this.connection.getRepository(ctx, role_entity_1.Role)
: this.connection.rawConnection.getRepository(role_entity_1.Role);
return repository.findOne({
where: { code },
});
}
/**
* Ensure that the SuperAdmin role exists and that it has all possible Permissions.
*/
async ensureSuperAdminRoleExists() {
const assignablePermissions = this.getAllAssignablePermissions();
try {
const superAdminRole = await this.getSuperAdminRole();
superAdminRole.permissions = assignablePermissions;
await this.connection.rawConnection.getRepository(role_entity_1.Role).save(superAdminRole, { reload: false });
}
catch (err) {
const defaultChannel = await this.channelService.getDefaultChannel();
await this.createRoleForChannels(request_context_1.RequestContext.empty(), {
code: shared_constants_1.SUPER_ADMIN_ROLE_CODE,
description: shared_constants_1.SUPER_ADMIN_ROLE_DESCRIPTION,
permissions: assignablePermissions,
}, [defaultChannel]);
}
}
/**
* The Customer Role is a special case which must always exist.
*/
async ensureCustomerRoleExists() {
try {
await this.getCustomerRole();
}
catch (err) {
const defaultChannel = await this.channelService.getDefaultChannel();
await this.createRoleForChannels(request_context_1.RequestContext.empty(), {
code: shared_constants_1.CUSTOMER_ROLE_CODE,
description: shared_constants_1.CUSTOMER_ROLE_DESCRIPTION,
permissions: [generated_types_1.Permission.Authenticated],
}, [defaultChannel]);
}
}
/**
* Since custom permissions can be added and removed by config, there may exist one or more Roles with
* invalid permissions (i.e. permissions that were set previously to a custom permission, which has been
* subsequently removed from config). This method should run on startup to ensure that any such invalid
* permissions are removed from those Roles.
*/
async ensureRolesHaveValidPermissions() {
const roles = await this.connection.rawConnection.getRepository(role_entity_1.Role).find();
const assignablePermissions = this.getAllAssignablePermissions();
for (const role of roles) {
const invalidPermissions = role.permissions.filter(p => !assignablePermissions.includes(p));
if (invalidPermissions.length) {
role.permissions = role.permissions.filter(p => assignablePermissions.includes(p));
await this.connection.rawConnection.getRepository(role_entity_1.Role).save(role);
}
}
}
createRoleForChannels(ctx, input, channels) {
const role = new role_entity_1.Role({
code: input.code,
description: input.description,
permissions: (0, unique_1.unique)([generated_types_1.Permission.Authenticated, ...input.permissions]),
});
role.channels = channels;
return this.connection.getRepository(ctx, role_entity_1.Role).save(role);
}
getAllAssignablePermissions() {
return (0, constants_1.getAllPermissionsMetadata)(this.configService.authOptions.customPermissions)
.filter(p => p.assignable)
.map(p => p.name);
}
};
exports.RoleService = RoleService;
exports.RoleService = RoleService = __decorate([
(0, common_1.Injectable)(),
(0, instrument_decorator_1.Instrument)(),
__metadata("design:paramtypes", [transactional_connection_1.TransactionalConnection,
channel_service_1.ChannelService,
list_query_builder_1.ListQueryBuilder,
config_service_1.ConfigService,
event_bus_1.EventBus,
request_context_cache_service_1.RequestContextCacheService])
], RoleService);
//# sourceMappingURL=role.service.js.map