access-token.integ.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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 { SCOPE } from '@growi/core/dist/interfaces';
  6. import type Crowi from '~/server/crowi';
  7. import type UserEvent from '~/server/events/user';
  8. import { AccessToken } from '~/server/models/access-token';
  9. import { parserForAccessToken } from './access-token';
  10. import type { AccessTokenParserReq } from './interfaces';
  11. vi.mock('@growi/core/dist/models/serializers', { spy: true });
  12. describe('access-token-parser middleware for access token with scopes', () => {
  13. let User;
  14. beforeAll(async() => {
  15. const crowiMock = mock<Crowi>({
  16. event: vi.fn().mockImplementation((eventName) => {
  17. if (eventName === 'user') {
  18. return mock<UserEvent>({
  19. on: vi.fn(),
  20. });
  21. }
  22. }),
  23. });
  24. const userModelFactory = (await import('../../models/user')).default;
  25. User = userModelFactory(crowiMock);
  26. });
  27. it('should call next if no access token is provided', async() => {
  28. // arrange
  29. const reqMock = mock<AccessTokenParserReq>({
  30. user: undefined,
  31. });
  32. const resMock = mock<Response>();
  33. await parserForAccessToken([])(reqMock, resMock);
  34. expect(reqMock.user).toBeUndefined();
  35. });
  36. it('should not authenticate with no scopes', async() => {
  37. // arrange
  38. const reqMock = mock<AccessTokenParserReq>({
  39. user: undefined,
  40. });
  41. const resMock = mock<Response>();
  42. expect(reqMock.user).toBeUndefined();
  43. // prepare a user
  44. const targetUser = await User.create({
  45. name: faker.person.fullName(),
  46. username: faker.string.uuid(),
  47. password: faker.internet.password(),
  48. lang: 'en_US',
  49. });
  50. // generate token with read:user:info scope
  51. const { token } = await AccessToken.generateToken(
  52. targetUser._id,
  53. new Date(Date.now() + 1000 * 60 * 60 * 24),
  54. );
  55. // act
  56. reqMock.query.access_token = token;
  57. await parserForAccessToken([])(reqMock, resMock);
  58. // assert
  59. expect(reqMock.user).toBeUndefined();
  60. expect(serializeUserSecurely).not.toHaveBeenCalled();
  61. });
  62. it('should authenticate with specific scope', async() => {
  63. // arrange
  64. const reqMock = mock<AccessTokenParserReq>({
  65. user: undefined,
  66. });
  67. const resMock = mock<Response>();
  68. expect(reqMock.user).toBeUndefined();
  69. // prepare a user
  70. const targetUser = await User.create({
  71. name: faker.person.fullName(),
  72. username: faker.string.uuid(),
  73. password: faker.internet.password(),
  74. lang: 'en_US',
  75. });
  76. // generate token with read:user:info scope
  77. const { token } = await AccessToken.generateToken(
  78. targetUser._id,
  79. new Date(Date.now() + 1000 * 60 * 60 * 24),
  80. [SCOPE.READ.USER_SETTINGS.INFO],
  81. );
  82. // act
  83. reqMock.query.access_token = token;
  84. await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO])(reqMock, resMock);
  85. // assert
  86. expect(reqMock.user).toBeDefined();
  87. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  88. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  89. });
  90. it('should reject with insufficient scopes', async() => {
  91. // arrange
  92. const reqMock = mock<AccessTokenParserReq>({
  93. user: undefined,
  94. });
  95. const resMock = mock<Response>();
  96. expect(reqMock.user).toBeUndefined();
  97. // prepare a user
  98. const targetUser = await User.create({
  99. name: faker.person.fullName(),
  100. username: faker.string.uuid(),
  101. password: faker.internet.password(),
  102. lang: 'en_US',
  103. });
  104. // generate token with read:user:info scope
  105. const { token } = await AccessToken.generateToken(
  106. targetUser._id,
  107. new Date(Date.now() + 1000 * 60 * 60 * 24),
  108. [SCOPE.READ.USER_SETTINGS.INFO],
  109. );
  110. // act - try to access with write:user:info scope
  111. reqMock.query.access_token = token;
  112. await parserForAccessToken([SCOPE.WRITE.USER_SETTINGS.INFO])(reqMock, resMock);
  113. // // assert
  114. expect(reqMock.user).toBeUndefined();
  115. expect(serializeUserSecurely).not.toHaveBeenCalled();
  116. });
  117. it('should authenticate with write scope implying read scope', async() => {
  118. // arrange
  119. const reqMock = mock<AccessTokenParserReq>({
  120. user: undefined,
  121. });
  122. const resMock = mock<Response>();
  123. expect(reqMock.user).toBeUndefined();
  124. // prepare a user
  125. const targetUser = await User.create({
  126. name: faker.person.fullName(),
  127. username: faker.string.uuid(),
  128. password: faker.internet.password(),
  129. lang: 'en_US',
  130. });
  131. // generate token with write:user:info scope
  132. const { token } = await AccessToken.generateToken(
  133. targetUser._id,
  134. new Date(Date.now() + 1000 * 60 * 60 * 24),
  135. [SCOPE.WRITE.USER_SETTINGS.INFO],
  136. );
  137. // act - try to access with read:user:info scope
  138. reqMock.query.access_token = token;
  139. await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO])(reqMock, resMock);
  140. // assert
  141. expect(reqMock.user).toBeDefined();
  142. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  143. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  144. });
  145. it('should authenticate with wildcard scope', async() => {
  146. // arrange
  147. const reqMock = mock<AccessTokenParserReq>({
  148. user: undefined,
  149. });
  150. const resMock = mock<Response>();
  151. // prepare a user
  152. const targetUser = await User.create({
  153. name: faker.person.fullName(),
  154. username: faker.string.uuid(),
  155. password: faker.internet.password(),
  156. lang: 'en_US',
  157. });
  158. // generate token with read:user:* scope
  159. const { token } = await AccessToken.generateToken(
  160. targetUser._id,
  161. new Date(Date.now() + 1000 * 60 * 60 * 24),
  162. [SCOPE.READ.USER_SETTINGS.ALL],
  163. );
  164. // act - try to access with read:user:info scope
  165. reqMock.query.access_token = token;
  166. await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO, SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN])(reqMock, resMock);
  167. // assert
  168. expect(reqMock.user).toBeDefined();
  169. expect(reqMock.user?._id).toStrictEqual(targetUser._id);
  170. expect(serializeUserSecurely).toHaveBeenCalledOnce();
  171. });
  172. });