Skip to content
Open
48 changes: 48 additions & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Splitwiser API Documentation

Welcome to the official API documentation for the Splitwiser backend. This document provides a comprehensive guide to understanding and interacting with our API.

---

## 1. Introduction

The Splitwiser API is a modern, RESTful service built with FastAPI that powers the Splitwiser application. It provides a complete set of endpoints for managing users, groups, expenses, and settlements, with a focus on security, performance, and ease of use.

This documentation is divided into several modules, each covering a distinct area of the API's functionality.

---

## 2. General Concepts

### **Authentication**

All API endpoints (with the exception of sign-up and login) are protected and require a valid **JSON Web Token (JWT)** to be included in the request.

* **Flow**: To access protected routes, you must first authenticate using one of the login endpoints to receive an `access_token`. This token must then be sent in the `Authorization` header of all subsequent requests with the `Bearer` scheme.
```
Authorization: Bearer <your_access_token>
```
* **Token Expiry**: Access tokens are short-lived. When one expires, you must use the provided `refresh_token` to obtain a new one.

For a complete guide on how to register, log in, and manage tokens, please see the detailed **[Authentication API Documentation](./authentication.md)**.

---

## 3. API Reference

The API is organized into logical modules. For detailed information on the endpoints, request/response models, and business logic for each module, please refer to the documents below.

* **[Authentication](./authentication.md)**: User sign-up, login (email & Google), and token management.
* **[Users](./users.md)**: Managing user profiles.
* **[Groups](./groups.md)**: Creating groups, managing membership, and handling roles.
* **[Expenses](./expenses.md)**: Creating, splitting, and managing expenses within groups.
* **[Settlements](./settlements.md)**: Managing debts, recording payments, and using the debt optimization engine.

---

## 4. Examples & Tools

To help you get started quickly, we provide practical examples and tools.

* **[cURL Examples](./examples/curl_examples.md)**: A collection of copy-pasteable `cURL` commands for interacting with the API from your terminal.
* **[Postman Collection](./examples/postman_collection.json)**: A comprehensive Postman collection that you can import to immediately start making requests to all available endpoints.
270 changes: 270 additions & 0 deletions docs/api/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# Authentication Service

## Overview
The Authentication Service in Splitwiser handles secure user registration, login, and session management using JWT (JSON Web Tokens) with refresh token rotation for enhanced security. It supports multiple authentication providers, including email/password and Google OAuth.This documentation is based on the backend implementation in `/backend/app/auth`.

This document provides a complete and definitive specification for the authentication API endpoints, based on the final implementation code from `routes.py`, the data models from `schemas.py`, the cryptographic logic from `security.py`, the business logic from `service.py`, and the dependency logic from `dependencies.py`.

---

## 1. System Architecture & Core Concepts

### **Security & Token Generation**

* **Password Hashing**: User passwords are never stored in plaintext. They are securely hashed using the **bcrypt** algorithm.
* **Access Tokens (JWT)**: Short-lived (15 min) JSON Web Tokens signed with the HS256 algorithm. They contain the user's ID (`sub`), an expiration timestamp (`exp`), and a `type` claim set to `"access"`.
* **Refresh Tokens**: Long-lived (30 days), cryptographically secure random strings used to obtain new access tokens. They are stored in the database and rotated upon use for enhanced security.
* **Password Reset Tokens**: Secure random strings with a short expiry (1 hour) used to authorize password changes.

### **Database Collections**

The authentication system relies on three primary MongoDB collections:

1. **`users`**: Stores core user profile information, including email, name, hashed password (for email-based auth), and the authentication provider (`email` or `google`).
2. **`refresh_tokens`**: Tracks all active refresh tokens. Each record links a token to a `user_id`, includes an expiration date, and has a `revoked` flag to manage sessions.
3. **`password_resets`**: Temporarily stores password reset tokens, linking them to a `user_id` with an expiration date and a `used` flag.

### **Firebase Integration**

* The **Firebase Admin SDK** is used exclusively on the server-side to verify Google ID tokens. The application is initialized using credentials from environment variables or a service account file.

---

## 2. Dependencies

The auth module relies on:
- `fastapi.security`: For OAuth2PasswordBearer .
- `pydantic`: For request/response schemas model.(pydantic model)
- `jose`: For JWT encoding/decoding.
- `passlib`: For password hashing.
- `MongoDB driver`: (e.g.,`pymongo`) for user storage.

---

## 3. Data Models and Schemas

### User Model (in `schemas.py`)
Represents a user in the database:

### **Usage Example**

```python

from pydantic import BaseModel, EmailStr
from typing import Optional

class User(BaseModel):
id: str = Field(alias="_id")
email: EmailStr #EmailStr to check the syntax of email
name: str
imageUrl: Optional[str] = None #Optional field
currency: str = "USD"
created_at: datetime
```
Comment on lines +58 to +63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify field naming consistency (snake_case vs camelCase)

