Skip to content

fix(mobile): pre-demo fixes — QR/approve swallowed-401, session expiry, NFC, hide Add-card#86

Merged
ahmetabdullahgultekin merged 4 commits into
mainfrom
fix/predemo-mobile-2026-06-03
Jun 3, 2026
Merged

fix(mobile): pre-demo fixes — QR/approve swallowed-401, session expiry, NFC, hide Add-card#86
ahmetabdullahgultekin merged 4 commits into
mainfrom
fix/predemo-mobile-2026-06-03

Conversation

@ahmetabdullahgultekin

Copy link
Copy Markdown
Contributor

Pre-demo (demo 2026-06-03) mobile fixes. Additive + reversible. Do NOT merge/deploy yet — review first, then trigger the signed CI APK build. Each fix was verified against origin/main before changing.

#1 + approve-login — swallowed-401 false "APPROVED" (root of "approved on phone, browser waits forever")

QrLoginApiImpl.approveSession and ApproveLoginApiImpl.decide POSTed without reading the response, unlike their sibling calls. A non-2xx (e.g. an expired bearer → 401/403) was silently swallowed: the phone flipped to APPROVED while the server session stayed PENDING and the originating (web) login never advanced.

  • Both now throw ResponseException(response, ...) on a non-success status, with the status code in the message so the shared ErrorMapper maps it to a user message.
  • The 401 case now also triggers the transparent refresh+retry from Claude/review module plan 014p edzxv bqfib2 grp pjhx6 r #2, so a stale-but-refreshable bearer recovers automatically instead of false-approving.
  • Files: shared/.../api/QrLoginApiImpl.kt (approveSession, ~L33), shared/.../api/ApproveLoginApiImpl.kt (decide, ~L25).

#2 — session dies after ~15 min

  • NetworkModule.kt: the refresh-on-401 HttpResponseValidator could not re-fire the failed request (its own TODO) → the first call after the 15-min access-token lifetime surfaced as a logout. Replaced with an HttpSend interceptor (part of ktor-client-coreno new dependency; the project deliberately avoids ktor-client-auth, see desktopApp/.../RefreshInterceptor.kt) that refreshes under the existing rotation mutex and retries the original request once with the fresh bearer. Refresh logic extracted to a reusable refreshAccessToken() helper.
  • TokenManager.kt: isAuthenticated() checked token presence only; now tolerates an expired access token when a refresh token exists (decodes the JWT exp claim via base64url + kotlinx.serialization; fail-open for non-JWT tokens). A returning user is no longer bounced to login when a silent refresh is possible.

#7 — NFC won't read (OS detects card, app stays "ready to scan")

  • Removed the foreground TECH_DISCOVERED intent-filter + nfc_tech_filter meta-data from AndroidManifest.xml so reader mode wins over the system tag-dispatcher.
  • Relaxed AndroidNfcService.processTag (~L173): previously dropped a tap unless state was exactly WaitingForCard; now only rejects a tap while a read is already in flight (Reading).
  • ⚠️ NOT verifiable without a physical device — needs on-device adb logcat confirmation before being trusted for the demo. Fully reversible (comments document how).

#8 — "Add card" dead-end

  • Hidden the DashboardScreen "Add card" QuickAction. CardScanScreen is a camera photo wizard that never uploads/persists. Screen + nav route left intact; uncomment to restore once OCR/upload backend exists.

Verification

  • :shared:compileDebugKotlinAndroid ✅ · :androidApp:compileDebugKotlin + processDebugMainManifest
  • New tests: QrApproveLoginStatusCheckTest (6) + TokenManagerAuthExpiryTest (4) — all pass.
  • Full shared unit suite (forced rerun): 489 tests, 0 failures, 0 errors.
  • Build host cannot run an emulator, so Fingerprint Frontend #7's runtime behaviour is unverified by design.

Still needs on-device verification

  • Fingerprint Frontend #7 NFC — both the manifest change and the state-guard relaxation are best-effort and require adb logcat on a real device with a real chip before the demo. Treat live chip-read as not-demo-safe until confirmed.

🤖 Generated with Claude Code

Ahmet Abdullah Gultekin and others added 4 commits June 3, 2026 13:31
…y, NFC, hide Add-card

Four additive, reversible pre-demo fixes (demo 2026-06-03). Verified each against
origin/main before changing.

#1 + approve-login silent-approve swallow
- QrLoginApiImpl.approveSession and ApproveLoginApiImpl.decide POSTed without
  reading the response, so a non-2xx (e.g. expired bearer → 401/403) was
  swallowed: the phone flipped to APPROVED while the server session stayed
  PENDING and the originating (web) login never advanced. Both now throw a
  ResponseException on a non-success status (status code included in the message
  so ErrorMapper maps it). The 401 path now also triggers the transparent
  refresh+retry below, so a stale-but-refreshable bearer recovers automatically.

#2 session ~15-min logout
- NetworkModule: the refresh-on-401 HttpResponseValidator could not re-fire the
  failed request (its own TODO) → first call after the 15-min access-token
  lifetime surfaced as a logout. Replaced it with an HttpSend interceptor (part
  of ktor-client-core — no new dependency; the project deliberately avoids
  ktor-client-auth, see desktopApp RefreshInterceptor) that refreshes under the
  existing rotation mutex AND retries the original request once with the fresh
  bearer. Refresh logic extracted to a reusable refreshAccessToken() helper.
- TokenManager.isAuthenticated() checked token PRESENCE only; now tolerates an
  expired access token when a refresh token exists (decodes the JWT exp claim via
  base64url + kotlinx.serialization; fail-open for non-JWT tokens), so a
  returning user is not bounced to login when a silent refresh is possible.

#7 NFC won't read (OS detects card, app stays "ready to scan")
- Removed the foreground TECH_DISCOVERED intent-filter + nfc_tech_filter
  meta-data from AndroidManifest so reader mode (enableReaderMode) wins instead
  of competing with the system tag-dispatcher.
- Relaxed AndroidNfcService.processTag: previously dropped a tap unless state was
  exactly WaitingForCard; now only rejects a tap while a read is already in
  flight (Reading), so an Idle/Completed/Error tap starts a fresh read.
- NOTE: NOT verifiable without a physical device — needs on-device `adb logcat`
  confirmation before being trusted. Fully reversible.

#8 "Add card" dead-end button
- Hidden the DashboardScreen "Add card" QuickAction: CardScanScreen is a camera
  photo wizard that never uploads/persists anything. Screen + nav route left
  intact (reversible — uncomment to restore once OCR/upload backend exists).

Tests: +10 (QrApproveLoginStatusCheckTest 6, TokenManagerAuthExpiryTest 4).
Compile: :shared:compileDebugKotlinAndroid + :androidApp:compileDebugKotlin +
processDebugMainManifest all green. Full shared unit suite: 489 tests, 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…crash)

ActivityHistoryViewModel and AuditLogDashboardViewModel depend on
AuditLogRepository, but #83 wired the ViewModel without registering
AuditLogApi (NetworkModule) or AuditLogRepository (RepositoryModule).
Opening Activity History / the audit-log dashboard on the release build
throws Koin NoDefinitionFoundException -> FATAL crash. Verified on-device.
Adds the two missing single<> bindings (matches the existing pattern).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces the approve-login approver screen (Screen.ApproveLogin) on the
Dashboard Quick Actions grid (was reachable only via Profile). New
StringKey.DASH_LOGIN_REQUESTS (EN 'Login Requests' / TR 'Giriş İstekleri').
Additive nav tile; opens the already-registered ApproveLoginScreen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…navailable message (#5)

- Relabel the 3 confusingly-similar buttons into an ordered flow (camera MRZ fill / read chip / read any card)
- Show a friendly message for the known NO_TRUST_STORE passive-auth case instead of a raw error
- Clarify YYMMDD date fields

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ahmetabdullahgultekin ahmetabdullahgultekin merged commit cf205a5 into main Jun 3, 2026
4 checks passed
ahmetabdullahgultekin added a commit that referenced this pull request Jun 6, 2026
…otes (#89)

- CHANGELOG: promote [Unreleased] → [5.3.1] - 2026-06-06; add the MFA
  transport/IO retry fix (#87, headline) and cross-device/session fixes (#86)
  that had landed post-5.3.0 without changelog entries; keep the #82/#83 items.
- CLAUDE.md: refresh stale "Latest: v5.2.3 / versionCode 10" → "v5.3.1 /
  versionCode 13"; document the CI signed-build command (host has no keystore
  pw); add a "v5.3.1 — MFA stale-connection retry" section.

Co-authored-by: Ahmet Abdullah Gultekin <rollingcat.help@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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