Browse Source

feat(server): reset admin password using cli command in the server container (#928)

Jason Rasmussen 2 years ago
parent
commit
02bc84062e

+ 2 - 1
server/Dockerfile

@@ -26,9 +26,10 @@ RUN mkdir -p /usr/src/app/dist \
 
 
 COPY --from=builder /usr/src/app/node_modules ./node_modules
 COPY --from=builder /usr/src/app/node_modules ./node_modules
 COPY --from=builder /usr/src/app/dist ./dist
 COPY --from=builder /usr/src/app/dist ./dist
+COPY --from=builder /usr/src/app/bin ./bin
 
 
 RUN npm prune --production
 RUN npm prune --production
 
 
 VOLUME /usr/src/app/upload
 VOLUME /usr/src/app/upload
 
 
-EXPOSE 3001
+EXPOSE 3001

+ 11 - 0
server/apps/cli/src/app.module.ts

@@ -0,0 +1,11 @@
+import { DatabaseModule } from '@app/database';
+import { UserEntity } from '@app/database/entities/user.entity';
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { PromptPasswordQuestions, ResetAdminPasswordCommand } from './commands/reset-admin-password.command';
+
+@Module({
+  imports: [DatabaseModule, TypeOrmModule.forFeature([UserEntity])],
+  providers: [ResetAdminPasswordCommand, PromptPasswordQuestions],
+})
+export class AppModule {}

+ 52 - 0
server/apps/cli/src/commands/reset-admin-password.command.ts

@@ -0,0 +1,52 @@
+import { UserEntity } from '@app/database/entities/user.entity';
+import { InjectRepository } from '@nestjs/typeorm';
+import bcrypt from 'bcrypt';
+import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
+import { randomBytes } from 'node:crypto';
+import { Repository } from 'typeorm';
+
+@Command({
+  name: 'reset-admin-password',
+  description: 'Reset the admin password',
+})
+export class ResetAdminPasswordCommand extends CommandRunner {
+  constructor(
+    private readonly inquirer: InquirerService,
+    @InjectRepository(UserEntity) private userRepository: Repository<UserEntity>,
+  ) {
+    super();
+  }
+
+  async run(): Promise<void> {
+    let { password } = await this.inquirer.ask<{ password: string }>('prompt-password', undefined);
+    password = password || randomBytes(24).toString('base64').replace(/\W/g, '');
+
+    const salt = await bcrypt.genSalt();
+    const hashedPassword = await bcrypt.hash(password, salt);
+
+    const user = await this.userRepository.findOne({ where: { isAdmin: true } });
+    if (!user) {
+      console.log('Unable to reset password: no admin user.');
+      return;
+    }
+
+    user.salt = salt;
+    user.password = hashedPassword;
+    user.shouldChangePassword = true;
+
+    await this.userRepository.save(user);
+
+    console.log(`New password:\n${password}`);
+  }
+}
+
+@QuestionSet({ name: 'prompt-password' })
+export class PromptPasswordQuestions {
+  @Question({
+    message: 'Please choose a new password (optional)',
+    name: 'password',
+  })
+  parsePassword(val: string) {
+    return val;
+  }
+}

+ 7 - 0
server/apps/cli/src/immich.ts

@@ -0,0 +1,7 @@
+import { CommandFactory } from 'nest-commander';
+import { AppModule } from './app.module';
+
+async function bootstrap() {
+  await CommandFactory.run(AppModule, ['warn', 'error']);
+}
+bootstrap();

+ 9 - 0
server/apps/cli/tsconfig.app.json

@@ -0,0 +1,9 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "declaration": false,
+    "outDir": "../../dist/apps/cli"
+  },
+  "include": ["src/**/*", "../../libs/**/*"],
+  "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
+}

+ 1 - 0
server/bin/cli.sh

@@ -0,0 +1 @@
+node ./dist/apps/cli/apps/cli/src/immich "$@"

+ 17 - 6
server/nest-cli.json

@@ -6,13 +6,15 @@
   "compilerOptions": {
   "compilerOptions": {
     "webpack": false,
     "webpack": false,
     "tsConfigPath": "apps/immich/tsconfig.app.json",
     "tsConfigPath": "apps/immich/tsconfig.app.json",
-    "plugins": [  {
-      "name": "@nestjs/swagger",
-      "options": {
-        "classValidatorShim": false,
-        "introspectComments": true
+    "plugins": [
+      {
+        "name": "@nestjs/swagger",
+        "options": {
+          "classValidatorShim": false,
+          "introspectComments": true
+        }
       }
       }
-    }]
+    ]
   },
   },
   "projects": {
   "projects": {
     "immich": {
     "immich": {
@@ -33,6 +35,15 @@
         "tsConfigPath": "apps/microservices/tsconfig.app.json"
         "tsConfigPath": "apps/microservices/tsconfig.app.json"
       }
       }
     },
     },
+    "cli": {
+      "type": "application",
+      "root": "apps/cli",
+      "entryFile": "immich",
+      "sourceRoot": "apps/cli/src",
+      "compilerOptions": {
+        "tsConfigPath": "apps/cli/tsconfig.app.json"
+      }
+    },
     "common": {
     "common": {
       "type": "library",
       "type": "library",
       "root": "libs/common",
       "root": "libs/common",

File diff suppressed because it is too large
+ 138 - 234
server/package-lock.json


+ 5 - 1
server/package.json

@@ -5,9 +5,12 @@
   "author": "",
   "author": "",
   "private": true,
   "private": true,
   "license": "UNLICENSED",
   "license": "UNLICENSED",
+  "bin": {
+    "immich": "./bin/cli.sh"
+  },
   "scripts": {
   "scripts": {
     "prebuild": "rimraf dist",
     "prebuild": "rimraf dist",
-    "build": "nest build immich && nest build microservices",
+    "build": "nest build immich && nest build microservices && nest build cli",
     "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
     "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
     "start": "nest start",
     "start": "nest start",
     "start:dev": "nest start --watch",
     "start:dev": "nest start --watch",
@@ -59,6 +62,7 @@
     "local-reverse-geocoder": "^0.12.5",
     "local-reverse-geocoder": "^0.12.5",
     "lodash": "^4.17.21",
     "lodash": "^4.17.21",
     "luxon": "^3.0.3",
     "luxon": "^3.0.3",
+    "nest-commander": "^3.3.0",
     "passport": "^0.6.0",
     "passport": "^0.6.0",
     "passport-jwt": "^4.0.0",
     "passport-jwt": "^4.0.0",
     "pg": "^8.7.1",
     "pg": "^8.7.1",

Some files were not shown because too many files changed in this diff