refactor: replace can_upload boolean with role-based access control
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>
This commit is contained in:
+32
-25
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -40,10 +39,20 @@ func handleUser(store *users.Store, action string, args []string) {
|
||||
switch action {
|
||||
case "add":
|
||||
if len(args) < 1 {
|
||||
fatalf("usage: user add <username> [--upload]\n")
|
||||
fatalf("usage: user add <username> [--role reader|uploader|admin]\n")
|
||||
}
|
||||
username := args[0]
|
||||
canUpload := len(args) > 1 && args[1] == "--upload"
|
||||
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 {
|
||||
@@ -57,11 +66,11 @@ func handleUser(store *users.Store, action string, args []string) {
|
||||
fatalf("Passwörter stimmen nicht überein.\n")
|
||||
}
|
||||
|
||||
u, err := store.CreateUser(username, password, canUpload)
|
||||
u, err := store.CreateUser(username, password, role)
|
||||
if err != nil {
|
||||
fatalf("user anlegen: %v\n", err)
|
||||
}
|
||||
fmt.Printf("✓ Benutzer '%s' angelegt (upload: %s)\n", u.Username, yesNo(u.CanUpload))
|
||||
fmt.Printf("✓ Benutzer '%s' angelegt (rolle: %s)\n", u.Username, u.Role)
|
||||
|
||||
case "list":
|
||||
list, err := store.ListUsers()
|
||||
@@ -72,10 +81,10 @@ func handleUser(store *users.Store, action string, args []string) {
|
||||
fmt.Println("Keine Benutzer vorhanden.")
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-20s %s\n", "BENUTZERNAME", "UPLOAD")
|
||||
fmt.Printf("%-20s %s\n", "BENUTZERNAME", "ROLLE")
|
||||
fmt.Println(strings.Repeat("-", 32))
|
||||
for _, u := range list {
|
||||
fmt.Printf("%-20s %s\n", u.Username, yesNo(u.CanUpload))
|
||||
fmt.Printf("%-20s %s\n", u.Username, u.Role)
|
||||
}
|
||||
|
||||
case "delete":
|
||||
@@ -87,18 +96,18 @@ func handleUser(store *users.Store, action string, args []string) {
|
||||
}
|
||||
fmt.Printf("✓ Benutzer '%s' gelöscht.\n", args[0])
|
||||
|
||||
case "set-upload":
|
||||
case "set-role":
|
||||
if len(args) < 2 {
|
||||
fatalf("usage: user set-upload <username> <true|false>\n")
|
||||
fatalf("usage: user set-role <username> <reader|uploader|admin>\n")
|
||||
}
|
||||
val, err := strconv.ParseBool(args[1])
|
||||
role, err := users.ParseRole(args[1])
|
||||
if err != nil {
|
||||
fatalf("ungültiger Wert '%s', erwartet true oder false\n", args[1])
|
||||
fatalf("%v\n", err)
|
||||
}
|
||||
if err := store.SetUpload(args[0], val); err != nil {
|
||||
fatalf("upload-recht setzen: %v\n", err)
|
||||
if err := store.SetRole(args[0], role); err != nil {
|
||||
fatalf("rolle setzen: %v\n", err)
|
||||
}
|
||||
fmt.Printf("✓ Upload-Recht für '%s' auf %s gesetzt.\n", args[0], yesNo(val))
|
||||
fmt.Printf("✓ Rolle für '%s' auf '%s' gesetzt.\n", args[0], role)
|
||||
|
||||
default:
|
||||
usage()
|
||||
@@ -116,13 +125,6 @@ func readPassword(prompt string) (string, error) {
|
||||
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
|
||||
@@ -138,11 +140,16 @@ func fatalf(format string, args ...any) {
|
||||
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> [--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
|
||||
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)`)
|
||||
|
||||
Reference in New Issue
Block a user