Building "Login As Mom" Without Building a Permissions System
Every app with shared access eventually gets asked for "can my daughter manage my account for me." My instinct was full RBAC. What MyVitals actually needed - one person managing a household's lab reports - was closer to "let me act as this person entirely," not granular per-resource permissions. Building it safely was the interesting part. The model: an invite is a membership, not a separate thing FamilyMember is one row per (familyId, invitedEmail) , representing a pending invite and an accepted membership as the same document transitioning state. userId stays null until the invited email matches an account. One detail worth stealing: the invite token is cleared on acceptance via undefined , not null . There's a sparse unique index on it, and sparse indexes only exclude absent fields - null still counts as a value and collides the moment a second person accepts. One character between "works" and "second invite ever throws a duplicate key error." The permission check is one function, and it is never cached async function canActAs(trueUserId, targetUserId) { if (trueUserId === targetUserId) return true; return sharesAcceptedFamilyWith(trueUserId, targetUserId); // live DB read } Called on every request, not cached, not memoized. This is the decision I'd defend hardest: cache it and "removing someone revokes access immediately" becomes "...within roughly five minutes." One extra DB read per request buys a security property that's actually true instead of one that's true most of the time. Query param, not header ?asUserId= - because some frontend call sites are plain / tags with no way to attach a custom header. A query param works everywhere a URL works. Two identities, one footgun - req.effectiveUserId - whose data to read/write. Query by this. - req.actingUserId - who's really making the request, for audit fields. Using req.userId directly in a new controller doesn't error - it silently breaks acting-as. A family member switches to their mother's profile, uploads a report, and it lands under the acting user's own account instead. No warning, just a report filed under the wrong person's medical history. There's no type system catching this (plain ESM JS) - it survives on convention and code review alone. What this deliberately doesn't do No granular permissions, no "view but not edit." Symmetric, all-or-nothing, targeting one person managing a household - not a care team with clearance levels. If that ever changes, this model gets replaced, not extended. ๐ Try MyVitals now - invite a family member and switch profiles in ten seconds. Top comments (0)
Comments
No comments yet. Start the discussion.