feat(auth): Benutzer, Gruppen und Rechte als Datenmodell
Loest das eine fest verdrahtete Admin-Konto durch beliebig viele Konten ab, die in Gruppen organisiert sind. Die Zugriffskette lautet Benutzer -> Gruppe -> Domain -> Kurzlink: eine Domain gehoert zu genau einer Gruppe, ein Kurzlink haengt an seiner Domain. Wer Mitglied einer Gruppe ist, sieht damit alle Kurzlinks aller Domains dieser Gruppe. Ein Kurzlink hat bewusst keinen persoenlichen Eigentuemer - sonst wuerde das Loeschen eines Kontos gedruckte QR-Codes unbrauchbar machen. Zwei Rollenebenen: global "admin" (verwaltet Benutzer, Gruppen und Domains, sieht alles) bzw. "user", innerhalb einer Gruppe "editor" (anlegen und aendern) bzw. "viewer" (nur lesen). AccessPolicy beantwortet je Request, welche Domains sichtbar bzw. beschreibbar sind; die Repositories nehmen diese Liste als Filter. Dabei ist die Unterscheidung wesentlich: null heisst "keine Einschraenkung" (Administrator), ein leeres Array heisst "nichts" - und darf gerade nicht zu einer Abfrage ohne Bedingung werden. Der Zugang aus der .env (ADMIN_USER / ADMIN_PASSWORD_HASH) bleibt gueltig, steht aber ab jetzt als Notfallzugang neben den regulaeren Konten, statt sie zu ueberstimmen. Er hat Administratorrechte und existiert nur fuer den Fall, dass das letzte Administratorkonto gesperrt wurde oder sein Passwort verloren ging. Migration 003 uebernimmt das Bestandskonto aus der settings-Tabelle als Benutzer Nr. 1 mit unveraendertem Hash, legt die Gruppe "Standard" an, ordnet ihr alle vorhandenen Domains zu und raeumt die beiden alten settings-Schluessel auf. Nebenbei geschlossen: das Rate-Limiting am Login zaehlte nur nach IP-Adresse. Es zaehlt jetzt auch je Benutzername, sonst reicht ein Wechsel der Adresse, um alle Konten der Reihe nach durchzuprobieren. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
/**
|
||||
* Eine Stelle fuer die Regeln, die an jedes Passwort angelegt werden -
|
||||
* beim Einrichten, in der Benutzerverwaltung und beim Selbstaendern.
|
||||
*
|
||||
* Bewusst nur eine Mindestlaenge und keine Zeichenklassenpflicht: eine
|
||||
* lange Passphrase ist besser als "Sommer1!", und erzwungene Sonderzeichen
|
||||
* fuehren erfahrungsgemaess zu genau solchen Passwoertern.
|
||||
*/
|
||||
final class Password
|
||||
{
|
||||
public const MIN_LENGTH = 10;
|
||||
|
||||
/**
|
||||
* Prueft Passwort und Wiederholung.
|
||||
*
|
||||
* @return array<string,string> Feldname => Fehlermeldung, leer wenn alles passt
|
||||
*/
|
||||
public static function validationErrors(string $password, string $confirm): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (mb_strlen($password) < self::MIN_LENGTH) {
|
||||
$errors['password'] = sprintf('Das Passwort muss mindestens %d Zeichen lang sein.', self::MIN_LENGTH);
|
||||
}
|
||||
|
||||
// Sehr lange Eingaben abweisen: bcrypt schneidet nach 72 Byte ab,
|
||||
// alles darueber waere eine truegerische Sicherheit.
|
||||
if (strlen($password) > 72) {
|
||||
$errors['password'] = 'Das Passwort darf höchstens 72 Zeichen lang sein.';
|
||||
}
|
||||
|
||||
if ($password !== $confirm) {
|
||||
$errors['password_confirm'] = 'Die beiden Passwörter stimmen nicht überein.';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
/**
|
||||
* Regeln fuer Benutzernamen.
|
||||
*
|
||||
* Bewusst eng gefasst: der Name taucht in Meldungen und Protokollen auf
|
||||
* und wird ohne Ruecksicht auf Gross- und Kleinschreibung verglichen.
|
||||
* Ein enger Zeichenvorrat erspart dabei Ueberraschungen durch
|
||||
* Leerzeichen am Rand oder gleich aussehende Unicode-Zeichen.
|
||||
*/
|
||||
final class Username
|
||||
{
|
||||
public const PATTERN = '/^[A-Za-z0-9._@-]{3,64}$/';
|
||||
|
||||
public static function normalize(string $username): string
|
||||
{
|
||||
return trim($username);
|
||||
}
|
||||
|
||||
public static function validationError(string $username): ?string
|
||||
{
|
||||
if (preg_match(self::PATTERN, $username) === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'Der Benutzername muss 3-64 Zeichen lang sein (Buchstaben, Ziffern, . _ - @).';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user