access-token.integ.ts 6.3 KB

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