Skip to content

Commit 7108913

Browse files
committed
Refactor auth endpoints to /api and add login API
Moved change-password and logout PHP endpoints to /api for better organization. Added a new /api/login.php endpoint to handle login logic, including brute-force protection and session management. Updated frontend code and templates to use the new API routes.
1 parent db7df84 commit 7108913

6 files changed

Lines changed: 67 additions & 86 deletions

File tree

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22

3-
require_once __DIR__."/../lib/orm.php";
4-
require_once __DIR__."/../lib/settings.php";
5-
require_once __DIR__."/../conf/config.php";
3+
require_once __DIR__."/../../lib/orm.php";
4+
require_once __DIR__."/../../lib/settings.php";
5+
require_once __DIR__."/../../conf/config.php";
66

77
session_start();
88
header('Content-Type: application/json; charset=utf-8');

src/public/api/login.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
require_once __DIR__ . "/../../lib/orm.php";
3+
require_once __DIR__ . "/../../conf/config.php";
4+
5+
session_start();
6+
7+
function json_response(array $data, int $status = 200) {
8+
http_response_code($status);
9+
header('Content-Type: application/json; charset=utf-8');
10+
echo json_encode($data);
11+
exit;
12+
}
13+
14+
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
15+
json_response(['ok' => false, 'error' => 'method_not_allowed'], 405);
16+
}
17+
18+
$pwd = $_POST['password'] ?? '';
19+
20+
if (!isset($_SESSION['login_attempts'])) {
21+
$_SESSION['login_attempts'] = 0;
22+
$_SESSION['lockout_until'] = 0;
23+
}
24+
25+
$now = time();
26+
$maxAttempts = 5;
27+
$lockoutSeconds = 10;
28+
29+
if ($_SESSION['lockout_until'] > $now) {
30+
$remaining = $_SESSION['lockout_until'] - $now;
31+
json_response(['ok' => false, 'error' => 'locked', 'locked_for' => $remaining], 429);
32+
}
33+
34+
// ORM usage: get password hash
35+
$settingsManager = new PersistentEntityManager(KeyValue::class, $logger, DB, 'settings');
36+
$passwordObject = $settingsManager->find(["key" => "passwordHash"]);
37+
$passwordConfig = $passwordObject->value ?? '';
38+
39+
if (!password_verify($pwd, $passwordConfig)) {
40+
$_SESSION['login_attempts']++;
41+
if ($_SESSION['login_attempts'] >= $maxAttempts) {
42+
$_SESSION['lockout_until'] = $now + $lockoutSeconds;
43+
$_SESSION['login_attempts'] = 0;
44+
json_response(['ok' => false, 'error' => 'locked', 'locked_for' => $lockoutSeconds], 429);
45+
}
46+
json_response(['ok' => false, 'error' => 'invalid', 'attempts_left' => $maxAttempts - $_SESSION['login_attempts']], 401);
47+
}
48+
49+
session_regenerate_id(true);
50+
$_SESSION['user'] = [
51+
'username' => 'admin',
52+
'logged_in_at' => $now
53+
];
54+
55+
$_SESSION['login_attempts'] = 0;
56+
$_SESSION['lockout_until'] = 0;
57+
58+
json_response(['ok' => true, 'redirect' => '/home.php']);
59+
?>

src/public/index.php

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,13 @@
11
<?php
2-
require_once __DIR__ . "/../lib/orm.php";
32
require_once __DIR__ . "/../conf/config.php";
4-
53
session_start();
64

75
if (isset($_SESSION['user']) && !empty($_SESSION['user'])) {
86
header('Location: /home.php');
97
exit;
108
}
119

