Browse Source

feat(server): Add auth controls to all existing API operations

Michael Bromley 7 years ago
parent
commit
c973588d3d

+ 7 - 2
server/src/api/administrator/administrator.resolver.ts

@@ -1,26 +1,31 @@
 import { Mutation, Query, Resolver } from '@nestjs/graphql';
 
 import { Administrator } from '../../entity/administrator/administrator.entity';
+import { Permission } from '../../entity/role/permission';
 import { AdministratorService } from '../../service/administrator.service';
 import { ApplyIdCodec } from '../common/apply-id-codec-decorator';
+import { RolesGuard } from '../roles-guard';
 
 @Resolver('Administrator')
 export class AdministratorResolver {
     constructor(private administratorService: AdministratorService) {}
 
-    @Query('administrators')
+    @Query()
+    @RolesGuard([Permission.ReadAdministrator])
     @ApplyIdCodec()
     administrators(): Promise<Administrator[]> {
         return this.administratorService.findAll();
     }
 
-    @Query('administrator')
+    @Query()
+    @RolesGuard([Permission.ReadAdministrator])
     @ApplyIdCodec()
     administrator(obj, args): Promise<Administrator | undefined> {
         return this.administratorService.findOne(args.id);
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateAdministrator])
     @ApplyIdCodec()
     createAdministrator(_, args): Promise<Administrator> {
         const { input } = args;

+ 1 - 1
server/src/api/auth/auth.resolver.ts

@@ -29,8 +29,8 @@ export class AuthResolver {
     /**
      * Returns information about the current authenticated user.
      */
-    @RolesGuard([Permission.Authenticated])
     @Query()
+    @RolesGuard([Permission.Authenticated])
     async me(@Context('req') request: any) {
         const user = await this.authService.validateUser(request.user.identifier);
         return user ? this.publiclyAccessibleUser(user) : null;

+ 3 - 0
server/src/api/channel/channel.resolver.ts

@@ -1,13 +1,16 @@
 import { Args, Mutation, Resolver } from '@nestjs/graphql';
 
 import { Channel } from '../../entity/channel/channel.entity';
+import { Permission } from '../../entity/role/permission';
 import { ChannelService } from '../../service/channel.service';
+import { RolesGuard } from '../roles-guard';
 
 @Resolver('Channel')
 export class ChannelResolver {
     constructor(private channelService: ChannelService) {}
 
     @Mutation()
+    @RolesGuard([Permission.SuperAdmin])
     createChannel(@Args() args): Promise<Channel> {
         return this.channelService.create(args.code);
     }

+ 10 - 3
server/src/api/customer/customer.resolver.ts

@@ -3,32 +3,38 @@ import { PaginatedList } from 'shared/shared-types';
 
 import { Address } from '../../entity/address/address.entity';
 import { Customer } from '../../entity/customer/customer.entity';
+import { Permission } from '../../entity/role/permission';
 import { CustomerService } from '../../service/customer.service';
 import { ApplyIdCodec } from '../common/apply-id-codec-decorator';
+import { RolesGuard } from '../roles-guard';
 
 @Resolver('Customer')
 export class CustomerResolver {
     constructor(private customerService: CustomerService) {}
 
-    @Query('customers')
+    @Query()
+    @RolesGuard([Permission.ReadCustomer])
     @ApplyIdCodec()
     async customers(obj, args): Promise<PaginatedList<Customer>> {
         return this.customerService.findAll(args.options);
     }
 
-    @Query('customer')
+    @Query()
+    @RolesGuard([Permission.ReadCustomer])
     @ApplyIdCodec()
     async customer(obj, args): Promise<Customer | undefined> {
         return this.customerService.findOne(args.id);
     }
 
-    @ResolveProperty('addresses')
+    @ResolveProperty()
+    @RolesGuard([Permission.ReadCustomer])
     @ApplyIdCodec()
     async addresses(customer: Customer): Promise<Address[]> {
         return this.customerService.findAddressesByCustomerId(customer.id);
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCustomer])
     @ApplyIdCodec()
     async createCustomer(_, args): Promise<Customer> {
         const { input, password } = args;
@@ -36,6 +42,7 @@ export class CustomerResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCustomer])
     @ApplyIdCodec()
     async createCustomerAddress(_, args): Promise<Address> {
         const { customerId, input } = args;

+ 8 - 0
server/src/api/facet/facet.resolver.ts

@@ -11,28 +11,33 @@ import { DEFAULT_LANGUAGE_CODE } from '../../common/constants';
 import { Translated } from '../../common/types/locale-types';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
+import { Permission } from '../../entity/role/permission';
 import { I18nError } from '../../i18n/i18n-error';
 import { FacetValueService } from '../../service/facet-value.service';
 import { FacetService } from '../../service/facet.service';
 import { ApplyIdCodec } from '../common/apply-id-codec-decorator';
+import { RolesGuard } from '../roles-guard';
 
 @Resolver('Facet')
 export class FacetResolver {
     constructor(private facetService: FacetService, private facetValueService: FacetValueService) {}
 
     @Query()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     facets(obj, args): Promise<PaginatedList<Translated<Facet>>> {
         return this.facetService.findAll(args.languageCode, args.options);
     }
 
     @Query()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     async facet(obj, args): Promise<Translated<Facet> | undefined> {
         return this.facetService.findOne(args.id, args.languageCode);
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCatalog])
     @ApplyIdCodec()
     async createFacet(_, args: CreateFacetVariables): Promise<Translated<Facet>> {
         const { input } = args;
@@ -48,6 +53,7 @@ export class FacetResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async updateFacet(_, args: UpdateFacetVariables): Promise<Translated<Facet>> {
         const { input } = args;
@@ -55,6 +61,7 @@ export class FacetResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCatalog])
     @ApplyIdCodec()
     async createFacetValues(_, args: CreateFacetValuesVariables): Promise<Array<Translated<FacetValue>>> {
         const { input } = args;
@@ -67,6 +74,7 @@ export class FacetResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async updateFacetValues(_, args: UpdateFacetValuesVariables): Promise<Array<Translated<FacetValue>>> {
         const { input } = args;

+ 10 - 3
server/src/api/product-option/product-option.resolver.ts

@@ -4,9 +4,11 @@ import { CreateProductOptionGroupVariables } from 'shared/generated-types';
 import { Translated } from '../../common/types/locale-types';
 import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { ProductOption } from '../../entity/product-option/product-option.entity';
+import { Permission } from '../../entity/role/permission';
 import { ProductOptionGroupService } from '../../service/product-option-group.service';
 import { ProductOptionService } from '../../service/product-option.service';
 import { ApplyIdCodec } from '../common/apply-id-codec-decorator';
+import { RolesGuard } from '../roles-guard';
 
 @Resolver('ProductOptionGroup')
 export class ProductOptionResolver {
@@ -15,19 +17,22 @@ export class ProductOptionResolver {
         private productOptionService: ProductOptionService,
     ) {}
 
-    @Query('productOptionGroups')
+    @Query()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     productOptionGroups(obj, args): Promise<Array<Translated<ProductOptionGroup>>> {
         return this.productOptionGroupService.findAll(args.languageCode, args.filterTerm);
     }
 
-    @Query('productOptionGroup')
+    @Query()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     productOptionGroup(obj, args): Promise<Translated<ProductOptionGroup> | undefined> {
         return this.productOptionGroupService.findOne(args.id, args.languageCode);
     }
 
-    @ResolveProperty('options')
+    @ResolveProperty()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     async options(optionGroup: Translated<ProductOptionGroup>): Promise<Array<Translated<ProductOption>>> {
         if (optionGroup.options) {
@@ -38,6 +43,7 @@ export class ProductOptionResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCatalog])
     @ApplyIdCodec()
     async createProductOptionGroup(
         _,
@@ -56,6 +62,7 @@ export class ProductOptionResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async updateProductOptionGroup(_, args): Promise<Translated<ProductOptionGroup>> {
         const { input } = args;

+ 8 - 0
server/src/api/product/product.resolver.ts

@@ -47,6 +47,7 @@ export class ProductResolver {
     }
 
     @Query()
+    @RolesGuard([Permission.ReadCatalog])
     @ApplyIdCodec()
     async product(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -57,6 +58,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCatalog])
     @ApplyIdCodec()
     async createProduct(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -67,6 +69,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async updateProduct(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -77,6 +80,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec(['productId', 'optionGroupId'])
     async addOptionGroupToProduct(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -87,6 +91,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec(['productId', 'optionGroupId'])
     async removeOptionGroupFromProduct(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -97,6 +102,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.CreateCatalog])
     @ApplyIdCodec()
     async generateVariantsForProduct(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -108,6 +114,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async updateProductVariants(
         @Context(RequestContextPipe) ctx: RequestContext,
@@ -118,6 +125,7 @@ export class ProductResolver {
     }
 
     @Mutation()
+    @RolesGuard([Permission.UpdateCatalog])
     @ApplyIdCodec()
     async applyFacetValuesToProductVariants(
         @Context(RequestContextPipe) ctx: RequestContext,

+ 10 - 0
server/src/entity/role/permission.ts

@@ -4,15 +4,25 @@
 export enum Permission {
     // The Authenticated role means simply that the user is logged in
     Authenticated = 'Authenticated',
+    // SuperAdmin can perform the most sensitive tasks
+    SuperAdmin = 'SuperAdmin',
+
     // CRUD permissions on the various classes of entity
     CreateCatalog = 'CreateCatalog',
     ReadCatalog = 'ReadCatalog',
     UpdateCatalog = 'UpdateCatalog',
     DeleteCatalog = 'DeleteCatalog',
+
     CreateCustomer = 'CreateCustomer',
     ReadCustomer = 'ReadCustomer',
     UpdateCustomer = 'UpdateCustomer',
     DeleteCustomer = 'DeleteCustomer',
+
+    CreateAdministrator = 'CreateAdministrator',
+    ReadAdministrator = 'ReadAdministrator',
+    UpdateAdministrator = 'UpdateAdministrator',
+    DeleteAdministrator = 'DeleteAdministrator',
+
     CreateOrder = 'CreateOrder',
     ReadOrder = 'ReadOrder',
     UpdateOrder = 'UpdateOrder',