feat: create reset password backend service and route
This commit is contained in:
parent
5f32cb23fa
commit
a4571bc27c
4 changed files with 141 additions and 1 deletions
|
@ -142,3 +142,71 @@ describe('Test: disableTotp', () => {
|
|||
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: changeOperatorPassword', () => {
|
||||
it('should be accessible without an account', async () => {
|
||||
// arrange
|
||||
const caller = authRouter.createCaller({ session: null });
|
||||
let error;
|
||||
|
||||
// act
|
||||
try {
|
||||
await caller.changeOperatorPassword({ newPassword: '222' });
|
||||
} catch (e) {
|
||||
error = e as { code: string };
|
||||
}
|
||||
|
||||
// assert
|
||||
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||
});
|
||||
|
||||
it('should be accessible with an account', async () => {
|
||||
// arrange
|
||||
const caller = authRouter.createCaller({ session: { userId: 122 } });
|
||||
let error;
|
||||
|
||||
// act
|
||||
try {
|
||||
await caller.changeOperatorPassword({ newPassword: '222' });
|
||||
} catch (e) {
|
||||
error = e as { code: string };
|
||||
}
|
||||
|
||||
// assert
|
||||
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: resetPassword', () => {
|
||||
it('should not be accessible without an account', async () => {
|
||||
// arrange
|
||||
const caller = authRouter.createCaller({ session: null });
|
||||
let error;
|
||||
|
||||
// act
|
||||
try {
|
||||
await caller.changePassword({ currentPassword: '111', newPassword: '222' });
|
||||
} catch (e) {
|
||||
error = e as { code: string };
|
||||
}
|
||||
|
||||
// assert
|
||||
expect(error?.code).toBe('UNAUTHORIZED');
|
||||
});
|
||||
|
||||
it('should be accessible with an account', async () => {
|
||||
// arrange
|
||||
const caller = authRouter.createCaller({ session: { userId: 122 } });
|
||||
let error;
|
||||
|
||||
// act
|
||||
try {
|
||||
await caller.changePassword({ currentPassword: '111', newPassword: '222' });
|
||||
} catch (e) {
|
||||
error = e as { code: string };
|
||||
}
|
||||
|
||||
// assert
|
||||
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,11 @@ export const authRouter = router({
|
|||
isConfigured: publicProcedure.query(async () => AuthService.isConfigured()),
|
||||
// Password
|
||||
checkPasswordChangeRequest: publicProcedure.query(AuthServiceClass.checkPasswordChangeRequest),
|
||||
resetPassword: publicProcedure.input(z.object({ newPassword: z.string() })).mutation(({ input }) => AuthService.changeOperatorPassword({ newPassword: input.newPassword })),
|
||||
changeOperatorPassword: publicProcedure.input(z.object({ newPassword: z.string() })).mutation(({ input }) => AuthService.changeOperatorPassword({ newPassword: input.newPassword })),
|
||||
cancelPasswordChangeRequest: publicProcedure.mutation(AuthServiceClass.cancelPasswordChangeRequest),
|
||||
changePassword: protectedProcedure
|
||||
.input(z.object({ currentPassword: z.string(), newPassword: z.string() }))
|
||||
.mutation(({ input, ctx }) => AuthService.changePassword({ userId: Number(ctx.session.userId), ...input })),
|
||||
// Totp
|
||||
verifyTotp: publicProcedure.input(z.object({ totpSessionId: z.string(), totpCode: z.string() })).mutation(({ input }) => AuthService.verifyTotp(input)),
|
||||
getTotpUri: protectedProcedure.input(z.object({ password: z.string() })).mutation(({ input, ctx }) => AuthService.getTotpUri({ userId: Number(ctx.session.userId), password: input.password })),
|
||||
|
|
|
@ -640,3 +640,47 @@ describe('Test: cancelPasswordChangeRequest', () => {
|
|||
expect(fs.existsSync('/runtipi/state/password-change-request')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: changePassword', () => {
|
||||
it('should change the password of the user', async () => {
|
||||
// arrange
|
||||
const email = faker.internet.email();
|
||||
const user = await createUser({ email }, db);
|
||||
const newPassword = faker.internet.password();
|
||||
|
||||
// act
|
||||
await AuthService.changePassword({ userId: user.id, newPassword, currentPassword: 'password' });
|
||||
|
||||
// assert
|
||||
const updatedUser = await db.user.findUnique({ where: { id: user.id } });
|
||||
expect(updatedUser?.password).not.toBe(user.password);
|
||||
});
|
||||
|
||||
it('should throw if the user does not exist', async () => {
|
||||
// arrange
|
||||
const newPassword = faker.internet.password();
|
||||
|
||||
// act & assert
|
||||
await expect(AuthService.changePassword({ userId: 1, newPassword, currentPassword: 'password' })).rejects.toThrowError('User not found');
|
||||
});
|
||||
|
||||
it('should throw if the password is incorrect', async () => {
|
||||
// arrange
|
||||
const email = faker.internet.email();
|
||||
const user = await createUser({ email }, db);
|
||||
const newPassword = faker.internet.password();
|
||||
|
||||
// act & assert
|
||||
await expect(AuthService.changePassword({ userId: user.id, newPassword, currentPassword: 'wrongpassword' })).rejects.toThrowError('Current password is invalid');
|
||||
});
|
||||
|
||||
it('should throw if password is less than 8 characters', async () => {
|
||||
// arrange
|
||||
const email = faker.internet.email();
|
||||
const user = await createUser({ email }, db);
|
||||
const newPassword = faker.internet.password(7);
|
||||
|
||||
// act & assert
|
||||
await expect(AuthService.changePassword({ userId: user.id, newPassword, currentPassword: 'password' })).rejects.toThrowError('Password must be at least 8 characters');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -368,4 +368,29 @@ export class AuthServiceClass {
|
|||
|
||||
return true;
|
||||
};
|
||||
|
||||
public changePassword = async (params: { currentPassword: string; newPassword: string; userId: number }) => {
|
||||
const { currentPassword, newPassword, userId } = params;
|
||||
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const valid = await argon2.verify(user.password, currentPassword);
|
||||
|
||||
if (!valid) {
|
||||
throw new Error('Current password is invalid');
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
throw new Error('Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
const hash = await argon2.hash(newPassword);
|
||||
await this.prisma.user.update({ where: { id: user.id }, data: { password: hash, totp_enabled: false, totp_secret: null } });
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue