Skip to content

Feature: replace bundled credentials.json with real AuthenticationServices / iCloud Keychain integration #13

@jmcte

Description

@jmcte

Problem

The v2 product contract described in docs/NATIVE_ONLY_REDESIGN.md is "an app-assisted credential broker" that "uses public Apple APIs such as AuthenticationServices" to return iCloud Keychain credentials for associated domains.

The shipping implementation does not do that. native-app/Sources/NativeAppLib/BrokerCore.swift:

  • Imports AuthenticationServices but only touches ASCredentialIdentityStore.shared for side effect (line ~18). There is no ASAuthorizationController, no ASAuthorizationPasswordRequest, and no ASPasswordCredential handling.
  • Responds to apw login https://example.com by reading a bundled ~/.apw/native-app/credentials.json (see loginResponse at lines 266-325) that contains a plaintext demo password (apw-demo-password).
  • Uses NSAlert as the "approval UI" instead of Apple's native credential-selection picker.

That means APW today is a credential replayer, not a credential broker. Nothing in the shipped path talks to iCloud Keychain. Until this is fixed, apw login and apw fill cannot return real user credentials, domain expansion (#8) is moot, and the security posture in docs/SECURITY_POSTURE_AND_TESTING.md overstates what the app actually does.

Proposed Fix

  1. Introduce an AuthenticationServicesBroker type in NativeAppLib that:
    • Builds an ASAuthorizationPasswordRequest scoped to the requested associated domain.
    • Runs the request through ASAuthorizationController on the main thread, with the native app as the presentation context provider.
    • Converts ASPasswordCredential results into the existing ResponseEnvelope shape, preserving the userMediated: true semantic.
    • Maps ASAuthorizationError.Code values (.canceled, .failed, .invalidResponse, .notHandled, .unknown) to stable APW broker error codes so the Rust CLI can keep its typed error surface.
  2. Route the login and fill dispatch branches in BrokerCore.dispatch through the new broker instead of loadCredentials() / NSAlert.
  3. Keep the credentials-file path behind an explicit APW_DEMO=1 env flag (or a --demo install flag) so it can still be used for the bootstrap end-to-end test, but is never the default user-visible flow.
  4. Update docs/NATIVE_ONLY_REDESIGN.md Phase 3 exit criteria to reflect that AuthenticationServices is actually wired, and add a sentence to README.md distinguishing the demo mode from the real broker.

Acceptance Criteria

  • apw login https://<associated-domain> returns a real iCloud Keychain credential via ASAuthorizationController on a machine with that domain's AASA configured.
  • apw login never reads credentials.json unless APW_DEMO=1 is set.
  • All ASAuthorizationError cases map to documented broker error codes with human messages.
  • At least one integration test in native-app/Tests/NativeAppTests/ covers the cancel, denied, and success paths using an injected AuthenticationServicesBroker fake.
  • docs/NATIVE_ONLY_REDESIGN.md and docs/SECURITY_POSTURE_AND_TESTING.md reflect the real integration surface.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions