This commit is contained in:
@@ -818,8 +818,25 @@ function updatePasswordStrength() {
|
|||||||
data.domains.forEach(d => {
|
data.domains.forEach(d => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'list-group-item d-flex justify-content-between align-items-center';
|
item.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||||
item.innerHTML = '<span><strong>' + d.domain + '</strong><br><small class="text-muted">Approved: ' + d.confirmed_at + '</small></span>' +
|
|
||||||
'<button class="btn btn-sm btn-outline-danger" onclick="removeDomain(' + d.id + ')">Revoke</button>';
|
const details = document.createElement('span');
|
||||||
|
const domain = document.createElement('strong');
|
||||||
|
domain.textContent = d.domain;
|
||||||
|
const approvedAt = document.createElement('small');
|
||||||
|
approvedAt.className = 'text-muted';
|
||||||
|
approvedAt.textContent = 'Approved: ' + d.confirmed_at;
|
||||||
|
details.appendChild(domain);
|
||||||
|
details.appendChild(document.createElement('br'));
|
||||||
|
details.appendChild(approvedAt);
|
||||||
|
|
||||||
|
const revokeButton = document.createElement('button');
|
||||||
|
revokeButton.type = 'button';
|
||||||
|
revokeButton.className = 'btn btn-sm btn-outline-danger';
|
||||||
|
revokeButton.textContent = 'Revoke';
|
||||||
|
revokeButton.addEventListener('click', () => removeDomain(Number(d.id)));
|
||||||
|
|
||||||
|
item.appendChild(details);
|
||||||
|
item.appendChild(revokeButton);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -881,9 +898,30 @@ function updatePasswordStrength() {
|
|||||||
'sessions_revoked': 'Sessions revoked',
|
'sessions_revoked': 'Sessions revoked',
|
||||||
};
|
};
|
||||||
const label = actionLabels[e.action] || e.action;
|
const label = actionLabels[e.action] || e.action;
|
||||||
item.innerHTML = '<div class="d-flex w-100 justify-content-between"><strong>' + label + '</strong><small class="text-muted">' + e.created_at + '</small></div>' +
|
|
||||||
'<small class="text-muted">' + (e.ip ? e.ip + ' · ' : '') + (e.user_agent ? e.user_agent.substring(0, 60) + '...' : '') + '</small>' +
|
const header = document.createElement('div');
|
||||||
(e.details ? '<br><small>' + e.details + '</small>' : '');
|
header.className = 'd-flex w-100 justify-content-between';
|
||||||
|
const action = document.createElement('strong');
|
||||||
|
action.textContent = label;
|
||||||
|
const createdAt = document.createElement('small');
|
||||||
|
createdAt.className = 'text-muted';
|
||||||
|
createdAt.textContent = e.created_at;
|
||||||
|
header.appendChild(action);
|
||||||
|
header.appendChild(createdAt);
|
||||||
|
|
||||||
|
const metadata = document.createElement('small');
|
||||||
|
metadata.className = 'text-muted';
|
||||||
|
metadata.textContent = (e.ip ? e.ip + ' - ' : '') + (e.user_agent ? e.user_agent.substring(0, 60) + '...' : '');
|
||||||
|
|
||||||
|
item.appendChild(header);
|
||||||
|
item.appendChild(metadata);
|
||||||
|
|
||||||
|
if (e.details) {
|
||||||
|
const details = document.createElement('small');
|
||||||
|
details.textContent = e.details;
|
||||||
|
item.appendChild(document.createElement('br'));
|
||||||
|
item.appendChild(details);
|
||||||
|
}
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -904,7 +942,11 @@ function updatePasswordStrength() {
|
|||||||
data.sessions.forEach(s => {
|
data.sessions.forEach(s => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'list-group-item d-flex justify-content-between align-items-center';
|
item.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||||
item.innerHTML = '<span><strong>' + (s.user_agent || 'Unknown device') + '</strong></span>';
|
const device = document.createElement('span');
|
||||||
|
const deviceName = document.createElement('strong');
|
||||||
|
deviceName.textContent = s.user_agent || 'Unknown device';
|
||||||
|
device.appendChild(deviceName);
|
||||||
|
item.appendChild(device);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ if ($method === 'GET') {
|
|||||||
$result = mysqli_stmt_get_result($stmt);
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
$domains = [];
|
$domains = [];
|
||||||
while ($row = mysqli_fetch_assoc($result)) {
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$domain = normalize_redirect_host($row['domain'] ?? '');
|
||||||
|
if ($domain === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$row['domain'] = $domain;
|
||||||
|
$row['id'] = (int) $row['id'];
|
||||||
$domains[] = $row;
|
$domains[] = $row;
|
||||||
}
|
}
|
||||||
mysqli_stmt_close($stmt);
|
mysqli_stmt_close($stmt);
|
||||||
@@ -45,4 +51,4 @@ if ($method === 'GET') {
|
|||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid request method.'], 405);
|
echo json_encode(['success' => false, 'message' => 'Invalid request method.'], 405);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
$send_to = normalize_redirect_target($_SESSION["end_url"] ?? "/account/");
|
||||||
$domain = $input['domain'] ?? '';
|
$domain = is_external_domain($send_to);
|
||||||
|
|
||||||
if ($domain === '' || !isset($_SESSION['id'])) {
|
if ($domain === null || !isset($_SESSION['id'])) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Missing domain or not logged in.']);
|
echo json_encode(['success' => false, 'message' => 'Missing external domain or not logged in.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,4 +26,4 @@ mysqli_stmt_bind_param($stmt, 'is', $user_id, $domain);
|
|||||||
mysqli_stmt_execute($stmt);
|
mysqli_stmt_execute($stmt);
|
||||||
mysqli_stmt_close($stmt);
|
mysqli_stmt_close($stmt);
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
|
|||||||
@@ -282,6 +282,10 @@ function normalize_redirect_target(?string $target): string
|
|||||||
return '/account/';
|
return '/account/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalize_redirect_host($parts['host']) === null) {
|
||||||
|
return '/account/';
|
||||||
|
}
|
||||||
|
|
||||||
return $target;
|
return $target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,12 +314,11 @@ function is_external_domain(string $url): ?string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = normalize_redirect_host((string) parse_url($url, PHP_URL_HOST));
|
||||||
if ($host === null || $host === '') {
|
if ($host === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = strtolower($host);
|
|
||||||
if ($host === 'auth.jakach.ch' || str_ends_with($host, '.jakach.ch')) {
|
if ($host === 'auth.jakach.ch' || str_ends_with($host, '.jakach.ch')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -323,4 +326,23 @@ function is_external_domain(string $url): ?string
|
|||||||
return $host;
|
return $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalize_redirect_host(string $host): ?string
|
||||||
|
{
|
||||||
|
$host = rtrim(strtolower(trim($host)), '.');
|
||||||
|
|
||||||
|
if ($host === '' || strlen($host) > 253 || preg_match('/[\x00-\x20\x7f<>"\'`]/', $host)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($host, FILTER_VALIDATE_IP)) {
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user