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:
+15
-4
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user