Lumo Journal

The Bouncer Pattern: Inside Lumo's Guardian Device Sync System

Why we replaced convenient multi-device access with a "digital bouncer" that only lets one phone into your data at a time—and why your stolen device will hate us for it.

By Lumo TeamFeb 14, 20265 min read

Introduction: The Problem with "Convenience"

Let's be honest: traditional multi-device authentication is a security nightmare dressed up as a user experience feature. You sign in on your phone, your tablet, your laptop, that sketchy computer at the hotel business center you swore you'd never use again—and they all stay logged in forever.

For a personal finance app like Lumo, that's unacceptable. I didn't spend months building a secure transaction layer just to have someone's ex-girlfriend still accessing their account because they forgot to log out of their iPad six months ago.

So I did what any sane engineer would do: I stopped reading theoretical security books (boring) and built a practical, slightly paranoid device sync system I'm calling the Guardian Model.

The Guardian Model: One Ring to Rule Them All

The concept is simple, bordering on brutal:

  • One device has full access at any given time. This is the Guardian.
  • Want to switch to a new device? You become a Petitioner and politely wait outside.
  • The Guardian gets a modal: "Let this new device in?"
  • Approve: The Guardian is immediately evicted (kill switch), the Petitioner becomes the new Guardian.
  • Deny: The Petitioner stays in the digital cold.

No simultaneous access. No "maybe I can just check this real quick on my laptop while my phone is the Guardian." One device, full control, explicit handoffs.

How It Works (The Technical Tea)

The Data Model

We track device state in Convex with surgical precision:

// The critical fields in our users table
{
  activeDeviceId: "iphone-15-pro-abc123",        // Who's the boss right now
  activeDeviceDisplayName: "Abdul's iPhone",     // Pretty name for modals
  lastActiveDeviceTimestamp: 1707832800000,        // "Last seen" for stale detection
  pendingRequest: {                                // Someone's knocking
    deviceId: "pixel-8-xyz789",
    deviceName: "Abdul's Pixel",
    timestamp: 1707836400000
  }
}

The Happy Path (Sequence Diagram)

Here's exactly what happens when you try to sign in on your new phone while your old phone is the Guardian:

State Machine: What Mode Is My Device In?


Security Analysis: Where Are The Bodies Buried?

I'm not going to pretend this is perfect. Here is an honest threat model:

The Good (What We Mitigated)

ThreatMitigation
Lost device retains accessSingle active device policy. Old device is evicted immediately on new approval.
Session hijacking via stolen refresh tokensDevice ID binding. Even with valid Firebase token, wrong device ID = blocked.
Race condition: two GuardiansBackend re-fetches user state before updates. Last write wins, but Guardian check ensures only active device can approve.
Stale device lockout7-day auto-expiry. Lost your phone in the ocean? Wait a week or use recovery code.

The "Acceptable Risks" (What We Acknowledge)

RiskWhy We Accept It
Device ID spoofingDevice ID is client-generated. An attacker with valid Firebase credentials could spoof the active device ID. Mitigation: Device ID includes hardware-bound components (Android ID, iOS Keychain) making it non-trivial.
Recovery code brute force6-digit codes have 1M combinations. Mitigation: Rate limiting, max attempts, codes are single-use and generated with crypto-secure random.
Social engineeringUser could be tricked into approving attacker's device. Mitigation: Clear device naming, timestamp of request shown in modal.

The Architecture (C4 Context)

Edge Cases: Because Users Are Creative

The "Fell in the Toilet" Scenario

User drops Guardian phone in water. It's dead. They buy a new phone.

  • Day 1-6: User must use recovery code or wait
  • Day 7+: Stale device threshold reached, new device auto-approved

The "Approve Spam" Scenario

User keeps clicking approve on Guardian while Petitioner keeps retrying.

  • Mitigation: Each ensureUser call updates the timestamp. Old pending requests (>10 min) are treated as stale and overwritten.

The "Airplane Mode" Scenario

Guardian approves, but Petitioner is offline.

  • Handling: Petitioner queues the approval. When online, getUser subscription updates and grants access. Guardian is already evicted (kill switch is reactive, not proactive).

Closing: Why This Matters

I built this because I was tired of security theater. Most apps pretend that "sign out everywhere" buttons work instantly, or that session revocation is real-time. It's not. It's eventual consistency with a side of hope.

The Guardian model is pessimistic security: assume every other device is compromised until proven otherwise. It's slightly less convenient than "sign in everywhere," but infinitely more secure than "oops I left my account logged in at the Apple Store."

If you're a security researcher and you see a hole in this logic, please tell me. If you're a developer and want to implement this, steal this code—it's why I open-sourced the logic. If you're just here for the diagrams, I hope they were pretty.

Stay paranoid. Build secure.

Abdul Rafay

Founder, Syntax Lab TechnologySharing the journey at rafay99.com & lumo.rafay99.com

P.S. Yes, I know "Guardian" sounds like a Destiny reference. No, I don't play Destiny. Yes, the naming is staying anyway.