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.
@@ -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' || typeof totpCode !== 'string') {
72
- sendJson(res, 400, { success: false, error: 'username, password, and totpCode are required strings' });
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
- // QA-R3-018 + CROSS-R4-010: TOTP must be exactly 6 digits per RFC 6238
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, totpCode, ip);
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;
@@ -92,12 +92,19 @@
92
92
 
93
93
  try {
94
94
  const data = await setupAccount(username, password);
95
- // Show TOTP setup
96
- setupStatus.textContent = '';
97
- setupStatus.className = 'status-row';
98
- totpSecret.textContent = data.totpSecret;
99
- totpSetup.style.display = 'block';
100
- setupSubmit.style.display = 'none';
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 field is always visible (removed conditional show accessibility fix)
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thevoidforge",
3
- "version": "21.0.9",
3
+ "version": "21.0.10",
4
4
  "description": "From nothing, everything. A methodology framework for building with Claude Code.",
5
5
  "type": "module",
6
6
  "engines": {