mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(settings): move password reset to the end of the security section
Security now reads authenticator, then security keys, then passkeys, then the password — the password card was mounted first. Keep it outside the factor-status conditional so a stalled MFA read never takes it down; it just renders last instead of first. A source-order guard pins the sequence, since this class of ordering regression is easy to miss.
This commit is contained in:
1 parent
e344ebb781
commit
ee561eaa23
2 files changed
+26
-9
No files matched your search
@@ -72,14 +72,30 @@ describe("change-password card placement", () => {
|
||||
expect(render(<PasswordCard />)).toContain(SLOT);
|
||||
});
|
||||
|
||||
it("is mounted by Settings → Security before the factor status lands", () => {
|
||||
it("is mounted by Settings → Security even before the factor status lands", () => {
|
||||
// The mocked query never resolves, which is the case that matters: the
|
||||
// MFA read is still in flight (or failing) and the password card is
|
||||
// reachable regardless.
|
||||
// reachable regardless — it sits outside the status conditional.
|
||||
const html = render(<SecuritySection />);
|
||||
expect(html).toContain(SLOT);
|
||||
});
|
||||
|
||||
it("orders the password card last, after the passkey list", () => {
|
||||
// The section reads authenticator → security keys → passkeys → password.
|
||||
// The order lives in one component and the resolved cards only render once
|
||||
// the status query settles (the render mock keeps it in flight), so the
|
||||
// guard reads the source: the password card must come after the passkey
|
||||
// list. Re-reported once as an order regression; this pins it.
|
||||
const source = readFileSync(
|
||||
join(ROOT, "src/components/settings/security-section/index.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const passkeyIdx = source.indexOf("<PasskeyListSection");
|
||||
const passwordIdx = source.indexOf("<PasswordCard");
|
||||
expect(passkeyIdx).toBeGreaterThan(-1);
|
||||
expect(passwordIdx).toBeGreaterThan(passkeyIdx);
|
||||
});
|
||||
|
||||
it("has exactly one home in the tree", () => {
|
||||
const owners = walk(join(ROOT, "src"))
|
||||
.filter((file) => readFileSync(file, "utf8").includes("PasswordCard"))
|
||||
|
||||
@@ -50,13 +50,6 @@ export function SecuritySection() {
|
||||
<div className="space-y-6">
|
||||
{showNudge && <PasskeyUpgradeNudge />}
|
||||
|
||||
{/* The password leads, and it renders before the factor status has
|
||||
landed — it depends on nothing this page fetches. It is also the
|
||||
only way to change a password now that the Account card is gone, so
|
||||
a `/api/auth/me/mfa` that never answers must not take it down with
|
||||
the cards that do need it. */}
|
||||
<PasswordCard />
|
||||
|
||||
{isLoading || !status ? (
|
||||
<>
|
||||
<Skeleton className="h-40 w-full rounded-xl" />
|
||||
@@ -75,6 +68,14 @@ export function SecuritySection() {
|
||||
<PasskeyListSection isAuthenticated={isAuthenticated} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* The password comes last: authenticator, then security keys, then
|
||||
passkeys, then the password. It sits OUTSIDE the status conditional
|
||||
on purpose — it depends on nothing this page fetches and is the only
|
||||
way to change a password now that the Account card is gone, so a
|
||||
`/api/auth/me/mfa` that never answers must not take it down with the
|
||||
cards that do need the status. */}
|
||||
<PasswordCard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user