Skip to content

Release 2026.32 - #432

Merged
abroa01 merged 13 commits into
mainfrom
development
Aug 6, 2026
Merged

abroa01 merged 13 commits into
mainfrom
development

Conversation

@abroa01

@abroa01 abroa01 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

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:

  • Added the cordova-plugin-diagnostics-viewer and cordova-plugin-native-logs plugins and configured them for the project, enabling an in-app diagnostics viewer for easier troubleshooting. (.meteor/cordova-plugins, mobile-config.js) [1] [2]
  • Implemented 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:

  • Added a new "My devices" page, accessible from the dashboard dropdown, and a corresponding route for device management on mobile. (client/mobile/src/ui/components/AppRoutes.jsx, client/mobile/src/ui/components/DashboardHeader.jsx) [1] [2] [3]
  • Improved push notification handling: device revocation now logs out and wipes credentials, and foreground device-trust notifications (e.g., device added/removed, primary changed) are surfaced as in-app toasts. (client/mobile/push-notifications.js, client/mobile/src/ui/LandingPage.jsx) [1] [2] [3]

User Profile and Device Registration Improvements:

  • Refactored useUserProfile to 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]
  • Simplified device registration checks to avoid Minimongo projection conflicts, using a Meteor method instead of a subscription. (client/mobile/src/ui/hooks/useDeviceRegistration.js) [1] [2]

Security and Dependency Updates:

  • Added the ddp-rate-limiter package for brute-force protection on authentication/device management methods. (.meteor/packages)

Minor Updates:

  • Bumped the app version to "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.

abroa01 and others added 13 commits August 4, 2026 08:27
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
@abroa01
abroa01 merged commit 54de0fb into main Aug 6, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant