Initial financial project import
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
function send_private_no_store_headers(): void
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: 0');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('Referrer-Policy: same-origin');
|
||||
}
|
||||
|
||||
function csrf_token(): string
|
||||
{
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
function csrf_field(): string
|
||||
{
|
||||
return '<input type="hidden" name="csrf_token" value="' .
|
||||
htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') .
|
||||
'">';
|
||||
}
|
||||
|
||||
function verify_csrf_token(?string $token): bool
|
||||
{
|
||||
return is_string($token)
|
||||
&& isset($_SESSION['csrf_token'])
|
||||
&& hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
function require_valid_csrf_for_post(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (verify_csrf_token($_POST['csrf_token'] ?? null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
http_response_code(419);
|
||||
exit('Invalid CSRF token.');
|
||||
}
|
||||
|
||||
function enable_csrf_form_injection(): void
|
||||
{
|
||||
if (PHP_SAPI === 'cli' || defined('FINANCIAL_CSRF_INJECTION_ENABLED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
define('FINANCIAL_CSRF_INJECTION_ENABLED', true);
|
||||
|
||||
ob_start(static function (string $buffer): string {
|
||||
if (
|
||||
stripos($buffer, '<form') === false ||
|
||||
!preg_match('/method\s*=\s*["\']?post/i', $buffer)
|
||||
) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$field = csrf_field();
|
||||
|
||||
return preg_replace_callback(
|
||||
'/<form\b([^>]*)>/i',
|
||||
static function (array $matches) use ($field): string {
|
||||
$tag = $matches[0];
|
||||
|
||||
if (
|
||||
stripos($tag, 'method="post"') === false &&
|
||||
stripos($tag, "method='post'") === false &&
|
||||
!preg_match('/method\s*=\s*post/i', $tag)
|
||||
) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
if (stripos($tag, 'csrf_token') !== false) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
return $tag . $field;
|
||||
},
|
||||
$buffer
|
||||
) ?? $buffer;
|
||||
});
|
||||
}
|
||||
|
||||
function throttle_login_attempts(string $username): void
|
||||
{
|
||||
$key = 'login_attempts_' . hash('sha256', strtolower($username) . '|' . ($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
$now = time();
|
||||
$attempt = $_SESSION[$key] ?? ['count' => 0, 'first_at' => $now];
|
||||
|
||||
if (($now - (int)$attempt['first_at']) > 900) {
|
||||
$attempt = ['count' => 0, 'first_at' => $now];
|
||||
}
|
||||
|
||||
if ((int)$attempt['count'] >= 8) {
|
||||
throw new RuntimeException('로그인 시도가 많습니다. 잠시 후 다시 시도하세요.');
|
||||
}
|
||||
|
||||
$attempt['count']++;
|
||||
$_SESSION[$key] = $attempt;
|
||||
}
|
||||
|
||||
function clear_login_attempts(string $username): void
|
||||
{
|
||||
$key = 'login_attempts_' . hash('sha256', strtolower($username) . '|' . ($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
function login_user(array $user, bool $remember = false): void
|
||||
{
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION['user_id'] = (int)$user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
|
||||
if ($remember) {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
$expiresAt = date('Y-m-d H:i:s', strtotime('+30 days'));
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE users
|
||||
SET remember_token = ?, remember_expires_at = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$tokenHash, $expiresAt, $user['id']]);
|
||||
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$token,
|
||||
[
|
||||
'expires' => strtotime('+30 days'),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function logout_user(): void
|
||||
{
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE users
|
||||
SET remember_token = NULL, remember_expires_at = NULL
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
}
|
||||
|
||||
setcookie(
|
||||
'remember_token',
|
||||
'',
|
||||
[
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]
|
||||
);
|
||||
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'] ?? '', $params['secure'] ?? false, $params['httponly'] ?? false);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
function try_auto_login(): void
|
||||
{
|
||||
if (!empty($_SESSION['user_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($_COOKIE['remember_token'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $_COOKIE['remember_token'];
|
||||
$tokenHash = hash('sha256', $token);
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE remember_token = ?
|
||||
AND remember_expires_at IS NOT NULL
|
||||
AND remember_expires_at > NOW()
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$tokenHash]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
setcookie(
|
||||
'remember_token',
|
||||
'',
|
||||
[
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION['user_id'] = (int)$user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
|
||||
$newToken = bin2hex(random_bytes(32));
|
||||
$newHash = hash('sha256', $newToken);
|
||||
$expiresAt = date('Y-m-d H:i:s', strtotime('+30 days'));
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE users
|
||||
SET remember_token = ?, remember_expires_at = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$newHash, $expiresAt, $user['id']]);
|
||||
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$newToken,
|
||||
[
|
||||
'expires' => strtotime('+30 days'),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function check_auth(): void
|
||||
{
|
||||
try_auto_login();
|
||||
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
header('Location: /login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
send_private_no_store_headers();
|
||||
}
|
||||
|
||||
function user_id(): int
|
||||
{
|
||||
return (int)($_SESSION['user_id'] ?? 0);
|
||||
}
|
||||
|
||||
send_private_no_store_headers();
|
||||
require_valid_csrf_for_post();
|
||||
enable_csrf_form_injection();
|
||||
Reference in New Issue
Block a user