Repository navigation
Release 2026.32 - #432
Merged
Merged
Release 2026.32#432
Conversation
Feature: - New My Devices page (/settings/devices): list registered devices with platform, model, status, primary flag, current-device marker and last-used time; rename devices; set primary; revoke devices; approve/reject pending secondary devices from an approved device - Destructive actions (revoke, approve/reject pending) require step-up re-authentication: OS biometric prompt (device-bound secret) or account PIN - Revoking a device removes its record (incl. biometric secret + FCM token), invalidates other sessions, pushes a device_revoked notice that makes the revoked device wipe local state and sign out, and notifies remaining devices - Removing the last device deregisters the account entirely (re-registration goes back through first-device admin approval) - Primary flag is re-assigned atomically when the primary device is revoked - devices.lastUsed tracked on biometric login and notification responses - Insert-only deviceAuditLog records every rename/setPrimary/revoke/approval Security hardening (pre-existing gaps on the same data paths): - deviceDetails.byUser publication now requires the caller to be the owner and uses an inclusion projection; biometricSecret/fcmToken are never published - deviceDetails.byDevice (pre-login check) now exposes only deviceUUID and registration status - FCM-token/device lookup Meteor methods (getFCMTokenByUsername, getByUserId, etc.) and the deviceDetails registration method are no longer client-callable; converted to server-internal functions (previously anyone could fetch any user's FCM tokens or register an auto-approved device on any account) - devices.respondToSecondaryApproval now requires the authenticated owner - users.removeCompletely is now server-internal only - DDP rate limiting on users.loginWithBiometric, users.register and the new device management methods (10/min per connection) Tests: tests/deviceManagement.js covers ownership enforcement, step-up proof validation, primary promotion, last-device account removal, publication projection, and that internal methods are no longer exposed.
…ry flag, device icons
Addresses review feedback on the My Devices feature:
1. Revoke/approve step-up now tries BIOMETRIC FIRST (auto-prompted on open),
falling back to PIN only if biometrics are unavailable, fail, or the user
opts out. Adds Use PIN / Use biometrics toggles.
2. Profile name fixes:
- The hardened deviceDetails.byUser projection had dropped firstName/
lastName/email, so the dashboard greeting showed 'User'. Restored these
(the user's own data) to the projection.
- Profile edit called a non-existent 'user.updateProfile' method; now calls
updateUserProfile, which also keeps the denormalized DeviceDetails copy in
sync, and updates the session profile so the greeting refreshes.
3. A lone approved device is now treated as effectively primary (shows the
star, hides 'Make primary'); 'Make primary' only appears with 2+ approved
devices.
4. The current device now falls back to live captured OS info (model/platform)
when the stored record is 'Unknown', and device names derive from model or
a platform label instead of 'Unknown device'.
5. Nicer device cards: platform icons (Apple for iOS, smartphone for Android),
a watch-support line (Apple Watch / Wear OS) for approved devices, and a
clearer action row.
…d icons 1. Profile showed 'User': useUserProfile did a one-shot non-reactive Minimongo read at mount, racing the subscription. Now subscribes itself and tracks the user doc reactively (without clobbering in-progress edits). 2. Devices stuck as 'Unknown': records registered with placeholder model/platform never healed. New devices.updateInfo method lets each device self-report its live OS model/platform (and bump lastUsed) on dashboard load — auth'd, owner-scoped, length-capped, rate-limited — so every device sees real names for all devices after one app open. 3. Two 'Make primary' stars / no primary: re-registering an existing device (app reinstall) stomped isPrimary to false, leaving accounts with no primary device. registerDeviceDetails now preserves isFirstDevice/ isPrimary/isSecondaryDevice on re-registration, and devices.updateInfo self-heals accounts already in that state by promoting the oldest approved device. Primary is now labeled with an explicit 'Primary' badge. 4. Replaced generic phone glyphs with official Apple / Android brand icons (Simple Icons paths, currentColor). Tests: updateInfo happy path, primary-invariant repair, cross-user rejection.
Root cause of 'Unknown device' / missing primary flag persisting even with
fresh data: TWO publications published different projections of the same
top-level 'devices' field on the same DeviceDetails document —
deviceDetails.byUser (full whitelist) and the pre-login
deviceDetails.byDevice (only deviceUUID + status). Meteor's DDP merge box
tracks top-level fields only, so the stripped byDevice projection clobbered
the full one and the client saw devices without model/platform/isPrimary/
lastUsed ('Last used: Never' on a device whose lastUpdated always exists was
the tell). The byDevice subscription also leaked: its cleanup was returned
from inside Tracker.autorun, which ignores return values, so it stayed live
for the entire session.
Fix:
- Removed the deviceDetails.byDevice publication entirely (comment documents
why no second publication may be added over this collection).
- Pre-login registration check is now a method, devices.checkRegistrationByUUID
(returns only { registered, status }; rate-limited), with no Minimongo
footprint and therefore no merge conflict.
- useDeviceRegistration calls the method instead of subscribing, de-duped per
UUID.
Tests: registered + unregistered lookups for the new method (98 passing).
…mail verified reset, self-revoke sessions
- registerDeviceDetails no longer logs the full DeviceDetails document
(contained biometricSecret + fcmToken); logs found/deviceCount only.
- Successor-primary promotion on revoke is now guarded with
devices $not $elemMatch { isPrimary: true } so concurrent revoke/setPrimary
interleavings cannot produce two primaries.
- devices.revoke: self-revoke (target === actor) now clears ALL login tokens
including the caller's own — the client-side wipe is best-effort only.
- updateUserProfile resets emails.0.verified to false when the address
changes so a new address never inherits verified status.
Test: self-revoke with a live connection leaves zero resume tokens.
1. The primary device (or only approved device) can no longer be removed — the account always keeps at least one trusted device. Enforced server-side in devices.revoke (primary-device-protected) and the Remove button is hidden on the primary's card. To remove it, the primary role must be transferred to another device first. 2. devices.setPrimary is now a trust transfer requiring the CURRENT primary's consent: unless the request is initiated from the primary itself (or no approved primary exists), an actionable approve/reject push is sent to the current primary and the method waits (25s) for the response. The notificationId is delivered only in that push, so only the primary device can answer; notifications.handleResponse additionally enforces ownership + approved status. Fails closed on unreachable push, rejection, or timeout, and every outcome is audit-logged. Client shows a waiting state while the approval is pending. Also: Alert variants corrected to the design system's 'danger'. Tests updated: primary revoke blocked (incl. last device), non-primary revoke, direct transfer from primary, fail-closed transfer from a secondary.
Direct transfers (initiated from the current primary) previously happened silently. Every actual primary change — direct or approved — now sends an informational push (notificationType: primary_changed, non-actionable) to all approved devices except the initiating one, so the account owner learns about any trust change immediately.
…to all devices
devices.setPrimary is now human-confirmed in every case:
- Step-up re-authentication (biometric-first with PIN fallback) is REQUIRED
on the initiating device — an unlocked-but-unattended phone or hijacked
session can no longer silently change the primary, even when initiated
from the primary itself.
- Transfers initiated from a non-primary device additionally still require
the current primary's push approval (unchanged).
Trust-change notifications:
- Every actual primary change is announced to ALL approved devices
(including the initiator) after a 10s delay, so users can background the
app and receive it as a visible OS banner (iOS shows no banner for
foreground pushes); devices with the app open show an in-app toast.
- New-device additions now broadcast a 'New Device Added' notice to every
other registered device — both when approved via the registration flow
(primary push approval) and via My Devices (devices.approvePending).
- Shared notifyApprovedDevices helper; foreground toast handling for
primary_changed / device_added_info / device_removed_info in
push-notifications.js, rendered on the dashboard and My Devices.
Step-up modal (biometric-first, PIN fallback):
- Auto-triggers the biometric prompt directly on open — same direct
loadBiometricSecret pattern as the login flow. Deliberately NO
Fingerprint.isAvailable pre-check (it misreports Face ID on iOS
simulators) and no reliance on the startup Session flag (stale).
- Any biometric failure/cancel falls back to PIN entry with a hint;
empty-secret results are treated as failures so invalid proofs never
reach the server ('Match failed' fix for devices without enrolled
biometrics).
Tests: setPrimary now exercises mandatory reAuth (valid, invalid, and
approval-gated paths); 102 passing.
Add self-service device management ("My Devices") with security hardening
Add mobile diagnostics log viewer and email export
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces several new features and improvements to the mobile app, focusing on enhanced device management, diagnostics, and user experience. Notably, it adds an in-app diagnostics viewer, improves device registration and trust notifications, and refines user profile handling. The most important changes are grouped below:
Device Diagnostics and Logging Enhancements:
cordova-plugin-diagnostics-viewerandcordova-plugin-native-logsplugins and configured them for the project, enabling an in-app diagnostics viewer for easier troubleshooting. (.meteor/cordova-plugins,mobile-config.js) [1] [2]client/mobile/diagnostics.js, which initializes the diagnostics viewer, redacts sensitive log data, and allows users to send logs via email with device info. The diagnostics viewer is accessible from the dashboard menu. (client/mobile/diagnostics.js,client/mobile/src/ui/components/DashboardHeader.jsx,client/main.jsx) [1] [2] [3] [4]Device Management and Trust Notifications:
client/mobile/src/ui/components/AppRoutes.jsx,client/mobile/src/ui/components/DashboardHeader.jsx) [1] [2] [3]client/mobile/push-notifications.js,client/mobile/src/ui/LandingPage.jsx) [1] [2] [3]User Profile and Device Registration Improvements:
useUserProfileto use a reactive subscription for user profile data, ensuring the dashboard always shows up-to-date information. Profile updates now immediately sync with the in-memory session. (client/mobile/src/ui/hooks/useUserProfile.js) [1] [2]client/mobile/src/ui/hooks/useDeviceRegistration.js) [1] [2]Security and Dependency Updates:
ddp-rate-limiterpackage for brute-force protection on authentication/device management methods. (.meteor/packages)Minor Updates:
"1.5.0"(formatting change). (mobile-config.js)These changes collectively improve diagnostics, device management, and the overall user experience, especially for users managing multiple devices or requiring support.