Remote Data Source
data/datasources
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
- Receives a typed payload from the repository (LoginRequest, RegisterRequest…).
- Issues the HTTP call via the shared Dio instance (with auth interceptor disabled for /auth/*).
- Parses the response into UserModel + AuthTokenModel.
- 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/loginverifiedExchange 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" } }curlcurl -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/signupverifiedCreate 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" } }curlcurl -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/logoutbearerimplementedBest-effort server-side invalidation of the refresh token.
Request{ refreshToken: string }Responsevoid // 204
›Example payloads
Example request{ "refreshToken": "<opaque-token>" }curlcurl -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 progressTrigger 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 }Responsevoid // always 200 — anti-enumeration
›Example payloads
Example request{ "email": "amina@atlasdemo.example" }curlcurl -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/refreshchangedRotate 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>" }curlcurl -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 // _refreshInterceptorCalled 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.