refactor: reset admin password (#1335)

* refactor: reset-admin-password

* chore: docs
This commit is contained in:
Jason Rasmussen
2023-01-16 13:09:04 -05:00
committed by GitHub
parent 5a6a726014
commit 1e2f02613f
8 changed files with 100 additions and 40 deletions

View File

@@ -1,37 +1,38 @@
import { Inject } from '@nestjs/common';
import { UserResponseDto, UserService } from '@app/domain';
import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
import { randomBytes } from 'node:crypto';
import { IUserRepository, UserCore } from '@app/domain';
@Command({
name: 'reset-admin-password',
description: 'Reset the admin password',
})
export class ResetAdminPasswordCommand extends CommandRunner {
userCore: UserCore;
constructor(private readonly inquirer: InquirerService, @Inject(IUserRepository) userRepository: IUserRepository) {
constructor(private userService: UserService, private readonly inquirer: InquirerService) {
super();
this.userCore = new UserCore(userRepository);
}
async run(): Promise<void> {
const user = await this.userCore.getAdmin();
if (!user) {
console.log('Unable to reset password: no admin user.');
return;
}
const ask = (admin: UserResponseDto) => {
const { id, oauthId, email, firstName, lastName } = admin;
console.log(`Found Admin:
- ID=${id}
- OAuth ID=${oauthId}
- Email=${email}
- Name=${firstName} ${lastName}`);
const { password: providedPassword } = await this.inquirer.ask<{ password: string }>('prompt-password', undefined);
const password = providedPassword || randomBytes(24).toString('base64').replace(/\W/g, '');
return this.inquirer.ask<{ password: string }>('prompt-password', undefined).then(({ password }) => password);
};
await this.userCore.updateUser(user, user.id, { password });
try {
const { password, provided } = await this.userService.resetAdminPassword(ask);
if (providedPassword) {
console.log('The admin password has been updated.');
} else {
console.log(`The admin password has been updated to:\n${password}`);
if (provided) {
console.log(`The admin password has been updated.`);
} else {
console.log(`The admin password has been updated to:\n${password}`);
}
} catch (error) {
console.error(error);
console.error('Unable to reset admin password');
}
}
}

View File

@@ -340,4 +340,43 @@ describe('UserService', () => {
expect(userRepositoryMock.get).toHaveBeenCalledWith(adminUserAuth.id, undefined);
});
});
describe('resetAdminPassword', () => {
it('should only work when there is an admin account', async () => {
userRepositoryMock.getAdmin.mockResolvedValue(null);
const ask = jest.fn().mockResolvedValue('new-password');
await expect(sut.resetAdminPassword(ask)).rejects.toBeInstanceOf(BadRequestException);
expect(ask).not.toHaveBeenCalled();
});
it('should default to a random password', async () => {
userRepositoryMock.getAdmin.mockResolvedValue(adminUser);
const ask = jest.fn().mockResolvedValue(undefined);
const response = await sut.resetAdminPassword(ask);
const [id, update] = userRepositoryMock.update.mock.calls[0];
expect(response.provided).toBe(false);
expect(ask).toHaveBeenCalled();
expect(id).toEqual(adminUser.id);
expect(update.password).toBeDefined();
});
it('should use the supplied password', async () => {
userRepositoryMock.getAdmin.mockResolvedValue(adminUser);
const ask = jest.fn().mockResolvedValue('new-password');
const response = await sut.resetAdminPassword(ask);
const [id, update] = userRepositoryMock.update.mock.calls[0];
expect(response.provided).toBe(true);
expect(ask).toHaveBeenCalled();
expect(id).toEqual(adminUser.id);
expect(update.password).toBeDefined();
});
});
});

View File

@@ -1,4 +1,5 @@
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { randomBytes } from 'crypto';
import { ReadStream } from 'fs';
import { AuthUserDto } from '../auth';
import { IUserRepository } from '../user';
@@ -104,4 +105,18 @@ export class UserService {
}
return this.userCore.getUserProfileImage(user);
}
async resetAdminPassword(ask: (admin: UserResponseDto) => Promise<string | undefined>) {
const admin = await this.userCore.getAdmin();
if (!admin) {
throw new BadRequestException('Admin account does not exist');
}
const providedPassword = await ask(admin);
const password = providedPassword || randomBytes(24).toString('base64').replace(/\W/g, '');
await this.userCore.updateUser(admin, admin.id, { password });
return { admin, password, provided: !!providedPassword };
}
}