Introduces a three-tier role hierarchy: reader < uploader < admin. - internal/users/role.go: Role type, roleLevel map, AtLeast(min), ParseRole() — new roles added by inserting into roleLevel only - internal/users/store.go: versioned migrations via _schema_version table; v2 migration adds 'role' column and migrates existing can_upload data; SetRole() replaces SetUpload(); Session carries Role instead of CanUpload - internal/web/middleware.go: generic requireRole(minRole) middleware replaces the ad-hoc requireUpload - internal/web/handlers.go: upload routes use requireRole(RoleUploader); listBooks derives canUpload from sess.Role.AtLeast(RoleUploader) - cmd/admin/main.go: user add --role <reader|uploader|admin>, user set-role replaces user set-upload - README, copilot-instructions updated Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
157 lines
3.4 KiB
Go
157 lines
3.4 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"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> [--role reader|uploader|admin]\n")
|
||
}
|
||
username := args[0]
|
||
role := users.RoleReader
|
||
if len(args) > 1 && args[1] == "--role" {
|
||
if len(args) < 3 {
|
||
fatalf("--role erwartet einen Wert: reader, uploader oder admin\n")
|
||
}
|
||
r, err := users.ParseRole(args[2])
|
||
if err != nil {
|
||
fatalf("%v\n", err)
|
||
}
|
||
role = r
|
||
}
|
||
|
||
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, role)
|
||
if err != nil {
|
||
fatalf("user anlegen: %v\n", err)
|
||
}
|
||
fmt.Printf("✓ Benutzer '%s' angelegt (rolle: %s)\n", u.Username, u.Role)
|
||
|
||
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", "ROLLE")
|
||
fmt.Println(strings.Repeat("-", 32))
|
||
for _, u := range list {
|
||
fmt.Printf("%-20s %s\n", u.Username, u.Role)
|
||
}
|
||
|
||
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-role":
|
||
if len(args) < 2 {
|
||
fatalf("usage: user set-role <username> <reader|uploader|admin>\n")
|
||
}
|
||
role, err := users.ParseRole(args[1])
|
||
if err != nil {
|
||
fatalf("%v\n", err)
|
||
}
|
||
if err := store.SetRole(args[0], role); err != nil {
|
||
fatalf("rolle setzen: %v\n", err)
|
||
}
|
||
fmt.Printf("✓ Rolle für '%s' auf '%s' gesetzt.\n", args[0], role)
|
||
|
||
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 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
|
||
|
||
Rollen (aufsteigend):
|
||
reader Lesen und Herunterladen
|
||
uploader Zusätzlich: Bücher hochladen
|
||
admin Alle Rechte
|
||
|
||
Befehle:
|
||
user add <name> [--role reader|uploader|admin] Benutzer anlegen (Standard: reader)
|
||
user list Alle Benutzer auflisten
|
||
user delete <name> Benutzer löschen
|
||
user set-role <name> <reader|uploader|admin> Rolle ändern
|
||
|
||
Umgebungsvariablen:
|
||
USERS_DB Pfad zur SQLite-Datenbank (Standard: users.db)`)
|
||
}
|