api-token.integ.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { faker } from '@faker-js/faker';
  2. import type { AccessTokenParserReq } from '@growi/core/dist/interfaces/server';
  3. import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
  4. import type { Response } from 'express';
  5. import { mock } from 'vitest-mock-extended';
  6. import type Crowi from '~/server/crowi';
  7. import type UserEvent from '~/server/events/user';
  8. import { parserForApiToken } from './api-token';
  9. vi.mock('@growi/core/dist/models/serializers', { spy: true });
  10. describe('access-token-parser middleware', () => {
  11. // biome-ignore lint/suspicious/noImplicitAnyLet: ignore
  12. let User;
  13. beforeAll(async () => {
  14. const crowiMock = mock<Crowi>({
  15. events: {
  16. user: mock<UserEvent>({
  17. on: vi.fn(),
  18. }),
  19. },
  20. });
  21. const userModelFactory = (await import('../../models/user')).default;
  22. User = userModelFactory(crowiMock);
  23. });
  24. it('should call next if no access token is provided', async () => {
  25. // arrange
  26. const reqMock = mock<AccessTokenParserReq>({
  27. user: undefined,
  28. });
  29. const resMock = mock<Response>();
  30. expect(reqMock.user).toBeUndefined();
  31. // act
  32. await parserForApiToken(reqMock, resMock);
  33. // assert
  34. expect(reqMock.user).toBeUndefined();
  35. expect(serializeUserSecurely).not.toHaveBeenCalled();
  36. });
  37. it('should call next if the given access token is invalid', async () => {
  38. // arrange
  39. const reqMock = mock<AccessTokenParserReq>({
  40. user: undefined,
  41. });
  42. const resMock = mock<Response>();
  43. expect(reqMock.user).toBeUndefined();
  44. // act
  45. reqMock.query.access_token = 'invalidToken';
  46. await parserForApiToken(reqMock, resMock);
  47. // assert
  48. expect(reqMock.user).toBeUndefined();
  49. expect(serializeUserSecurely).not.toHaveBeenCalled();
  50. });
  51. it('should set req.user with a valid api token in query', async () => {
  52. // arrange
  53. const reqMock = mock<AccessTokenParserReq>({
  54. user: undefined,
  55. });
  56. const resMock = mock<Response>();
  57. const nextMock = vi.fn();
  58. expect(reqMock.user).toBeUndefined();
  59. // prepare a user with an access token
  60. const targetUser = await User.create({
  61. name: faker.person.fullName(),
  62. username: faker.string.uuid(),
  63. password: faker.internet.password(),
  64. lang: 'en_US',
  65. apiToken: faker.internet.password(),
  66. });
  67. // act
  68. reqMock.query.access_token = targetUser.apiToken;
  69. await parserForApiToken(reqMock, resMock);
  70. // assert
  71. expect(reqMock.user).toBeDefined();
  72. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  73. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  74. });
  75. it('should set req.user with a valid api token in body', async () => {
  76. // arrange
  77. const reqMock = mock<AccessTokenParserReq>({
  78. user: undefined,
  79. });
  80. const resMock = mock<Response>();
  81. expect(reqMock.user).toBeUndefined();
  82. // prepare a user with an access token
  83. const targetUser = await User.create({
  84. name: faker.person.fullName(),
  85. username: faker.string.uuid(),
  86. password: faker.internet.password(),
  87. lang: 'en_US',
  88. apiToken: faker.internet.password(),
  89. });
  90. // act
  91. reqMock.body.access_token = targetUser.apiToken;
  92. await parserForApiToken(reqMock, resMock);
  93. // assert
  94. expect(reqMock.user).toBeDefined();
  95. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  96. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  97. });
  98. it('should set req.user with a valid Bearer token in Authorization header', async () => {
  99. // arrange
  100. const reqMock = mock<AccessTokenParserReq>({
  101. user: undefined,
  102. headers: {
  103. authorization: undefined,
  104. },
  105. });
  106. const resMock = mock<Response>();
  107. expect(reqMock.user).toBeUndefined();
  108. // prepare a user with an access token
  109. const targetUser = await User.create({
  110. name: faker.person.fullName(),
  111. username: faker.string.uuid(),
  112. password: faker.internet.password(),
  113. lang: 'en_US',
  114. apiToken: faker.internet.password(),
  115. });
  116. // act
  117. reqMock.headers.authorization = `Bearer ${targetUser.apiToken}`;
  118. await parserForApiToken(reqMock, resMock);
  119. // assert
  120. expect(reqMock.user).toBeDefined();
  121. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  122. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  123. });
  124. it('should ignore non-Bearer Authorization header', async () => {
  125. // arrange
  126. const reqMock = mock<AccessTokenParserReq>({
  127. user: undefined,
  128. headers: {
  129. authorization: undefined,
  130. },
  131. });
  132. const resMock = mock<Response>();
  133. expect(reqMock.user).toBeUndefined();
  134. // Generate random string that is guaranteed to be invalid for Basic auth (1024 chars)
  135. const randomString = faker.string.alpha(1024);
  136. // act
  137. reqMock.headers.authorization = `Basic ${randomString}`; // Basic auth header with random string
  138. await parserForApiToken(reqMock, resMock);
  139. // assert
  140. expect(reqMock.user).toBeUndefined();
  141. expect(serializeUserSecurely).not.toHaveBeenCalled();
  142. });
  143. });