mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(core): Extract all Auth-related User columns into a separate entity (#9557)
Co-authored-by: Ricardo Espinoza <ricardo@n8n.io>
This commit is contained in:
committed by
GitHub
parent
08902bf941
commit
5887ed6498
@@ -3,7 +3,7 @@ import Container from 'typedi';
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import config from '@/config';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { AuthUserRepository } from '@db/repositories/authUser.repository';
|
||||
import { randomPassword } from '@/Ldap/helpers';
|
||||
import { TOTPService } from '@/Mfa/totp.service';
|
||||
|
||||
@@ -35,24 +35,22 @@ afterAll(async () => {
|
||||
describe('Enable MFA setup', () => {
|
||||
describe('Step one', () => {
|
||||
test('GET /qr should fail due to unauthenticated user', async () => {
|
||||
const response = await testServer.authlessAgent.get('/mfa/qr');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
await testServer.authlessAgent.get('/mfa/qr').expect(401);
|
||||
});
|
||||
|
||||
test('GET /qr should reuse secret and recovery codes until setup is complete', async () => {
|
||||
const firstCall = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
const firstCall = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
const secondCall = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
const secondCall = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
expect(firstCall.body.data.secret).toBe(secondCall.body.data.secret);
|
||||
expect(firstCall.body.data.recoveryCodes.join('')).toBe(
|
||||
secondCall.body.data.recoveryCodes.join(''),
|
||||
);
|
||||
|
||||
await testServer.authAgentFor(owner).delete('/mfa/disable');
|
||||
await testServer.authAgentFor(owner).delete('/mfa/disable').expect(200);
|
||||
|
||||
const thirdCall = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
const thirdCall = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
expect(firstCall.body.data.secret).not.toBe(thirdCall.body.data.secret);
|
||||
expect(firstCall.body.data.recoveryCodes.join('')).not.toBe(
|
||||
@@ -61,9 +59,7 @@ describe('Enable MFA setup', () => {
|
||||
});
|
||||
|
||||
test('GET /qr should return qr, secret and recovery codes', async () => {
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
const { data } = response.body;
|
||||
|
||||
@@ -77,93 +73,57 @@ describe('Enable MFA setup', () => {
|
||||
|
||||
describe('Step two', () => {
|
||||
test('POST /verify should fail due to unauthenticated user', async () => {
|
||||
const response = await testServer.authlessAgent.post('/mfa/verify');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
await testServer.authlessAgent.post('/mfa/verify').expect(401);
|
||||
});
|
||||
|
||||
test('POST /verify should fail due to invalid MFA token', async () => {
|
||||
const response = await testServer
|
||||
.authAgentFor(owner)
|
||||
.post('/mfa/verify')
|
||||
.send({ token: '123' });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token: '123' }).expect(400);
|
||||
});
|
||||
|
||||
test('POST /verify should fail due to missing token parameter', async () => {
|
||||
await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
|
||||
const response = await testServer.authAgentFor(owner).post('/mfa/verify').send({ token: '' });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token: '' }).expect(400);
|
||||
});
|
||||
|
||||
test('POST /verify should validate MFA token', async () => {
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
const { secret } = response.body.data;
|
||||
|
||||
const token = new TOTPService().generateTOTP(secret);
|
||||
|
||||
const { statusCode } = await testServer
|
||||
.authAgentFor(owner)
|
||||
.post('/mfa/verify')
|
||||
.send({ token });
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token }).expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Step three', () => {
|
||||
test('POST /enable should fail due to unauthenticated user', async () => {
|
||||
const response = await testServer.authlessAgent.post('/mfa/enable');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
await testServer.authlessAgent.post('/mfa/enable').expect(401);
|
||||
});
|
||||
|
||||
test('POST /verify should fail due to missing token parameter', async () => {
|
||||
const response = await testServer.authAgentFor(owner).post('/mfa/verify').send({ token: '' });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token: '' }).expect(400);
|
||||
});
|
||||
|
||||
test('POST /enable should fail due to invalid MFA token', async () => {
|
||||
await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
|
||||
const response = await testServer
|
||||
.authAgentFor(owner)
|
||||
.post('/mfa/enable')
|
||||
.send({ token: '123' });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
await testServer.authAgentFor(owner).post('/mfa/enable').send({ token: '123' }).expect(400);
|
||||
});
|
||||
|
||||
test('POST /enable should fail due to empty secret and recovery codes', async () => {
|
||||
const response = await testServer.authAgentFor(owner).post('/mfa/enable');
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
await testServer.authAgentFor(owner).post('/mfa/enable').expect(400);
|
||||
});
|
||||
|
||||
test('POST /enable should enable MFA in account', async () => {
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr');
|
||||
const response = await testServer.authAgentFor(owner).get('/mfa/qr').expect(200);
|
||||
|
||||
const { secret } = response.body.data;
|
||||
|
||||
const token = new TOTPService().generateTOTP(secret);
|
||||
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token });
|
||||
await testServer.authAgentFor(owner).post('/mfa/verify').send({ token }).expect(200);
|
||||
await testServer.authAgentFor(owner).post('/mfa/enable').send({ token }).expect(200);
|
||||
|
||||
const { statusCode } = await testServer
|
||||
.authAgentFor(owner)
|
||||
.post('/mfa/enable')
|
||||
.send({ token });
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
|
||||
const user = await Container.get(UserRepository).findOneOrFail({
|
||||
const user = await Container.get(AuthUserRepository).findOneOrFail({
|
||||
where: {},
|
||||
select: ['mfaEnabled', 'mfaRecoveryCodes', 'mfaSecret'],
|
||||
});
|
||||
|
||||
expect(user.mfaEnabled).toBe(true);
|
||||
@@ -177,13 +137,10 @@ describe('Disable MFA setup', () => {
|
||||
test('POST /disable should disable login with MFA', async () => {
|
||||
const { user } = await createUserWithMfaEnabled();
|
||||
|
||||
const response = await testServer.authAgentFor(user).delete('/mfa/disable');
|
||||
await testServer.authAgentFor(user).delete('/mfa/disable').expect(200);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const dbUser = await Container.get(UserRepository).findOneOrFail({
|
||||
const dbUser = await Container.get(AuthUserRepository).findOneOrFail({
|
||||
where: { id: user.id },
|
||||
select: ['mfaEnabled', 'mfaRecoveryCodes', 'mfaSecret'],
|
||||
});
|
||||
|
||||
expect(dbUser.mfaEnabled).toBe(false);
|
||||
@@ -198,42 +155,39 @@ describe('Change password with MFA enabled', () => {
|
||||
|
||||
const newPassword = randomPassword();
|
||||
|
||||
const response = await testServer
|
||||
await testServer
|
||||
.authAgentFor(user)
|
||||
.patch('/me/password')
|
||||
.send({ currentPassword: rawPassword, newPassword });
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
.send({ currentPassword: rawPassword, newPassword })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('POST /change-password should fail due to missing MFA token', async () => {
|
||||
await createUserWithMfaEnabled();
|
||||
|
||||
const newPassword = randomValidPassword();
|
||||
|
||||
const resetPasswordToken = uniqueId();
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
await testServer.authlessAgent
|
||||
.post('/change-password')
|
||||
.send({ password: newPassword, token: resetPasswordToken });
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
.send({ password: newPassword, token: resetPasswordToken })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
test('POST /change-password should fail due to invalid MFA token', async () => {
|
||||
await createUserWithMfaEnabled();
|
||||
|
||||
const newPassword = randomValidPassword();
|
||||
|
||||
const resetPasswordToken = uniqueId();
|
||||
|
||||
const response = await testServer.authlessAgent.post('/change-password').send({
|
||||
password: newPassword,
|
||||
token: resetPasswordToken,
|
||||
mfaToken: randomDigit(),
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
await testServer.authlessAgent
|
||||
.post('/change-password')
|
||||
.send({
|
||||
password: newPassword,
|
||||
token: resetPasswordToken,
|
||||
mfaToken: randomDigit(),
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
test('POST /change-password should update password', async () => {
|
||||
@@ -247,13 +201,14 @@ describe('Change password with MFA enabled', () => {
|
||||
|
||||
const mfaToken = new TOTPService().generateTOTP(rawSecret);
|
||||
|
||||
const response = await testServer.authlessAgent.post('/change-password').send({
|
||||
password: newPassword,
|
||||
token: resetPasswordToken,
|
||||
mfaToken,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
await testServer.authlessAgent
|
||||
.post('/change-password')
|
||||
.send({
|
||||
password: newPassword,
|
||||
token: resetPasswordToken,
|
||||
mfaToken,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const loginResponse = await testServer
|
||||
.authAgentFor(user)
|
||||
@@ -262,9 +217,9 @@ describe('Change password with MFA enabled', () => {
|
||||
email: user.email,
|
||||
password: newPassword,
|
||||
mfaToken: new TOTPService().generateTOTP(rawSecret),
|
||||
});
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(loginResponse.statusCode).toBe(200);
|
||||
expect(loginResponse.body).toHaveProperty('data');
|
||||
});
|
||||
});
|
||||
@@ -275,30 +230,14 @@ describe('Login', () => {
|
||||
|
||||
const user = await createUser({ password });
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password });
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('GET /login should include hasRecoveryCodesLeft property in response', async () => {
|
||||
const response = await testServer.authAgentFor(owner).get('/login');
|
||||
|
||||
const { data } = response.body;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
expect(data.hasRecoveryCodesLeft).toBeDefined();
|
||||
await testServer.authlessAgent.post('/login').send({ email: user.email, password }).expect(200);
|
||||
});
|
||||
|
||||
test('GET /login should not include mfaSecret and mfaRecoveryCodes property in response', async () => {
|
||||
const response = await testServer.authAgentFor(owner).get('/login');
|
||||
const response = await testServer.authAgentFor(owner).get('/login').expect(200);
|
||||
|
||||
const { data } = response.body;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
expect(data.recoveryCodes).not.toBeDefined();
|
||||
expect(data.mfaSecret).not.toBeDefined();
|
||||
});
|
||||
@@ -306,22 +245,20 @@ describe('Login', () => {
|
||||
test('POST /login with email/password should fail when mfa is enabled', async () => {
|
||||
const { user, rawPassword } = await createUserWithMfaEnabled();
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword });
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
.send({ email: user.email, password: rawPassword })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
describe('Login with MFA token', () => {
|
||||
test('POST /login should fail due to invalid MFA token', async () => {
|
||||
const { user, rawPassword } = await createUserWithMfaEnabled();
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword, mfaToken: 'wrongvalue' });
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
.send({ email: user.email, password: rawPassword, mfaToken: 'wrongvalue' })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
test('POST /login should fail due two MFA step needed', async () => {
|
||||
@@ -329,9 +266,9 @@ describe('Login', () => {
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword });
|
||||
.send({ email: user.email, password: rawPassword })
|
||||
.expect(401);
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
expect(response.body.code).toBe(998);
|
||||
});
|
||||
|
||||
@@ -342,11 +279,11 @@ describe('Login', () => {
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword, mfaToken: token });
|
||||
.send({ email: user.email, password: rawPassword, mfaToken: token })
|
||||
.expect(200);
|
||||
|
||||
const data = response.body.data;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(data.mfaEnabled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -355,11 +292,10 @@ describe('Login', () => {
|
||||
test('POST /login should fail due to invalid MFA recovery code', async () => {
|
||||
const { user, rawPassword } = await createUserWithMfaEnabled();
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword, mfaRecoveryCode: 'wrongvalue' });
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
.send({ email: user.email, password: rawPassword, mfaRecoveryCode: 'wrongvalue' })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
test('POST /login should succeed with MFA recovery code', async () => {
|
||||
@@ -367,38 +303,19 @@ describe('Login', () => {
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword, mfaRecoveryCode: rawRecoveryCodes[0] });
|
||||
.send({ email: user.email, password: rawPassword, mfaRecoveryCode: rawRecoveryCodes[0] })
|
||||
.expect(200);
|
||||
|
||||
const data = response.body.data;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(data.mfaEnabled).toBe(true);
|
||||
expect(data.hasRecoveryCodesLeft).toBe(true);
|
||||
|
||||
const dbUser = await Container.get(UserRepository).findOneOrFail({
|
||||
const dbUser = await Container.get(AuthUserRepository).findOneOrFail({
|
||||
where: { id: user.id },
|
||||
select: ['mfaEnabled', 'mfaRecoveryCodes', 'mfaSecret'],
|
||||
});
|
||||
|
||||
// Make sure the recovery code used was removed
|
||||
expect(dbUser.mfaRecoveryCodes.length).toBe(rawRecoveryCodes.length - 1);
|
||||
expect(dbUser.mfaRecoveryCodes.includes(rawRecoveryCodes[0])).toBe(false);
|
||||
});
|
||||
|
||||
test('POST /login with MFA recovery code should update hasRecoveryCodesLeft property', async () => {
|
||||
const { user, rawPassword, rawRecoveryCodes } = await createUserWithMfaEnabled({
|
||||
numberOfRecoveryCodes: 1,
|
||||
});
|
||||
|
||||
const response = await testServer.authlessAgent
|
||||
.post('/login')
|
||||
.send({ email: user.email, password: rawPassword, mfaRecoveryCode: rawRecoveryCodes[0] });
|
||||
|
||||
const data = response.body.data;
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(data.mfaEnabled).toBe(true);
|
||||
expect(data.hasRecoveryCodesLeft).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user