clay-server 2.36.2-beta.3 → 2.36.2-beta.5
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/lib/public/modules/app-misc.js +58 -42
- package/lib/server-settings.js +6 -1
- package/package.json +1 -1
|
@@ -289,79 +289,85 @@ export function hideConfirm() {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
export function showForceChangePinOverlay() {
|
|
292
|
+
// Inject the same .pin-digit / .pin-wrap CSS the login page uses, so this
|
|
293
|
+
// overlay behaves identically: visible focus ring, filled state, etc.
|
|
294
|
+
// The login page (lib/pages.js) loads this CSS inline; the main app
|
|
295
|
+
// doesn't, so we add it once here.
|
|
296
|
+
if (!document.getElementById("fcp-style")) {
|
|
297
|
+
var st = document.createElement("style");
|
|
298
|
+
st.id = "fcp-style";
|
|
299
|
+
st.textContent =
|
|
300
|
+
"#force-change-pin-overlay .pin-wrap{display:flex;gap:8px;justify-content:center;margin-bottom:16px}" +
|
|
301
|
+
"#force-change-pin-overlay .pin-digit{width:44px;height:56px;background:var(--input-bg);border:1.5px solid var(--border);border-radius:8px;color:var(--text);font-family:'Courier New',Courier,'Roboto Mono',monospace;font-size:28px;font-weight:700;text-align:center;line-height:56px;outline:none;caret-color:transparent;transition:border-color 0.15s,box-shadow 0.15s}" +
|
|
302
|
+
"#force-change-pin-overlay .pin-digit:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-20,rgba(124,58,237,0.25))}" +
|
|
303
|
+
"#force-change-pin-overlay .pin-digit.filled{color:var(--text)}";
|
|
304
|
+
document.head.appendChild(st);
|
|
305
|
+
}
|
|
306
|
+
|
|
292
307
|
var ov = document.createElement("div");
|
|
293
308
|
ov.id = "force-change-pin-overlay";
|
|
294
309
|
ov.style.cssText = "position:fixed;inset:0;background:var(--bg,#0e0e10);z-index:99999;display:flex;align-items:center;justify-content:center;flex-direction:column";
|
|
295
310
|
ov.innerHTML = '<div style="width:100%;max-width:380px;padding:24px;text-align:center">' +
|
|
296
311
|
'<h2 style="margin:0 0 8px;color:var(--text,#fff);font-size:22px">Set your new PIN</h2>' +
|
|
297
312
|
'<p style="margin:0 0 24px;color:var(--text-secondary,#aaa);font-size:14px">Your temporary PIN has expired. Please set a new 6-digit PIN to continue.</p>' +
|
|
298
|
-
'<div
|
|
299
|
-
'<input class="
|
|
300
|
-
'<input class="
|
|
301
|
-
'<input class="
|
|
302
|
-
'<input class="
|
|
303
|
-
'<input class="
|
|
304
|
-
'<input class="
|
|
313
|
+
'<div class="pin-wrap" id="fcp-boxes">' +
|
|
314
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
315
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
316
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
317
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
318
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
319
|
+
'<input class="pin-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off">' +
|
|
305
320
|
'</div>' +
|
|
306
|
-
'<button id="fcp-save" disabled style="width:100%;padding:12px;border:none;border-radius:10px;background:var(--accent
|
|
307
|
-
'<div id="fcp-err" style="margin-top:12px;color
|
|
321
|
+
'<button id="fcp-save" disabled style="width:100%;padding:12px;border:none;border-radius:10px;background:var(--accent);color:#fff;font-size:15px;font-weight:600;cursor:pointer;opacity:0.5;transition:opacity 0.15s">Save PIN</button>' +
|
|
322
|
+
'<div id="fcp-err" style="margin-top:12px;color:var(--error,#ef4444);font-size:13px;min-height:1.3em"></div>' +
|
|
323
|
+
'<button id="fcp-logout" type="button" style="margin-top:8px;background:none;border:none;color:var(--text-dimmer,#888);font-size:13px;cursor:pointer;text-decoration:underline">Log out and start over</button>' +
|
|
308
324
|
'</div>';
|
|
309
325
|
document.body.appendChild(ov);
|
|
310
326
|
|
|
311
|
-
var boxes = ov.querySelectorAll(".
|
|
327
|
+
var boxes = ov.querySelectorAll(".pin-digit");
|
|
312
328
|
var saveBtn = ov.querySelector("#fcp-save");
|
|
313
329
|
var errEl = ov.querySelector("#fcp-err");
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
".fcp-digit.filled{border-color:var(--accent,#7c3aed)}";
|
|
324
|
-
document.head.appendChild(st);
|
|
330
|
+
// Mirror the login screen's pattern (lib/pages.js pinBoxScript): keep an
|
|
331
|
+
// explicit digits[] array as the source of truth, render bullets visually,
|
|
332
|
+
// enable the button when length === 6.
|
|
333
|
+
var digits = ["", "", "", "", "", ""];
|
|
334
|
+
|
|
335
|
+
function setDigit(idx, v) {
|
|
336
|
+
digits[idx] = v;
|
|
337
|
+
boxes[idx].value = v ? "\u2022" : "";
|
|
338
|
+
boxes[idx].classList.toggle("filled", v.length > 0);
|
|
325
339
|
}
|
|
326
340
|
|
|
327
341
|
function getPin() {
|
|
328
|
-
|
|
329
|
-
for (var i = 0; i < boxes.length; i++) pin += boxes[i].value;
|
|
330
|
-
return pin;
|
|
342
|
+
return digits.join("");
|
|
331
343
|
}
|
|
332
344
|
|
|
333
345
|
function updateBtn() {
|
|
334
|
-
var
|
|
335
|
-
var ready = pin.length === 6 && /^[0-9]{6}$/.test(pin);
|
|
346
|
+
var ready = getPin().length === 6;
|
|
336
347
|
saveBtn.disabled = !ready;
|
|
337
348
|
saveBtn.style.opacity = ready ? "1" : "0.5";
|
|
338
349
|
}
|
|
339
350
|
|
|
340
|
-
function refreshFilled(idx) {
|
|
341
|
-
boxes[idx].classList.toggle("filled", boxes[idx].value.length > 0);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
351
|
for (var i = 0; i < boxes.length; i++) {
|
|
345
352
|
(function (idx) {
|
|
346
353
|
boxes[idx].addEventListener("input", function () {
|
|
347
|
-
// Coerce to a single digit. If user types non-numeric, drop it.
|
|
348
354
|
var raw = this.value.replace(/[^0-9]/g, "");
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
355
|
+
if (!raw) { setDigit(idx, ""); updateBtn(); return; }
|
|
356
|
+
var v = raw.charAt(raw.length - 1);
|
|
357
|
+
setDigit(idx, v);
|
|
358
|
+
if (v && idx < 5) boxes[idx + 1].focus();
|
|
352
359
|
updateBtn();
|
|
353
360
|
});
|
|
354
361
|
boxes[idx].addEventListener("keydown", function (e) {
|
|
355
362
|
if (e.key === "Backspace") {
|
|
356
|
-
if (!
|
|
357
|
-
|
|
358
|
-
refreshFilled(idx - 1);
|
|
363
|
+
if (!digits[idx] && idx > 0) {
|
|
364
|
+
setDigit(idx - 1, "");
|
|
359
365
|
boxes[idx - 1].focus();
|
|
360
366
|
} else {
|
|
361
|
-
|
|
362
|
-
refreshFilled(idx);
|
|
367
|
+
setDigit(idx, "");
|
|
363
368
|
}
|
|
364
369
|
updateBtn();
|
|
370
|
+
return;
|
|
365
371
|
}
|
|
366
372
|
if (e.key === "ArrowLeft" && idx > 0) boxes[idx - 1].focus();
|
|
367
373
|
if (e.key === "ArrowRight" && idx < 5) boxes[idx + 1].focus();
|
|
@@ -372,10 +378,9 @@ export function showForceChangePinOverlay() {
|
|
|
372
378
|
boxes[idx].addEventListener("keypress", function (e) { e.stopPropagation(); });
|
|
373
379
|
boxes[idx].addEventListener("paste", function (e) {
|
|
374
380
|
e.preventDefault();
|
|
375
|
-
var text = (e.clipboardData || window.clipboardData).getData("text").replace(/[^0-9]/g, "").
|
|
381
|
+
var text = (e.clipboardData || window.clipboardData).getData("text").replace(/[^0-9]/g, "").slice(0, 6);
|
|
376
382
|
for (var j = 0; j < text.length && (idx + j) < 6; j++) {
|
|
377
|
-
|
|
378
|
-
refreshFilled(idx + j);
|
|
383
|
+
setDigit(idx + j, text.charAt(j));
|
|
379
384
|
}
|
|
380
385
|
if (text.length > 0) {
|
|
381
386
|
var focusIdx = Math.min(idx + text.length, 5);
|
|
@@ -413,6 +418,17 @@ export function showForceChangePinOverlay() {
|
|
|
413
418
|
});
|
|
414
419
|
}
|
|
415
420
|
saveBtn.addEventListener("click", doSave);
|
|
421
|
+
|
|
422
|
+
var logoutBtn = ov.querySelector("#fcp-logout");
|
|
423
|
+
if (logoutBtn) {
|
|
424
|
+
logoutBtn.addEventListener("click", function () {
|
|
425
|
+
fetch("/auth/logout", { method: "POST" }).then(function () {
|
|
426
|
+
location.href = "/";
|
|
427
|
+
}).catch(function () {
|
|
428
|
+
location.href = "/";
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
416
432
|
}
|
|
417
433
|
|
|
418
434
|
export function sendExtensionCommand(command, args, requestId) {
|
package/lib/server-settings.js
CHANGED
|
@@ -332,7 +332,12 @@ function attachSettings(ctx) {
|
|
|
332
332
|
res.end('{"error":"PIN must be exactly 6 digits"}');
|
|
333
333
|
return;
|
|
334
334
|
}
|
|
335
|
-
|
|
335
|
+
// Forced PIN change after temporary PIN login: skip currentPin
|
|
336
|
+
// verification. The user authenticated with the temp PIN to
|
|
337
|
+
// establish this session, so requiring them to re-enter it adds
|
|
338
|
+
// friction without security benefit. The session cookie is the
|
|
339
|
+
// proof of possession.
|
|
340
|
+
if (mu.pinHash && !mu.mustChangePin) {
|
|
336
341
|
if (!data.currentPin || typeof data.currentPin !== "string" || !/^\d{6}$/.test(data.currentPin)) {
|
|
337
342
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
338
343
|
res.end('{"error":"Current PIN is required"}');
|
package/package.json
CHANGED