feat: auth system - login/logout/session with rate limiting
Dieser Commit ist enthalten in:
Ursprung
581888e142
Commit
1c59b667f2
2 geänderte Dateien mit 88 neuen und 0 gelöschten Zeilen
66
edu/api/auth.php
Normale Datei
66
edu/api/auth.php
Normale Datei
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
// /api/login, /api/logout, /api/me
|
||||
|
||||
if ($resource === 'login' && get_method() === 'POST') {
|
||||
$body = get_json_body();
|
||||
$username = trim($body['username'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
|
||||
if (!$username || !$password) {
|
||||
json_error('Benutzername und Passwort erforderlich');
|
||||
}
|
||||
|
||||
// Rate limiting via session
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$key = 'login_attempts_' . $ip;
|
||||
$attempts = $_SESSION[$key] ?? ['count' => 0, 'first' => time()];
|
||||
if (time() - $attempts['first'] > 60) {
|
||||
$attempts = ['count' => 0, 'first' => time()];
|
||||
}
|
||||
if ($attempts['count'] >= 5) {
|
||||
json_error('Zu viele Anmeldeversuche. Bitte eine Minute warten.', 429);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id, username, password_hash, display_name, is_admin FROM users WHERE username = :u");
|
||||
$stmt->execute([':u' => $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
$attempts['count']++;
|
||||
$_SESSION[$key] = $attempts;
|
||||
json_error('Benutzername oder Passwort falsch', 401);
|
||||
}
|
||||
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['is_admin'] = $user['is_admin'];
|
||||
unset($_SESSION[$key]);
|
||||
|
||||
json_ok(['user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'display_name' => $user['display_name'],
|
||||
'is_admin' => (bool) $user['is_admin']
|
||||
]]);
|
||||
}
|
||||
|
||||
if ($resource === 'logout' && get_method() === 'POST') {
|
||||
session_destroy();
|
||||
json_ok(['ok' => true]);
|
||||
}
|
||||
|
||||
if ($resource === 'me' && get_method() === 'GET') {
|
||||
$user_id = require_auth();
|
||||
$stmt = $pdo->prepare("SELECT id, username, display_name, is_admin FROM users WHERE id = :id");
|
||||
$stmt->execute([':id' => $user_id]);
|
||||
$user = $stmt->fetch();
|
||||
if (!$user) { session_destroy(); json_error('Benutzer nicht gefunden', 401); }
|
||||
json_ok(['user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'display_name' => $user['display_name'],
|
||||
'is_admin' => (bool) $user['is_admin']
|
||||
]]);
|
||||
}
|
||||
|
||||
json_error('Unbekannte Auth-Aktion', 404);
|
||||
22
edu/js/auth.js
Normale Datei
22
edu/js/auth.js
Normale Datei
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
var Auth = {
|
||||
handleLogin: function(e) {
|
||||
e.preventDefault();
|
||||
var user = document.getElementById('login-user').value.trim();
|
||||
var pass = document.getElementById('login-pass').value;
|
||||
var errorEl = document.getElementById('login-error');
|
||||
errorEl.style.display = 'none';
|
||||
|
||||
API.post('/login', { username: user, password: pass })
|
||||
.then(function(data) { App.setUser(data.user); })
|
||||
.catch(function(err) {
|
||||
errorEl.textContent = err.message || 'Anmeldung fehlgeschlagen';
|
||||
errorEl.style.display = 'block';
|
||||
});
|
||||
},
|
||||
|
||||
handleLogout: function() {
|
||||
API.post('/logout').catch(function() {}).then(function() { App.showLogin(); });
|
||||
}
|
||||
};
|
||||
Laden …
Tabelle hinzufügen
Einen Link hinzufügen
In neuem Issue referenzieren