From 1e2f02613f1a2b9eee14bea60d4d7e4384519dd3 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 16 Jan 2023 13:09:04 -0500 Subject: [PATCH] refactor: reset admin password (#1335) * refactor: reset-admin-password * chore: docs --- .../features/img/disable-password-login.png | Bin 0 -> 5050 bytes .../features/img/enable-password-login.png | Bin 0 -> 4840 bytes .../features/img/reset-admin-password.png | Bin 0 -> 16514 bytes docs/docs/features/server-commands.md | 25 +++-------- docs/docs/guides/docker-help.md | 20 ++++++++- .../commands/reset-admin-password.command.ts | 41 +++++++++--------- .../libs/domain/src/user/user.service.spec.ts | 39 +++++++++++++++++ server/libs/domain/src/user/user.service.ts | 15 +++++++ 8 files changed, 100 insertions(+), 40 deletions(-) create mode 100644 docs/docs/features/img/disable-password-login.png create mode 100644 docs/docs/features/img/enable-password-login.png create mode 100644 docs/docs/features/img/reset-admin-password.png diff --git a/docs/docs/features/img/disable-password-login.png b/docs/docs/features/img/disable-password-login.png new file mode 100644 index 0000000000000000000000000000000000000000..e83059b763f239753e7919035c775e5c2111bc0e GIT binary patch literal 5050 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 p?f-sf4 literal 0 HcmV?d00001 diff --git a/docs/docs/features/img/enable-password-login.png b/docs/docs/features/img/enable-password-login.png new file mode 100644 index 0000000000000000000000000000000000000000..e4827a2c9d291b983766b3aa9cf1292397984194 GIT binary patch literal 4840 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 literal 0 HcmV?d00001 diff --git a/docs/docs/features/img/reset-admin-password.png b/docs/docs/features/img/reset-admin-password.png new file mode 100644 index 0000000000000000000000000000000000000000..57ee230d348c8db7e5d9a959c2f8200abefec584 GIT binary patch literal 16514 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 literal 0 HcmV?d00001 diff --git a/docs/docs/features/server-commands.md b/docs/docs/features/server-commands.md index d8c017cade..3ad7d3363f 100644 --- a/docs/docs/features/server-commands.md +++ b/docs/docs/features/server-commands.md @@ -11,29 +11,18 @@ The `immich-server` docker image comes preinstalled with an administrative CLI ( ## How to run a command -To run a command, connect to the container and then execute it by running `immich `. +To run a command, [connect](/docs/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich `. ## Examples -```bash title="Reset Admin Password" -docker exec -it immich_server sh +Reset Admin Password -/usr/src/app$ immich reset-admin-password -? Please choose a new password (optional) immich-is-awesome-unlike-this-password -New password: -immich-is-awesome-unlike-this-password -``` +![Reset Admin Password](./img/reset-admin-password.png) -```bash title="Disable Password Login" -docker exec -it immich_server sh +Disable Password Login -/usr/src/app$ immich disable-password-login -Password login has been disabled. -``` +![Disable Password Login](./img/disable-password-login.png) -```bash title="Enable Password Login" -docker exec -it immich_server sh +Enabled Password Login -/usr/src/app$ immich enable-password-login -Password login has been enabled. -``` +![Enable Password Login](./img/enable-password-login.png) diff --git a/docs/docs/guides/docker-help.md b/docs/docs/guides/docker-help.md index a2e633efd6..e85d62258a 100644 --- a/docs/docs/guides/docker-help.md +++ b/docs/docs/guides/docker-help.md @@ -4,11 +4,27 @@ sidebar_position: 1 # Docker Help -## Logs +## Containers -```bash title="Log Examples" +```bash docker ps # see a list of running containers docker ps -a # see a list of running and stopped containers +``` + +## Attach to a Container + +```bash +docker exec -it # attach to a container with a command +docker exec -it immich_server sh +docker exec -it immich_microservices sh +docker exec -it immich_machine_learning sh +docker exec -it immich_web sh +docker exec -it immich_proxy sh +``` + +## Logs + +```bash docker logs # see the logs for a specific container (by id or name) docker logs immich_server diff --git a/server/apps/cli/src/commands/reset-admin-password.command.ts b/server/apps/cli/src/commands/reset-admin-password.command.ts index 2c66c8a961..b12a549963 100644 --- a/server/apps/cli/src/commands/reset-admin-password.command.ts +++ b/server/apps/cli/src/commands/reset-admin-password.command.ts @@ -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 { - 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'); } } } diff --git a/server/libs/domain/src/user/user.service.spec.ts b/server/libs/domain/src/user/user.service.spec.ts index ebafa8ac68..fac5166a1a 100644 --- a/server/libs/domain/src/user/user.service.spec.ts +++ b/server/libs/domain/src/user/user.service.spec.ts @@ -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(); + }); + }); }); diff --git a/server/libs/domain/src/user/user.service.ts b/server/libs/domain/src/user/user.service.ts index 77d52c9087..e0d02876b9 100644 --- a/server/libs/domain/src/user/user.service.ts +++ b/server/libs/domain/src/user/user.service.ts @@ -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) { + 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 }; + } }