$value) { if (is_scalar($value) || $value === null) { $safeDetail[(string)$key] = $value; } } $record = [ 'time' => date('c'), 'event' => $event, 'username' => substr($username, 0, 100), 'success' => $success, 'ip' => admin_client_ip(), 'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255), 'detail' => $safeDetail, ]; @file_put_contents( $dir . '/admin_audit.log', json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, FILE_APPEND | LOCK_EX ); } function admin_audit_log_path(): string { return __DIR__ . '/var/admin_audit.log'; } function admin_mfa_configured(): bool { return defined('ADMIN_TOTP_SECRET') && ADMIN_TOTP_SECRET !== ''; } // 인증 페이지 공통: 세션 유휴(idle) + 절대(absolute) 수명 타임아웃을 강제한다. // 모든 인증 페이지가 logged_in 확인 직후 호출해야 한다(대시보드 외 페이지에서도 // 절대 수명이 보장되도록 — 한 페이지에서만 검사하면 다른 페이지로 우회 가능). // 만료 시 세션을 파기하고 login.php?expired 로 보낸 뒤 종료한다. // - 유휴(idle): 마지막 활동(last_activity)에서 SESSION_TIMEOUT 초과 // - 절대(absolute): 로그인 시각(login_time, 갱신하지 않음)에서 SESSION_ABSOLUTE_TIMEOUT 초과 // 호출 전 config.php(SESSION_TIMEOUT/SESSION_ABSOLUTE_TIMEOUT) 와 session_start() 필요. function admin_enforce_session_timeout(): void { $idle_at = $_SESSION['last_activity'] ?? ($_SESSION['login_time'] ?? null); $login_at = $_SESSION['login_time'] ?? null; $idle_expired = isset($idle_at) && (time() - $idle_at) > SESSION_TIMEOUT; $absolute_expired = isset($login_at) && (time() - $login_at) > SESSION_ABSOLUTE_TIMEOUT; if ($idle_expired || $absolute_expired) { session_destroy(); header('Location: login.php?expired'); exit; } $_SESSION['last_activity'] = time(); }