- Session-based login (username/password, 30-day cookie) - SQLite user store with bcrypt password hashing (modernc.org/sqlite) - Per-user upload permission (can_upload flag) - Admin CLI (cmd/admin) for user management: user add/list/delete/set-upload - Upload handler for EPUB/PDF with path-traversal protection - All routes protected by requireAuth middleware; /upload additionally requires requireUpload - Login/logout UI, upload form, logout button in header - New env var: USERS_DB (default: users.db, gitignored) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
150 lines
3.2 KiB
Go
150 lines
3.2 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"syscall"
|
||
|
||
"github.com/arnef/ebooks/internal/users"
|
||
"golang.org/x/term"
|
||
)
|
||
|
||
func main() {
|
||
if len(os.Args) < 3 {
|
||
usage()
|
||
os.Exit(1)
|
||
}
|
||
|
||
dbPath := getenv("USERS_DB", "users.db")
|
||
store, err := users.Open(dbPath)
|
||
if err != nil {
|
||
fatalf("open db: %v\n", err)
|
||
}
|
||
defer store.Close()
|
||
|
||
resource := os.Args[1]
|
||
action := os.Args[2]
|
||
|
||
switch resource {
|
||
case "user":
|
||
handleUser(store, action, os.Args[3:])
|
||
default:
|
||
usage()
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
func handleUser(store *users.Store, action string, args []string) {
|
||
switch action {
|
||
case "add":
|
||
if len(args) < 1 {
|
||
fatalf("usage: user add <username> [--upload]\n")
|
||
}
|
||
username := args[0]
|
||
canUpload := len(args) > 1 && args[1] == "--upload"
|
||
|
||
password, err := readPassword(fmt.Sprintf("Passwort für '%s': ", username))
|
||
if err != nil {
|
||
fatalf("passwort lesen: %v\n", err)
|
||
}
|
||
confirm, err := readPassword("Passwort bestätigen: ")
|
||
if err != nil {
|
||
fatalf("passwort lesen: %v\n", err)
|
||
}
|
||
if password != confirm {
|
||
fatalf("Passwörter stimmen nicht überein.\n")
|
||
}
|
||
|
||
u, err := store.CreateUser(username, password, canUpload)
|
||
if err != nil {
|
||
fatalf("user anlegen: %v\n", err)
|
||
}
|
||
fmt.Printf("✓ Benutzer '%s' angelegt (upload: %s)\n", u.Username, yesNo(u.CanUpload))
|
||
|
||
case "list":
|
||
list, err := store.ListUsers()
|
||
if err != nil {
|
||
fatalf("user auflisten: %v\n", err)
|
||
}
|
||
if len(list) == 0 {
|
||
fmt.Println("Keine Benutzer vorhanden.")
|
||
return
|
||
}
|
||
fmt.Printf("%-20s %s\n", "BENUTZERNAME", "UPLOAD")
|
||
fmt.Println(strings.Repeat("-", 32))
|
||
for _, u := range list {
|
||
fmt.Printf("%-20s %s\n", u.Username, yesNo(u.CanUpload))
|
||
}
|
||
|
||
case "delete":
|
||
if len(args) < 1 {
|
||
fatalf("usage: user delete <username>\n")
|
||
}
|
||
if err := store.DeleteUser(args[0]); err != nil {
|
||
fatalf("user löschen: %v\n", err)
|
||
}
|
||
fmt.Printf("✓ Benutzer '%s' gelöscht.\n", args[0])
|
||
|
||
case "set-upload":
|
||
if len(args) < 2 {
|
||
fatalf("usage: user set-upload <username> <true|false>\n")
|
||
}
|
||
val, err := strconv.ParseBool(args[1])
|
||
if err != nil {
|
||
fatalf("ungültiger Wert '%s', erwartet true oder false\n", args[1])
|
||
}
|
||
if err := store.SetUpload(args[0], val); err != nil {
|
||
fatalf("upload-recht setzen: %v\n", err)
|
||
}
|
||
fmt.Printf("✓ Upload-Recht für '%s' auf %s gesetzt.\n", args[0], yesNo(val))
|
||
|
||
default:
|
||
usage()
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
func readPassword(prompt string) (string, error) {
|
||
fmt.Print(prompt)
|
||
b, err := term.ReadPassword(int(syscall.Stdin))
|
||
fmt.Println()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return string(b), nil
|
||
}
|
||
|
||
func yesNo(b bool) string {
|
||
if b {
|
||
return "ja"
|
||
}
|
||
return "nein"
|
||
}
|
||
|
||
func getenv(key, fallback string) string {
|
||
if v := os.Getenv(key); v != "" {
|
||
return v
|
||
}
|
||
return fallback
|
||
}
|
||
|
||
func fatalf(format string, args ...any) {
|
||
fmt.Fprintf(os.Stderr, format, args...)
|
||
os.Exit(1)
|
||
}
|
||
|
||
func usage() {
|
||
fmt.Fprintln(os.Stderr, `ebooks-admin – Benutzerverwaltung
|
||
|
||
Befehle:
|
||
user add <name> [--upload] Benutzer anlegen (optional: Upload-Recht)
|
||
user list Alle Benutzer auflisten
|
||
user delete <name> Benutzer löschen
|
||
user set-upload <name> <true|false> Upload-Recht ändern
|
||
|
||
Umgebungsvariablen:
|
||
USERS_DB Pfad zur SQLite-Datenbank (Standard: users.db)`)
|
||
}
|