| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- import { faker } from '@faker-js/faker';
- import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
- import type { Response } from 'express';
- import { mock } from 'vitest-mock-extended';
- import { SCOPE } from '@growi/core/dist/interfaces';
- import type Crowi from '~/server/crowi';
- import type UserEvent from '~/server/events/user';
- import { AccessToken } from '~/server/models/access-token';
- import { parserForAccessToken } from './access-token';
- import type { AccessTokenParserReq } from './interfaces';
- vi.mock('@growi/core/dist/models/serializers', { spy: true });
- describe('access-token-parser middleware for access token with scopes', () => {
- let User;
- beforeAll(async() => {
- const crowiMock = mock<Crowi>({
- event: vi.fn().mockImplementation((eventName) => {
- if (eventName === 'user') {
- return mock<UserEvent>({
- on: vi.fn(),
- });
- }
- }),
- });
- const userModelFactory = (await import('../../models/user')).default;
- User = userModelFactory(crowiMock);
- });
- it('should call next if no access token is provided', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- await parserForAccessToken([])(reqMock, resMock);
- expect(reqMock.user).toBeUndefined();
- });
- it('should not authenticate with no scopes', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- expect(reqMock.user).toBeUndefined();
- // prepare a user
- const targetUser = await User.create({
- name: faker.person.fullName(),
- username: faker.string.uuid(),
- password: faker.internet.password(),
- lang: 'en_US',
- });
- // generate token with read:user:info scope
- const { token } = await AccessToken.generateToken(
- targetUser._id,
- new Date(Date.now() + 1000 * 60 * 60 * 24),
- );
- // act
- reqMock.query.access_token = token;
- await parserForAccessToken([])(reqMock, resMock);
- // assert
- expect(reqMock.user).toBeUndefined();
- expect(serializeUserSecurely).not.toHaveBeenCalled();
- });
- it('should authenticate with specific scope', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- expect(reqMock.user).toBeUndefined();
- // prepare a user
- const targetUser = await User.create({
- name: faker.person.fullName(),
- username: faker.string.uuid(),
- password: faker.internet.password(),
- lang: 'en_US',
- });
- // generate token with read:user:info scope
- const { token } = await AccessToken.generateToken(
- targetUser._id,
- new Date(Date.now() + 1000 * 60 * 60 * 24),
- [SCOPE.READ.USER_SETTINGS.INFO],
- );
- // act
- reqMock.query.access_token = token;
- await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO])(reqMock, resMock);
- // assert
- expect(reqMock.user).toBeDefined();
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
- });
- it('should reject with insufficient scopes', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- expect(reqMock.user).toBeUndefined();
- // prepare a user
- const targetUser = await User.create({
- name: faker.person.fullName(),
- username: faker.string.uuid(),
- password: faker.internet.password(),
- lang: 'en_US',
- });
- // generate token with read:user:info scope
- const { token } = await AccessToken.generateToken(
- targetUser._id,
- new Date(Date.now() + 1000 * 60 * 60 * 24),
- [SCOPE.READ.USER_SETTINGS.INFO],
- );
- // act - try to access with write:user:info scope
- reqMock.query.access_token = token;
- await parserForAccessToken([SCOPE.WRITE.USER_SETTINGS.INFO])(reqMock, resMock);
- // // assert
- expect(reqMock.user).toBeUndefined();
- expect(serializeUserSecurely).not.toHaveBeenCalled();
- });
- it('should authenticate with write scope implying read scope', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- expect(reqMock.user).toBeUndefined();
- // prepare a user
- const targetUser = await User.create({
- name: faker.person.fullName(),
- username: faker.string.uuid(),
- password: faker.internet.password(),
- lang: 'en_US',
- });
- // generate token with write:user:info scope
- const { token } = await AccessToken.generateToken(
- targetUser._id,
- new Date(Date.now() + 1000 * 60 * 60 * 24),
- [SCOPE.WRITE.USER_SETTINGS.INFO],
- );
- // act - try to access with read:user:info scope
- reqMock.query.access_token = token;
- await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO])(reqMock, resMock);
- // assert
- expect(reqMock.user).toBeDefined();
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
- });
- it('should authenticate with wildcard scope', async() => {
- // arrange
- const reqMock = mock<AccessTokenParserReq>({
- user: undefined,
- });
- const resMock = mock<Response>();
- // prepare a user
- const targetUser = await User.create({
- name: faker.person.fullName(),
- username: faker.string.uuid(),
- password: faker.internet.password(),
- lang: 'en_US',
- });
- // generate token with read:user:* scope
- const { token } = await AccessToken.generateToken(
- targetUser._id,
- new Date(Date.now() + 1000 * 60 * 60 * 24),
- [SCOPE.READ.USER_SETTINGS.ALL],
- );
- // act - try to access with read:user:info scope
- reqMock.query.access_token = token;
- await parserForAccessToken([SCOPE.READ.USER_SETTINGS.INFO, SCOPE.READ.USER_SETTINGS.API.ACCESS_TOKEN])(reqMock, resMock);
- // assert
- expect(reqMock.user).toBeDefined();
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
- });
- });
|