diff --git a/Dockerfile b/Dockerfile index d5b024f..254fa69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -97,4 +97,5 @@ RUN if [ ! -f /app/.env ]; then \ EXPOSE 4005 -CMD ["./main"] +ENTRYPOINT ["./main"] +CMD [] diff --git a/README.md b/README.md index 8e14f83..55d7ebd 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ version: "3" services: postgresus: + container_name: postgresus image: rostislavdugin/postgresus:latest ports: - "4005:4005" @@ -89,6 +90,7 @@ services: restart: unless-stopped postgresus-db: + container_name: postgresus-db image: postgres:17 # we use default values, but do not expose # PostgreSQL ports so it is safe @@ -98,7 +100,6 @@ services: - POSTGRES_PASSWORD=Q1234567 volumes: - ./pgdata:/var/lib/postgresql/data - container_name: postgresus-db command: -p 5437 shm_size: 10gb healthcheck: @@ -127,6 +128,14 @@ docker compose up -d 6. **Add notifications** (optional): Configure email, Telegram, Slack, or webhook notifications 7. **Save and start**: Postgresus will validate settings and begin the backup schedule +### 🔑 Resetting Admin Password + +If you need to reset the admin password, you can use the built-in password reset command: + +```bash +docker exec -it postgresus ./main --new-password="YourNewSecurePassword123" +``` + --- ## 📝 License diff --git a/backend/cmd/main.go b/backend/cmd/main.go index c9678ea..0b9ce77 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "flag" "log/slog" "net/http" "os" @@ -19,7 +20,6 @@ import ( "postgresus-backend/internal/features/restores" "postgresus-backend/internal/features/storages" "postgresus-backend/internal/features/users" - "postgresus-backend/internal/storage" env_utils "postgresus-backend/internal/util/env" files_utils "postgresus-backend/internal/util/files" "postgresus-backend/internal/util/logger" @@ -44,9 +44,14 @@ func main() { runMigrations(log) - go generateSwaggerDocs(log) + // Handle password reset if flag is provided + newPassword := flag.String("new-password", "", "Set a new password for the user") + flag.Parse() + if *newPassword != "" { + resetPassword(*newPassword, log) + } - _ = storage.GetDb() + go generateSwaggerDocs(log) gin.SetMode(gin.ReleaseMode) ginApp := gin.Default() @@ -60,6 +65,20 @@ func main() { startServerWithGracefulShutdown(log, ginApp) } +func resetPassword(newPassword string, log *slog.Logger) { + log.Info("Resetting password...") + + userService := users.GetUserService() + err := userService.ChangePassword(newPassword) + if err != nil { + log.Error("Failed to reset password", "error", err) + os.Exit(1) + } + + log.Info("Password reset successfully") + os.Exit(0) +} + func startServerWithGracefulShutdown(log *slog.Logger, app *gin.Engine) { host := "" if config.GetEnv().EnvMode == env_utils.EnvModeDevelopment { diff --git a/backend/internal/features/users/service.go b/backend/internal/features/users/service.go index 4f3ec36..85fbbd6 100644 --- a/backend/internal/features/users/service.go +++ b/backend/internal/features/users/service.go @@ -101,6 +101,19 @@ func (s *UserService) GetUserFromToken(token string) (*user_models.User, error) return nil, err } + if passwordCreationTimeUnix, ok := claims["passwordCreationTime"].(float64); ok { + tokenPasswordTime := time.Unix(int64(passwordCreationTimeUnix), 0) + + tokenTimeSeconds := tokenPasswordTime.Truncate(time.Second) + userTimeSeconds := user.PasswordCreationTime.Truncate(time.Second) + + if !tokenTimeSeconds.Equal(userTimeSeconds) { + return nil, errors.New("password has been changed, please sign in again") + } + } else { + return nil, errors.New("invalid token claims: missing password creation time") + } + return user, nil } @@ -143,10 +156,11 @@ func (s *UserService) GenerateAccessToken(user *user_models.User) (*SignInRespon tenYearsExpiration := time.Now().UTC().Add(time.Hour * 24 * 365 * 10) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": user.ID, - "exp": tenYearsExpiration.Unix(), - "iat": time.Now().UTC().Unix(), - "role": string(user.Role), + "sub": user.ID, + "exp": tenYearsExpiration.Unix(), + "iat": time.Now().UTC().Unix(), + "role": string(user.Role), + "passwordCreationTime": user.PasswordCreationTime.Unix(), }) tokenString, err := token.SignedString([]byte(secretKey)) diff --git a/docker-compose.yml.example b/docker-compose.yml.example index e1e6650..cbe67ac 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -15,6 +15,7 @@ services: - "4005:4005" volumes: - ./postgresus-data:/app/postgresus-data + container_name: postgresus-local depends_on: postgresus-db: condition: service_healthy diff --git a/install-postgresus.sh b/install-postgresus.sh index bc777a4..5e3ed1d 100644 --- a/install-postgresus.sh +++ b/install-postgresus.sh @@ -62,6 +62,7 @@ version: "3" services: postgresus: + container_name: postgresus image: rostislavdugin/postgresus:latest ports: - "4005:4005" @@ -73,6 +74,7 @@ services: restart: unless-stopped postgresus-db: + container_name: postgresus-db image: postgres:17 # we use default values, but do not expose # PostgreSQL ports so it is safe @@ -82,7 +84,6 @@ services: - POSTGRES_PASSWORD=Q1234567 volumes: - ./pgdata:/var/lib/postgresql/data - container_name: postgresus-db command: -p 5437 shm_size: 10gb healthcheck: