fix: default permissions to true on API failure to prevent UI hiding

- When the permission check API call fails (timeout, network error, etc.),
  permissions now default to true instead of false
- This prevents UI elements like the WiFi tab from disappearing when the
  permission check has a transient failure
- The API still enforces access server-side, so no security impact
- Added __checkFailed flag to PermissionMap for observability
This commit is contained in:
2026-02-27 18:12:14 -06:00
parent 0e634c84ff
commit 27755d4a00
3 changed files with 30 additions and 16 deletions
+4 -2
View File
@@ -51,12 +51,14 @@ describe("permissions helpers", () => {
}); });
}); });
it("defaults requested permissions to false on API error", async () => { it("defaults requested permissions to true on API error and marks __checkFailed", async () => {
mockCheckPermissions.mockRejectedValueOnce(new Error("request failed")); mockCheckPermissions.mockRejectedValueOnce(new Error("request failed"));
const result = await checkPermissions("token", ["a", "b"]); const result = await checkPermissions("token", ["a", "b"]);
expect(result).toEqual({ a: false, b: false }); expect(result.a).toBe(true);
expect(result.b).toBe(true);
expect(result.__checkFailed).toBe(true);
}); });
it("hasPermission returns true only for explicit true values", () => { it("hasPermission returns true only for explicit true values", () => {
+13 -6
View File
@@ -1,6 +1,9 @@
import { optima } from "$lib"; import { optima } from "$lib";
export type PermissionMap = Record<string, boolean>; export type PermissionMap = Record<string, boolean> & {
/** Set to `true` when the permission check itself failed (API error, timeout, etc.) */
__checkFailed?: boolean;
};
/** /**
* Check multiple permissions for the current user and return a map of * Check multiple permissions for the current user and return a map of
@@ -34,11 +37,15 @@ export async function checkPermissions(
}, {}); }, {});
} catch (err) { } catch (err) {
console.error("Permission check failed:", err); console.error("Permission check failed:", err);
// Default every requested permission to false on failure // Default every requested permission to true on failure so the UI
return permissions.reduce<PermissionMap>((map, p) => { // doesn't hide features that the user may actually be allowed to use.
map[p] = false; // The API will still enforce access if the user truly lacks permission.
return map; const map = permissions.reduce<PermissionMap>((m, p) => {
}, {}); m[p] = true;
return m;
}, {} as PermissionMap);
map.__checkFailed = true;
return map;
} }
} }
+13 -8
View File
@@ -85,14 +85,19 @@
</svg> </svg>
</button> </button>
<form action="?/login" method="POST" onsubmit={handleSubmit} use:enhance={() => { <form
// Called when the form action response comes back from the server action="?/login"
return async ({ update }) => { method="POST"
formActionDone = true; onsubmit={handleSubmit}
$loading = false; use:enhance={() => {
await update(); // Called when the form action response comes back from the server
}; return async ({ update }) => {
}}> formActionDone = true;
$loading = false;
await update();
};
}}
>
<input type="hidden" name="callbackKey" value={uriData.callbackKey} /> <input type="hidden" name="callbackKey" value={uriData.callbackKey} />
<button <button
type="submit" type="submit"