thevoidforge 21.0.9 → 21.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/wizard/api/auth.js +16 -10
- package/dist/wizard/ui/login.js +23 -8
- package/package.json +1 -1
package/dist/wizard/api/auth.js
CHANGED
|
@@ -9,8 +9,8 @@ import { audit } from '../lib/audit-log.js';
|
|
|
9
9
|
import { sendJson } from '../lib/http-helpers.js';
|
|
10
10
|
// POST /api/auth/setup — Create initial admin user (only when no users exist)
|
|
11
11
|
addRoute('POST', '/api/auth/setup', async (req, res) => {
|
|
12
|
-
if (!isRemoteMode()) {
|
|
13
|
-
sendJson(res, 400, { success: false, error: 'Auth setup is only available in remote mode' });
|
|
12
|
+
if (!isRemoteMode() && !isLanMode()) {
|
|
13
|
+
sendJson(res, 400, { success: false, error: 'Auth setup is only available in remote or LAN mode' });
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
// Rate-limit the setup endpoint (prevents race-to-setup attacks)
|
|
@@ -58,8 +58,8 @@ addRoute('POST', '/api/auth/setup', async (req, res) => {
|
|
|
58
58
|
});
|
|
59
59
|
// POST /api/auth/login — Authenticate with username + password + TOTP
|
|
60
60
|
addRoute('POST', '/api/auth/login', async (req, res) => {
|
|
61
|
-
if (!isRemoteMode()) {
|
|
62
|
-
sendJson(res, 400, { success: false, error: 'Auth is only required in remote mode' });
|
|
61
|
+
if (!isRemoteMode() && !isLanMode()) {
|
|
62
|
+
sendJson(res, 400, { success: false, error: 'Auth is only required in remote or LAN mode' });
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
const body = await parseJsonBody(req);
|
|
@@ -68,19 +68,25 @@ addRoute('POST', '/api/auth/login', async (req, res) => {
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
const { username, password, totpCode } = body;
|
|
71
|
-
if (typeof username !== 'string' || typeof password !== 'string'
|
|
72
|
-
sendJson(res, 400, { success: false, error: 'username
|
|
71
|
+
if (typeof username !== 'string' || typeof password !== 'string') {
|
|
72
|
+
sendJson(res, 400, { success: false, error: 'username and password are required strings' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// LAN mode: TOTP is optional (password-only auth)
|
|
76
|
+
const requireTotp = isRemoteMode();
|
|
77
|
+
const totp = typeof totpCode === 'string' ? totpCode : '';
|
|
78
|
+
if (requireTotp && (totp.length !== 6 || !/^\d{6}$/.test(totp))) {
|
|
79
|
+
sendJson(res, 400, { success: false, error: 'totpCode must be exactly 6 digits' });
|
|
73
80
|
return;
|
|
74
81
|
}
|
|
75
82
|
// Cap field lengths to prevent DoS via oversized PBKDF2 input
|
|
76
|
-
|
|
77
|
-
if (username.length > 64 || password.length > 256 || totpCode.length !== 6 || !/^\d{6}$/.test(totpCode)) {
|
|
83
|
+
if (username.length > 64 || password.length > 256) {
|
|
78
84
|
sendJson(res, 400, { success: false, error: 'Field length exceeded' });
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
81
87
|
const ip = getClientIp(req);
|
|
82
|
-
await audit('login_attempt', ip, username.slice(0, 64), { method: 'password+totp' });
|
|
83
|
-
const result = await login(username, password,
|
|
88
|
+
await audit('login_attempt', ip, username.slice(0, 64), { method: requireTotp ? 'password+totp' : 'password' });
|
|
89
|
+
const result = await login(username, password, totp, ip);
|
|
84
90
|
if ('error' in result) {
|
|
85
91
|
await audit('login_failure', ip, username.slice(0, 64), { reason: result.error });
|
|
86
92
|
const status = result.retryAfterMs ? 429 : 401;
|
package/dist/wizard/ui/login.js
CHANGED
|
@@ -92,12 +92,19 @@
|
|
|
92
92
|
|
|
93
93
|
try {
|
|
94
94
|
const data = await setupAccount(username, password);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
if (requireTotp && data.totpSecret) {
|
|
96
|
+
// Show TOTP setup (remote mode)
|
|
97
|
+
setupStatus.textContent = '';
|
|
98
|
+
setupStatus.className = 'status-row';
|
|
99
|
+
totpSecret.textContent = data.totpSecret;
|
|
100
|
+
totpSetup.style.display = 'block';
|
|
101
|
+
setupSubmit.style.display = 'none';
|
|
102
|
+
} else {
|
|
103
|
+
// LAN mode — skip TOTP, go straight to login
|
|
104
|
+
setupStatus.textContent = 'Account created. Please log in.';
|
|
105
|
+
setupStatus.className = 'status-row success';
|
|
106
|
+
setTimeout(function () { showLogin(); }, 1000);
|
|
107
|
+
}
|
|
101
108
|
} catch (err) {
|
|
102
109
|
setupStatus.textContent = err.message;
|
|
103
110
|
setupStatus.className = 'status-row error';
|
|
@@ -111,7 +118,15 @@
|
|
|
111
118
|
|
|
112
119
|
// ── Login flow ─────────────────────────────────────
|
|
113
120
|
|
|
114
|
-
// TOTP
|
|
121
|
+
// Detect if TOTP is required (remote mode) or optional (LAN mode)
|
|
122
|
+
var requireTotp = true;
|
|
123
|
+
fetch('/api/auth/session').then(function (r) { return r.json(); }).then(function (d) {
|
|
124
|
+
var data = d.data || {};
|
|
125
|
+
if (data.lanMode && !data.remoteMode) {
|
|
126
|
+
requireTotp = false;
|
|
127
|
+
if (totpField) totpField.style.display = 'none';
|
|
128
|
+
}
|
|
129
|
+
}).catch(function () {});
|
|
115
130
|
|
|
116
131
|
loginSubmit.addEventListener('click', async () => {
|
|
117
132
|
const username = loginUsername.value.trim();
|
|
@@ -123,7 +138,7 @@
|
|
|
123
138
|
loginStatus.className = 'status-row error';
|
|
124
139
|
return;
|
|
125
140
|
}
|
|
126
|
-
if (!totp || totp.length !== 6) {
|
|
141
|
+
if (requireTotp && (!totp || totp.length !== 6)) {
|
|
127
142
|
loginStatus.textContent = 'Enter your 6-digit authenticator code';
|
|
128
143
|
loginStatus.className = 'status-row error';
|
|
129
144
|
loginTotp.focus();
|