Fix passkey length and add reset action

Introduce a single PASSKEY_LENGTH constant (6) and enforce it across passkey config/load/save/update, removing the previous user-adjustable length/clamping. Add resetSequence() to clear stored hash/salt, reset length/attempts/lockout and persist the cleared state. Update settings UI: relabel "Change Passkey" to "Reset Passkey", show "6 digits" and "Fixed for testing", remove length adjustment controls/handlers, and call state.passkey.resetSequence() when resetting so setup will run on next lock.
This commit is contained in:
2026-02-16 11:50:07 +13:00
parent 3a2c3f9ff1
commit c890636f03
2 changed files with 20 additions and 22 deletions
+15 -4
View File
@@ -1,11 +1,12 @@
const PASSKEY_STORAGE_KEY = "nebula.passkey.v1"; const PASSKEY_STORAGE_KEY = "nebula.passkey.v1";
const PASSKEY_VERSION = 1; const PASSKEY_VERSION = 1;
const PASSKEY_LENGTH = 6;
const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const FALLBACK_CONFIG = { const FALLBACK_CONFIG = {
enabled: true, enabled: true,
length: 6, length: PASSKEY_LENGTH,
requireConfirm: false, requireConfirm: false,
keyboardSupport: true, keyboardSupport: true,
maxAttempts: 5, maxAttempts: 5,
@@ -49,7 +50,7 @@ export const loadPasskeyConfig = () => {
return { return {
...FALLBACK_CONFIG, ...FALLBACK_CONFIG,
...parsed, ...parsed,
length: clamp(Number(parsed.length ?? FALLBACK_CONFIG.length), 4, 8), length: PASSKEY_LENGTH,
maxAttempts: clamp(Number(parsed.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), maxAttempts: clamp(Number(parsed.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10),
cooldownSeconds: clamp(Number(parsed.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), cooldownSeconds: clamp(Number(parsed.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120),
requireConfirm: Boolean(parsed.requireConfirm ?? FALLBACK_CONFIG.requireConfirm), requireConfirm: Boolean(parsed.requireConfirm ?? FALLBACK_CONFIG.requireConfirm),
@@ -76,7 +77,7 @@ export const savePasskeyConfig = (config) => {
...FALLBACK_CONFIG, ...FALLBACK_CONFIG,
...config, ...config,
version: PASSKEY_VERSION, version: PASSKEY_VERSION,
length: clamp(Number(config.length ?? FALLBACK_CONFIG.length), 4, 8), length: PASSKEY_LENGTH,
maxAttempts: clamp(Number(config.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), maxAttempts: clamp(Number(config.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10),
cooldownSeconds: clamp(Number(config.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), cooldownSeconds: clamp(Number(config.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120),
}; };
@@ -150,13 +151,22 @@ export const createPasskeyController = () => {
const updateConfig = (partial) => { const updateConfig = (partial) => {
Object.assign(config, partial); Object.assign(config, partial);
config.length = clamp(Number(config.length), 4, 8); config.length = PASSKEY_LENGTH;
config.maxAttempts = clamp(Number(config.maxAttempts), 1, 10); config.maxAttempts = clamp(Number(config.maxAttempts), 1, 10);
config.cooldownSeconds = clamp(Number(config.cooldownSeconds), 5, 120); config.cooldownSeconds = clamp(Number(config.cooldownSeconds), 5, 120);
persist(); persist();
return { ...config }; return { ...config };
}; };
const resetSequence = () => {
config.hash = "";
config.salt = "";
config.length = PASSKEY_LENGTH;
failedAttempts = 0;
lockoutUntil = 0;
persist();
};
const getConfig = () => ({ ...config }); const getConfig = () => ({ ...config });
return { return {
@@ -164,6 +174,7 @@ export const createPasskeyController = () => {
updateConfig, updateConfig,
verifySequence, verifySequence,
setSequence, setSequence,
resetSequence,
inLockout, inLockout,
getLockoutRemainingMs, getLockoutRemainingMs,
hasPasskey: () => Boolean(config.hash && config.salt), hasPasskey: () => Boolean(config.hash && config.salt),
+5 -18
View File
@@ -36,13 +36,13 @@ const SETTINGS_TEMPLATE = `
<p class="muted" data-passkey-enabled>Enabled</p> <p class="muted" data-passkey-enabled>Enabled</p>
</button> </button>
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="1" data-toggle="passkey-change" data-focus-key="passkey-change"> <button class="focusable settings-card" data-focusable="true" data-row="1" data-col="1" data-toggle="passkey-change" data-focus-key="passkey-change">
<p class="settings-card-title">Change Passkey</p> <p class="settings-card-title">Reset Passkey</p>
<p class="muted" data-passkey-change>Open setup on next lock</p> <p class="muted" data-passkey-change>Clear and set a new passkey</p>
</button> </button>
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="2" data-toggle="passkey-length" data-focus-key="passkey-length"> <button class="focusable settings-card" data-focusable="true" data-row="1" data-col="2" data-toggle="passkey-length" data-focus-key="passkey-length">
<p class="settings-card-title">Required Length</p> <p class="settings-card-title">Required Length</p>
<p class="muted" data-passkey-length>6 inputs</p> <p class="muted" data-passkey-length>6 digits</p>
<p class="muted">Use LB / RB to adjust</p> <p class="muted">Fixed for testing</p>
</button> </button>
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="3" data-toggle="passkey-confirm" data-focus-key="passkey-confirm"> <button class="focusable settings-card" data-focusable="true" data-row="1" data-col="3" data-toggle="passkey-confirm" data-focus-key="passkey-confirm">
<p class="settings-card-title">Require Confirm</p> <p class="settings-card-title">Require Confirm</p>
@@ -185,6 +185,7 @@ export const createSettingsView = ({ state, renderView }) => {
} }
if (toggle === "passkey-change") { if (toggle === "passkey-change") {
state.passkey.resetSequence();
state.passkeySetupRequired = true; state.passkeySetupRequired = true;
state.locked = true; state.locked = true;
state.activeView = "lock"; state.activeView = "lock";
@@ -193,8 +194,6 @@ export const createSettingsView = ({ state, renderView }) => {
} }
if (toggle === "passkey-length") { if (toggle === "passkey-length") {
const current = state.passkey.getConfig().length;
updatePasskey({ length: current >= 8 ? 4 : current + 1 });
return; return;
} }
@@ -221,18 +220,6 @@ export const createSettingsView = ({ state, renderView }) => {
return false; return false;
} }
if (action === "l1") {
const next = state.passkey.getConfig().length - 1;
updatePasskey({ length: next < 4 ? 8 : next });
return true;
}
if (action === "r1") {
const next = state.passkey.getConfig().length + 1;
updatePasskey({ length: next > 8 ? 4 : next });
return true;
}
return false; return false;
}, },
}; };