mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-10 08:17:59 -05:00
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:
349
docs/developer-guide/authentication.md
Normal file
349
docs/developer-guide/authentication.md
Normal 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
|
||||
120
docs/developer-guide/setup-dev-env.md
Normal file
120
docs/developer-guide/setup-dev-env.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
- 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`.
|
||||
166
docs/developer-guide/supported-types.md
Normal file
166
docs/developer-guide/supported-types.md
Normal 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 |
|
||||
Reference in New Issue
Block a user