fix(backend): bootstrap admin from env instead of seeding admin/admin
007_seed_data.sql shipped a fixed admin account whose bcrypt hash decodes to the password "admin", giving every deployment the same known credentials. The seed row is removed; UserService.EnsureAdmin now creates the administrator on startup from ADMIN_USERNAME / ADMIN_PASSWORD. It is idempotent and never overwrites an existing password, so an operator who rotates the admin password keeps it across restarts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -21,6 +22,36 @@ func NewUserService(users port.UserRepo, audit *AuditService) *UserService {
|
||||
return &UserService{users: users, audit: audit}
|
||||
}
|
||||
|
||||
// EnsureAdmin creates the initial administrator account if it does not already
|
||||
// exist. It is idempotent and never overwrites an existing user's password, so
|
||||
// an operator who has changed the admin password keeps it across restarts.
|
||||
func (s *UserService) EnsureAdmin(ctx context.Context, username, password string) error {
|
||||
if username == "" || password == "" {
|
||||
return fmt.Errorf("EnsureAdmin: username and password must be set")
|
||||
}
|
||||
|
||||
if _, err := s.users.GetByName(ctx, username); err == nil {
|
||||
return nil // already exists
|
||||
} else if !errors.Is(err, domain.ErrNotFound) {
|
||||
return fmt.Errorf("EnsureAdmin: lookup: %w", err)
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("EnsureAdmin: hash: %w", err)
|
||||
}
|
||||
_, err = s.users.Create(ctx, &domain.User{
|
||||
Name: username,
|
||||
Password: string(hash),
|
||||
IsAdmin: true,
|
||||
CanCreate: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("EnsureAdmin: create: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Self-service
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user