Implement Bufdir API authentication handler
epic-bufdir-report-export-api-integration-task-002 — Implement authentication with Bufdir's API inside the bufdir-api-client. Support OAuth2 client-credentials flow and API-key fallback as candidates until the API negotiation with Norse Digital Products concludes. Encapsulate token refresh logic, handle 401 re-auth, and expose a single authenticate() method consumed by the submission pipeline. Store credentials via the secure vault rather than environment variables directly.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Handles integration between different epics or system components. Requires coordination across multiple development streams.
Implementation Notes
Architecture: implement BufdirAuthHandler as a Dart class inside a Supabase Edge Function (Deno TypeScript) rather than inside the Flutter app — the mobile app calls the Edge Function, which handles authentication and credential retrieval server-side, then proxies the authenticated request to Bufdir. This keeps all Bufdir credentials server-side. The Flutter-side BufdirApiClientImpl should only know the Edge Function URL and the user's Supabase JWT. For the token concurrency issue (multiple simultaneous requests triggering duplicate token fetches), use a Dart Completer pattern: if a token fetch is in progress, queue subsequent calls on the same Completer rather than issuing new requests.
For the OAuth2/API-key mode switching, define a BufdirCredentialConfig sealed class with two subtypes: BufdirOAuth2Config and BufdirApiKeyConfig — the vault returns one of these, and the handler switches behavior via pattern matching. Ensure the token expiry buffer (60s) is configurable via a constant for easy tuning during API integration testing.
Testing Requirements
Unit tests with mocktail mocking the HTTP client and vault client: (1) successful OAuth2 flow — mock token endpoint returning valid token, assert BufdirAuthToken fields populated correctly; (2) cache hit — call authenticate() twice, assert HTTP client called exactly once; (3) token expiry within 60s — mock token with expires_in=50, assert second authenticate() call triggers new token fetch; (4) 401 retry — mock first call returning 401, second returning 200, assert refreshToken called and final result is success; (5) double 401 — mock both calls returning 401, assert BufdirAuthException thrown; (6) vault failure — mock vault throwing, assert BufdirAuthException thrown with VaultUnavailable error type; (7) API-key fallback — mock null OAuth2 credentials, assert API key header attached. Integration test (against Supabase Edge Function test environment): authenticate() with real vault credentials succeeds. No tests should use real Bufdir API endpoints.
Norse Digital Products has not yet completed API negotiations with Bufdir. If negotiations stall or Bufdir's API design diverges significantly from expectations, the API client may need substantial rework, or the epic may be blocked indefinitely.
Mitigation & Contingency
Mitigation: Implement the client against a locally defined stub of the expected Bufdir API schema. Isolate all Bufdir-specific schema mapping in a single adapter class so that changes to the actual API schema require changes in only one place. Keep the epic in 'interface-ready' status until real API credentials are available for integration testing.
Contingency: If API negotiations are not completed within the planned window, defer this epic without impact on any other epic — the PDF/CSV fallback path from Epics 1–4 delivers full standalone value. Mark the epic as blocked and resurface when negotiations conclude.
Bufdir API credentials stored in the application or edge function environment could be exposed through misconfigured secrets management, log leakage, or a compromised deployment pipeline, allowing unauthorised Bufdir submissions on behalf of the organisation.
Mitigation & Contingency
Mitigation: Store all Bufdir API credentials exclusively in Supabase Vault (or the integration credential vault component), never in client-side code or environment variables accessible to the Flutter app. Transmit credentials only from within the edge function, not from the Flutter client. Implement credential rotation support from the outset.
Contingency: If a credential leak is detected, immediately revoke and rotate the affected API credentials through Bufdir's credential management portal, audit submission logs for any unauthorised calls, and notify Bufdir's technical contact per the API agreement's security incident clause.