high priority medium complexity integration pending integration specialist Tier 0

Acceptance Criteria

OAuth2 client credentials flow successfully obtains an access token from the Azure AD token endpoint for the HLF Dynamics 365 tenant
Access token is cached in memory with an expiry timestamp; a new token is requested automatically when fewer than 60 seconds remain before expiry
Azure AD client_id, client_secret, and tenant_id are read exclusively from the encrypted server-side credential vault — never from mobile app bundles or plain environment files
Authenticated HTTP client (Dart http.Client or Dio) exposes a method `getAuthenticatedClient()` returning a client with Bearer token pre-attached to all requests
Token refresh is thread-safe: concurrent requests during refresh do not trigger multiple simultaneous token fetches (mutex/lock pattern applied)
Authentication failure (invalid credentials, tenant unreachable) throws a typed `DynamicsAuthException` with structured error context
No Azure AD credentials or tokens are ever logged in plain text — logs show only masked identifiers (e.g., client_id prefix, token expiry timestamp)
Unit test: mock Azure AD token endpoint returns 200 → client correctly parses and caches `access_token` and `expires_in`
Unit test: mock returns 401 → `DynamicsAuthException` is thrown with `authFailure` error code
Unit test: token within 60s of expiry → refresh is triggered before next request

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Microsoft Dynamics 365 REST API
Azure AD OAuth2 token endpoint (client_credentials grant)
data models
certification
performance requirements
Token fetch must complete within 3 seconds on a stable network
Cached token lookup must be synchronous and complete in under 1ms
Refresh must not block downstream sync calls for more than 5 seconds
security requirements
Azure AD credentials stored server-side only — never in mobile app binary or flutter_secure_storage
OAuth2 client_credentials flow (no user delegation) — scope limited to Dynamics read/write certification data only
Tokens stored in memory only (not persisted to disk) within Edge Function runtime
OAuth token rotation enforced via Azure AD token lifetime policies
Minimal required Dynamics permissions — read certification, write sync metadata only
All credential vault reads must be scoped to HLF organisation via credential isolation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a Dart class `DynamicsAuthProvider` with a single public method `getAuthenticatedClient()`. Use a `Completer` or `Mutex` (from the `mutex` package) to prevent concurrent token refreshes. Store token and expiry as private instance variables (`_accessToken`, `_tokenExpiresAt`). The credential vault interface should be injected as a dependency (e.g., `CredentialVaultRepository`) so it can be mocked in tests.

Since this runs server-side (Edge Functions/Deno), flutter_secure_storage is NOT applicable — use the Supabase Edge Function environment secrets. Keep the token refresh logic in a private `_refreshToken()` method. Expose only `getAuthenticatedClient()` publicly. Do not store `client_secret` in any log output.

Consider using a `dio` interceptor pattern so all downstream requests automatically re-authenticate on 401 responses.

Testing Requirements

Unit tests using flutter_test with a mock HTTP client (MockClient from http package) covering: successful token acquisition and caching, automatic refresh when expiry < 60s, concurrent refresh deduplication via mutex, 401 error → typed DynamicsAuthException, network timeout → DynamicsAuthException with timeout code, and credential vault unavailability. Test coverage target: 100% of public methods in the auth module. No real Azure AD calls in unit tests — all HTTP interactions mocked.

Component
HLF Dynamics Sync Service
service medium
Epic Risks (3)
high impact medium prob integration

HLF Dynamics portal webhook API contract may be undocumented, subject to change, or require a separate authentication flow not yet agreed upon with HLF. If the contract changes post-implementation, the sync service silently fails and expired peer mentors remain on public listings.

Mitigation & Contingency

Mitigation: Obtain the official Dynamics webhook specification and test credentials from HLF before starting HLFDynamicsSyncService implementation. Agree on a versioned webhook contract and request a staging endpoint for integration testing.

Contingency: If the contract is unavailable, stub the sync service behind a feature flag and ship without Dynamics sync initially. Queue sync events locally and replay once the contract is confirmed.

high impact medium prob security

Supabase RLS policies for certifications must correctly scope data to the coordinator's chapter without leaking cross-organisation data, particularly complex in multi-chapter membership scenarios. A misconfigured policy could expose peer mentor PII to wrong coordinators.

Mitigation & Contingency

Mitigation: Write RLS policies against the established org-hierarchy schema used by other tables. Peer review all policies before migration deployment. Add integration tests that assert cross-organisation data isolation using test accounts with different org scopes.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected query endpoint and apply a hotfix migration. Audit access logs in Supabase for any cross-org data access events.

medium impact low prob technical

Storing renewal history as a JSONB field rather than a normalised table simplifies queries but makes retrospective schema changes (adding fields to history entries) harder and could cause issues if history grows very large for long-tenured mentors.

Mitigation & Contingency

Mitigation: Define a versioned JSONB entry schema (include a schema_version field in each entry) so future migrations can transform old entries. Add a size guard in the repository to warn if renewal_history exceeds 500 entries.

Contingency: If JSONB approach proves limiting, add a normalised certification_renewal_events table and migrate history entries in a background job, keeping the JSONB field as a read cache.