Control 보안 정책 설정 확장
This commit is contained in:
+27
-4
@@ -6,6 +6,8 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
$csrf = csrf_token();
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
clear_remember_token();
|
||||
$_SESSION = [];
|
||||
@@ -20,9 +22,24 @@ if (isset($_GET['logout'])) {
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_password'])) {
|
||||
if (hash_equals(APP_PASSWORD, (string)$_POST['login_password'])) {
|
||||
$now = time();
|
||||
$failures = array_values(array_filter(
|
||||
(array)($_SESSION['login_failures'] ?? []),
|
||||
static fn($ts): bool => is_numeric($ts) && $now - (int)$ts <= (int)setting_value('security.login_window_seconds')
|
||||
));
|
||||
$_SESSION['login_failures'] = $failures;
|
||||
$lockedUntil = (int)($_SESSION['login_locked_until'] ?? 0);
|
||||
$csrfOk = !(bool)setting_value('security.login_csrf_required')
|
||||
|| hash_equals($csrf, (string)($_POST['csrf'] ?? ''));
|
||||
|
||||
if ($lockedUntil > $now) {
|
||||
$error = '로그인 잠금 중입니다. 잠시 후 다시 시도하세요.';
|
||||
} elseif (!$csrfOk) {
|
||||
$error = '보안 토큰이 올바르지 않습니다.';
|
||||
} elseif (hash_equals(APP_PASSWORD, (string)$_POST['login_password'])) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['control_login'] = true;
|
||||
unset($_SESSION['login_failures'], $_SESSION['login_locked_until']);
|
||||
if (!empty($_POST['remember_login'])) {
|
||||
issue_remember_token();
|
||||
} else {
|
||||
@@ -30,12 +47,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_password'])) {
|
||||
}
|
||||
header('Location: /');
|
||||
exit;
|
||||
} else {
|
||||
$failures[] = $now;
|
||||
$_SESSION['login_failures'] = $failures;
|
||||
if (count($failures) >= (int)setting_value('security.login_max_failures')) {
|
||||
$_SESSION['login_locked_until'] = $now + (int)setting_value('security.login_lock_seconds');
|
||||
}
|
||||
$error = '비밀번호가 올바르지 않습니다.';
|
||||
}
|
||||
$error = '비밀번호가 올바르지 않습니다.';
|
||||
}
|
||||
|
||||
$loggedIn = signed_in();
|
||||
$csrf = csrf_token();
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
@@ -113,7 +135,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
||||
.notice{position:fixed;right:20px;bottom:20px;min-width:220px;max-width:420px;padding:14px 16px;border-radius:14px;font-weight:500;background:var(--table-head);color:var(--text);border:1px solid var(--line);box-shadow:var(--shadow);z-index:99}.notice:empty{display:none}.notice[data-type=success]{border-color:rgba(53,196,107,.5)}.notice[data-type=error]{border-color:rgba(255,95,87,.5)}
|
||||
.status-value.interactive{cursor:help;text-decoration:underline;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:4px}.runtime-tooltip{position:fixed;left:0;top:0;z-index:110;display:none;width:min(520px,calc(100vw - 28px));max-height:calc(100vh - 28px);overflow:hidden;padding:14px 16px;border:1px solid rgba(84,101,128,.78);border-radius:16px;background:var(--card);color:var(--text);box-shadow:0 18px 48px rgba(0,0,0,.38);font-size:13px;line-height:1.55;white-space:pre-line;word-break:keep-all;overflow-wrap:anywhere;pointer-events:none}.runtime-tooltip[data-open="1"]{display:block}.runtime-tooltip::before{content:"";position:absolute;left:18px;top:-7px;width:12px;height:12px;border-left:1px solid rgba(84,101,128,.78);border-top:1px solid rgba(84,101,128,.78);background:var(--card);transform:rotate(45deg)}html[data-theme="light"] .runtime-tooltip{background:#f8fbff;border-color:#aebed2;color:#172033;box-shadow:0 18px 44px rgba(28,43,64,.18)}html[data-theme="light"] .runtime-tooltip::before{background:#f8fbff;border-color:#aebed2}
|
||||
.dialog-layer{position:fixed;inset:0;display:none;align-items:center;justify-content:center;padding:22px;background:rgba(4,7,12,.68);backdrop-filter:blur(8px);z-index:120}.dialog-layer[data-open="1"]{display:flex}.dialog-box{width:min(430px,100%);border:1px solid rgba(84,101,128,.78);border-radius:20px;background:var(--card);box-shadow:0 24px 70px rgba(0,0,0,.42);padding:20px}.dialog-box h3{margin:0 0 8px;font-size:19px;font-weight:500}.dialog-message{margin:0 0 16px;color:var(--sub);line-height:1.5;white-space:pre-wrap}.dialog-input{width:100%;height:48px;margin-bottom:14px;border-radius:14px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 14px;outline:none}.dialog-input:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px}.dialog-actions .btn{min-width:84px}.dialog-actions .btn.red{background:#c62828}
|
||||
.settings-layer{align-items:stretch}.settings-box{width:min(980px,100%);max-height:calc(100vh - 44px);display:grid;grid-template-rows:auto minmax(0,1fr) auto;padding:0;overflow:hidden}.settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line)}.settings-sub{margin:0;color:var(--sub);font-size:13px}.settings-body{overflow:auto;padding:16px 20px;display:grid;gap:14px}.settings-group{border:1px solid var(--line);border-radius:16px;background:var(--card2);padding:14px}.settings-group h4{margin:0 0 12px;font-size:15px}.settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.settings-field{display:grid;gap:7px;min-width:0}.settings-label{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--sub);font-size:13px}.settings-label span:last-child{white-space:nowrap;color:var(--sub)}.settings-control{display:flex;align-items:center;gap:9px}.settings-control input{width:100%;height:42px;border-radius:12px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 12px;outline:none}.settings-control input:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.settings-default{font-size:12px;color:var(--sub);white-space:nowrap}.settings-actions{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--line)}
|
||||
.settings-layer{align-items:stretch}.settings-box{width:min(980px,100%);max-height:calc(100vh - 44px);display:grid;grid-template-rows:auto minmax(0,1fr) auto;padding:0;overflow:hidden}.settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid var(--line)}.settings-sub{margin:0;color:var(--sub);font-size:13px}.settings-body{overflow:auto;padding:16px 20px;display:grid;gap:14px}.settings-group{border:1px solid var(--line);border-radius:16px;background:var(--card2);padding:14px}.settings-group h4{margin:0 0 12px;font-size:15px}.settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.settings-field{display:grid;gap:7px;min-width:0}.settings-label{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--sub);font-size:13px}.settings-label span:last-child{white-space:nowrap;color:var(--sub)}.settings-control{display:flex;align-items:center;gap:9px}.settings-control input,.settings-select{width:100%;height:42px;border-radius:12px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 12px;outline:none}.settings-control input:focus,.settings-select:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.settings-toggle{display:flex;align-items:center;width:100%;height:42px;border:1px solid var(--line);border-radius:12px;background:var(--input);padding:4px;cursor:pointer}.settings-toggle input{position:absolute;opacity:0;pointer-events:none}.settings-toggle span{display:grid;place-items:center;width:72px;height:32px;border-radius:10px;background:#334155;color:#cbd5e1;font-weight:700;font-size:12px}.settings-toggle input:checked+span{background:var(--blue);color:#fff}.settings-default{font-size:12px;color:var(--sub);white-space:nowrap}.settings-actions{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--line)}
|
||||
@media(max-width:1320px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
@media(max-width:1100px){.layout{grid-template-columns:1fr}.chart-grid,.resource-grid{grid-template-columns:1fr}.topbar{align-items:flex-start;flex-direction:column}.topbar-right{width:100%}.topbar-right .btn{flex:1}.stat-grid{grid-template-columns:1fr}.resource-head{align-items:flex-start;flex-direction:column}.baseline-pill{text-align:left;white-space:normal}}
|
||||
@media(max-width:720px){.settings-grid{grid-template-columns:1fr}.settings-head{align-items:stretch;flex-direction:column}.settings-actions{display:grid;grid-template-columns:1fr 1fr}.settings-box{max-height:calc(100vh - 24px)}}
|
||||
@@ -126,6 +148,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
||||
<h1><?= e(APP_NAME) ?></h1>
|
||||
<p>Fan and WiFi control panel</p>
|
||||
<input type="password" name="login_password" class="input" placeholder="Password" autofocus>
|
||||
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
|
||||
<label class="remember-row">
|
||||
<input type="checkbox" name="remember_login" value="1" checked>
|
||||
<span>자동로그인 유지</span>
|
||||
|
||||
Reference in New Issue
Block a user