request-context.service.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { Injectable } from '@nestjs/common';
  2. import { Request } from 'express';
  3. import { LanguageCode, Permission } from 'shared/generated-types';
  4. import { idsAreEqual } from '../../common/utils';
  5. import { ConfigService } from '../../config/config.service';
  6. import { Channel } from '../../entity/channel/channel.entity';
  7. import { AuthenticatedSession } from '../../entity/session/authenticated-session.entity';
  8. import { Session } from '../../entity/session/session.entity';
  9. import { User } from '../../entity/user/user.entity';
  10. import { I18nError } from '../../i18n/i18n-error';
  11. import { ChannelService } from '../../service/providers/channel.service';
  12. import { RequestContext } from './request-context';
  13. export const REQUEST_CONTEXT_KEY = 'vendureRequestContext';
  14. /**
  15. * Creates new RequestContext instances.
  16. */
  17. @Injectable()
  18. export class RequestContextService {
  19. constructor(private channelService: ChannelService, private configService: ConfigService) {}
  20. /**
  21. * Creates a new RequestContext based on an Express request object.
  22. */
  23. async fromRequest(
  24. req: Request,
  25. requiredPermissions?: Permission[],
  26. session?: Session,
  27. ): Promise<RequestContext> {
  28. const channelToken = this.getChannelToken(req);
  29. const channel = this.channelService.getChannelFromToken(channelToken);
  30. const hasOwnerPermission = !!requiredPermissions && requiredPermissions.includes(Permission.Owner);
  31. const languageCode = this.getLanguageCode(req);
  32. const user = session && (session as AuthenticatedSession).user;
  33. const isAuthorized = this.userHasRequiredPermissionsOnChannel(requiredPermissions, channel, user);
  34. const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission;
  35. return new RequestContext({
  36. channel,
  37. languageCode,
  38. session,
  39. isAuthorized,
  40. authorizedAsOwnerOnly,
  41. });
  42. }
  43. private getChannelToken(req: Request): string {
  44. const tokenKey = this.configService.channelTokenKey;
  45. let channelToken: string;
  46. if (req && req.query && req.query[tokenKey]) {
  47. channelToken = req.query[tokenKey];
  48. } else if (req && req.headers && req.headers[tokenKey]) {
  49. channelToken = req.headers[tokenKey] as string;
  50. } else {
  51. throw new I18nError('error.no-valid-channel-specified');
  52. }
  53. return channelToken;
  54. }
  55. private getLanguageCode(req: Request): LanguageCode | undefined {
  56. return req.body && req.body.variables && req.body.variables.languageCode;
  57. }
  58. private isAuthenticatedSession(session?: Session): session is AuthenticatedSession {
  59. return !!session && !!(session as AuthenticatedSession).user;
  60. }
  61. private userHasRequiredPermissionsOnChannel(
  62. permissions: Permission[] = [],
  63. channel?: Channel,
  64. user?: User,
  65. ): boolean {
  66. if (!user || !channel) {
  67. return false;
  68. }
  69. const permissionsOnChannel = user.roles
  70. .filter(role => role.channels.find(c => idsAreEqual(c.id, channel.id)))
  71. .reduce((output, role) => [...output, ...role.permissions], [] as Permission[]);
  72. return this.arraysIntersect(permissions, permissionsOnChannel);
  73. }
  74. /**
  75. * Returns true if any element of arr1 appears in arr2.
  76. */
  77. private arraysIntersect<T>(arr1: T[], arr2: T[]): boolean {
  78. return arr1.reduce((intersects, role) => {
  79. return intersects || arr2.includes(role);
  80. }, false);
  81. }
  82. }