|
@@ -45,7 +45,7 @@ describe('access-token-parser middleware', () => {
|
|
|
expect(reqMock.user).toBeUndefined();
|
|
expect(reqMock.user).toBeUndefined();
|
|
|
|
|
|
|
|
// act
|
|
// act
|
|
|
- await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
+ await accessTokenParser([SCOPE.READ.USER.ALL])(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
|
// assert
|
|
// assert
|
|
|
expect(reqMock.user).toBeUndefined();
|
|
expect(reqMock.user).toBeUndefined();
|
|
@@ -53,108 +53,7 @@ describe('access-token-parser middleware', () => {
|
|
|
expect(nextMock).toHaveBeenCalled();
|
|
expect(nextMock).toHaveBeenCalled();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should call next if the given access token is invalid', async() => {
|
|
|
|
|
- // arrange
|
|
|
|
|
- const reqMock = mock<AccessTokenParserReq>({
|
|
|
|
|
- user: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- const resMock = mock<Response>();
|
|
|
|
|
- const nextMock = vi.fn();
|
|
|
|
|
-
|
|
|
|
|
- expect(reqMock.user).toBeUndefined();
|
|
|
|
|
-
|
|
|
|
|
- // act
|
|
|
|
|
- reqMock.query.access_token = 'invalidToken';
|
|
|
|
|
- await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
-
|
|
|
|
|
- // assert
|
|
|
|
|
- expect(reqMock.user).toBeUndefined();
|
|
|
|
|
- expect(serializeUserSecurely).not.toHaveBeenCalled();
|
|
|
|
|
- expect(nextMock).toHaveBeenCalled();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it('should set req.user with a valid api token in query', async() => {
|
|
|
|
|
- // arrange
|
|
|
|
|
- const reqMock = mock<AccessTokenParserReq>({
|
|
|
|
|
- user: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- const resMock = mock<Response>();
|
|
|
|
|
- const nextMock = vi.fn();
|
|
|
|
|
-
|
|
|
|
|
- expect(reqMock.user).toBeUndefined();
|
|
|
|
|
-
|
|
|
|
|
- // prepare a user with an access token
|
|
|
|
|
- const targetUser = await User.create({
|
|
|
|
|
- name: faker.person.fullName(),
|
|
|
|
|
- username: faker.string.uuid(),
|
|
|
|
|
- password: faker.internet.password(),
|
|
|
|
|
- lang: 'en_US',
|
|
|
|
|
- apiToken: faker.internet.password(),
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // act
|
|
|
|
|
- reqMock.query.access_token = targetUser.apiToken;
|
|
|
|
|
- await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
-
|
|
|
|
|
- // assert
|
|
|
|
|
- expect(reqMock.user).toBeDefined();
|
|
|
|
|
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
|
|
|
|
|
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
|
|
|
- expect(nextMock).toHaveBeenCalled();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it('should set req.user with a valid api token in body', async() => {
|
|
|
|
|
- // arrange
|
|
|
|
|
- const reqMock = mock<AccessTokenParserReq>({
|
|
|
|
|
- user: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- const resMock = mock<Response>();
|
|
|
|
|
- const nextMock = vi.fn();
|
|
|
|
|
-
|
|
|
|
|
- expect(reqMock.user).toBeUndefined();
|
|
|
|
|
-
|
|
|
|
|
- // prepare a user with an access token
|
|
|
|
|
- const targetUser = await User.create({
|
|
|
|
|
- name: faker.person.fullName(),
|
|
|
|
|
- username: faker.string.uuid(),
|
|
|
|
|
- password: faker.internet.password(),
|
|
|
|
|
- lang: 'en_US',
|
|
|
|
|
- apiToken: faker.internet.password(),
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // act
|
|
|
|
|
- reqMock.body.access_token = targetUser.apiToken;
|
|
|
|
|
- await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
-
|
|
|
|
|
- // assert
|
|
|
|
|
- expect(reqMock.user).toBeDefined();
|
|
|
|
|
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
|
|
|
|
|
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
|
|
|
- expect(nextMock).toHaveBeenCalled();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-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 authenticate with no scopes', async() => {
|
|
|
|
|
|
|
+ it('should not authenticate with no scopes', async() => {
|
|
|
// arrange
|
|
// arrange
|
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
|
user: undefined,
|
|
user: undefined,
|
|
@@ -172,24 +71,24 @@ describe('access-token-parser middleware for access token with scopes', () => {
|
|
|
lang: 'en_US',
|
|
lang: 'en_US',
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // generate token with read:user:info scope
|
|
|
|
|
|
|
+ // generate token with write:user:info scope
|
|
|
const { token } = await AccessToken.generateToken(
|
|
const { token } = await AccessToken.generateToken(
|
|
|
targetUser._id,
|
|
targetUser._id,
|
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
|
|
|
+ [SCOPE.WRITE.USER.INFO],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // act
|
|
|
|
|
|
|
+ // act - try to access with read:user:info scope
|
|
|
reqMock.query.access_token = token;
|
|
reqMock.query.access_token = token;
|
|
|
await accessTokenParser()(reqMock, resMock, nextMock);
|
|
await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
|
// assert
|
|
// assert
|
|
|
- expect(reqMock.user).toBeDefined();
|
|
|
|
|
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
|
|
|
|
|
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
|
|
|
|
|
+ expect(reqMock.user).toBeUndefined();
|
|
|
|
|
+ expect(serializeUserSecurely).not.toHaveBeenCalledOnce();
|
|
|
expect(nextMock).toHaveBeenCalled();
|
|
expect(nextMock).toHaveBeenCalled();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should authenticate with specific scope', async() => {
|
|
|
|
|
|
|
+ it('should authenticate from api-token', async() => {
|
|
|
// arrange
|
|
// arrange
|
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
|
user: undefined,
|
|
user: undefined,
|
|
@@ -205,18 +104,18 @@ describe('access-token-parser middleware for access token with scopes', () => {
|
|
|
username: faker.string.uuid(),
|
|
username: faker.string.uuid(),
|
|
|
password: faker.internet.password(),
|
|
password: faker.internet.password(),
|
|
|
lang: 'en_US',
|
|
lang: 'en_US',
|
|
|
|
|
+ apiToken: faker.internet.password(),
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // generate token with read:user:info scope
|
|
|
|
|
- const { token } = await AccessToken.generateToken(
|
|
|
|
|
|
|
+ // generate token with write:user:info scope
|
|
|
|
|
+ await AccessToken.generateToken(
|
|
|
targetUser._id,
|
|
targetUser._id,
|
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
|
[SCOPE.READ.USER.INFO],
|
|
[SCOPE.READ.USER.INFO],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // act
|
|
|
|
|
- reqMock.query.access_token = token;
|
|
|
|
|
- await accessTokenParser()(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
+ reqMock.query.access_token = targetUser.apiToken;
|
|
|
|
|
+ await accessTokenParser([SCOPE.WRITE.USER.INFO])(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
|
// assert
|
|
// assert
|
|
|
expect(reqMock.user).toBeDefined();
|
|
expect(reqMock.user).toBeDefined();
|
|
@@ -225,43 +124,7 @@ describe('access-token-parser middleware for access token with scopes', () => {
|
|
|
expect(nextMock).toHaveBeenCalled();
|
|
expect(nextMock).toHaveBeenCalled();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should reject with insufficient scopes', async() => {
|
|
|
|
|
- // arrange
|
|
|
|
|
- const reqMock = mock<AccessTokenParserReq>({
|
|
|
|
|
- user: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- const resMock = mock<Response>();
|
|
|
|
|
- const nextMock = vi.fn();
|
|
|
|
|
-
|
|
|
|
|
- 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.INFO],
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // act - try to access with write:user:info scope
|
|
|
|
|
- reqMock.query.access_token = token;
|
|
|
|
|
- await accessTokenParser([SCOPE.WRITE.USER.INFO])(reqMock, resMock, nextMock);
|
|
|
|
|
-
|
|
|
|
|
- // // assert
|
|
|
|
|
- expect(reqMock.user).toBeUndefined();
|
|
|
|
|
- expect(serializeUserSecurely).not.toHaveBeenCalled();
|
|
|
|
|
- expect(nextMock).toHaveBeenCalled();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- it('should authenticate with write scope implying read scope', async() => {
|
|
|
|
|
|
|
+ it('should authenticate from access-token', async() => {
|
|
|
// arrange
|
|
// arrange
|
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
const reqMock = mock<AccessTokenParserReq>({
|
|
|
user: undefined,
|
|
user: undefined,
|
|
@@ -277,16 +140,16 @@ describe('access-token-parser middleware for access token with scopes', () => {
|
|
|
username: faker.string.uuid(),
|
|
username: faker.string.uuid(),
|
|
|
password: faker.internet.password(),
|
|
password: faker.internet.password(),
|
|
|
lang: 'en_US',
|
|
lang: 'en_US',
|
|
|
|
|
+ apiToken: faker.internet.password(),
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// generate token with write:user:info scope
|
|
// generate token with write:user:info scope
|
|
|
const { token } = await AccessToken.generateToken(
|
|
const { token } = await AccessToken.generateToken(
|
|
|
targetUser._id,
|
|
targetUser._id,
|
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
new Date(Date.now() + 1000 * 60 * 60 * 24),
|
|
|
- [SCOPE.WRITE.USER.INFO],
|
|
|
|
|
|
|
+ [SCOPE.READ.USER.INFO],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- // act - try to access with read:user:info scope
|
|
|
|
|
reqMock.query.access_token = token;
|
|
reqMock.query.access_token = token;
|
|
|
await accessTokenParser([SCOPE.READ.USER.INFO])(reqMock, resMock, nextMock);
|
|
await accessTokenParser([SCOPE.READ.USER.INFO])(reqMock, resMock, nextMock);
|
|
|
|
|
|
|
@@ -296,39 +159,4 @@ describe('access-token-parser middleware for access token with scopes', () => {
|
|
|
expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
|
expect(nextMock).toHaveBeenCalled();
|
|
expect(nextMock).toHaveBeenCalled();
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- it('should authenticate with wildcard scope', async() => {
|
|
|
|
|
- // arrange
|
|
|
|
|
- const reqMock = mock<AccessTokenParserReq>({
|
|
|
|
|
- user: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- const resMock = mock<Response>();
|
|
|
|
|
- const nextMock = vi.fn();
|
|
|
|
|
-
|
|
|
|
|
- // 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.ALL],
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // act - try to access with read:user:info scope
|
|
|
|
|
- reqMock.query.access_token = token;
|
|
|
|
|
- await accessTokenParser([SCOPE.READ.USER.INFO, SCOPE.READ.USER.API.ACCESS_TOKEN])(reqMock, resMock, nextMock);
|
|
|
|
|
-
|
|
|
|
|
- // assert
|
|
|
|
|
- expect(reqMock.user).toBeDefined();
|
|
|
|
|
- expect(reqMock.user?._id).toStrictEqual(targetUser._id);
|
|
|
|
|
- expect(serializeUserSecurely).toHaveBeenCalledOnce();
|
|
|
|
|
- expect(nextMock).toHaveBeenCalled();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
});
|
|
});
|