high priority high complexity api pending api specialist Tier 1

Acceptance Criteria

BufdirAuthHandler class is implemented with an authenticate() method that returns a Future<BufdirAuthToken>
OAuth2 client-credentials flow is implemented: POST to token endpoint with client_id and client_secret, parse access_token and expires_in from response
API-key fallback is implemented: if OAuth2 is not configured (null client credentials), attach API key as Authorization: ApiKey {key} header
Authentication mode is determined at runtime by vault configuration — no compile-time flag or hardcoded mode switch
Token caching: a valid non-expired token is returned from memory cache without making a new network call
Token expiry: if cached token expires within 60 seconds, a new token is fetched proactively before the current one expires
401 response handling: when the submission pipeline receives a 401, BufdirAuthHandler.refreshToken() is called exactly once and the request is retried — a second 401 causes BufdirSubmissionStatus.authenticationError to be returned without further retries
Credentials are retrieved from the Supabase Edge Function secure vault (not from flutter_secure_storage on the mobile client — authentication happens server-side)
BufdirAuthHandler is injectable/mockable via its abstract interface IBufdirAuthHandler for unit testing
authenticate() throws a typed BufdirAuthException (not generic Exception) on credential vault failure or token endpoint error
All token endpoint requests are made via HTTPS only — HTTP URLs must throw an assertion error in debug mode
No credentials, tokens, or client secrets appear in application logs at any log level

Technical Requirements

frameworks
Dart http package or dio for HTTP requests
Supabase Edge Functions (Deno) for server-side credential retrieval and token exchange
dart:convert for JSON parsing
apis
Bufdir Reporting API OAuth2 token endpoint (URL TBD from Norse Digital Products negotiation)
Supabase Edge Functions REST API (for credential vault retrieval)
Bufdir API-key endpoint (fallback)
data models
bufdir_export_audit_log (authentication failures must be recorded in audit log with error status)
bufdir_column_schema (organization_id used to scope credential vault lookup)
performance requirements
Token cache hit (non-expired token) must add less than 1ms overhead to request pipeline
Token exchange network call must complete within 5 seconds — timeout after 5s with BufdirAuthException
Concurrent authenticate() calls must not issue duplicate token requests — use a mutex/completer pattern to serialize
security requirements
Client secret and API key stored only in Supabase Edge Function environment variables — never in mobile app binary or flutter_secure_storage
Authentication implemented entirely server-side via Edge Functions — mobile client only sends the organisation JWT, never Bufdir credentials
BufdirAuthToken must not be serialized to disk on the mobile client — memory-only cache
Token endpoint URL must be validated against an allowlist before making requests — prevents SSRF in Edge Function context
Bufdir credentials in vault must be per-organisation — Blindeforbundet credentials must never be accessible to other organisations
All authentication-related exceptions must be caught and wrapped in BufdirAuthException — raw HTTP errors must not propagate with credential details
Organisation credentials stored in integration credential vault on server — never in mobile client (per Bufdir Reporting API security requirements)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Integration Task

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.

Component
Bufdir API Client
service high
Epic Risks (2)
medium impact high prob dependency

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.

high impact low prob security

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.