first commit
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
# API Reference
|
||||
|
||||
Base URL (local dev): `http://localhost:8080`
|
||||
|
||||
All request/response bodies are JSON. Authenticated endpoints rely on the
|
||||
`session_id` cookie set by `/login` or the Google OAuth callback - include
|
||||
it automatically by using a cookie-aware HTTP client (browsers do this
|
||||
natively; with `curl`, use `-c cookies.txt -b cookies.txt`).
|
||||
|
||||
---
|
||||
|
||||
## `GET /health`
|
||||
|
||||
Liveness check. No authentication, no rate limiting beyond the global
|
||||
limit.
|
||||
|
||||
**Response `200`**
|
||||
```json
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `POST /register`
|
||||
|
||||
Creates a new password-based account.
|
||||
|
||||
Rate limited to **5 requests/minute per IP** (shared with `/login`).
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{ "email": "hamid@example.com", "password": "secret123" }
|
||||
```
|
||||
- `email` - required, must be unique across all accounts.
|
||||
- `password` - required, minimum 8 characters.
|
||||
|
||||
**Response `201`**
|
||||
```json
|
||||
{ "id": 1, "email": "hamid@example.com" }
|
||||
```
|
||||
|
||||
**Errors**
|
||||
| Status | Body | Cause |
|
||||
|---|---|---|
|
||||
| 400 | `{"error":"invalid request body"}` | Malformed JSON |
|
||||
| 400 | `{"error":"email and password are required"}` | Missing field |
|
||||
| 400 | `{"error":"password must be at least 8 characters"}` | Password too short |
|
||||
| 409 | `{"error":"email already registered"}` | Email already taken |
|
||||
| 429 | (rate limit response) | Too many requests from this IP |
|
||||
| 500 | `{"error":"internal error"}` | Unexpected server/database failure |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"hamid@example.com","password":"secret123"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `POST /login`
|
||||
|
||||
Authenticates with email + password and starts a server-side session.
|
||||
|
||||
Rate limited to **5 requests/minute per IP** (shared with `/register`).
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{ "email": "hamid@example.com", "password": "secret123" }
|
||||
```
|
||||
|
||||
**Response `200`**
|
||||
```json
|
||||
{ "id": 1, "email": "hamid@example.com" }
|
||||
```
|
||||
Also sets a `session_id` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` in
|
||||
production).
|
||||
|
||||
**Errors**
|
||||
| Status | Body | Cause |
|
||||
|---|---|---|
|
||||
| 400 | `{"error":"invalid request body"}` | Malformed JSON |
|
||||
| 401 | `{"error":"invalid email or password"}` | No such email, OR wrong password (identical message for both, deliberately - see Security notes in the README) |
|
||||
| 429 | (rate limit response) | Too many requests from this IP |
|
||||
| 500 | `{"error":"internal error"}` | Unexpected server/database failure |
|
||||
|
||||
```bash
|
||||
curl -c cookies.txt -X POST http://localhost:8080/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"hamid@example.com","password":"secret123"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `POST /logout`
|
||||
|
||||
Destroys the current session (deletes it from Redis, expires the cookie).
|
||||
Not rate-limited beyond the global limit - deliberately excluded from the
|
||||
strict `/login`/`/register` limit so a legitimate user can always log out.
|
||||
|
||||
**Response `200`**
|
||||
```json
|
||||
{ "message": "logged out" }
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt -c cookies.txt -X POST http://localhost:8080/logout
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `GET /me`
|
||||
|
||||
**Requires authentication** (a valid `session_id` cookie from a prior
|
||||
login). Returns the currently logged-in user.
|
||||
|
||||
**Response `200`**
|
||||
```json
|
||||
{ "id": 1, "email": "hamid@example.com" }
|
||||
```
|
||||
|
||||
**Errors**
|
||||
| Status | Body | Cause |
|
||||
|---|---|---|
|
||||
| 401 | `{"error":"unauthorized"}` | No session, expired session, or the session's user no longer exists |
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt http://localhost:8080/me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `GET /auth/google/login`
|
||||
|
||||
Redirects the browser to Google's OAuth2 consent screen. **Must be opened
|
||||
in an actual browser** - this endpoint returns an HTTP redirect, and the
|
||||
subsequent Google login page cannot be driven via `curl`.
|
||||
|
||||
**Response**: `307 Temporary Redirect` to `accounts.google.com`.
|
||||
|
||||
```
|
||||
open http://localhost:8080/auth/google/login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `GET /auth/google/callback`
|
||||
|
||||
Google redirects here automatically after the user approves access. Not
|
||||
meant to be called directly - `state` and `code` query parameters are
|
||||
supplied by Google.
|
||||
|
||||
On success: creates a new user (or links Google to an existing
|
||||
email-matched account), starts a session exactly like `/login` does, and
|
||||
returns:
|
||||
|
||||
**Response `200`**
|
||||
```json
|
||||
{ "id": 2, "email": "hamid@gmail.com" }
|
||||
```
|
||||
|
||||
**Errors**
|
||||
| Status | Body | Cause |
|
||||
|---|---|---|
|
||||
| 400 | `{"error":"invalid oauth state"}` | Missing/mismatched CSRF state - usually means the flow wasn't started via `/auth/google/login`, or the session expired mid-flow |
|
||||
| 400 | `{"error":"missing code"}` | Google didn't include an authorization code |
|
||||
| 500 | `{"error":"internal error"}` | Token exchange, Google API call, or database failure |
|
||||
|
||||
---
|
||||
|
||||
## General notes
|
||||
|
||||
- **CORS**: browser-based requests from an origin not listed in
|
||||
`ALLOWED_ORIGINS` will be blocked by the browser itself, before this
|
||||
API's own logic ever runs. Non-browser clients (curl, mobile apps,
|
||||
server-to-server) are unaffected by CORS entirely.
|
||||
- **Rate limiting**: exceeding a limit returns HTTP `429 Too Many
|
||||
Requests`. The global limit (100/min/IP) applies to every route; the
|
||||
strict limit (5/min/IP) applies only to `/register` and `/login`, and
|
||||
stacks with the global limit.
|
||||
- All error responses share the same shape: `{"error": "<message>"}`.
|
||||
Reference in New Issue
Block a user