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.
@@ -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 style="display:flex;gap:8px;justify-content:center;margin-bottom:16px" id="fcp-boxes">' +
299
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
300
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
301
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
302
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
303
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
304
- '<input class="fcp-digit" type="password" maxlength="1" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-alt,#f5f5f5);color:var(--text,#fff);outline:none;caret-color:var(--accent,#7c3aed)">' +
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,#7c3aed);color:#fff;font-size:15px;font-weight:600;cursor:pointer;opacity:0.5">Save PIN</button>' +
307
- '<div id="fcp-err" style="margin-top:12px;color:#ef4444;font-size:13px"></div>' +
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(".fcp-digit");
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
- // Inject focus styling. Without this the inputs use outline:none with no
316
- // replacement, so users cannot tell which box is focused. Caret is also
317
- // forced via caret-color on the input style.
318
- if (!document.getElementById("fcp-style")) {
319
- var st = document.createElement("style");
320
- st.id = "fcp-style";
321
- st.textContent =
322
- ".fcp-digit:focus{border-color:var(--accent,#7c3aed)!important;box-shadow:0 0 0 3px rgba(124,58,237,0.25)}" +
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
- var pin = "";
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 pin = getPin();
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
- this.value = raw.length > 0 ? raw.charAt(raw.length - 1) : "";
350
- refreshFilled(idx);
351
- if (this.value && idx < 5) boxes[idx + 1].focus();
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 (!boxes[idx].value && idx > 0) {
357
- boxes[idx - 1].value = "";
358
- refreshFilled(idx - 1);
363
+ if (!digits[idx] && idx > 0) {
364
+ setDigit(idx - 1, "");
359
365
  boxes[idx - 1].focus();
360
366
  } else {
361
- boxes[idx].value = "";
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, "").substring(0, 6);
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
- boxes[idx + j].value = text.charAt(j);
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) {
@@ -332,7 +332,12 @@ function attachSettings(ctx) {
332
332
  res.end('{"error":"PIN must be exactly 6 digits"}');
333
333
  return;
334
334
  }
335
- if (mu.pinHash) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.36.2-beta.3",
3
+ "version": "2.36.2-beta.5",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",