Browse Source

feat(admin-ui): Add ApiType to RequestContext

Will be useful in analyzing certain events (such as logins)
Michael Bromley 6 years ago
parent
commit
9b55c17583

+ 21 - 0
packages/core/src/api/common/get-api-type.ts

@@ -0,0 +1,21 @@
+import { GraphQLResolveInfo } from 'graphql';
+
+/**
+ * @description
+ * Which of the GraphQL APIs the current request came via.
+ *
+ * @docsCategory request
+ */
+export type ApiType = 'admin' | 'shop';
+
+/**
+ * Inspects the GraphQL "info" resolver argument to determine which API
+ * the request came through.
+ */
+export function getApiType(info: GraphQLResolveInfo): ApiType {
+    const query = info.schema.getQueryType();
+    if (query) {
+        return !!query.getFields().administrators ? 'admin' : 'shop';
+    }
+    return 'shop';
+}

+ 5 - 0
packages/core/src/api/common/request-context.service.ts

@@ -1,6 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { LanguageCode, Permission } from '@vendure/common/lib/generated-types';
 import { Request } from 'express';
+import { GraphQLResolveInfo } from 'graphql';
 
 import { idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
@@ -9,6 +10,7 @@ import { AuthenticatedSession } from '../../entity/session/authenticated-session
 import { Session } from '../../entity/session/session.entity';
 import { User } from '../../entity/user/user.entity';
 import { ChannelService } from '../../service/services/channel.service';
+import { getApiType } from './get-api-type';
 
 import { RequestContext } from './request-context';
 
@@ -26,11 +28,13 @@ export class RequestContextService {
      */
     async fromRequest(
         req: Request,
+        info: GraphQLResolveInfo,
         requiredPermissions?: Permission[],
         session?: Session,
     ): Promise<RequestContext> {
         const channelToken = this.getChannelToken(req);
         const channel = this.channelService.getChannelFromToken(channelToken);
+        const apiType = getApiType(info);
 
         const hasOwnerPermission = !!requiredPermissions && requiredPermissions.includes(Permission.Owner);
         const languageCode = this.getLanguageCode(req);
@@ -39,6 +43,7 @@ export class RequestContextService {
         const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission;
         const translationFn = (req as any).t;
         return new RequestContext({
+            apiType,
             channel,
             languageCode,
             session,

+ 13 - 3
packages/core/src/api/common/request-context.ts

@@ -8,13 +8,14 @@ import { AuthenticatedSession } from '../../entity/session/authenticated-session
 import { Session } from '../../entity/session/session.entity';
 import { User } from '../../entity/user/user.entity';
 
+import { ApiType } from './get-api-type';
+
 /**
  * @description
  * The RequestContext holds information relevant to the current request, which may be
  * required at various points of the stack.
  *
- * @docsCategory
- * @docsWeight 1
+ * @docsCategory request
  */
 export class RequestContext {
     private readonly _languageCode: LanguageCode;
@@ -23,11 +24,13 @@ export class RequestContext {
     private readonly _isAuthorized: boolean;
     private readonly _authorizedAsOwnerOnly: boolean;
     private readonly _translationFn: i18next.TranslationFunction;
+    private readonly _apiType: ApiType;
 
     /**
      * @internal
      */
     constructor(options: {
+        apiType: ApiType;
         channel: Channel;
         session?: Session;
         languageCode?: LanguageCode;
@@ -35,7 +38,8 @@ export class RequestContext {
         authorizedAsOwnerOnly: boolean;
         translationFn?: i18next.TranslationFunction;
     }) {
-        const { channel, session, languageCode, translationFn } = options;
+        const { apiType, channel, session, languageCode, translationFn } = options;
+        this._apiType = apiType;
         this._channel = channel;
         this._session = session;
         this._languageCode =
@@ -45,6 +49,10 @@ export class RequestContext {
         this._translationFn = translationFn || (((key: string) => key) as any);
     }
 
+    get apiType(): ApiType {
+        return this._apiType;
+    }
+
     get channel(): Channel {
         return this._channel;
     }
@@ -77,6 +85,7 @@ export class RequestContext {
     }
 
     /**
+     * @description
      * True if the current session is authorized to access the current resolver method.
      */
     get isAuthorized(): boolean {
@@ -84,6 +93,7 @@ export class RequestContext {
     }
 
     /**
+     * @description
      * True if the current anonymous session is only authorized to operate on entities that
      * are owned by the current session.
      */

+ 2 - 6
packages/core/src/api/decorators/api.decorator.ts

@@ -1,7 +1,7 @@
 import { createParamDecorator } from '@nestjs/common';
 import { GraphQLResolveInfo } from 'graphql';
 
-export type ApiType = 'admin' | 'shop';
+import { getApiType } from '../common/get-api-type';
 
 /**
  * Resolver param decorator which returns which Api the request came though.
@@ -9,9 +9,5 @@ export type ApiType = 'admin' | 'shop';
  * depending whether it is being called from the shop API or the admin API.
  */
 export const Api = createParamDecorator((data, [root, args, ctx, info]) => {
-    const query = (info as GraphQLResolveInfo).schema.getQueryType();
-    if (query) {
-        return !!query.getFields().administrators ? 'admin' : 'shop';
-    }
-    return 'shop';
+    return getApiType(info);
 });

+ 5 - 2
packages/core/src/api/middleware/auth-guard.ts

@@ -3,6 +3,7 @@ import { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { Permission } from '@vendure/common/lib/generated-types';
 import { Request, Response } from 'express';
+import { GraphQLResolveInfo } from 'graphql';
 
 import { ForbiddenError } from '../../common/error/errors';
 import { ConfigService } from '../../config/config.service';
@@ -29,7 +30,9 @@ export class AuthGuard implements CanActivate {
     ) {}
 
     async canActivate(context: ExecutionContext): Promise<boolean> {
-        const ctx = GqlExecutionContext.create(context).getContext();
+        const graphQlContext = GqlExecutionContext.create(context);
+        const ctx = graphQlContext.getContext();
+        const info = graphQlContext.getInfo<GraphQLResolveInfo>();
         const req: Request = ctx.req;
         const res: Response = ctx.res;
         const authDisabled = this.configService.authOptions.disableAuth;
@@ -37,7 +40,7 @@ export class AuthGuard implements CanActivate {
         const isPublic = !!permissions && permissions.includes(Permission.Public);
         const hasOwnerPermission = !!permissions && permissions.includes(Permission.Owner);
         const session = await this.getSession(req, res, hasOwnerPermission);
-        const requestContext = await this.requestContextService.fromRequest(req, permissions, session);
+        const requestContext = await this.requestContextService.fromRequest(req, info, permissions, session);
         (req as any)[REQUEST_CONTEXT_KEY] = requestContext;
 
         if (authDisabled || !permissions || isPublic) {

+ 2 - 1
packages/core/src/api/resolvers/entity/collection-entity.resolver.ts

@@ -7,8 +7,9 @@ import { Translated } from '../../../common/types/locale-types';
 import { Collection, Product, ProductVariant } from '../../../entity';
 import { CollectionService } from '../../../service/services/collection.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
+import { ApiType } from '../../common/get-api-type';
 import { RequestContext } from '../../common/request-context';
-import { Api, ApiType } from '../../decorators/api.decorator';
+import { Api } from '../../decorators/api.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Collection')

+ 2 - 1
packages/core/src/api/resolvers/entity/product-entity.resolver.ts

@@ -6,8 +6,9 @@ import { ProductVariant } from '../../../entity/product-variant/product-variant.
 import { Product } from '../../../entity/product/product.entity';
 import { CollectionService } from '../../../service/services/collection.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
+import { ApiType } from '../../common/get-api-type';
 import { RequestContext } from '../../common/request-context';
-import { Api, ApiType } from '../../decorators/api.decorator';
+import { Api } from '../../decorators/api.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Product')

+ 2 - 1
packages/core/src/api/resolvers/entity/product-variant-entity.resolver.ts

@@ -4,8 +4,9 @@ import { Translated } from '../../../common/types/locale-types';
 import { FacetValue, ProductOption } from '../../../entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
+import { ApiType } from '../../common/get-api-type';
 import { RequestContext } from '../../common/request-context';
-import { Api, ApiType } from '../../decorators/api.decorator';
+import { Api } from '../../decorators/api.decorator';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('ProductVariant')

+ 1 - 0
packages/core/src/data-import/providers/importer/importer.ts

@@ -128,6 +128,7 @@ export class Importer {
         } else {
             const channel = await this.channelService.getDefaultChannel();
             return new RequestContext({
+                apiType: 'admin',
                 isAuthorized: true,
                 authorizedAsOwnerOnly: false,
                 channel,

+ 1 - 0
packages/core/src/data-import/providers/populator/populator.ts

@@ -112,6 +112,7 @@ export class Populator {
     private async createRequestContext(data: InitialData) {
         const channel = await this.channelService.getDefaultChannel();
         const ctx = new RequestContext({
+            apiType: 'admin',
             isAuthorized: true,
             authorizedAsOwnerOnly: false,
             channel,

+ 1 - 0
packages/core/src/service/helpers/tax-calculator/tax-calculator-test-fixtures.ts

@@ -76,6 +76,7 @@ export function createRequestContext(pricesIncludeTax: boolean): RequestContext
         pricesIncludeTax,
     });
     const ctx = new RequestContext({
+        apiType: 'admin',
         channel,
         authorizedAsOwnerOnly: false,
         languageCode: LanguageCode.en,