Refactor developer guide into modular sections

Split the monolithic developer-guide.md into three focused documents: setup-dev-env.md, authentication.md, and supported-types.md. Updated mkdocs.yml navigation to reflect the new structure, improving documentation clarity and maintainability.
This commit is contained in:
João Vitória Silva
2025-12-15 21:48:55 +00:00
parent 2121196ee3
commit 26e08007ca
5 changed files with 639 additions and 404 deletions

View File

@@ -0,0 +1,349 @@
# Handling authentication
Endurain supports integration with other apps through a comprehensive authentication system that includes standard username/password authentication, Multi-Factor Authentication (MFA), OAuth/SSO integration, and JWT-based session management.
## API Requirements
- **Add a header:** Every request must include an `X-Client-Type` header with either `web` or `mobile` as the value. Requests with other values will receive a `403` error.
- **Authorization:** Every request must include an `Authorization: Bearer <access token>` header with a valid (new or refreshed) access token.
## Token Handling
### Token Lifecycle
- The backend generates an `access_token` valid for 15 minutes (default) and a `refresh_token` valid for 7 days (default). This follows the best practice of short-lived and long-lived tokens for authentication sessions.
- The `access_token` is used for authorization; The `refresh_token` is used to refresh the `access_token`.
- Token expiration times can be customized via environment variables (see Configuration section below).
### Client-Specific Token Delivery
- **For web apps**: The backend sends access/refresh tokens as HTTP-only cookies:
- `endurain_access_token` (HttpOnly, Secure in production)
- `endurain_refresh_token` (HttpOnly, Secure in production)
- `endurain_csrf_token` (HttpOnly, for CSRF protection)
- **For mobile apps**: Tokens are included in the response body as JSON.
## Authentication Flows
### Standard Login Flow
1. Client sends credentials to `/token` endpoint
2. Backend validates credentials
3. If MFA is enabled, backend requests MFA code
4. If MFA is disabled or verified, backend generates tokens
5. Tokens are delivered based on client type (cookies for web, JSON for mobile)
### OAuth/SSO Flow
1. Client requests list of enabled providers from `/identity-providers`
2. Client initiates OAuth by redirecting to `/identity-providers/login/{idp_slug}`
3. User authenticates with the OAuth provider
4. Provider redirects back to `/identity-providers/callback/{idp_slug}` with authorization code
5. Backend exchanges code for provider tokens and user info
6. Backend creates or links user account and generates session tokens
7. User is redirected to the app with active session
### Token Refresh Flow
1. When access token expires, client sends refresh token to `/refresh`
2. Backend validates refresh token and session
3. New access token is generated and returned
4. Refresh token may be rotated based on configuration
## API Endpoints
The API is reachable under `/api/v1`. Below are the authentication-related endpoints. Complete API documentation is available on the backend docs (`http://localhost:98/api/v1/docs` or `http://ip_address:98/api/v1/docs` or `https://domain/api/v1/docs`):
### Core Authentication Endpoints
| What | Url | Expected Information | Rate Limit |
| ---- | --- | -------------------- | ---------- |
| **Authorize** | `/token` | `FORM` with the fields `username` and `password`. This will be sent in clear text, use of HTTPS is highly recommended | 5 requests/min per IP |
| **Refresh Token** | `/refresh` | header `Authorization Bearer: <Refresh Token>` | - |
| **Verify MFA** | `/mfa/verify` | JSON `{'username': <username>, 'mfa_code': '123456'}` | - |
| **Logout** | `/logout` | header `Authorization Bearer: <Access Token>` | - |
### OAuth/SSO Endpoints
| What | Url | Expected Information | Rate Limit |
| ---- | --- | -------------------- | ---------- |
| **Get Enabled Providers** | `/identity-providers` | None (public endpoint) | - |
| **Initiate OAuth Login** | `/identity-providers/login/{idp_slug}` | Query param: `redirect=<path>` (optional) | 10 requests/min per IP |
| **OAuth Callback** | `/identity-providers/callback/{idp_slug}` | Query params: `code=<code>`, `state=<state>` | Configurable |
| **Link IdP to Account** | `/identity-providers/login/{idp_slug}?link=true` | Requires authenticated session | 10 requests/min per IP |
### Example Resource Endpoints
| What | Url | Expected Information |
| ---- | --- | -------------------- |
| **Activity Upload** | `/activities/create/upload` | .gpx, .tcx, .gz or .fit file |
| **Set Weight** | `/health/weight` | JSON `{'weight': <number>, 'created_at': 'yyyy-MM-dd'}` |
## MFA Authentication Flow
When Multi-Factor Authentication (MFA) is enabled for a user, the authentication process requires two steps:
### Step 1: Initial Login Request
Make a standard login request to `/token`:
**Request:**
```http
POST /api/v1/token
Content-Type: application/x-www-form-urlencoded
X-Client-Type: web|mobile
username=user@example.com&password=userpassword
```
**Response (when MFA is enabled):**
- **Web clients**: HTTP 202 Accepted
```json
{
"mfa_required": true,
"username": "example",
"message": "MFA verification required"
}
```
- **Mobile clients**: HTTP 200 OK
```json
{
"mfa_required": true,
"username": "example",
"message": "MFA verification required"
}
```
### Step 2: MFA Verification
Complete the login by providing the MFA code to `/mfa/verify`:
**Request:**
```http
POST /api/v1/mfa/verify
Content-Type: application/json
X-Client-Type: web|mobile
{
"username": "user@example.com",
"mfa_code": "123456"
}
```
**Response (successful verification):**
- **Web clients**: Tokens are set as HTTP-only cookies
```json
{
"session_id": "unique_session_id"
}
```
- **Mobile clients**: Tokens are returned in response body
```json
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"session_id": "unique_session_id",
"token_type": "Bearer",
"expires_in": 900,
}
```
### Error Handling
- **No pending MFA login**: HTTP 400 Bad Request
```json
{
"detail": "No pending MFA login found for this username"
}
```
- **Invalid MFA code**: HTTP 401 Unauthorized
```json
{
"detail": "Invalid MFA code"
}
```
### Important Notes
- The pending MFA login session is temporary and will expire if not completed within a reasonable time
- After successful MFA verification, the pending login is automatically cleaned up
- The user must still be active at the time of MFA verification
- If no MFA is enabled for the user, the standard single-step authentication flow applies
## OAuth/SSO Integration
### Supported Identity Providers
Endurain supports OAuth/SSO integration with various identity providers out of the box:
- Authelia
- Authentik
- Casdoor
- Keycloak
- Pocket ID
The system is extensible and can be configured to work with:
- Google
- GitHub
- Microsoft Entra ID
- Others/custom OIDC providers
### OAuth Configuration
Identity providers must be configured with the following parameters:
- `client_id`: OAuth client identifier
- `client_secret`: OAuth client secret
- `authorization_endpoint`: Provider's authorization URL
- `token_endpoint`: Provider's token exchange URL
- `userinfo_endpoint`: Provider's user information URL
- `redirect_uri`: Callback URL (typically `/api/v1/identity-providers/callback/{idp_slug}`)
### Linking Accounts
Users can link their Endurain account to an OAuth provider:
1. User must be authenticated with a valid session
2. Navigate to `/identity-providers/login/{idp_slug}?link=true`
3. Authenticate with the identity provider
4. Provider is linked to the existing account
### OAuth Token Response
When authenticating via OAuth, the response format matches the standard authentication:
- **Web clients**: Tokens set as HTTP-only cookies, redirected to app
- **Mobile clients**: Tokens returned in JSON format
## Configuration
### Environment Variables
The following environment variables control authentication behavior:
| Variable | Description | Default | Required |
| -------- | ----------- | ------- | -------- |
| `SECRET_KEY` | Secret key for JWT signing | - | Yes |
| `ALGORITHM` | JWT signing algorithm | `HS256` | No |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | Access token lifetime in minutes | `15` | No |
| `REFRESH_TOKEN_EXPIRE_DAYS` | Refresh token lifetime in days | `7` | No |
| `BACKEND_CORS_ORIGINS` | Allowed CORS origins | `[]` | No |
### Cookie Configuration
For web clients, cookies are configured with:
- **HttpOnly**: Prevents JavaScript access (security measure)
- **Secure**: Only sent over HTTPS in production
- **SameSite**: Protection against CSRF attacks
- **Domain**: Set to match your application domain
- **Path**: Set to `/` for application-wide access
## Security Scopes
Endurain uses OAuth-style scopes to control API access. Each scope controls access to specific resource groups:
### Available Scopes
| Scope | Description | Access Level |
| ----- | ----------- | ------------ |
| `profile` | User profile information | Read/Write |
| `users:read` | Read user data | Read-only |
| `users:write` | Modify user data | Write |
| `gears:read` | Read gear/equipment data | Read-only |
| `gears:write` | Modify gear/equipment data | Write |
| `activities:read` | Read activity data | Read-only |
| `activities:write` | Create/modify activities | Write |
| `health:read` | Read health metrics (weight, sleep, steps) | Read-only |
| `health:write` | Record health metrics | Write |
| `health_targets:read` | Read health targets | Read-only |
| `health_targets:write` | Modify health targets | Write |
| `sessions:read` | View active sessions | Read-only |
| `sessions:write` | Manage sessions | Write |
| `server_settings:read` | View server configuration | Read-only |
| `server_settings:write` | Modify server settings | Write (Admin) |
| `identity_providers:read` | View OAuth providers | Read-only |
| `identity_providers:write` | Configure OAuth providers | Write (Admin) |
### Scope Usage
Scopes are automatically assigned based on user permissions and are embedded in JWT tokens. API endpoints validate required scopes before processing requests.
## Common Error Responses
### HTTP Status Codes
| Status Code | Description | Common Causes |
| ----------- | ----------- | ------------- |
| `400 Bad Request` | Invalid request format | Missing required fields, invalid JSON, no pending MFA login |
| `401 Unauthorized` | Authentication failed | Invalid credentials, expired token, invalid MFA code |
| `403 Forbidden` | Access denied | Invalid client type, insufficient permissions, missing required scope |
| `404 Not Found` | Resource not found | Invalid session ID, user not found, endpoint doesn't exist |
| `429 Too Many Requests` | Rate limit exceeded | Too many login attempts, OAuth requests exceeded limit |
| `500 Internal Server Error` | Server error | Database connection issues, configuration errors |
### Example Error Responses
**Invalid Client Type:**
```json
{
"detail": "Invalid client type. Must be 'web' or 'mobile'"
}
```
**Expired Token:**
```json
{
"detail": "Token has expired"
}
```
**Invalid Credentials:**
```json
{
"detail": "Incorrect username or password"
}
```
**Rate Limit Exceeded:**
```json
{
"detail": "Rate limit exceeded. Please try again later."
}
```
**Missing Required Scope:**
```json
{
"detail": "Insufficient permissions. Required scope: activities:write"
}
```
## Best Practices
### For Client Applications
1. **Always use HTTPS** in production to protect credentials and tokens
2. **Store tokens securely**:
- Web: Use HTTP-only cookies (handled automatically)
- Mobile: Use secure storage (Keychain on iOS, KeyStore on Android)
3. **Implement token refresh** before access token expires
4. **Handle rate limits** with exponential backoff
5. **Validate SSL certificates** to prevent man-in-the-middle attacks
6. **Clear tokens on logout** to prevent unauthorized access
### For Security
1. **Never expose `SECRET_KEY`** in client code or version control
2. **Use strong, randomly generated secrets** for production
3. **Enable MFA** for enhanced account security
4. **Monitor failed login attempts** for suspicious activity
5. **Rotate refresh tokens** periodically for long-lived sessions
6. **Use appropriate scopes** - request only the permissions needed
### For OAuth/SSO
1. **Validate state parameter** to prevent CSRF attacks
2. **Use PKCE** (Proof Key for Code Exchange) for mobile apps
3. **Implement proper redirect URL validation** to prevent open redirects
4. **Handle provider errors gracefully** with user-friendly messages
5. **Support account linking** to allow users to connect multiple providers

