arch-atlas

Remote Data Source

data/datasources

data

Overview

Thin Dio-based client that knows the auth API surface (login, signup, logout, reset). Speaks JSON in, UserModel/AuthTokenModel out — nothing more.

User Flow

  1. Receives a typed payload from the repository (LoginRequest, RegisterRequest…).
  2. Issues the HTTP call via the shared Dio instance (with auth interceptor disabled for /auth/*).
  3. Parses the response into UserModel + AuthTokenModel.
  4. Throws ServerException / NetworkException — never returns failures.

Cases & Edge Cases

  • DioException with type connectionTimeout/receiveTimeout → NetworkException.
  • Non-2xx status → ServerException(statusCode, message).
  • Malformed JSON → FormatException — let it bubble; repository maps to AuthFailure.unexpected.

Code References

  • lib/features/auth/data/datasources/remote/auth_remote_datasource.dart
  • lib/core/network/dio_client.dart

API Calls

  • POST/auth/loginverified

    Exchange email + password for an access/refresh token pair and the user profile.

    Request
    { email: string, password: string }
    Response
    { accessToken: string, refreshToken: string, user: User }
    Example payloads
    Example request
    {
      "email": "amina@atlasdemo.example",
      "password": "correct-horse-battery"
    }
    Example response
    {
      "accessToken": "<jwt>",
      "refreshToken": "<opaque-token>",
      "user": { "id": "usr_01H9…", "email": "amina@atlasdemo.example", "displayName": "Amina" }
    }
    curl
    curl -X POST 'https://api.dev.atlasdemo.example/auth/login' \
      -H 'Content-Type: application/json' \
      --data '{
      "email": "amina@atlasdemo.example",
      "password": "correct-horse-battery"
    }'
    Errors
    • ·400 invalid payload
    • ·401 wrong credentials
    • ·429 too many attempts
    lib/features/auth/data/datasources/remote/auth_remote_datasource.dart:31 // login()

    No Authorization header — the auth interceptor is disabled for /auth/*.

  • POST/auth/signupverified

    Create an account and return a freshly-issued session (same shape as login).

    Request
    { email: string, password: string, displayName: string }
    Response
    { accessToken: string, refreshToken: string, user: User }
    Example payloads
    Example request
    {
      "email": "new.user@atlasdemo.example",
      "password": "correct-horse-battery",
      "displayName": "New User"
    }
    Example response
    {
      "accessToken": "<jwt>",
      "refreshToken": "<opaque-token>",
      "user": { "id": "usr_01HA…", "email": "new.user@atlasdemo.example", "displayName": "New User" }
    }
    curl
    curl -X POST 'https://api.dev.atlasdemo.example/auth/signup' \
      -H 'Content-Type: application/json' \
      --data '{
      "email": "new.user@atlasdemo.example",
      "password": "correct-horse-battery",
      "displayName": "New User"
    }'
    Errors
    • ·400 server-side validation (field messages)
    • ·409 email already in use
    lib/features/auth/data/datasources/remote/auth_remote_datasource.dart:54 // signup()
  • POST/auth/logoutbearerimplemented

    Best-effort server-side invalidation of the refresh token.

    Request
    { refreshToken: string }
    Response
    void  // 204
    Example payloads
    Example request
    { "refreshToken": "<opaque-token>" }
    curl
    curl -X POST 'https://api.dev.atlasdemo.example/auth/logout' \
      -H 'Authorization: Bearer <TOKEN>' \
      -H 'Content-Type: application/json' \
      --data '{ "refreshToken": "<opaque-token>" }'
    Errors
    • ·401 token already invalid — treated as success
    lib/features/auth/data/datasources/remote/auth_remote_datasource.dart:72 // logout()

    Fire-and-forget: local session is cleared regardless of the response.

  • POST/auth/resetin progress

    Trigger a password-reset email for the given address.

    Deep-link handling for the reset email is still being wired in the app.

    Request
    { email: string }
    Response
    void  // always 200 — anti-enumeration
    Example payloads
    Example request
    { "email": "amina@atlasdemo.example" }
    curl
    curl -X POST 'https://api.dev.atlasdemo.example/auth/reset' \
      -H 'Content-Type: application/json' \
      --data '{ "email": "amina@atlasdemo.example" }'
    Errors
    • ·429 rate-limited
    lib/features/auth/data/datasources/remote/auth_remote_datasource.dart:88 // resetPassword()

    Backend answers 200 even for unknown emails — the UI must show a neutral confirmation.

  • POST/auth/refreshchanged

    Rotate the access token using the stored refresh token.

    2026-07-10: backend renamed refresh_token → refreshToken and now also rotates the refresh token in the response. App still sends the old field — update DioClient interceptor.

    Request
    { refreshToken: string }
    Response
    { accessToken: string, refreshToken: string }
    Example payloads
    Example request
    { "refreshToken": "<opaque-token>" }
    Example response
    { "accessToken": "<jwt>", "refreshToken": "<rotated-opaque-token>" }
    curl
    curl -X POST 'https://api.dev.atlasdemo.example/auth/refresh' \
      -H 'Content-Type: application/json' \
      --data '{ "refreshToken": "<opaque-token>" }'
    Errors
    • ·401 refresh token expired → force re-login
    lib/core/network/dio_client.dart:61 // _refreshInterceptor

    Called by the Dio interceptor on 401 — a mutex guarantees only one refresh runs at a time.

Notes

Stays exception-throwing on purpose. Either-conversion happens one level up (in the repository), so this file remains a 'dumb' adapter.