Users in this doc use created_at (snake_case) while other resources (e.g., settlements) use createdAt (camelCase). Please verify what the backend actually returns for user payloads and align docs consistently.

Run this script to check user schema/response conventions in the backend:

Also applies to: 111-112, 200-202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for user response models/serializers ==="
rg -n -C2 -S -g '!**/node_modules/**' -P '\b(created_at|createdAt)\b' backend/app | sed -n '1,200p'

echo
echo "=== Look for Pydantic schemas for users ==="
rg -n -C2 -S -P 'class\s+User\w*\s*\(BaseModel\)' backend/app | sed -n '1,200p'

echo
echo "=== Inspect routes returning user payloads ==="
rg -n -C3 -S -P 'return\s+.*(user|UserResponse|AuthResponse)' backend/app | sed -n '1,200p'

Length of output: 17782


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== docs/api/authentication.md (lines 1-160) ==="
sed -n '1,160p' docs/api/authentication.md || true
echo

echo "=== backend/app/user/service.py (full) ==="
sed -n '1,240p' backend/app/user/service.py || true
echo

echo "=== backend/app/user/schemas.py (full) ==="
sed -n '1,200p' backend/app/user/schemas.py || true
echo

echo "=== backend/app/auth/schemas.py (full) ==="
sed -n '1,240p' backend/app/auth/schemas.py || true
echo

echo "=== backend/app/auth/routes.py (full) ==="
sed -n '1,320p' backend/app/auth/routes.py || true
echo

echo "=== backend/app/user/routes.py (full) ==="
sed -n '1,200p' backend/app/user/routes.py || true

Length of output: 23496


Fix: unify user timestamp field casing (createdAt vs created_at)

Verified: the codebase is inconsistent — user endpoints return camelCase timestamps, while auth responses use snake_case and the docs currently show snake_case. Please standardize.

Findings (verified):

  • backend/app/user/service.py — transform_user_document returns "createdAt" / "updatedAt" (camelCase).
  • backend/app/user/schemas.py — UserProfileResponse uses createdAt / updatedAt (camelCase).
  • backend/app/user/routes.py — GET /users/me uses UserProfileResponse (so returns camelCase).
  • backend/app/auth/schemas.py — UserResponse defines created_at (snake_case) and is used by auth endpoints (backend/app/auth/routes.py).
  • docs/api/authentication.md — example User model shows created_at (snake_case).

Recommended minimal fixes:

  • Prefer camelCase (createdAt) to match most existing resources (groups/expenses/etc.). Update auth schema to expose camelCase while accepting current DB field:

    • Change in backend/app/auth/schemas.py:

      Before

      class UserResponse(BaseModel):
          id: str = Field(alias="_id")
          email: str
          name: str
          imageUrl: Optional[str] = None
          currency: str = "USD"
          created_at: datetime
      
          model_config = ConfigDict(populate_by_name=True)

      After

      class UserResponse(BaseModel):
          id: str = Field(alias="_id")
          email: str
          name: str
          imageUrl: Optional[str] = None
          currency: str = "USD"
          createdAt: datetime = Field(alias="created_at")
      
          model_config = ConfigDict(populate_by_name=True)

    This accepts the DB key created_at but exposes createdAt in API responses, keeping backwards-safe population and aligning with other resources.

  • Update docs/api/authentication.md examples to use createdAt / updatedAt (camelCase).

  • Optional: consider normalizing service code to consistently return the same shape (e.g., reuse user_service.transform_user_document in auth flows) to avoid future drift.

Files to update:

  • backend/app/auth/schemas.py (change field and alias)
  • docs/api/authentication.md (update example usage)
  • (optional) backend/app/auth/service.py and backend/app/auth/routes.py — verify they pass through user dicts consistently (no extra mapping needed once schema change is applied).
🤖 Prompt for AI Agents
In docs/api/authentication.md around lines 58 to 63, the docs and auth schema
use snake_case created_at while the rest of the API uses camelCase createdAt;
update backend/app/auth/schemas.py to expose createdAt by adding a Field alias
for the DB key (accept created_at but return createdAt) and ensure model config
allows population by name, then update docs/api/authentication.md examples to
use createdAt/updatedAt; optionally verify backend/app/auth/service.py and
backend/app/auth/routes.py pass through the user dict unchanged so the new
schema alias works without further mapping.


### Token Schemas (in `schemas.py`)

### **Usage Example**

```python
class TokenData(BaseModel):
sub: str # The user's ID
```

---

## 4.API Endpoints

All endpoints are prefixed with `/auth` via an APIRouter in `routes.py`.

### **`POST /signup/email`**
**Description**: Registers a new user with their email, password, and name and returns authentication tokens and user information.

**Parameters**:
- Request Body (JSON):
- `email`: string (required, valid email)
- `password`: string (required)
- `name` : string (required)

**Request BODY** (`EmailSignupRequest`):
```json
{
"email": "user@example.com",
"password": "a-strong-password", // min_length: 6
"name": "John Doe" // min_length: 1
}
```

