[research] Verification reminder for your OWN unverified sessions #123

Open
opened 2026-09-17 15:43:35 -04:00 by jared · 1 comment
Owner

Not other people's devices (that is #113 session hygiene / the existing composer warning). This is: you logged in on a new device, never verified it (no cross-signing signature), and nothing ever tells you — so that device can't read backed-up history and shows as a red shield to everyone else.

Research first (correctness matters — wrong prompts here erode trust)

  1. Exact SDK signals: crypto.getUserDeviceInfo / DeviceVerificationStatus.crossSigningVerified for this device (mx.getDeviceId()), cross-signing set-up state (isCrossSigningReady), secret storage state — enumerate every combination and what the correct nudge is (verify via another device / set up cross-signing / restore from backup), reusing the existing flows in components/DeviceVerificationSetup.tsx, BackupRestore.tsx.
  2. Timing: nudge on the next launch after 24 h, not during first-run (the user is mid-onboarding); once per 7 days after that; never on a device that is verified; stop entirely after a dismissal count (3?).
  3. Where: sidebar-bottom banner like the unverified-device warning vs. a dot on Settings → Devices. Recommend one.
  4. Interplay with #110 (key backup banner) so two banners never stack — a single "security" banner with priority ordering.
  5. OIDC/Authelia accounts: confirm the flows behave identically (they should — verification is client crypto, not auth).
    Deliverable: findings + one design in this issue; implementation after review.
Not other people's devices (that is #113 session hygiene / the existing composer warning). This is: you logged in on a new device, never verified it (no cross-signing signature), and nothing ever tells you — so that device can't read backed-up history and shows as a red shield to everyone else. ### Research first (correctness matters — wrong prompts here erode trust) 1. Exact SDK signals: `crypto.getUserDeviceInfo` / `DeviceVerificationStatus.crossSigningVerified` for **this** device (`mx.getDeviceId()`), cross-signing set-up state (`isCrossSigningReady`), secret storage state — enumerate every combination and what the correct nudge is (verify via another device / set up cross-signing / restore from backup), reusing the existing flows in `components/DeviceVerificationSetup.tsx`, `BackupRestore.tsx`. 2. Timing: nudge on the **next launch after 24 h**, not during first-run (the user is mid-onboarding); once per 7 days after that; never on a device that is verified; stop entirely after a dismissal count (3?). 3. Where: sidebar-bottom banner like the unverified-device warning vs. a dot on Settings → Devices. Recommend one. 4. Interplay with #110 (key backup banner) so two banners never stack — a single "security" banner with priority ordering. 5. OIDC/Authelia accounts: confirm the flows behave identically (they should — verification is client crypto, not auth). Deliverable: findings + one design in this issue; implementation after review.
jared added this to the Features 2026-Q4 milestone 2026-09-17 15:43:35 -04:00
jared added the enhancementpriority: mediumsecurityarea: auth-sessionresearch labels 2026-09-17 15:43:35 -04:00
jared self-assigned this 2026-09-17 15:43:35 -04:00
Author
Owner

Findings (2026-09-20, code audit)

1. Signals we already compute, and every combination

# cross-signing published? (useCrossSigningActive = m.cross_signing.master account-data present) this device signed? (crypto.getDeviceVerificationStatus(me, myDeviceId).crossSigningVerifieduseDeviceVerificationStatus) key backup on server? (crypto.getKeyBackupInfo()useKeyBackupInfo) meaning correct nudge
A no account never set up cross-signing (fresh account, or reset) "Set up device verification"DeviceVerificationSetup (bootstraps SSSS + cross-signing + backup in one go)
B yes no yes the case in this issue: logged in elsewhere, never verified here; history is in backup but this device can't unlock it, and it shows a red shield to others "Verify this device" → Settings → Devices → VerifyOtherDeviceTile (request from another device) or "Verify Manually" (recovery key/passphrase → ManualVerification, which also restores the backup)
C yes no no cross-signing exists but no backup (someone turned it off) same as B; verification alone fixes the red shield, history for old rooms stays lost — say so honestly
D yes yes no verified, no backup that is #110's banner, not this one
E yes yes yes healthy nothing, ever
F Unsupported (getDeviceVerificationStatus returns null: crypto not ready / no rust store yet) transient on boot nothing; re-evaluate on DeviceListChange (already wired)

Existing surfaces: the sidebar already shows the red shield tab (UnverifiedTab.tsx) for case B/C and an orange one for other unverified devices, and Settings → Devices already explains the two paths (Verification.tsx). So "nothing ever tells you" is slightly off — it is a 24 px icon with a tooltip and no words, which is why nobody acts on it.

2. Timing

Use the session's creation time (the sessions store keeps it; add createdAt if it doesn't survive a reload — it's one field on the persisted session). Rules: show only when now − createdAt ≥ 24 h and status is B or C (never A during onboarding: the setup dialog is already offered at Settings → Devices, and a first-run nudge collides with the E2EE onboarding), re-show at most every 7 days, stop after 3 dismissals — all three counters in localStorage under lotus-verify-nudge-<deviceId> (per device on purpose; cleared by clearPlaintextCaches on logout). Verified → the record is deleted so a later reset starts fresh.

3. Where

