request-context.service.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { Injectable } from '@nestjs/common';
  2. import { LanguageCode, Permission } from '@vendure/common/lib/generated-types';
  3. import { ID } from '@vendure/common/lib/shared-types';
  4. import { Request } from 'express';
  5. import { GraphQLResolveInfo } from 'graphql';
  6. import ms from 'ms';
  7. import { ApiType, getApiType } from '../../../api/common/get-api-type';
  8. import { RequestContext } from '../../../api/common/request-context';
  9. import { idsAreEqual } from '../../../common/utils';
  10. import { ConfigService } from '../../../config/config.service';
  11. import { CachedSession, CachedSessionUser } from '../../../config/session-cache/session-cache-strategy';
  12. import { Channel } from '../../../entity/channel/channel.entity';
  13. import { User } from '../../../entity/index';
  14. import { ChannelService } from '../../services/channel.service';
  15. import { getUserChannelsPermissions } from '../utils/get-user-channels-permissions';
  16. /**
  17. * @description
  18. * Creates new {@link RequestContext} instances.
  19. *
  20. * @docsCategory request
  21. */
  22. @Injectable()
  23. export class RequestContextService {
  24. /** @internal */
  25. constructor(private channelService: ChannelService, private configService: ConfigService) {}
  26. /**
  27. * @description
  28. * Creates a RequestContext based on the config provided. This can be useful when interacting
  29. * with services outside the request-response cycle, for example in stand-alone scripts or in
  30. * worker jobs.
  31. *
  32. * @since 1.5.0
  33. */
  34. async create(config: {
  35. req?: Request;
  36. apiType: ApiType;
  37. channelOrToken?: Channel | string;
  38. languageCode?: LanguageCode;
  39. user?: User;
  40. activeOrderId?: ID;
  41. }): Promise<RequestContext> {
  42. const { req, apiType, channelOrToken, languageCode, user, activeOrderId } = config;
  43. let channel: Channel;
  44. if (channelOrToken instanceof Channel) {
  45. channel = channelOrToken;
  46. } else if (typeof channelOrToken === 'string') {
  47. channel = await this.channelService.getChannelFromToken(channelOrToken);
  48. } else {
  49. channel = await this.channelService.getDefaultChannel();
  50. }
  51. let session: CachedSession | undefined;
  52. if (user) {
  53. const channelPermissions = user.roles ? getUserChannelsPermissions(user) : [];
  54. session = {
  55. user: {
  56. id: user.id,
  57. identifier: user.identifier,
  58. verified: user.verified,
  59. channelPermissions,
  60. },
  61. id: '__dummy_session_id__',
  62. token: '__dummy_session_token__',
  63. expires: new Date(Date.now() + ms('1y')),
  64. cacheExpiry: ms('1y'),
  65. activeOrderId,
  66. };
  67. }
  68. return new RequestContext({
  69. req,
  70. apiType,
  71. channel,
  72. languageCode,
  73. session,
  74. isAuthorized: true,
  75. authorizedAsOwnerOnly: false,
  76. });
  77. }
  78. /**
  79. * @description
  80. * Creates a new RequestContext based on an Express request object. This is used internally
  81. * in the API layer by the AuthGuard, and creates the RequestContext which is then passed
  82. * to all resolvers & controllers.
  83. */
  84. async fromRequest(
  85. req: Request,
  86. info?: GraphQLResolveInfo,
  87. requiredPermissions?: Permission[],
  88. session?: CachedSession,
  89. ): Promise<RequestContext> {
  90. const channelToken = this.getChannelToken(req);
  91. const channel = await this.channelService.getChannelFromToken(channelToken);
  92. const apiType = getApiType(info);
  93. const hasOwnerPermission = !!requiredPermissions && requiredPermissions.includes(Permission.Owner);
  94. const languageCode = this.getLanguageCode(req, channel);
  95. const user = session && session.user;
  96. const isAuthorized = this.userHasRequiredPermissionsOnChannel(requiredPermissions, channel, user);
  97. const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission;
  98. const translationFn = (req as any).t;
  99. return new RequestContext({
  100. req,
  101. apiType,
  102. channel,
  103. languageCode,
  104. session,
  105. isAuthorized,
  106. authorizedAsOwnerOnly,
  107. translationFn,
  108. });
  109. }
  110. private getChannelToken(req: Request<any, any, any, { [key: string]: any }>): string {
  111. const tokenKey = this.configService.apiOptions.channelTokenKey;
  112. let channelToken = '';
  113. if (req && req.query && req.query[tokenKey]) {
  114. channelToken = req.query[tokenKey];
  115. } else if (req && req.headers && req.headers[tokenKey]) {
  116. channelToken = req.headers[tokenKey] as string;
  117. }
  118. return channelToken;
  119. }
  120. private getLanguageCode(req: Request, channel: Channel): LanguageCode | undefined {
  121. return (
  122. (req.query && (req.query.languageCode as LanguageCode)) ??
  123. channel.defaultLanguageCode ??
  124. this.configService.defaultLanguageCode
  125. );
  126. }
  127. /**
  128. * TODO: Deprecate and remove, since this function is now handled internally in the RequestContext.
  129. * @private
  130. */
  131. private userHasRequiredPermissionsOnChannel(
  132. permissions: Permission[] = [],
  133. channel?: Channel,
  134. user?: CachedSessionUser,
  135. ): boolean {
  136. if (!user || !channel) {
  137. return false;
  138. }
  139. const permissionsOnChannel = user.channelPermissions.find(c => idsAreEqual(c.id, channel.id));
  140. if (permissionsOnChannel) {
  141. return this.arraysIntersect(permissionsOnChannel.permissions, permissions);
  142. }
  143. return false;
  144. }
  145. /**
  146. * Returns true if any element of arr1 appears in arr2.
  147. */
  148. private arraysIntersect<T>(arr1: T[], arr2: T[]): boolean {
  149. return arr1.reduce((intersects, role) => {
  150. return intersects || arr2.includes(role);
  151. }, false as boolean);
  152. }
  153. }