**Responses** (`AuthResponse`):
- Note: AuthResponse conatin another pydhantic model : `UserResponse`
- 201 Created: Successful registration.
```json
{
"access_token": "your_access_token_jwt",
"refresh_token": "your_refresh_token",
"user": {
"id": "mongodb_user_id",
"email": "user@example.com",
"name": "John Doe",
"imageUrl": null,
"currency": "USD",
"created_at": "2025-08-12T18:49:48.000Z"
}
}
```
- 400 Bad Request: User already exists or invalid input.
- 422 Unprocessable Entity: Validation error (e.g., invalid email).

**Authentication**: None.

### **`POST /login/email`**
**Description**: Authenticates a user with email and password. Verifies credentials, generates JWT tokens, and adds a new session.

**Parameters**:
- Request Body (JSON):
- `email`: string (required)
- `password`: string (required)

**Request Example**:
* **Request Body** (`EmailLoginRequest`):
```json
{
"email": "user@example.com",
"password": "the-correct-password"
}
```
* **Response** (`AuthResponse`): The response format is identical to the sign-up endpoint.

Error :
- 500 if authentication fails due to an unexpected error


### **`POST /login/google`**
**Description**: Handles Google OAuth login. Validates the Google ID token, creates or logs in the user, and returns Splitwiser access and refresh tokens.

**Parameters**:
- Request Body (JSON):
- `GoogleLoginRequest`: string (required, Google ID token)


* **Request Body** (`GoogleLoginRequest`):
```json
{
"id_token": "google_id_token_from_client"
}
```
* **Response** (`AuthResponse`): The response format is identical to the sign-up endpoint.

- 500 If Google authentication fails.


### **`POST /refresh`**
**Description**: Refreshes the access token using a valid refresh token. Rotates the refresh token and updates the session.

**Parameters**:
- Request Body (JSON):
- `RefreshTokenRequest`: string (required)

* **Request Body** (`RefreshTokenRequest`):
```json
{
"refresh_token": "the_valid_refresh_token"
}
```

* **Response** (`TokenResponse`):
- 200 OK: New tokens issued.
```json
{
"access_token": "your_new_access_token",
"refresh_token": "your_new_rotated_refresh_token"
}
```
- 401 Unauthorized: Invalid or expired refresh token.

### **`POST /token/verify`**
**Description**: Verifies an access token and returns the corresponding user's information.
* **Request Body** (`TokenVerifyRequest`):
```json
{
"access_token": "the_access_token_to_verify"
}
```
* **Response** (`UserResponse`):
```json
{
"id": "mongodb_user_id",
"email": "user@example.com",
"name": "John Doe",
"imageUrl": "url_to_avatar_or_null",
"currency": "USD",
"created_at": "2025-08-12T18:49:48.000Z"
}
```

### **`POST /password/reset/request`**
**Description**: Initiates the password reset process for a user.Initiates a password reset process by sending a reset link to the provided email address.

* **Request Body** (`PasswordResetRequest`):
```json
{
"email": "user-who-forgot-password@example.com"
}
```
* **Response** (`SuccessResponse`):
```json
{
"success": true,
"message": "If the email exists, a reset link has been sent"
}
```
### **`POST /password/reset/confirm`**

Sets a new password for a user using a valid reset token.

* **Request Body** (`PasswordResetConfirm`):
```json
{
"reset_token": "the_token_from_the_reset_email",
"new_password": "a_new_strong_password" // min_length: 6
}
```
* **Response** (`SuccessResponse`):
```json
{
"success": true,
"message": "Password has been reset successfully"
}

- 400 Bad Request: Invalid or expired reset token.


## Authentication Flow

1. **Registration/Login**: User sends credentials to /signup or /login. Server validates, creates/hashes, and issues tokens.
2. **Protected Routes**: Use `Depends(get_current_user)` in FastAPI, which verifies the access token.
3. **Token Refresh**: Client monitors access token expiry and calls /refresh with refresh token.
4. **Rotation**: On refresh, new refresh token is issued, old one invalidated.
5. **Google OAuth**: Client gets Google token, sends to server. Server verifies with Google API, links to user.

## Error Codes

- 400: Bad request (e.g., duplicate email).
- 401: Unauthorized (invalid credentials/token).
- 403: Forbidden (inactive user).
- 422: Validation error (Pydantic errors).
- 500: Internal server error.

## Common Use Cases

- **New User Signup**: POST /auth/signup/email → Receive tokens → Use access token for API calls.
- **Existing User Login**: POST /auth/login/email → Tokens.
- **Google Sign-In**: Integrate with Google SDK on frontend, send token to /auth/google/login.
- **Session Refresh**: When access token expires, use refresh token to get new pair.

## Security Considerations

- Store access tokens in memory (not localStorage for web).
- Regularly rotate secrets for JWT signing.

For more details on the overall API, see `micro-plan.md`.
Loading
Loading