From ba94ff4da374c114730ddd158f0fd0aa556758a3 Mon Sep 17 00:00:00 2001 From: itz4blitz Date: Tue, 13 Jan 2026 23:11:12 -0500 Subject: [PATCH] feat: add admin password reset via environment variable Add ADMIN_PASSWORD_RESET environment variable that allows emergency password reset by setting the flag to "true" and restarting the container. This clears the setup_completed flag and admin credentials while preserving all other data (logs, issues, settings). Changes: - Add ADMIN_PASSWORD_RESET env var schema - Add resetAdminAccount() method to SettingsService - Check env var on startup and display reset message - Pass env var through docker-compose.yml - Document in .env.example and .env.tpl - Bump version to 0.6.0 --- .env.example | 7 +++++ .env.tpl | 10 +++++++ apps/backend/package.json | 2 +- apps/backend/src/config/env.ts | 3 +++ apps/backend/src/main.ts | 20 ++++++++++++++ .../src/modules/settings/settings.service.ts | 27 +++++++++++++++++++ apps/frontend/package.json | 2 +- docker-compose.yml | 2 ++ package.json | 2 +- 9 files changed, 72 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 2848de6..995cbfd 100644 --- a/.env.example +++ b/.env.example @@ -84,6 +84,13 @@ WHISPARR_API_KEY= # Session settings (JWT expiration, idle timeout) are configured via the # Security tab in the Settings UI. These values are stored in the database. +# Admin Password Reset (emergency reset if you forget your password) +# Set to "true" and restart the container to reset the admin account. +# This allows you to re-run the setup flow at /setup. +# All other data (logs, issues, settings) is preserved. +# IMPORTANT: Remove this env var after resetting to prevent repeated resets! +# ADMIN_PASSWORD_RESET=true + # ============================================================================= # HEALTH CHECK (optional) # ============================================================================= diff --git a/.env.tpl b/.env.tpl index 8b9f838..f23c363 100644 --- a/.env.tpl +++ b/.env.tpl @@ -84,6 +84,16 @@ PROWLARR_API_KEY={{ op://Development/Logarr-Dev/PROWLARR_API_KEY }} WHISPARR_URL={{ op://Development/Logarr-Dev/WHISPARR_URL }} WHISPARR_API_KEY={{ op://Development/Logarr-Dev/WHISPARR_API_KEY }} +# ============================================================================= +# SECURITY & AUTHENTICATION +# ============================================================================= +# Admin Password Reset (emergency reset if you forget your password) +# Set to "true" and restart the container to reset the admin account. +# This allows you to re-run the setup flow at /setup. +# All other data (logs, issues, settings) is preserved. +# IMPORTANT: Remove this env var after resetting to prevent repeated resets! +# ADMIN_PASSWORD_RESET=true + # ============================================================================= # LOG FILE PATHS (optional - for file-based log ingestion) # These are HOST paths used in development (native) mode. diff --git a/apps/backend/package.json b/apps/backend/package.json index b474cf4..1e34c59 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@logarr/backend", - "version": "0.5.5", + "version": "0.6.0", "private": true, "description": "Logarr NestJS backend API", "scripts": { diff --git a/apps/backend/src/config/env.ts b/apps/backend/src/config/env.ts index c457181..f3d4152 100644 --- a/apps/backend/src/config/env.ts +++ b/apps/backend/src/config/env.ts @@ -11,6 +11,9 @@ const envSchema = z.object({ REDIS_URL: z.string(), CORS_ORIGIN: z.string().min(1), LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).optional(), + // Optional: If set to "true", resets admin account on startup (allows re-running /setup) + // After reset, this env var is ignored until removed and re-added + ADMIN_PASSWORD_RESET: z.enum(['true', 'false']).optional(), }); export type Env = z.infer; diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index 4f2c5d4..c5fedc3 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -5,6 +5,7 @@ import { Logger } from 'nestjs-pino'; import { AppModule } from './app.module'; import { validateEnv, type Env } from './config/env'; import { AuthService } from './modules/auth/auth.service'; +import { SettingsService } from './modules/settings/settings.service'; // Cache validated env for use in bootstrap let cachedEnv: Env; @@ -54,6 +55,25 @@ async function bootstrap() { const logger = app.get(Logger); const authService = app.get(AuthService); + const settingsService = app.get(SettingsService); + + // Check if admin password reset is requested via env var + if (env.ADMIN_PASSWORD_RESET === 'true') { + await settingsService.resetAdminAccount(); + console.log(''); + console.log('╔════════════════════════════════════════════════════════════╗'); + console.log('║ ADMIN ACCOUNT RESET ║'); + console.log('╠════════════════════════════════════════════════════════════╣'); + console.log('║ Admin account has been reset. ║'); + console.log('║ All other data (logs, issues, settings) is preserved. ║'); + console.log('║ ║'); + console.log('║ Navigate to /setup to create a new admin account. ║'); + console.log('║ ║'); + console.log('║ Remove ADMIN_PASSWORD_RESET=true from your env file ║'); + console.log('║ to prevent this message on next restart. ║'); + console.log('╚════════════════════════════════════════════════════════════╝'); + console.log(''); + } // Check if setup is required and log the setup token const setupStatus = await authService.getSetupStatus(); diff --git a/apps/backend/src/modules/settings/settings.service.ts b/apps/backend/src/modules/settings/settings.service.ts index ed876a7..50f670f 100644 --- a/apps/backend/src/modules/settings/settings.service.ts +++ b/apps/backend/src/modules/settings/settings.service.ts @@ -515,4 +515,31 @@ export class SettingsService { async getAdminCreatedAt(): Promise { return this.getSetting(SETTINGS_KEYS.SECURITY_ADMIN_CREATED_AT, null); } + + /** + * Reset admin account to allow re-setup via /setup endpoint. + * Clears the setup_completed flag and admin credentials while preserving all other data. + */ + async resetAdminAccount(): Promise { + await this.db + .delete(schema.appSettings) + .where( + eq( + schema.appSettings.key, + SETTINGS_KEYS.SECURITY_SETUP_COMPLETED + ) + ); + // Also clear admin credentials so setup can create a fresh account + await this.db + .delete(schema.appSettings) + .where( + eq(schema.appSettings.key, SETTINGS_KEYS.SECURITY_ADMIN_USERNAME) + ); + await this.db + .delete(schema.appSettings) + .where( + eq(schema.appSettings.key, SETTINGS_KEYS.SECURITY_ADMIN_PASSWORD_HASH) + ); + this.logger.warn('Admin account reset - user must complete setup again'); + } } diff --git a/apps/frontend/package.json b/apps/frontend/package.json index c5fccd8..f945db1 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.5.5", + "version": "0.6.0", "private": true, "scripts": { "dev": "next dev --turbopack", diff --git a/docker-compose.yml b/docker-compose.yml index 0ffd329..0ab2ea3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,6 +107,8 @@ services: WHISPARR_LOGS_PATH_1: ${WHISPARR_LOGS_PATH_1:-} WHISPARR_LOGS_PATH_2: ${WHISPARR_LOGS_PATH_2:-} WHISPARR_LOGS_PATH_3: ${WHISPARR_LOGS_PATH_3:-} + # Admin password reset - set to 'true' to reset admin account on startup + ADMIN_PASSWORD_RESET: ${ADMIN_PASSWORD_RESET:-} volumes: # Media server log mounts (optional - set paths in .env for file-based log ingestion) # In the Sources UI, configure paths as: /plex-logs, /jellyfin-logs, /emby-logs, etc. diff --git a/package.json b/package.json index 7c9caab..69d3b42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logarr", - "version": "0.5.5", + "version": "0.6.0", "private": true, "description": "Unified logging for your media stack", "scripts": {