256 lines
8.9 KiB
PHP
256 lines
8.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Access-Control-Allow-Methods: POST, DELETE, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/session_bootstrap.php';
|
|
}
|
|
|
|
require_once __DIR__ . '/../../../../administration/includes/config.php';
|
|
|
|
function blockedNamesRespond(array $payload, int $status): void
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function getAuthorizationToken(): ?string
|
|
{
|
|
$header = '';
|
|
|
|
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
|
$header = trim((string)$_SERVER['HTTP_AUTHORIZATION']);
|
|
} elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
|
$header = trim((string)$_SERVER['REDIRECT_HTTP_AUTHORIZATION']);
|
|
}
|
|
|
|
if ($header === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^Bearer\s+(.+)$/i', $header, $matches)) {
|
|
return trim($matches[1]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function tableExists(PDO $pdo, string $schema, string $table): bool
|
|
{
|
|
$stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table');
|
|
$stmt->execute([
|
|
':schema' => $schema,
|
|
':table' => $table,
|
|
]);
|
|
|
|
return (int)$stmt->fetchColumn() > 0;
|
|
}
|
|
|
|
function getTableColumns(PDO $pdo, string $schema, string $table): array
|
|
{
|
|
$stmt = $pdo->prepare('SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table');
|
|
$stmt->execute([
|
|
':schema' => $schema,
|
|
':table' => $table,
|
|
]);
|
|
|
|
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
return is_array($columns) ? $columns : [];
|
|
}
|
|
|
|
function resolveUserIdFromBearer(PDO $pdo, string $rawToken): ?int
|
|
{
|
|
$token = trim($rawToken);
|
|
if ($token === '') {
|
|
return null;
|
|
}
|
|
|
|
$schema = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
|
|
if ($schema === '') {
|
|
return null;
|
|
}
|
|
|
|
$candidates = [
|
|
['table' => 'remember_tokens', 'user' => 'user_id', 'token' => 'token', 'expires' => 'expires_at', 'revoked' => null],
|
|
['table' => 'user_tokens', 'user' => 'user_id', 'token' => 'token', 'expires' => 'expires_at', 'revoked' => 'revoked_at'],
|
|
['table' => 'api_tokens', 'user' => 'user_id', 'token' => 'token', 'expires' => 'expires_at', 'revoked' => 'revoked_at'],
|
|
['table' => 'access_tokens', 'user' => 'user_id', 'token' => 'token', 'expires' => 'expires_at', 'revoked' => 'revoked_at'],
|
|
['table' => 'auth_tokens', 'user' => 'user_id', 'token' => 'token', 'expires' => 'expires_at', 'revoked' => 'revoked_at'],
|
|
];
|
|
|
|
$hashes = [$token, hash('sha256', $token)];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (!tableExists($pdo, $schema, $candidate['table'])) {
|
|
continue;
|
|
}
|
|
|
|
$columns = getTableColumns($pdo, $schema, $candidate['table']);
|
|
if (!in_array($candidate['user'], $columns, true) || !in_array($candidate['token'], $columns, true)) {
|
|
continue;
|
|
}
|
|
|
|
$sql = 'SELECT `' . $candidate['user'] . '` AS user_id FROM `' . $candidate['table'] . '` '
|
|
. 'WHERE `' . $candidate['token'] . '` IN (:raw, :sha)';
|
|
|
|
if ($candidate['expires'] !== null && in_array($candidate['expires'], $columns, true)) {
|
|
$sql .= ' AND (`' . $candidate['expires'] . '` IS NULL OR `' . $candidate['expires'] . '` > NOW())';
|
|
}
|
|
if ($candidate['revoked'] !== null && in_array($candidate['revoked'], $columns, true)) {
|
|
$sql .= ' AND `' . $candidate['revoked'] . '` IS NULL';
|
|
}
|
|
|
|
$sql .= ' ORDER BY user_id DESC LIMIT 1';
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute([
|
|
':raw' => $hashes[0],
|
|
':sha' => $hashes[1],
|
|
]);
|
|
|
|
$userId = (int)($stmt->fetchColumn() ?: 0);
|
|
if ($userId > 0) {
|
|
return $userId;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function resolveAdminUserId(PDO $pdo): ?int
|
|
{
|
|
if (!empty($_SESSION['logged_in']) && !empty($_SESSION['role']) && $_SESSION['role'] === 'admin') {
|
|
$sessionUserId = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
|
|
if ($sessionUserId > 0) {
|
|
return $sessionUserId;
|
|
}
|
|
|
|
$sessionUsername = isset($_SESSION['username']) ? trim((string)$_SESSION['username']) : '';
|
|
if ($sessionUsername !== '') {
|
|
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = :u AND role = :role LIMIT 1');
|
|
$stmt->execute([
|
|
':u' => $sessionUsername,
|
|
':role' => 'admin',
|
|
]);
|
|
$resolvedId = (int)($stmt->fetchColumn() ?: 0);
|
|
if ($resolvedId > 0) {
|
|
$_SESSION['user_id'] = $resolvedId;
|
|
return $resolvedId;
|
|
}
|
|
}
|
|
}
|
|
|
|
$token = getAuthorizationToken();
|
|
if ($token === null) {
|
|
return null;
|
|
}
|
|
|
|
$tokenUserId = resolveUserIdFromBearer($pdo, $token);
|
|
if ($tokenUserId === null) {
|
|
return null;
|
|
}
|
|
|
|
$stmt = $pdo->prepare('SELECT id FROM users WHERE id = :id AND role = :role LIMIT 1');
|
|
$stmt->execute([
|
|
':id' => $tokenUserId,
|
|
':role' => 'admin',
|
|
]);
|
|
|
|
$adminId = (int)($stmt->fetchColumn() ?: 0);
|
|
return $adminId > 0 ? $adminId : null;
|
|
}
|
|
|
|
function ensureBlockedUsernamesTable(PDO $pdo): void
|
|
{
|
|
$pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS blocked_usernames (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(20) NOT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by INT NULL,
|
|
UNIQUE KEY unique_blocked_username (name),
|
|
KEY idx_blocked_created_by (created_by)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
|
);
|
|
}
|
|
|
|
$requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
if (!in_array($requestMethod, ['POST', 'DELETE'], true)) {
|
|
blockedNamesRespond(['message' => 'Metoda niedozwolona. Użyj POST lub DELETE.'], 405);
|
|
}
|
|
|
|
if (!isset($pdo) || !($pdo instanceof PDO)) {
|
|
blockedNamesRespond(['message' => 'Błąd połączenia z bazą danych.'], 500);
|
|
}
|
|
|
|
$adminId = resolveAdminUserId($pdo);
|
|
if ($adminId === null) {
|
|
blockedNamesRespond(['message' => 'Brak autoryzacji administratora.'], 401);
|
|
}
|
|
|
|
$rawBody = file_get_contents('php://input');
|
|
$body = json_decode((string)$rawBody, true);
|
|
if (!is_array($body)) {
|
|
blockedNamesRespond(['message' => 'Nieprawidłowy JSON.'], 400);
|
|
}
|
|
|
|
try {
|
|
ensureBlockedUsernamesTable($pdo);
|
|
|
|
if ($requestMethod === 'DELETE') {
|
|
$blockedId = (int)($body['id'] ?? 0);
|
|
if ($blockedId <= 0) {
|
|
blockedNamesRespond(['message' => 'Nieprawidłowy identyfikator blokady.'], 400);
|
|
}
|
|
|
|
$deleteStmt = $pdo->prepare('DELETE FROM blocked_usernames WHERE id = :id LIMIT 1');
|
|
$deleteStmt->execute([':id' => $blockedId]);
|
|
|
|
if ($deleteStmt->rowCount() < 1) {
|
|
blockedNamesRespond(['message' => 'Nie znaleziono wskazanej zablokowanej nazwy.'], 404);
|
|
}
|
|
|
|
blockedNamesRespond(['message' => 'Nazwa użytkownika została usunięta z listy zablokowanych.'], 200);
|
|
}
|
|
|
|
$name = trim((string)($body['name'] ?? ''));
|
|
if ($name === '') {
|
|
blockedNamesRespond(['message' => 'Nazwa użytkownika nie może być pusta.'], 400);
|
|
}
|
|
|
|
if (!preg_match('/^[A-Za-z0-9_&!]{1,20}$/', $name)) {
|
|
blockedNamesRespond(['message' => 'Niepoprawny format nazwy użytkownika.'], 400);
|
|
}
|
|
|
|
$existsBlockedStmt = $pdo->prepare('SELECT id FROM blocked_usernames WHERE name = :name LIMIT 1');
|
|
$existsBlockedStmt->execute([':name' => $name]);
|
|
$alreadyBlocked = (bool)$existsBlockedStmt->fetchColumn();
|
|
|
|
$existsUserStmt = $pdo->prepare('SELECT id FROM users WHERE username = :name AND (disabled IS NULL OR disabled = 0) LIMIT 1');
|
|
$existsUserStmt->execute([':name' => $name]);
|
|
$alreadyUser = (bool)$existsUserStmt->fetchColumn();
|
|
|
|
if ($alreadyBlocked || $alreadyUser) {
|
|
blockedNamesRespond(['message' => 'Nazwa jest już zablokowana lub istnieje jako aktywny użytkownik.'], 409);
|
|
}
|
|
|
|
$insertStmt = $pdo->prepare('INSERT INTO blocked_usernames (name, created_by) VALUES (:name, :created_by)');
|
|
$insertStmt->execute([
|
|
':name' => $name,
|
|
':created_by' => $adminId,
|
|
]);
|
|
|
|
blockedNamesRespond(['message' => 'Nazwa użytkownika została zablokowana pomyślnie.'], 201);
|
|
} catch (Throwable $e) {
|
|
blockedNamesRespond(['message' => 'Błąd serwera podczas zapisu blokady.'], 500);
|
|
}
|