Recommendation: a sidebar-bottom banner (same slot and style as the existing unverified-device icon, but with words: "Verify this device — so you can read older messages and others see you as trusted · Verify · Not now") rather than a dot on Settings → Devices. A dot is what we have today in effect, and it doesn't convert. Mobile: the same banner above the composer's room list.

4. One security banner (#110)

Fold both into a single SecurityBanner with a priority list: verify-device (B/C) > key-backup (#110's D) > nothing. One banner slot, one dismissal store keyed by banner id, so they never stack and B's dismissal doesn't hide D later.

5. OIDC / Authelia

Identical: everything above is client-side rust-crypto state (device signatures, SSSS, backup). The only OIDC-specific wrinkle is account-management redirects for resetting cross-signing (accountManagementActions.crossSigningReset is already handled in Verification.tsx); the nudge never needs it.

Design (one, for review)

  • hooks/useVerifyNudge.ts: pure decision shouldShowVerifyNudge({ status, crossSigningActive, sessionCreatedAt, record, now }) (unit-tested) + the localStorage record.
  • components/SecurityBanner.tsx rendered once in the sidebar footer, choosing between verify-device and key-backup (#110) content by priority; "Verify" opens Settings → Devices (via settingsRequestAtom, like #159); "Not now" bumps the dismissal count.
  • Remove nothing: the shield icon stays for the always-on state, the banner is the timed nudge.
    ~150 lines + tests. Waiting for a go before building (per the issue: implementation after review).
## Findings (2026-09-20, code audit) ### 1. Signals we already compute, and every combination | # | cross-signing published? (`useCrossSigningActive` = `m.cross_signing.master` account-data present) | this device signed? (`crypto.getDeviceVerificationStatus(me, myDeviceId).crossSigningVerified` → `useDeviceVerificationStatus`) | key backup on server? (`crypto.getKeyBackupInfo()` → `useKeyBackupInfo`) | meaning | correct nudge | |---|---|---|---|---|---| | A | no | — | — | account never set up cross-signing (fresh account, or reset) | **"Set up device verification"** → `DeviceVerificationSetup` (bootstraps SSSS + cross-signing + backup in one go) | | B | yes | **no** | yes | the case in this issue: logged in elsewhere, never verified here; history is in backup but this device can't unlock it, and it shows a red shield to others | **"Verify this device"** → Settings → Devices → `VerifyOtherDeviceTile` (request from another device) or "Verify Manually" (recovery key/passphrase → `ManualVerification`, which also restores the backup) | | C | yes | no | no | cross-signing exists but no backup (someone turned it off) | same as B; verification alone fixes the red shield, history for old rooms stays lost — say so honestly | | D | yes | yes | no | verified, no backup | that is #110's banner, not this one | | E | yes | yes | yes | healthy | nothing, ever | | F | `Unsupported` (`getDeviceVerificationStatus` returns null: crypto not ready / no rust store yet) | — | — | transient on boot | nothing; re-evaluate on `DeviceListChange` (already wired) | Existing surfaces: the sidebar **already** shows the red shield tab (`UnverifiedTab.tsx`) for case B/C and an orange one for other unverified devices, and Settings → Devices already explains the two paths (`Verification.tsx`). So "nothing ever tells you" is slightly off — it is a 24 px icon with a tooltip and no words, which is why nobody acts on it. ### 2. Timing Use the session's creation time (the `sessions` store keeps it; add `createdAt` if it doesn't survive a reload — it's one field on the persisted session). Rules: show only when `now − createdAt ≥ 24 h` and status is B or C (never A during onboarding: the setup dialog is already offered at Settings → Devices, and a first-run nudge collides with the E2EE onboarding), re-show at most every 7 days, stop after 3 dismissals — all three counters in `localStorage` under `lotus-verify-nudge-<deviceId>` (per device on purpose; cleared by `clearPlaintextCaches` on logout). Verified → the record is deleted so a later reset starts fresh. ### 3. Where Recommendation: a **sidebar-bottom banner** (same slot and style as the existing unverified-device icon, but with words: "Verify this device — so you can read older messages and others see you as trusted · Verify · Not now") rather than a dot on Settings → Devices. A dot is what we have today in effect, and it doesn't convert. Mobile: the same banner above the composer's room list. ### 4. One security banner (#110) Fold both into a single `SecurityBanner` with a priority list: `verify-device` (B/C) > `key-backup` (#110's D) > nothing. One banner slot, one dismissal store keyed by banner id, so they never stack and B's dismissal doesn't hide D later. ### 5. OIDC / Authelia Identical: everything above is client-side rust-crypto state (device signatures, SSSS, backup). The only OIDC-specific wrinkle is account-management redirects for *resetting* cross-signing (`accountManagementActions.crossSigningReset` is already handled in `Verification.tsx`); the nudge never needs it. ## Design (one, for review) - `hooks/useVerifyNudge.ts`: pure decision `shouldShowVerifyNudge({ status, crossSigningActive, sessionCreatedAt, record, now })` (unit-tested) + the localStorage record. - `components/SecurityBanner.tsx` rendered once in the sidebar footer, choosing between verify-device and key-backup (#110) content by priority; "Verify" opens Settings → Devices (via `settingsRequestAtom`, like #159); "Not now" bumps the dismissal count. - Remove nothing: the shield icon stays for the always-on state, the banner is the timed nudge. ~150 lines + tests. Waiting for a go before building (per the issue: implementation after review).
jared added the needs-human-review label 2026-09-20 13:49:50 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#123