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