Security: verwijder hardcoded wachtwoord, voeg random-wachtwoord-generator toe bij eerste installatie
This commit is contained in:
+421
-20
@@ -1,18 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AdminAuth - File-based authentication for CodePress Admin
|
||||
* File-based authenticatie en autorisatie voor het CodePress admin paneel.
|
||||
*
|
||||
* Beheert gebruikerssessies, rollen met permissies, CSRF-tokens, brute-force
|
||||
* lockout en gebruikersbeheer. Alle data wordt opgeslagen in JSON-bestanden
|
||||
* (geen database).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class AdminAuth
|
||||
{
|
||||
/**
|
||||
* Applicatie-configuratie uit config.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array Applicatie-configuratie.
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* Admin-configuratie geladen uit admin.json (gebruikers en security-instellingen).
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array Admin-configuratie met keys 'users' en 'security'.
|
||||
*/
|
||||
private array $adminConfig;
|
||||
|
||||
/**
|
||||
* Pad naar het JSON-bestand met mislukte inlogpogingen voor brute-force lockout.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string Absoluut pad naar login_attempts.json.
|
||||
*/
|
||||
private string $lockFile;
|
||||
|
||||
/**
|
||||
* Role definitions with permissions.
|
||||
* Each role maps to a list of allowed route prefixes.
|
||||
* 'admin' has wildcard '*' access.
|
||||
* Tijdelijk gegenereerd wachtwoord bij eerste installatie.
|
||||
*
|
||||
* Wordt gevuld wanneer loadAdminConfig() een nieuwe admin.json aanmaakt
|
||||
* met een cryptografisch veilig willekeurig wachtwoord. De login-pagina
|
||||
* toont dit wachtwoord eenmalig via getGeneratedPassword().
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string Leeg tenzij net aangemaakt bij eerste installatie.
|
||||
*/
|
||||
private string $generatedPassword = '';
|
||||
|
||||
/**
|
||||
* Permissies per rol.
|
||||
*
|
||||
* Elke rol mapt naar een lijst van toegestane route-prefixen. De rol
|
||||
* 'admin' heeft wildcard '*' toegang tot alle routes.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string, string[]> Map van rol-sleutel naar lijst met toegestane routes.
|
||||
*/
|
||||
public const ROLE_PERMISSIONS = [
|
||||
'admin' => ['*'],
|
||||
@@ -22,7 +64,10 @@ class AdminAuth
|
||||
];
|
||||
|
||||
/**
|
||||
* Human-readable role labels.
|
||||
* Leesbare rol-labels voor weergave in de admin-interface.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string, string> Map van rol-sleutel naar label.
|
||||
*/
|
||||
public const ROLE_LABELS = [
|
||||
'admin' => 'Admin',
|
||||
@@ -31,6 +76,16 @@ class AdminAuth
|
||||
'site-admin' => 'Site Admin',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor: initialiseer authenticatie met de applicatie-configuratie.
|
||||
*
|
||||
* Laadt de admin-configuratie, bepaalt het pad naar het lockout-bestand en
|
||||
* start de sessie met de juiste security-instellingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $appConfig Applicatie-configuratie; vereist keys 'log_file' en 'admin_config'.
|
||||
*/
|
||||
public function __construct(array $appConfig)
|
||||
{
|
||||
$this->config = $appConfig;
|
||||
@@ -39,6 +94,17 @@ class AdminAuth
|
||||
$this->startSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Laadt de admin-configuratie uit admin.json.
|
||||
*
|
||||
* Als het bestand nog niet bestaat wordt het aangemaakt vanuit
|
||||
* admin.json.example, of indien die ook ontbreekt met een standaard admin-
|
||||
* gebruiker. Geeft een lege structuur terug als de JSON ongeldig is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array Admin-configuratie met keys 'users' en 'security'.
|
||||
*/
|
||||
private function loadAdminConfig(): array
|
||||
{
|
||||
$path = $this->config['admin_config'];
|
||||
@@ -48,11 +114,19 @@ class AdminAuth
|
||||
if (file_exists($examplePath)) {
|
||||
@copy($examplePath, $path);
|
||||
} else {
|
||||
// Genereer een cryptografisch veilig wachtwoord bij eerste
|
||||
// installatie. Het wachtwoord wordt nooit in de broncode
|
||||
// opgeslagen; de hash wordt weggeschreven naar admin.json
|
||||
// en het plaintext wachtwoord éénmalig getoond op het
|
||||
// inlogscherm via getGeneratedPassword().
|
||||
$plainPassword = $this->generateRandomPassword();
|
||||
$this->generatedPassword = $plainPassword;
|
||||
|
||||
$defaultAdminConfig = [
|
||||
'users' => [
|
||||
[
|
||||
'username' => 'admin',
|
||||
'password_hash' => password_hash('admin', PASSWORD_BCRYPT),
|
||||
'password_hash' => password_hash($plainPassword, PASSWORD_BCRYPT),
|
||||
'role' => 'admin',
|
||||
'created' => date('Y-m-d'),
|
||||
]
|
||||
@@ -74,6 +148,55 @@ class AdminAuth
|
||||
return is_array($data) ? $data : ['users' => [], 'security' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Genereer een cryptografisch veilig willekeurig wachtwoord.
|
||||
*
|
||||
* Gebruik random_bytes() (CSPRNG) met een mix van alfanumerieke tekens
|
||||
* en een vaste lengte, zodat het wachtwoord voldoende entropie heeft
|
||||
* en veilig te typen is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Willekeurig wachtwoord van 16 alfanumerieke tekens.
|
||||
*/
|
||||
private function generateRandomPassword(int $length = 16): string
|
||||
{
|
||||
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$max = strlen($alphabet) - 1;
|
||||
$password = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$password .= $alphabet[random_int(0, $max)];
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geef het bij eerste installatie gegenereerde wachtwoord terug.
|
||||
*
|
||||
* De login-pagina gebruikt dit om het wachtwoord eenmalig te tonen
|
||||
* zodra admin.json is aangemaakt. Na de eerste succesvolle login
|
||||
* wordt het veld geleegd. Geeft een lege string terug wanneer er
|
||||
* geen nieuw wachtwoord is gegenereerd in deze request.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Plaintext wachtwoord of lege string.
|
||||
*/
|
||||
public function getGeneratedPassword(): string
|
||||
{
|
||||
return $this->generatedPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slaat de huidige admin-configuratie op naar admin.json.
|
||||
*
|
||||
* Schrijft de interne admin-configuratie weg als pretty-printed JSON met
|
||||
* behoud van unicode-tekens.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function saveAdminConfig(): void
|
||||
{
|
||||
file_put_contents(
|
||||
@@ -82,6 +205,17 @@ class AdminAuth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start de sessie met veilige cookie-parameters en handhaaft de session timeout.
|
||||
*
|
||||
* Stelt cookie-flags in (HttpOnly, SameSite=Strict, Secure bij HTTPS), start
|
||||
* de sessie en beëindigt deze bij inactiviteit langer dan de geconfigureerde
|
||||
* timeout. Vernieuwt de activiteit-timestamp bij geauthenticeerde gebruikers.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function startSession(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
@@ -113,6 +247,20 @@ class AdminAuth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifieert inloggegevens en start een admin-sessie bij succes.
|
||||
*
|
||||
* Controleert eerst of het account niet is vergrendeld door brute-force
|
||||
* protection. Bij een fout wachtwoord wordt een mislukte poging geregistreerd
|
||||
* en gelogd. Bij succes worden mislukte pogingen gewist en de sessie
|
||||
* gevuld met gebruiker, rol en CSRF-token.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $password Wachtwoord (plaintext).
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function login(string $username, string $password): array
|
||||
{
|
||||
// Check brute-force lockout
|
||||
@@ -145,6 +293,16 @@ class AdminAuth
|
||||
return ['success' => true, 'message' => 'Ingelogd.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Logt de huidige gebruiker uit en vernietigt de sessie.
|
||||
*
|
||||
* Maakt de sessie-array leeg, verwijdert het sessie-cookie en vernietigt de
|
||||
* sessie. De uitlogactie wordt gelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$user = $_SESSION['admin_user'] ?? 'unknown';
|
||||
@@ -160,11 +318,28 @@ class AdminAuth
|
||||
$this->log('info', "Uitgelogd: {$user}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Controleert of de huidige bezoeker is ingelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien een admin-gebruiker in de sessie staat.
|
||||
*/
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return isset($_SESSION['admin_user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Geeft de gegevens van de ingelogde gebruiker, verrijkt met profielvelden.
|
||||
*
|
||||
* Levert username en rol uit de sessie en vult deze aan met email,
|
||||
* author_name en author_email uit admin.json indien aanwezig.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array|null Gebruikersdata als array, of null indien niet ingelogd.
|
||||
*/
|
||||
public function getCurrentUser(): ?array
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -186,8 +361,14 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role of the current user.
|
||||
* Returns the override role if set (for testing), otherwise the real role.
|
||||
* Geeft de huidige rol van de ingelogde gebruiker.
|
||||
*
|
||||
* Retourneert de override-rol indien ingesteld (voor test-doeleinden), anders
|
||||
* de werkelijke rol.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Rol-sleutel.
|
||||
*/
|
||||
public function getCurrentRole(): string
|
||||
{
|
||||
@@ -198,7 +379,11 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real role of the current user (ignoring any override).
|
||||
* Geeft de werkelijke rol van de ingelogde gebruiker, eventuele override negerend.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Rol-sleutel.
|
||||
*/
|
||||
public function getRealRole(): string
|
||||
{
|
||||
@@ -206,7 +391,11 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has an active role override.
|
||||
* Controleert of de ingelogde gebruiker een actieve rol-override heeft.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien een override-rol is ingesteld.
|
||||
*/
|
||||
public function hasRoleOverride(): bool
|
||||
{
|
||||
@@ -214,8 +403,16 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily switch to another role for testing purposes.
|
||||
* Only admin users can do this; the override cannot grant more than admin.
|
||||
* Wisselt tijdelijk naar een andere rol voor test-doeleinden.
|
||||
*
|
||||
* Alleen werkelijke admins kunnen wisselen; admins kunnen niet wisselen naar
|
||||
* 'admin' (zinloos) en enkel naar lagere rollen. Slaat de override op in de
|
||||
* sessie. De actie wordt gelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $role Doel-rol-sleutel.
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function switchRole(string $role): array
|
||||
{
|
||||
@@ -240,7 +437,13 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the role override back to the real admin role.
|
||||
* Reset de rol-override terug naar de werkelijke admin-rol.
|
||||
*
|
||||
* Verwijdert de override uit de sessie indien aanwezig en logt de actie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function resetRole(): array
|
||||
{
|
||||
@@ -253,7 +456,15 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has permission to access a route.
|
||||
* Controleert of de huidige gebruiker een bepaalde route mag openen.
|
||||
*
|
||||
* Gebruikt de permissies van de huidige rol; de wildcard '*' geeft toegang
|
||||
* tot alles. Onbekende rollen krijgen enkel 'dashboard' en 'logout'.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $route Route-sleutel die gecontroleerd moet worden.
|
||||
* @return bool True indien de gebruiker de route mag benaderen.
|
||||
*/
|
||||
public function hasPermission(string $route): bool
|
||||
{
|
||||
@@ -268,7 +479,11 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available roles.
|
||||
* Geeft alle beschikbare rollen met hun leesbare labels.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string, string> Map van rol-sleutel naar label.
|
||||
*/
|
||||
public static function getRoles(): array
|
||||
{
|
||||
@@ -276,13 +491,28 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role label.
|
||||
* Geeft het leesbare label voor een rol.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $role Rol-sleutel.
|
||||
* @return string Leesbaar label, of de rol-sleutel zelf indien onbekend.
|
||||
*/
|
||||
public static function getRoleLabel(string $role): string
|
||||
{
|
||||
return self::ROLE_LABELS[$role] ?? $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geeft het huidige CSRF-token, of genereert een nieuw token indien afwezig.
|
||||
*
|
||||
* Slaat een cryptografisch willekeurig token op in de sessie en retourneert
|
||||
* dit voor gebruik in admin-formulieren.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string 64-tekens hexadecimaal CSRF-token.
|
||||
*/
|
||||
public function getCsrfToken(): string
|
||||
{
|
||||
if (!isset($_SESSION['admin_csrf_token'])) {
|
||||
@@ -291,11 +521,31 @@ class AdminAuth
|
||||
return $_SESSION['admin_csrf_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifieert een meegegeven CSRF-token tegen het token in de sessie.
|
||||
*
|
||||
* Gebruikt hash_equals om timing-attacks te voorkomen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $token Token uit het formulier of verzoek.
|
||||
* @return bool True indien het token overeenkomt met het sessie-token.
|
||||
*/
|
||||
public function verifyCsrf(string $token): bool
|
||||
{
|
||||
return isset($_SESSION['admin_csrf_token']) && hash_equals($_SESSION['admin_csrf_token'], $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regeneert het CSRF-token in de sessie.
|
||||
*
|
||||
* Maakt een nieuw cryptografisch willekeurig token aan, nuttig na
|
||||
* privilege-wijzigingen of na het verwerken van een formulier.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function regenerateCsrfToken(): void
|
||||
{
|
||||
$_SESSION['admin_csrf_token'] = bin2hex(random_bytes(32));
|
||||
@@ -303,6 +553,16 @@ class AdminAuth
|
||||
|
||||
// --- User Management ---
|
||||
|
||||
/**
|
||||
* Geeft alle gebruikers met hun profiel- en rolinformatie.
|
||||
*
|
||||
* Levert een array geïndexeerd op gebruikersnaam, aangevuld met het
|
||||
* leesbare rol-label en eventuele profielvelden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string, array> Map van gebruikersnaam naar gebruikersdata.
|
||||
*/
|
||||
public function getUsers(): array
|
||||
{
|
||||
$users = [];
|
||||
@@ -321,6 +581,23 @@ class AdminAuth
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Voegt een nieuwe gebruiker toe aan de admin-configuratie.
|
||||
*
|
||||
* Controleert op dubbele gebruikersnamen, minimum wachtwoordlengte (8) en
|
||||
* geldigheid van de rol. Het wachtwoord wordt opgeslagen als bcrypt-hash.
|
||||
* Wijzigingen worden direct weggeschreven en gelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $password Wachtwoord (plaintext, minimaal 8 tekens).
|
||||
* @param string $role Rol-sleutel. Default 'admin'.
|
||||
* @param string $email E-mailadres van de gebruiker. Default ''.
|
||||
* @param string $authorName Naam voor auteur-metadata. Default ''.
|
||||
* @param string $authorEmail E-mail voor auteur-metadata. Default ''.
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function addUser(string $username, string $password, string $role = 'admin', string $email = '', string $authorName = '', string $authorEmail = ''): array
|
||||
{
|
||||
if ($this->findUser($username)) {
|
||||
@@ -348,7 +625,18 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the profile (email, author_name, author_email) of a user.
|
||||
* Werkt de profielvelden (email, author_name, author_email) van een gebruiker bij.
|
||||
*
|
||||
* Zoekt de gebruiker op naam en overschrijft de profielvelden, slaat de
|
||||
* configuratie op en logt de wijziging.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $email E-mailadres. Default ''.
|
||||
* @param string $authorName Naam voor auteur-metadata. Default ''.
|
||||
* @param string $authorEmail E-mail voor auteur-metadata. Default ''.
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function updateUserProfile(string $username, string $email = '', string $authorName = '', string $authorEmail = ''): array
|
||||
{
|
||||
@@ -366,8 +654,17 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the role of an existing user.
|
||||
* A user can never change their own role (security guard).
|
||||
* Wijzigt de rol van een bestaande gebruiker.
|
||||
*
|
||||
* Een gebruiker kan nooit zijn eigen rol wijzigen (security guard). De rol
|
||||
* moet een bekende sleutel uit ROLE_PERMISSIONS zijn. Wijzigingen worden
|
||||
* opgeslagen en gelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $role Nieuwe rol-sleutel.
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function changeRole(string $username, string $role): array
|
||||
{
|
||||
@@ -388,6 +685,17 @@ class AdminAuth
|
||||
return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijdert een gebruiker uit de admin-configuratie.
|
||||
*
|
||||
* Een gebruiker kan zichzelf niet verwijderen. De gebruikerslijst wordt
|
||||
* gefilterd, opgeslagen en de actie gelogd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function deleteUser(string $username): array
|
||||
{
|
||||
if ($username === ($_SESSION['admin_user'] ?? '')) {
|
||||
@@ -403,6 +711,18 @@ class AdminAuth
|
||||
return ['success' => true, 'message' => 'Gebruiker verwijderd.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zet een nieuw wachtwoord voor een gebruiker.
|
||||
*
|
||||
* Controleert de minimum wachtwoordlengte (8), hasht het wachtwoord met de
|
||||
* standaard PHP-hash en slaat de wijziging op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $newPassword Nieuw wachtwoord (plaintext, minimaal 8 tekens).
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function changePassword(string $username, string $newPassword): array
|
||||
{
|
||||
if (strlen($newPassword) < 8) {
|
||||
@@ -419,6 +739,19 @@ class AdminAuth
|
||||
return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wijzigt het eigen wachtwoord na verificatie van het huidige wachtwoord.
|
||||
*
|
||||
* Verifieert eerst het huidige wachtwoord tegen de opgeslagen hash, controleert
|
||||
* de lengte van het nieuwe wachtwoord (minimaal 8) en slaat de nieuwe hash op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @param string $currentPassword Huidig wachtwoord (plaintext).
|
||||
* @param string $newPassword Nieuw wachtwoord (plaintext, minimaal 8 tekens).
|
||||
* @return array Resultaat met keys 'success' (bool) en 'message' (string).
|
||||
*/
|
||||
public function changeOwnPassword(string $username, string $currentPassword, string $newPassword): array
|
||||
{
|
||||
$user = $this->findUser($username);
|
||||
@@ -444,6 +777,14 @@ class AdminAuth
|
||||
|
||||
// --- Private helpers ---
|
||||
|
||||
/**
|
||||
* Zoekt een gebruiker op gebruikersnaam in de admin-configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return array|null Gebruikersdata als array, of null indien niet gevonden.
|
||||
*/
|
||||
private function findUser(string $username): ?array
|
||||
{
|
||||
foreach ($this->adminConfig['users'] ?? [] as $user) {
|
||||
@@ -454,6 +795,18 @@ class AdminAuth
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controleert of een gebruiker momenteel is vergrendeld door brute-force protection.
|
||||
*
|
||||
* Vergelijkt het aantal mislukte pogingen en de verstreken tijd sinds de
|
||||
* laatste poging met de geconfigureerde limieten. Verloopt de lockout dan
|
||||
* worden de pogingen gewist.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return array Resultaat met keys 'locked' (bool) en optioneel 'remaining' (int, seconden).
|
||||
*/
|
||||
private function checkLockout(string $username): array
|
||||
{
|
||||
$attempts = $this->getFailedAttempts();
|
||||
@@ -477,6 +830,17 @@ class AdminAuth
|
||||
return ['locked' => false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registreert een mislukte inlogpoging voor een gebruiker.
|
||||
*
|
||||
* Verhoogt de teller en actualiseert de timestamp van de laatste poging,
|
||||
* waarna de gegevens worden weggeschreven naar het lockout-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return void
|
||||
*/
|
||||
private function recordFailedAttempt(string $username): void
|
||||
{
|
||||
$attempts = $this->getFailedAttempts();
|
||||
@@ -488,6 +852,16 @@ class AdminAuth
|
||||
file_put_contents($this->lockFile, json_encode($attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wist alle mislukte inlogpogingen voor een gebruiker.
|
||||
*
|
||||
* Verwijdert de gebruiker uit het lockout-bestand en slaat dit op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return void
|
||||
*/
|
||||
private function clearFailedAttempts(string $username): void
|
||||
{
|
||||
$attempts = $this->getFailedAttempts();
|
||||
@@ -496,13 +870,28 @@ class AdminAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all failed login attempts for a user (public, used by CLI reset).
|
||||
* Wist de lockout voor een gebruiker (publiek, gebruikt door CLI reset).
|
||||
*
|
||||
* Wrapper rond clearFailedAttempts die ook van buiten de class kan worden
|
||||
* aangeroepen, bijvoorbeeld door een CLI-tool voor het resetten van vergrendelingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $username Gebruikersnaam.
|
||||
* @return void
|
||||
*/
|
||||
public function clearLockout(string $username): void
|
||||
{
|
||||
$this->clearFailedAttempts($username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leest alle mislukte inlogpogingen uit het lockout-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string, array{count: int, last_attempt: int}> Map van gebruikersnaam naar poging-data.
|
||||
*/
|
||||
private function getFailedAttempts(): array
|
||||
{
|
||||
if (!file_exists($this->lockFile)) {
|
||||
@@ -512,6 +901,18 @@ class AdminAuth
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schrijft een bericht naar het logbestand met timestamp, level en IP-adres.
|
||||
*
|
||||
* Maakt de logdirectory aan indien nodig. Het IP-adres wordt via RequestLogger
|
||||
* bepaald indien beschikbaar, anders via REMOTE_ADDR met een fallback.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $level Log-niveau (bijv. 'info', 'warning').
|
||||
* @param string $message Bericht dat gelogd moet worden.
|
||||
* @return void
|
||||
*/
|
||||
private function log(string $level, string $message): void
|
||||
{
|
||||
$logFile = $this->config['log_file'];
|
||||
|
||||
Reference in New Issue
Block a user