Skip to content

Commit

Permalink
feat(admin-ui): Add ApiType to RequestContext
Browse files Browse the repository at this point in the history
Will be useful in analyzing certain events (such as logins)
  • Loading branch information
michaelbromley committed Apr 26, 2019
1 parent 290a576 commit 9b55c17
Show file tree
Hide file tree
Showing 11 changed files with 55 additions and 14 deletions.
21 changes: 21 additions & 0 deletions packages/core/src/api/common/get-api-type.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions packages/core/src/api/common/request-context.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';

Expand All @@ -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);
Expand All @@ -39,6 +43,7 @@ export class RequestContextService {
const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission;
const translationFn = (req as any).t;
return new RequestContext({
apiType,
channel,
languageCode,
session,
Expand Down
16 changes: 13 additions & 3 deletions packages/core/src/api/common/request-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,19 +24,22 @@ 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;
isAuthorized: boolean;
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 =
Expand All @@ -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;
}
Expand Down Expand Up @@ -77,13 +85,15 @@ export class RequestContext {
}

/**
* @description
* True if the current session is authorized to access the current resolver method.
*/
get isAuthorized(): boolean {
return this._isAuthorized;
}

/**
* @description
* True if the current anonymous session is only authorized to operate on entities that
* are owned by the current session.
*/
Expand Down
8 changes: 2 additions & 6 deletions packages/core/src/api/decorators/api.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
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.
* This is useful because sometimes the same resolver will have different behaviour
* 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);
});
7 changes: 5 additions & 2 deletions packages/core/src/api/middleware/auth-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,15 +30,17 @@ 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;
const permissions = this.reflector.get<Permission[]>(PERMISSIONS_METADATA_KEY, context.getHandler());
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class Importer {
} else {
const channel = await this.channelService.getDefaultChannel();
return new RequestContext({
apiType: 'admin',
isAuthorized: true,
authorizedAsOwnerOnly: false,
channel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function createRequestContext(pricesIncludeTax: boolean): RequestContext
pricesIncludeTax,
});
const ctx = new RequestContext({
apiType: 'admin',
channel,
authorizedAsOwnerOnly: false,
languageCode: LanguageCode.en,
Expand Down

0 comments on commit 9b55c17

Please sign in to comment.