Skip to content

Commit 8738f6e

Browse files
authored
feat(rest-express): create mfa endpoints (#1047)
1 parent 3e930bc commit 8738f6e

File tree

10 files changed

+468
-1
lines changed

10 files changed

+468
-1
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { associate, associateByMfaToken } from '../../../src/endpoints/mfa/associate';
2+
3+
const res: any = {
4+
json: jest.fn(),
5+
status: jest.fn(() => res),
6+
};
7+
8+
const mfaService = {
9+
associate: jest.fn(() => ({ mfaService: 'mfaServiceTest' })),
10+
associateByMfaToken: jest.fn(() => ({ mfaService: 'mfaServiceTest' })),
11+
};
12+
13+
describe('associate', () => {
14+
beforeEach(() => {
15+
jest.clearAllMocks();
16+
});
17+
18+
it('should return an error if user is not logged in', async () => {
19+
const middleware = associate({} as any);
20+
await middleware({} as any, res);
21+
expect(res.status).toBeCalledWith(401);
22+
expect(res.json).toBeCalledWith({ message: 'Unauthorized' });
23+
});
24+
25+
it('should call mfa associate method', async () => {
26+
const req = {
27+
userId: 'userIdTest',
28+
body: {
29+
type: 'typeBody',
30+
params: 'paramsBody',
31+
},
32+
infos: {
33+
ip: 'ipTest',
34+
userAgent: 'userAgentTest',
35+
},
36+
};
37+
const middleware = associate({
38+
getService: () => mfaService,
39+
} as any);
40+
await middleware(req as any, res);
41+
expect(mfaService.associate).toBeCalledWith(
42+
'userIdTest',
43+
req.body.type,
44+
req.body.params,
45+
req.infos
46+
);
47+
expect(res.json).toBeCalledWith({ mfaService: 'mfaServiceTest' });
48+
});
49+
50+
it('Sends error if it was thrown', async () => {
51+
const error = { message: 'Error' };
52+
mfaService.associate.mockImplementationOnce(() => {
53+
throw error;
54+
});
55+
const req = {
56+
userId: 'userIdTest',
57+
body: {
58+
type: 'typeBody',
59+
params: 'paramsBody',
60+
},
61+
infos: {
62+
ip: 'ipTest',
63+
userAgent: 'userAgentTest',
64+
},
65+
};
66+
const middleware = associate({
67+
getService: () => mfaService,
68+
} as any);
69+
await middleware(req as any, res);
70+
expect(mfaService.associate).toBeCalledWith(
71+
'userIdTest',
72+
req.body.type,
73+
req.body.params,
74+
req.infos
75+
);
76+
expect(res.status).toHaveBeenCalledWith(400);
77+
expect(res.json).toHaveBeenCalledWith(error);
78+
});
79+
});
80+
81+
describe('associateByMfaToken', () => {
82+
beforeEach(() => {
83+
jest.clearAllMocks();
84+
});
85+
86+
it('should call mfa associateByMfaToken method', async () => {
87+
const req = {
88+
body: {
89+
mfaToken: 'mfaTokenBody',
90+
type: 'typeBody',
91+
params: 'paramsBody',
92+
},
93+
infos: {
94+
ip: 'ipTest',
95+
userAgent: 'userAgentTest',
96+
},
97+
};
98+
const middleware = associateByMfaToken({
99+
getService: () => mfaService,
100+
} as any);
101+
await middleware(req as any, res);
102+
expect(mfaService.associateByMfaToken).toBeCalledWith(
103+
req.body.mfaToken,
104+
req.body.type,
105+
req.body.params,
106+
req.infos
107+
);
108+
expect(res.json).toBeCalledWith({ mfaService: 'mfaServiceTest' });
109+
});
110+
111+
it('Sends error if it was thrown', async () => {
112+
const error = { message: 'Error' };
113+
mfaService.associateByMfaToken.mockImplementationOnce(() => {
114+
throw error;
115+
});
116+
const req = {
117+
body: {
118+
mfaToken: 'mfaTokenBody',
119+
type: 'typeBody',
120+
params: 'paramsBody',
121+
},
122+
infos: {
123+
ip: 'ipTest',
124+
userAgent: 'userAgentTest',
125+
},
126+
};
127+
const middleware = associateByMfaToken({
128+
getService: () => mfaService,
129+
} as any);
130+
await middleware(req as any, res);
131+
expect(mfaService.associateByMfaToken).toBeCalledWith(
132+
req.body.mfaToken,
133+
req.body.type,
134+
req.body.params,
135+
req.infos
136+
);
137+
expect(res.status).toHaveBeenCalledWith(400);
138+
expect(res.json).toHaveBeenCalledWith(error);
139+
});
140+
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {
2+
authenticators,
3+
authenticatorsByMfaToken,
4+
} from '../../../src/endpoints/mfa/authenticators';
5+
6+
const res: any = {
7+
json: jest.fn(),
8+
status: jest.fn(() => res),
9+
};
10+
11+
const mfaService = {
12+
findUserAuthenticators: jest.fn(() => ({ mfaService: 'mfaServiceTest' })),
13+
findUserAuthenticatorsByMfaToken: jest.fn(() => ({ mfaService: 'mfaServiceTest' })),
14+
};
15+
16+
describe('authenticators', () => {
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
});
20+
21+
it('should return an error if user is not logged in', async () => {
22+
const middleware = authenticators({} as any);
23+
await middleware({} as any, res);
24+
expect(res.status).toBeCalledWith(401);
25+
expect(res.json).toBeCalledWith({ message: 'Unauthorized' });
26+
});
27+
28+
it('should call mfa findUserAuthenticators method', async () => {
29+
const req = {
30+
userId: 'userIdTest',
31+
infos: {
32+
ip: 'ipTest',
33+
userAgent: 'userAgentTest',
34+
},
35+
};
36+
const middleware = authenticators({
37+
getService: () => mfaService,
38+
} as any);
39+
await middleware(req as any, res);
40+
expect(mfaService.findUserAuthenticators).toBeCalledWith('userIdTest');
41+
expect(res.json).toBeCalledWith({ mfaService: 'mfaServiceTest' });
42+
});
43+
44+
it('Sends error if it was thrown', async () => {
45+
const error = { message: 'Error' };
46+
mfaService.findUserAuthenticators.mockImplementationOnce(() => {
47+
throw error;
48+
});
49+
const req = {
50+
userId: 'userIdTest',
51+
infos: {
52+
ip: 'ipTest',
53+
userAgent: 'userAgentTest',
54+
},
55+
};
56+
const middleware = authenticators({
57+
getService: () => mfaService,
58+
} as any);
59+
await middleware(req as any, res);
60+
expect(mfaService.findUserAuthenticators).toBeCalledWith('userIdTest');
61+
expect(res.status).toHaveBeenCalledWith(400);
62+
expect(res.json).toHaveBeenCalledWith(error);
63+
});
64+
});
65+
66+
describe('authenticatorsByMfaToken', () => {
67+
beforeEach(() => {
68+
jest.clearAllMocks();
69+
});
70+
71+
it('should call mfa findUserAuthenticatorsByMfaToken method', async () => {
72+
const req = {
73+
query: {
74+
mfaToken: 'mfaTokenBody',
75+
},
76+
infos: {
77+
ip: 'ipTest',
78+
userAgent: 'userAgentTest',
79+
},
80+
};
81+
const middleware = authenticatorsByMfaToken({
82+
getService: () => mfaService,
83+
} as any);
84+
await middleware(req as any, res);
85+
expect(mfaService.findUserAuthenticatorsByMfaToken).toBeCalledWith(req.query.mfaToken);
86+
expect(res.json).toBeCalledWith({ mfaService: 'mfaServiceTest' });
87+
});
88+
89+
it('Sends error if it was thrown', async () => {
90+
const error = { message: 'Error' };
91+
mfaService.findUserAuthenticatorsByMfaToken.mockImplementationOnce(() => {
92+
throw error;
93+
});
94+
const req = {
95+
query: {
96+
mfaToken: 'mfaTokenBody',
97+
},
98+
infos: {
99+
ip: 'ipTest',
100+
userAgent: 'userAgentTest',
101+
},
102+
};
103+
const middleware = authenticatorsByMfaToken({
104+
getService: () => mfaService,
105+
} as any);
106+
await middleware(req as any, res);
107+
expect(mfaService.findUserAuthenticatorsByMfaToken).toBeCalledWith(req.query.mfaToken);
108+
expect(res.status).toHaveBeenCalledWith(400);
109+
expect(res.json).toHaveBeenCalledWith(error);
110+
});
111+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { challenge } from '../../../src/endpoints/mfa/challenge';
2+
3+
const res: any = {
4+
json: jest.fn(),
5+
status: jest.fn(() => res),
6+
};
7+
8+
const mfaService = {
9+
challenge: jest.fn(() => ({ mfaService: 'mfaServiceTest' })),
10+
};
11+
12+
describe('challenge', () => {
13+
beforeEach(() => {
14+
jest.clearAllMocks();
15+
});
16+
17+
it('should call mfa associateByMfaToken method', async () => {
18+
const req = {
19+
body: {
20+
mfaToken: 'mfaTokenBody',
21+
authenticatorId: 'authenticatorIdBody',
22+
},
23+
infos: {
24+
ip: 'ipTest',
25+
userAgent: 'userAgentTest',
26+
},
27+
};
28+
const middleware = challenge({
29+
getService: () => mfaService,
30+
} as any);
31+
await middleware(req as any, res);
32+
expect(mfaService.challenge).toBeCalledWith(
33+
req.body.mfaToken,
34+
req.body.authenticatorId,
35+
req.infos
36+
);
37+
expect(res.json).toBeCalledWith({ mfaService: 'mfaServiceTest' });
38+
});
39+
40+
it('Sends error if it was thrown', async () => {
41+
const error = { message: 'Error' };
42+
mfaService.challenge.mockImplementationOnce(() => {
43+
throw error;
44+
});
45+
const req = {
46+
body: {
47+
mfaToken: 'mfaTokenBody',
48+
authenticatorId: 'authenticatorIdBody',
49+
},
50+
infos: {
51+
ip: 'ipTest',
52+
userAgent: 'userAgentTest',
53+
},
54+
};
55+
const middleware = challenge({
56+
getService: () => mfaService,
57+
} as any);
58+
await middleware(req as any, res);
59+
expect(mfaService.challenge).toBeCalledWith(
60+
req.body.mfaToken,
61+
req.body.authenticatorId,
62+
req.infos
63+
);
64+
expect(res.status).toHaveBeenCalledWith(400);
65+
expect(res.json).toHaveBeenCalledWith(error);
66+
});
67+
});

packages/rest-express/__tests__/express-middleware.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ describe('express middleware', () => {
3636
expect((router.get as jest.Mock).mock.calls[0][0]).toBe('test/user');
3737
});
3838

39+
it('Defines password endpoints when mfa service is present', () => {
40+
accountsExpress(
41+
{
42+
getServices: () => ({
43+
mfa: {},
44+
}),
45+
} as any,
46+
{ path: 'test' }
47+
);
48+
expect((router.post as jest.Mock).mock.calls[0][0]).toBe('test/impersonate');
49+
expect((router.post as jest.Mock).mock.calls[1][0]).toBe('test/user');
50+
expect((router.post as jest.Mock).mock.calls[2][0]).toBe('test/refreshTokens');
51+
expect((router.post as jest.Mock).mock.calls[3][0]).toBe('test/logout');
52+
expect((router.post as jest.Mock).mock.calls[4][0]).toBe('test/:service/verifyAuthentication');
53+
expect((router.post as jest.Mock).mock.calls[5][0]).toBe('test/:service/authenticate');
54+
55+
expect((router.post as jest.Mock).mock.calls[6][0]).toBe('test/mfa/associate');
56+
expect((router.post as jest.Mock).mock.calls[7][0]).toBe('test/mfa/associateByMfaToken');
57+
expect((router.post as jest.Mock).mock.calls[8][0]).toBe('test/mfa/challenge');
58+
59+
expect((router.get as jest.Mock).mock.calls[0][0]).toBe('test/user');
60+
expect((router.get as jest.Mock).mock.calls[1][0]).toBe('test/mfa/authenticators');
61+
expect((router.get as jest.Mock).mock.calls[2][0]).toBe('test/mfa/authenticatorsByMfaToken');
62+
});
63+
3964
it('Defines password endpoints when password service is present', () => {
4065
accountsExpress(
4166
{
@@ -51,6 +76,7 @@ describe('express middleware', () => {
5176
expect((router.post as jest.Mock).mock.calls[3][0]).toBe('test/logout');
5277
expect((router.post as jest.Mock).mock.calls[4][0]).toBe('test/:service/verifyAuthentication');
5378
expect((router.post as jest.Mock).mock.calls[5][0]).toBe('test/:service/authenticate');
79+
5480
expect((router.post as jest.Mock).mock.calls[6][0]).toBe('test/password/register');
5581
expect((router.post as jest.Mock).mock.calls[7][0]).toBe('test/password/verifyEmail');
5682
expect((router.post as jest.Mock).mock.calls[8][0]).toBe('test/password/resetPassword');

packages/rest-express/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"author": "Tim Mikeladze",
4040
"license": "MIT",
4141
"devDependencies": {
42+
"@accounts/mfa": "^0.29.0",
4243
"@accounts/password": "^0.29.0",
4344
"@accounts/server": "^0.29.0",
4445
"@types/express": "4.17.7",

0 commit comments

Comments
 (0)