Files
ebooks/cmd/admin/main.go
T
arnefandCopilot a66c370de4 feat: add authentication and book upload
- 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>
2026-08-11 19:11:40 +02:00

150 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)`)
}