auth-guard.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
  2. import { Reflector } from '@nestjs/core';
  3. import { Permission } from '@vendure/common/lib/generated-types';
  4. import { Request, Response } from 'express';
  5. import { ForbiddenError } from '../../common/error/errors';
  6. import { ConfigService } from '../../config/config.service';
  7. import { LogLevel } from '../../config/logger/vendure-logger';
  8. import { CachedSession } from '../../config/session-cache/session-cache-strategy';
  9. import { Customer } from '../../entity/customer/customer.entity';
  10. import { RequestContextService } from '../../service/helpers/request-context/request-context.service';
  11. import { ChannelService } from '../../service/services/channel.service';
  12. import { CustomerService } from '../../service/services/customer.service';
  13. import { SessionService } from '../../service/services/session.service';
  14. import { extractSessionToken } from '../common/extract-session-token';
  15. import { isFieldResolver } from '../common/is-field-resolver';
  16. import { parseContext } from '../common/parse-context';
  17. import {
  18. internal_getRequestContext,
  19. internal_setRequestContext,
  20. RequestContext,
  21. } from '../common/request-context';
  22. import { setSessionToken } from '../common/set-session-token';
  23. import { PERMISSIONS_METADATA_KEY } from '../decorators/allow.decorator';
  24. /**
  25. * @description
  26. * A guard which:
  27. *
  28. * 1. checks for the existence of a valid session token in the request and if found,
  29. * attaches the current User entity to the request.
  30. * 2. enforces any permissions required by the target handler (resolver, field resolver or route),
  31. * and throws a ForbiddenError if those permissions are not present.
  32. */
  33. @Injectable()
  34. export class AuthGuard implements CanActivate {
  35. strategy: any;
  36. constructor(
  37. private reflector: Reflector,
  38. private configService: ConfigService,
  39. private requestContextService: RequestContextService,
  40. private sessionService: SessionService,
  41. private customerService: CustomerService,
  42. private channelService: ChannelService,
  43. ) {}
  44. async canActivate(context: ExecutionContext): Promise<boolean> {
  45. const { req, res, info } = parseContext(context);
  46. const targetIsFieldResolver = isFieldResolver(info);
  47. const permissions = this.reflector.get<Permission[]>(PERMISSIONS_METADATA_KEY, context.getHandler());
  48. if (targetIsFieldResolver && !permissions) {
  49. return true;
  50. }
  51. const authDisabled = this.configService.authOptions.disableAuth;
  52. const isPublic = !!permissions && permissions.includes(Permission.Public);
  53. const hasOwnerPermission = !!permissions && permissions.includes(Permission.Owner);
  54. let requestContext: RequestContext;
  55. if (targetIsFieldResolver) {
  56. requestContext = internal_getRequestContext(req);
  57. } else {
  58. const session = await this.getSession(req, res, hasOwnerPermission);
  59. requestContext = await this.requestContextService.fromRequest(req, info, permissions, session);
  60. const requestContextShouldBeReinitialized = await this.setActiveChannel(requestContext, session);
  61. if (requestContextShouldBeReinitialized) {
  62. requestContext = await this.requestContextService.fromRequest(
  63. req,
  64. info,
  65. permissions,
  66. session,
  67. );
  68. }
  69. internal_setRequestContext(req, requestContext, context);
  70. }
  71. if (authDisabled || !permissions || isPublic) {
  72. return true;
  73. } else {
  74. const canActivate =
  75. requestContext.userHasPermissions(permissions) || requestContext.authorizedAsOwnerOnly;
  76. if (!canActivate) {
  77. throw new ForbiddenError(LogLevel.Verbose);
  78. } else {
  79. return canActivate;
  80. }
  81. }
  82. }
  83. private async setActiveChannel(
  84. requestContext: RequestContext,
  85. session?: CachedSession,
  86. ): Promise<boolean> {
  87. if (!session) {
  88. return false;
  89. }
  90. // In case the session does not have an activeChannelId or the activeChannelId
  91. // does not correspond to the current channel, the activeChannelId on the session is set
  92. const activeChannelShouldBeSet =
  93. !session.activeChannelId || session.activeChannelId !== requestContext.channelId;
  94. if (activeChannelShouldBeSet) {
  95. await this.sessionService.setActiveChannel(session, requestContext.channel);
  96. if (requestContext.activeUserId) {
  97. const customer = await this.customerService.findOneByUserId(
  98. requestContext,
  99. requestContext.activeUserId,
  100. false,
  101. );
  102. // To avoid assigning the customer to the active channel on every request,
  103. // it is only done on the first request and whenever the channel changes
  104. if (customer) {
  105. try {
  106. await this.channelService.assignToChannels(requestContext, Customer, customer.id, [
  107. requestContext.channelId,
  108. ]);
  109. } catch (e: any) {
  110. const isDuplicateError =
  111. e.code === 'ER_DUP_ENTRY' /* mySQL/MariaDB */ ||
  112. e.code === '23505'; /* postgres */
  113. if (isDuplicateError) {
  114. // For a duplicate error, this means that concurrent requests have resulted in attempting to
  115. // assign the Customer to the channel more than once. In this case we can safely ignore the
  116. // error as the Customer was successfully assigned in the earlier call.
  117. // See https://github.com/vendure-ecommerce/vendure/issues/834
  118. } else {
  119. throw e;
  120. }
  121. }
  122. }
  123. }
  124. return true;
  125. }
  126. return false;
  127. }
  128. private async getSession(
  129. req: Request,
  130. res: Response,
  131. hasOwnerPermission: boolean,
  132. ): Promise<CachedSession | undefined> {
  133. const sessionToken = extractSessionToken(req, this.configService.authOptions.tokenMethod);
  134. let serializedSession: CachedSession | undefined;
  135. if (sessionToken) {
  136. serializedSession = await this.sessionService.getSessionFromToken(sessionToken);
  137. if (serializedSession) {
  138. return serializedSession;
  139. }
  140. // if there is a token but it cannot be validated to a Session,
  141. // then the token is no longer valid and should be unset.
  142. setSessionToken({
  143. req,
  144. res,
  145. authOptions: this.configService.authOptions,
  146. rememberMe: false,
  147. sessionToken: '',
  148. });
  149. }
  150. if (hasOwnerPermission && !serializedSession) {
  151. serializedSession = await this.sessionService.createAnonymousSession();
  152. setSessionToken({
  153. sessionToken: serializedSession.token,
  154. rememberMe: true,
  155. authOptions: this.configService.authOptions,
  156. req,
  157. res,
  158. });
  159. }
  160. return serializedSession;
  161. }
  162. }