Add passwd command, fix template rendering, update deployment docs

- Add `passwd` CLI command to reset user passwords
- Fix web UI templates: parse each page template with layout so blocks
  render correctly (was outputting empty pages)
- Add login error logging for debugging auth failures
- Update README with deploy workflow and container management commands
- Update RUNBOOK for Docker-on-deimos deployment (replaces systemd refs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 08:27:31 -07:00
parent da148a577d
commit 2185bbe563
6 changed files with 212 additions and 79 deletions

View File

@@ -0,0 +1,77 @@
package main
import (
"fmt"
"os"
"strings"
"git.wntrmute.dev/kyle/eng-pad-server/internal/auth"
"git.wntrmute.dev/kyle/eng-pad-server/internal/config"
"git.wntrmute.dev/kyle/eng-pad-server/internal/db"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var passwdCmd = &cobra.Command{
Use: "passwd <username>",
Short: "Set password for a user",
Args: cobra.ExactArgs(1),
RunE: runPasswd,
}
func init() {
rootCmd.AddCommand(passwdCmd)
}
func runPasswd(cmd *cobra.Command, args []string) error {
username := args[0]
cfg, err := config.Load(cfgFile)
if err != nil {
return err
}
database, err := db.Open(cfg.Database.Path)
if err != nil {
return err
}
defer func() { _ = database.Close() }()
// Verify user exists.
var userID int64
err = database.QueryRow("SELECT id FROM users WHERE username = ?", username).Scan(&userID)
if err != nil {
return fmt.Errorf("user %q not found", username)
}
fmt.Print("New password: ")
passBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()
if err != nil {
return fmt.Errorf("read password: %w", err)
}
password := strings.TrimSpace(string(passBytes))
if password == "" {
return fmt.Errorf("password cannot be empty")
}
params := auth.Argon2Params{
Memory: cfg.Auth.Argon2Memory,
Time: cfg.Auth.Argon2Time,
Threads: cfg.Auth.Argon2Threads,
}
hash, err := auth.HashPassword(password, params)
if err != nil {
return fmt.Errorf("hash password: %w", err)
}
_, err = database.Exec("UPDATE users SET password_hash = ?, updated_at = unixepoch() WHERE id = ?", hash, userID)
if err != nil {
return fmt.Errorf("update password: %w", err)
}
fmt.Printf("Password updated for %q.\n", username)
return nil
}