api-token.integ.ts 5.1 KB

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