Add household admin roles: invite gating, promote/demote, removal, passwords
Every household now always has exactly one or more admins. Admins can invite new members (previously open to any parent -- now gated), promote/ demote other admins, remove parents, and directly set another parent's password (no email infra exists for a reset-link flow, so this is a direct admin-sets-the-value action). Every parent can change their own password with current-password confirmation. A sole admin can't remove themselves or demote until they promote someone else -- this falls out of a single "household must have >=1 admin" invariant rather than needing special-case code. Schema: parents.is_admin, added via a new idempotent ensureColumn() helper (SQLite has no ADD COLUMN IF NOT EXISTS, and this needed to run safely against the already-populated production parents table on next boot, not just fresh installs). A boot-time backfill promotes the earliest-created parent in any household with zero admins -- verified against a simulated copy of the real production schema/data (including the exact "spouse joined via invite" scenario), confirming correct promotion and clean idempotency across repeated boots. Fixed a real foreign-key landmine along the way: household_invites. used_by_parent_id had no ON DELETE clause, so deleting any parent who'd ever accepted an invite -- i.e. any spouse, in this app's normal usage -- would have thrown a constraint violation. src/lib/removeParent.js nulls that reference before deleting, wrapped in an explicit transaction (first use of manual BEGIN/COMMIT/ROLLBACK in this codebase, verified working with node:sqlite before relying on it). Verified extensively: every route's permission/edge cases via curl (cross-household isolation, sole-admin guards, password round-trips via real login), and the full UI flow (promote/demote/remove/both password modals/leave-with-error-toast) across two independent real browser sessions acting as admin and non-admin simultaneously. Full-app regression and a Docker build/boot check both pass with the new code in place.
This commit is contained in:
@@ -107,6 +107,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change own password modal -->
|
||||
<div class="modal-backdrop hidden" id="changeOwnPasswordModal">
|
||||
<div class="modal">
|
||||
<h2>Change my password</h2>
|
||||
<form id="changeOwnPasswordForm">
|
||||
<div class="field">
|
||||
<label for="currentPasswordInput">Current password</label>
|
||||
<input type="password" id="currentPasswordInput" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="newOwnPasswordInput">New password</label>
|
||||
<input type="password" id="newOwnPasswordInput" minlength="8" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="tool primary">Change password</button>
|
||||
<button type="button" class="tool" id="cancelChangeOwnPasswordBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin sets another parent's password modal -->
|
||||
<div class="modal-backdrop hidden" id="setParentPasswordModal">
|
||||
<div class="modal">
|
||||
<h2>Set password for <span id="setPasswordParentName"></span></h2>
|
||||
<form id="setParentPasswordForm">
|
||||
<input type="hidden" id="setPasswordParentId">
|
||||
<div class="field">
|
||||
<label for="newParentPasswordInput">New password</label>
|
||||
<input type="password" id="newParentPasswordInput" minlength="8" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="tool primary">Set password</button>
|
||||
<button type="button" class="tool" id="cancelSetParentPasswordBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kiosk link modal -->
|
||||
<div class="modal-backdrop hidden" id="kioskModal">
|
||||
<div class="modal">
|
||||
|
||||
Reference in New Issue
Block a user