# Dart / Flutter Mobile API

This backend exposes a versioned mobile API for Flutter and Dart clients.

Base path:

```text
/api/v1
```

## Public endpoints

- `GET /api/v1/health`
- `GET /api/v1/bootstrap`
- `GET /api/v1/athletes`
- `GET /api/v1/athletes/{id}`
- `GET /api/v1/events`
- `GET /api/v1/events/{slug}`
- `GET /api/v1/lookups/sports`
- `GET /api/v1/lookups/disciplines?sport_id=1`
- `GET /api/v1/lookups/federations`

## Auth endpoints

Unauthenticated:

- `POST /api/v1/auth/security`
- `POST /api/v1/auth/passkey/challenge`
- `POST /api/v1/auth/login`

Authenticated with Sanctum bearer token:

- `GET /api/v1/auth/me`
- `POST /api/v1/auth/logout`
- `GET /api/v1/auth/passkeys`
- `POST /api/v1/auth/passkeys/options`
- `POST /api/v1/auth/passkeys`
- `DELETE /api/v1/auth/passkeys/{id}`

Use bearer token auth:

```http
Authorization: Bearer <token>
Accept: application/json
```

## Login flow

### 1. Inspect security requirements

Request:

```json
{
  "email": "user@example.com",
  "password": "secret"
}
```

Response:

```json
{
  "data": {
    "email": "user@example.com",
    "firebase_otp_enabled": true,
    "firebase_otp_required": true,
    "phone_hint": "+2547******89",
    "passkey_enabled": true,
    "passkey_enrolled": true,
    "passkey_required": true,
    "security_endpoint": "https://example.com/api/v1/auth/security",
    "passkey_challenge_endpoint": "https://example.com/api/v1/auth/passkey/challenge",
    "login_endpoint": "https://example.com/api/v1/auth/login"
  }
}
```

### 2. If passkey is required, request a challenge

Request:

```json
{
  "email": "user@example.com",
  "password": "secret"
}
```

Response:

```json
{
  "data": {
    "challenge_token": "c8c7f2d8-....",
    "expires_in": 300,
    "public_key": {
      "challenge": "...",
      "timeout": 60000,
      "rpId": "portal.example.com",
      "allowCredentials": [
        {
          "id": "...",
          "type": "public-key",
          "transports": ["internal", "hybrid", "usb", "nfc", "ble"]
        }
      ],
      "userVerification": "preferred"
    }
  }
}
```

### 3. Complete login

Payload fields:

- required: `email`, `password`, `device_name`
- conditional: `firebase_id_token`
- conditional: `passkey_challenge_token`
- conditional: `passkey_assertion`

Example:

```json
{
  "email": "user@example.com",
  "password": "secret",
  "device_name": "flutter-android",
  "firebase_id_token": "firebase-id-token",
  "passkey_challenge_token": "c8c7f2d8-....",
  "passkey_assertion": {
    "credential_id": "...",
    "client_data_json": "...",
    "authenticator_data": "...",
    "signature": "..."
  }
}
```

## Passkey enrollment flow

1. Authenticate normally and store the bearer token.
2. Call `POST /api/v1/auth/passkeys/options`.
3. Use the returned `challenge_token` and `public_key` options in your passkey plugin.
4. Send the resulting credential payload to `POST /api/v1/auth/passkeys`.

## Flutter client package

This repository now includes a reusable package at:

```text
mobile/team_kenya_api_client
```

It provides:

- typed models for all current endpoints
- paginated response parsing
- endpoint services for bootstrap, auth, athletes, events, and lookups
- `MobileAuthCoordinator` to orchestrate Firebase OTP and passkey login

## Native mobile MFA/passkey prerequisites

Firebase OTP:

- configure the Flutter app with your Firebase project
- obtain the Firebase ID token on-device
- send that token as `firebase_id_token` to the backend

Passkeys:

- the backend relying-party ID defaults to `PASSKEY_RP_ID` or the host from `APP_URL`
- configure allowed native app origins in `.env`:

```dotenv
PASSKEY_RP_ID=portal.example.com
PASSKEY_MOBILE_ORIGINS=android:apk-key-hash:YOUR_HASH,ios:bundle-id:com.example.app
```

Without those values, native passkey assertions from Android/iOS apps will be rejected by origin validation.

## Environment

- Set `APP_URL` to the public backend URL
- Set `CORS_ALLOWED_ORIGINS` for Flutter web origins if needed
- Native Android and iOS apps do not use browser CORS restrictions