12-
// default template variables used when rendering the page (GET)
13-
$login_error = false;
14-
15-
// If this is a POST -> handle login API and return JSON
16-
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
17-
18-
// basic JSON response helper
19-
function json_response(array $data, int $status = 200) {
20-
http_response_code($status);
21-
header('Content-Type: application/json; charset=utf-8');
22-
echo json_encode($data);
23-
exit;
24-
}
25-
26-
// read password from POST
27-
$pwd = isset($_POST['password']) ? (string) $_POST['password'] : '';
28-
29-
// simple brute-force protection stored in session
30-
if (!isset($_SESSION['login_attempts'])) {
31-
$_SESSION['login_attempts'] = 0;
32-
$_SESSION['lockout_until'] = 0;
33-
}
34-
35-
$now = time();
36-
$maxAttempts = 5;
37-
$lockoutSeconds = 10; // lockout length after max attempts
38-
39-
if ($_SESSION['lockout_until'] > $now) {
40-
$remaining = $_SESSION['lockout_until'] - $now;
41-
json_response(['ok' => false, 'error' => 'locked', 'locked_for' => $remaining], 429);
42-
}
43-
44-
// load password from settings table (your ORM usage)
45-
$settingsManager = new PersistentEntityManager(KeyValue::class, $logger, DB, 'settings');
46-
$passwordObject = $settingsManager->find(["key" => "passwordHash"]);
47-
$passwordConfig = $passwordObject->value ?? '';
48-
49-
50-
$ok = false;
51-
$ok = password_verify($pwd, $passwordConfig);
52-
53-
if (! $ok) {
54-
$_SESSION['login_attempts'] += 1;
55-
if ($_SESSION['login_attempts'] >= $maxAttempts) {
56-
$_SESSION['lockout_until'] = $now + $lockoutSeconds;
57-
$_SESSION['login_attempts'] = 0; // reset attempts after enforcing lockout
58-
json_response(['ok' => false, 'error' => 'locked', 'locked_for' => $lockoutSeconds], 429);
59-
} else {
60-
json_response(['ok' => false, 'error' => 'invalid', 'attempts_left' => $maxAttempts - $_SESSION['login_attempts']], 401);
61-
}
62-
}
63-
64-
// success: start session (already started) and set session vars
65-
session_regenerate_id(true);
66-
$_SESSION['user'] = [
67-
'username' => 'admin',
68-
'logged_in_at' => $now
69-
];
70-
71-
// reset attempts on success
72-
$_SESSION['login_attempts'] = 0;
73-
$_SESSION['lockout_until'] = 0;
74-
75-
// respond with success
76-
json_response(['ok' => true, 'redirect' => '/home.php']);
77-
// json_response already exits
78-
}
79-
80-
// If it's not POST, execution continues and your template is rendered.
81-
// You can set $login_error here based on query params or session if you want:
82-
if (isset($_GET['error']) && $_GET['error'] === '1') {
83-
$login_error = true;
84-
}
85-
10+
$login_error = isset($_GET['error']) && $_GET['error'] === '1';
8611
?>
8712
{% extends 'templates/base.j2' %}
8813
{% block title %}CronDNS Login{% endblock %}
@@ -114,18 +39,15 @@ function json_response(array $data, int $status = 200) {
11439
{% endblock %}
11540

11641
{% block scripts %}
117-
/* ---------- Password visibility toggle ---------- */
11842
const pwdInput = document.getElementById('pwd');
11943
const toggleBtn = document.getElementById('togglePwd');
120-
12144
toggleBtn.addEventListener('click', () => {
12245
const type = pwdInput.type === 'password' ? 'text' : 'password';
12346
pwdInput.type = type;
12447
toggleBtn.classList.toggle('ti-eye-off', type === 'text');
12548
toggleBtn.classList.toggle('ti-eye', type === 'password');
12649
});
12750

128-
/* ---------- Login: replace simulated handler with real fetch ---------- */
12951
const loginBtn = document.getElementById('loginBtn');
13052
const loginForm = document.getElementById('loginForm');
13153
let lockoutTimer = null;
@@ -143,7 +65,7 @@ function json_response(array $data, int $status = 200) {
14365
const form = new FormData();
14466
form.append('password', pwd);
14567

146-
const resp = await fetch('/login.php', {
68+
const resp = await fetch('/api/login.php', {
14769
method: 'POST',
14870
body: form,
14971
credentials: 'same-origin'
@@ -152,7 +74,7 @@ function json_response(array $data, int $status = 200) {
15274
const data = await resp.json();
15375

15476
if (resp.ok && data.ok) {
155-
window.location.href = data.redirect || '/dashboard';
77+
window.location.href = data.redirect || '/home.php';
15678
return;
15779
}
15880

src/public/settings.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
pwdError.style.display = 'none';
115115

116116
// send to server via fetch
117-
fetch('/change-password.php', {
117+
fetch('/api/change-password.php', {
118118
method: 'POST',
119119
headers: { 'Content-Type': 'application/json' },
120120
body: JSON.stringify({ password: p1 })

src/templates/dashboard.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ if (!isset($_SESSION['user']) || empty($_SESSION['user'])) {
2626
<div class="sidebar-spacer"></div>
2727

2828
<div class="sidebar-footer">
29-
<a href="/logout.php" class="sidebar-logout">
29+
<a href="/api/logout.php" class="sidebar-logout">
3030
<i class="ti ti-logout"></i> Logout
3131
</a>
3232
<div class="sidebar-version">v{{ cfg.version }}</div>

0 commit comments

Comments
 (0)