View File

@@ -0,0 +1,120 @@
# Setup a dev environment
Bellow are the steps to create a dev environment. Examples bellow will use Endurain repo, but you should adapt those for your scenario (forked repo, etc).
- Clone the repo to your dev machine:
```bash
cd <folder_to_store_code>
git clone https://github.com/endurain-project/endurain.git # this will clone the repo structure to the previous folder inside a folder called endurain
```
## Docker image and backend logic
Make sure Docker is installed, more info [here](https://docs.docker.com/get-started/introduction/get-docker-desktop/).
- On the project root folder, create a new Docker image, the example bellow uses `unified-image` as the image name:
```bash
docker build -f docker/Dockerfile -t unified-image .
```
- Go to the project root folder and create a file called `docker-compose.yml` and adapt it to your needs. Example bellow:
```conf
services:
endurain:
container_name: endurain
image: unified-image # based on image that will be created above
environment:
- TZ=Europe/Lisbon # change if needed. Default is UTC
- DB_HOST=postgres
- DB_PORT=5432
- DB_PASSWORD=changeme
- SECRET_KEY=changeme # openssl rand -hex 32
- FERNET_KEY=changeme # https://fernetkeygen.com or python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
- ENDURAIN_HOST=http://localhost:8080 # change if needed
- BEHIND_PROXY=false
- ENVIRONMENT=development
volumes:
- <folder_to_store_code>/backend/app:/app/backend # this will replace the backend code logic with yours. Any changes in the code need a container reboot for them to apply
ports:
- "8080:8080" # change if needed
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:latest
container_name: postgres
environment:
- POSTGRES_PASSWORD=changeme
- POSTGRES_DB=endurain
- POSTGRES_USER=endurain
- PGDATA=/var/lib/postgresql/data/pgdata
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U endurain"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- <path_to_container_folders>/postgres:/var/lib/postgresql/data
restart: unless-stopped
adminer:
container_name: adminer
image: adminer
ports:
- 8081:8080
restart: unless-stopped
```
- Start your project based on the docker compose file created before:
```bash
docker compose up -d
```
- To stop the project:
```bash
docker compose down
```
- To remove the create `unified-image` Docker image:
```bash
docker image remove unified-image
```
- Backend uses [Poetry](https://python-poetry.org/) for dependency management. You may need to install Python and Poetry if dependency management is necessary.
## Frontend
Make sure you have an up-to-date version of [Node.js](https://nodejs.org/) installed.
- Go to the root of the project and move to frontend/app folder and install the dependencies:
```bash
cd frontend/app
npm install
```
- Create a file called `.env.local` inside frontend/app and add the following to it:
```conf
VITE_ENDURAIN_HOST=http://localhost:8080 # Adapt this based on the docker compose of your dev environment
```
- After the dependencies are installed run the frontend:
```bash
npm run dev
```
- After the frontend starts running, it should be available in the port `5173`. You should now be able to access the dev environment at `http://localhost:5173`. Screenshot bellow shows the output from the `npm run dev`. Adapt the port based on the command output.
![Frontend running](../assets/developer-guide/npm_run_dev.png)
- Some processes, like token refresh may redirect your dev env from port `5173` to `8080` (or other, depending on your compose file). If this happens simply navigate again to `5173`.

View File

@@ -0,0 +1,166 @@
# Supported types within Endurain
## Supported activity types
The table bellow details the activity types supported by Endurain.
| Name | Value |
| ---- | --- |
| Run | 1 |
| Trail run | 2 |
| Track run | 34 |
| Treadmill run | 40 |
| Virtual run | 3 |
| Road cycling | 4 |
| Gravel cycling | 5 |
| MTB cycling | 6 |
| Commuting cycling | 27 |
| Mixed surface cycling | 29 |
| Virtual cycling | 7 |
| Indoor cycling | 28 |
| E-Bike cycling | 35 |
| E-Bike mountain cycling | 36 |
| Indoor swimming | 8 |
| Open water swimming | 9 |
| General workout | 10 |
| Walk | 11 |
| Indoor walk | 31 |
| Hike | 12 |
| Rowing | 13 |
| Yoga | 14 |
| Alpine ski | 15 |
| Nordic Ski | 16 |
| Snowboard | 17 |
| Ice Skate | 37 |
| Transition | 18 |
| Strength Training | 19 |
| Crossfit | 20 |
| Tennis | 21 |
| Table Tennis | 22 |
| Badminton | 23 |
| Squash | 24 |
| Racquetball | 25 |
| Pickleball | 26 |
| Padel | 39 |
| Windsurf | 30 |
| Stand up paddling | 32 |
| Surf | 33 |
| Soccer | 38 |
| Cardio training | 41 |
| Kayaking | 42 |
| Sailing | 43 |
| Snow shoeing | 44 |
| Inline skating | 45 |
## Supported gear types
The table bellow details the gear types supported by Endurain.
| Name | Value | Notes |
|--------------------|-------|-----------------------------------------|
| bike | 1 | N/A |
| shoes | 2 | N/A |
| wetsuit | 3 | N/A |
| racquet | 4 | N/A |
| ski | 5 | N/A |
| snowboard | 6 | N/A |
| windsurf | 7 | N/A |
| water_sports_board | 8 | Example: stand up paddle and surf board |
## Supported bike component gear types
The table bellow details the bike gear component types supported by Endurain:
| Value |
|-----------------------------|
| back_break_oil |
| back_break_pads |
| back_break_rotor |
| back_tire |
| back_tube |
| back_tubeless_sealant |
| back_tubeless_rim_tape |
| back_wheel |
| back_wheel_valve |
| bottom_bracket |
| bottle_cage |
| cassette |
| chain |
| computer_mount |
| crank_left_power_meter |
| crank_right_power_meter |
| crankset |
| crankset_power_meter |
| fork |
| frame |
| front_break_oil |
| front_break_pads |
| front_break_rotor |
| front_derailleur |
| front_shifter |
| front_tire |
| front_tube |
| front_tubeless_sealant |
| front_tubeless_rim_tape |
| front_wheel |
| front_wheel_valve |
| grips |
| handlebar |
| handlebar_tape |
| headset |
| pedals |
| pedals_left_power_meter |
| pedals_power_meter |
| pedals_right_power_meter |
| rear_derailleur |
| rear_shifter |
| saddle |
| seatpost |
| stem |
## Supported shoes component gear types
The table bellow details the shoes component gear types supported by Endurain:
| Value |
|-----------------------------|
| cleats |
| insoles |
| laces |
## Supported racquet component gear types
The table bellow details the racquet component gear types supported by Endurain:
| Value |
|-----------------------------|
| basegrip |
| bumpers |
| grommets |
| overgrip |
| strings |
## Supported windsurf component gear types
The table bellow details the windsurf component gear types supported by Endurain:
| Value |
|-----------------------------|
| sail |
| board |
| mast |
| boom |
| mast_extension |
| mast_base |
| mast_universal_joint |
| fin |
| footstraps |
| harness_lines |
| rigging_lines |
| footpad |
| impact_vest |
| lifeguard_vest |
| helmet |
| wing |
| front_foil |
| stabilizer |
| fuselage |