Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
373
cloud/maplepress-backend/docs/API/README.md
Normal file
373
cloud/maplepress-backend/docs/API/README.md
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
# MaplePress Backend API Documentation
|
||||
|
||||
This directory contains comprehensive API documentation for the MaplePress backend, organized by endpoint.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
## Quick Links
|
||||
|
||||
### General
|
||||
- [Health Check](health-check.md) - `GET /health`
|
||||
|
||||
### Authentication & User Management
|
||||
- [Register User & Tenant](register.md) - `POST /api/v1/register`
|
||||
- [Login](login.md) - `POST /api/v1/login`
|
||||
- [Refresh Token](refresh-token.md) - `POST /api/v1/refresh`
|
||||
- [Hello (Authenticated)](hello.md) - `POST /api/v1/hello`
|
||||
- [Get User Profile](get-user-profile.md) - `GET /api/v1/me`
|
||||
|
||||
### Tenant Management
|
||||
- [Create Tenant](create-tenant.md) - `POST /api/v1/tenants`
|
||||
- [Get Tenant by ID](get-tenant-by-id.md) - `GET /api/v1/tenants/{id}`
|
||||
- [Get Tenant by Slug](get-tenant-by-slug.md) - `GET /api/v1/tenants/slug/{slug}`
|
||||
|
||||
### User Management
|
||||
- [Create User](create-user.md) - `POST /api/v1/users`
|
||||
- [Get User by ID](get-user-by-id.md) - `GET /api/v1/users/{id}`
|
||||
|
||||
### Site Management
|
||||
- [Create WordPress Site](create-site.md) - `POST /api/v1/sites`
|
||||
- [List WordPress Sites](list-sites.md) - `GET /api/v1/sites`
|
||||
- [Get WordPress Site](get-site.md) - `GET /api/v1/sites/{id}`
|
||||
- [Delete WordPress Site](delete-site.md) - `DELETE /api/v1/sites/{id}`
|
||||
- [Rotate Site API Key](rotate-site-api-key.md) - `POST /api/v1/sites/{id}/rotate-api-key`
|
||||
|
||||
### WordPress Plugin API
|
||||
- [Verify API Key](plugin-verify-api-key.md) - `GET /api/v1/plugin/status`
|
||||
|
||||
---
|
||||
|
||||
## Authentication Overview
|
||||
|
||||
MaplePress uses a **dual authentication system**:
|
||||
|
||||
### 1. JWT Authentication (for Dashboard Users)
|
||||
|
||||
Used for user-facing dashboard endpoints (managing sites, users, tenants).
|
||||
|
||||
**Format**: `Authorization: JWT {access_token}`
|
||||
|
||||
**Endpoints**:
|
||||
- All `/api/v1/sites` endpoints
|
||||
- All `/api/v1/users` endpoints
|
||||
- All `/api/v1/tenants` endpoints
|
||||
|
||||
**How to get JWT**:
|
||||
1. Register: `POST /api/v1/register`
|
||||
2. Login: `POST /api/v1/login`
|
||||
3. Use returned `access_token` in Authorization header
|
||||
|
||||
### 2. API Key Authentication (for WordPress Plugins)
|
||||
|
||||
Used for WordPress plugin communication with the backend.
|
||||
|
||||
**Format**: `Authorization: Bearer {api_key}`
|
||||
|
||||
**Endpoints**:
|
||||
- All `/api/v1/plugin/*` endpoints (status, sync, search, etc.)
|
||||
|
||||
**How to get API Key**:
|
||||
1. Create a site via dashboard: `POST /api/v1/sites`
|
||||
2. Copy the `api_key` from response (shown only once!)
|
||||
3. Configure WordPress plugin with the API key
|
||||
|
||||
**API Key Format**: `live_sk_{40_random_characters}` or `test_sk_{40_random_characters}`
|
||||
|
||||
**Security**:
|
||||
- API keys are hashed using SHA-256 before storage
|
||||
- Never logged or displayed after initial creation
|
||||
- Can be rotated if compromised using the rotate-api-key endpoint
|
||||
- API key middleware validates and populates site context in requests
|
||||
- Only keys with `live_sk_` or `test_sk_` prefix are accepted
|
||||
|
||||
---
|
||||
|
||||
## Test Mode vs Live Mode
|
||||
|
||||
MaplePress automatically generates different API key types based on your backend environment configuration.
|
||||
|
||||
### Test Mode (`test_sk_` keys)
|
||||
|
||||
**Automatically enabled when:**
|
||||
- `APP_ENVIRONMENT=development` in `.env`
|
||||
|
||||
**Use for:**
|
||||
- Local development with `localhost` URLs
|
||||
- Testing and experimentation
|
||||
- CI/CD pipelines
|
||||
|
||||
**Features:**
|
||||
- Test keys work identically to live keys
|
||||
- Separate from production data
|
||||
- Can be used for integration testing
|
||||
- Generated automatically in development environment
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# In your .env file:
|
||||
APP_ENVIRONMENT=development
|
||||
|
||||
# Create a site (automatically gets test_sk_ key):
|
||||
curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT $TOKEN" \
|
||||
-d '{
|
||||
"domain": "localhost:8081",
|
||||
"site_url": "http://localhost:8081"
|
||||
}'
|
||||
```
|
||||
|
||||
Response will include: `"api_key": "test_sk_abc123..."`
|
||||
|
||||
### Live Mode (`live_sk_` keys)
|
||||
|
||||
**Automatically enabled when:**
|
||||
- `APP_ENVIRONMENT=production` in `.env`
|
||||
|
||||
**Use for:**
|
||||
- Production WordPress sites
|
||||
- Public-facing websites
|
||||
- Real customer data
|
||||
|
||||
**Features:**
|
||||
- Production-grade API keys
|
||||
- Should be kept secure and never committed to version control
|
||||
- Used for real traffic and billing
|
||||
- Generated automatically in production environment
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# In your .env file:
|
||||
APP_ENVIRONMENT=production
|
||||
|
||||
# Create a site (automatically gets live_sk_ key):
|
||||
curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT $TOKEN" \
|
||||
-d '{
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com"
|
||||
}'
|
||||
```
|
||||
|
||||
Response will include: `"api_key": "live_sk_xyz789..."`
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
The API key type is **automatically determined** by the `APP_ENVIRONMENT` variable in `.env`:
|
||||
|
||||
```bash
|
||||
# Development - Generates test_sk_ keys
|
||||
APP_ENVIRONMENT=development
|
||||
|
||||
# Production - Generates live_sk_ keys
|
||||
APP_ENVIRONMENT=production
|
||||
```
|
||||
|
||||
**Two simple options:**
|
||||
- `development` = test keys (`test_sk_*`)
|
||||
- `production` = live keys (`live_sk_*`)
|
||||
|
||||
**No manual configuration needed!** The backend automatically generates the appropriate key type based on your environment.
|
||||
|
||||
---
|
||||
|
||||
## Testing Workflow
|
||||
|
||||
Here's a complete workflow to test the API from registration to creating sites:
|
||||
|
||||
### 1. Register a new user and tenant
|
||||
|
||||
```bash
|
||||
# Save the response to extract tokens
|
||||
# Note: timezone is optional and defaults to UTC if not provided
|
||||
RESPONSE=$(curl -X POST http://localhost:8000/api/v1/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "admin@mycompany.com",
|
||||
"password": "SecurePass123!",
|
||||
"first_name": "Admin",
|
||||
"last_name": "User",
|
||||
"name": "Admin User",
|
||||
"tenant_name": "My Company",
|
||||
"tenant_slug": "my-company",
|
||||
"agree_terms_of_service": true,
|
||||
"agree_promotions": false,
|
||||
"agree_to_tracking_across_third_party_apps_and_services": false
|
||||
}')
|
||||
|
||||
echo $RESPONSE | jq .
|
||||
|
||||
# Extract tokens (requires jq)
|
||||
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
|
||||
TENANT_ID=$(echo $RESPONSE | jq -r '.tenant_id')
|
||||
```
|
||||
|
||||
### 2. Login with existing credentials
|
||||
|
||||
```bash
|
||||
# Login to get fresh tokens
|
||||
LOGIN_RESPONSE=$(curl -X POST http://localhost:8000/api/v1/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "admin@mycompany.com",
|
||||
"password": "SecurePass123!",
|
||||
"tenant_id": "'$TENANT_ID'"
|
||||
}')
|
||||
|
||||
echo $LOGIN_RESPONSE | jq .
|
||||
|
||||
# Extract new access token
|
||||
ACCESS_TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.access_token')
|
||||
```
|
||||
|
||||
### 3. Get tenant information
|
||||
|
||||
```bash
|
||||
# By ID
|
||||
curl -X GET http://localhost:8000/api/v1/tenants/$TENANT_ID \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" | jq .
|
||||
|
||||
# By slug
|
||||
curl -X GET http://localhost:8000/api/v1/tenants/slug/my-company \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" | jq .
|
||||
```
|
||||
|
||||
### 4. Create a new WordPress site
|
||||
|
||||
```bash
|
||||
SITE_RESPONSE=$(curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT $ACCESS_TOKEN" \
|
||||
-d '{
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com"
|
||||
}')
|
||||
|
||||
echo $SITE_RESPONSE | jq .
|
||||
|
||||
# Extract site ID and API key
|
||||
SITE_ID=$(echo $SITE_RESPONSE | jq -r '.id')
|
||||
API_KEY=$(echo $SITE_RESPONSE | jq -r '.api_key')
|
||||
```
|
||||
|
||||
### 5. Verify API key (as WordPress plugin)
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/plugin/status \
|
||||
-H "Authorization: Bearer $API_KEY" | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Tenancy
|
||||
|
||||
This API implements multi-tenancy where:
|
||||
- Each tenant is isolated from other tenants
|
||||
- Users belong to specific tenants
|
||||
- The `X-Tenant-ID` header is required for tenant-scoped operations (in development mode)
|
||||
- In production, the tenant context will be extracted from the JWT token
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
MaplePress uses **RFC 9457 (Problem Details for HTTP APIs)** for standardized, machine-readable error responses.
|
||||
|
||||
**Standard**: [RFC 9457 - Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc9457)
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
### Error Response Format
|
||||
|
||||
All error responses follow the RFC 9457 format:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Error Type",
|
||||
"status": 400,
|
||||
"detail": "Human-readable explanation of the error"
|
||||
}
|
||||
```
|
||||
|
||||
### Validation Errors (400 Bad Request)
|
||||
|
||||
For validation errors, an additional `errors` field provides field-level details:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Validation Error",
|
||||
"status": 400,
|
||||
"detail": "One or more validation errors occurred",
|
||||
"errors": {
|
||||
"email": ["Invalid email format", "Email is required"],
|
||||
"password": ["Password must be at least 8 characters"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Common HTTP Status Codes
|
||||
|
||||
- `200 OK`: Successful GET request
|
||||
- `201 Created`: Successful resource creation
|
||||
- `400 Bad Request`: Invalid input or missing required fields (with validation errors)
|
||||
- `401 Unauthorized`: Authentication required or invalid token
|
||||
- `403 Forbidden`: Authenticated but not authorized
|
||||
- `404 Not Found`: Resource not found
|
||||
- `409 Conflict`: Resource already exists (duplicate)
|
||||
- `429 Too Many Requests`: Rate limit exceeded
|
||||
- `500 Internal Server Error`: Server-side error
|
||||
|
||||
### Example Error Responses
|
||||
|
||||
**401 Unauthorized:**
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**409 Conflict:**
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Conflict",
|
||||
"status": 409,
|
||||
"detail": "Email already exists"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error:**
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to process request"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development vs Production
|
||||
|
||||
**Development Mode** (current):
|
||||
- Tenant context via `X-Tenant-ID` header
|
||||
- Less strict validation
|
||||
- Debug logging enabled
|
||||
- Test API keys (`test_sk_*`) generated
|
||||
|
||||
**Production Mode**:
|
||||
- Tenant context extracted from JWT token claims
|
||||
- Strict validation
|
||||
- Info/Error logging only
|
||||
- Live API keys (`live_sk_*`) generated
|
||||
110
cloud/maplepress-backend/docs/API/create-site.md
Normal file
110
cloud/maplepress-backend/docs/API/create-site.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Create WordPress Site
|
||||
|
||||
**POST /api/v1/sites**
|
||||
|
||||
Create a new WordPress site and generate API credentials for the WordPress plugin.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: JWT {access_token}` (tenant is automatically determined from JWT)
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| domain | string | Yes | WordPress site domain (e.g., example.com) |
|
||||
| site_url | string | Yes | Full WordPress site URL (e.g., https://example.com) |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||
-d '{
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com"
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com",
|
||||
"api_key": "live_sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
|
||||
"verification_token": "mvp_xyz789abc123",
|
||||
"status": "pending",
|
||||
"search_index_name": "site_a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
}
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- The `api_key` is shown **only once** - save it immediately!
|
||||
- The site starts with `status: "pending"` until verified
|
||||
- The `verification_token` should be used by the WordPress plugin for site verification
|
||||
- The `search_index_name` is the Meilisearch index for this site
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Validation Error Response** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Validation Error",
|
||||
"status": 400,
|
||||
"detail": "One or more validation errors occurred",
|
||||
"errors": {
|
||||
"domain": ["Invalid domain format", "Domain is required"],
|
||||
"site_url": ["Invalid URL format", "Site URL is required"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**Common Validation Error Messages**:
|
||||
|
||||
| Field | Error Messages |
|
||||
|-------|----------------|
|
||||
| domain | "Invalid domain format", "Domain is required" |
|
||||
| site_url | "Invalid URL format", "Site URL is required" |
|
||||
|
||||
**Other Error Responses**:
|
||||
|
||||
- `401 Unauthorized`: Missing or invalid JWT token
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
- `409 Conflict`: Domain already registered by another user
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Conflict",
|
||||
"status": 409,
|
||||
"detail": "Domain already exists"
|
||||
}
|
||||
```
|
||||
|
||||
- `500 Internal Server Error`: Server error
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to create site"
|
||||
}
|
||||
```
|
||||
88
cloud/maplepress-backend/docs/API/create-tenant.md
Normal file
88
cloud/maplepress-backend/docs/API/create-tenant.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Create Tenant
|
||||
|
||||
**POST /api/v1/tenants**
|
||||
|
||||
Create a new tenant (organization).
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| name | string | Yes | Tenant/organization name |
|
||||
| slug | string | Yes | URL-friendly tenant identifier |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/tenants \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||
-d '{
|
||||
"name": "TechStart Inc",
|
||||
"slug": "techstart"
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "850e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "TechStart Inc",
|
||||
"slug": "techstart",
|
||||
"status": "active",
|
||||
"created_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Invalid input:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Invalid request body format"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Missing or invalid JWT token:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**409 Conflict** - Tenant slug already exists:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Conflict",
|
||||
"status": 409,
|
||||
"detail": "Tenant slug already exists"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to create tenant"
|
||||
}
|
||||
```
|
||||
91
cloud/maplepress-backend/docs/API/create-user.md
Normal file
91
cloud/maplepress-backend/docs/API/create-user.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Create User
|
||||
|
||||
**POST /api/v1/users**
|
||||
|
||||
Create a new user within a tenant.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Tenant Context**: Required
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: JWT {access_token}`
|
||||
- `X-Tenant-ID: {tenant_id}` (required in development mode)
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| name | string | Yes | User's full name |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||
-H "X-Tenant-ID: 850e8400-e29b-41d4-a716-446655440000" \
|
||||
-d '{
|
||||
"email": "jane@techstart.com",
|
||||
"name": "Jane Smith"
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "950e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "jane@techstart.com",
|
||||
"name": "Jane Smith",
|
||||
"created_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Invalid input:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Invalid request body format"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**409 Conflict** - Email already exists:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Conflict",
|
||||
"status": 409,
|
||||
"detail": "User email already exists in this tenant"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to create user"
|
||||
}
|
||||
```
|
||||
74
cloud/maplepress-backend/docs/API/delete-site.md
Normal file
74
cloud/maplepress-backend/docs/API/delete-site.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Delete WordPress Site
|
||||
|
||||
**DELETE /api/v1/sites/{id}**
|
||||
|
||||
Delete a WordPress site and all associated data.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | Site ID |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:8000/api/v1/sites/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Site deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- This is a **hard delete** - removes the site from all Cassandra tables
|
||||
- The site's API key will immediately stop working
|
||||
- The Meilisearch index should also be deleted (implement separately)
|
||||
- This action cannot be undone
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Site not found or doesn't belong to your tenant"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to delete site"
|
||||
}
|
||||
```
|
||||
90
cloud/maplepress-backend/docs/API/get-site.md
Normal file
90
cloud/maplepress-backend/docs/API/get-site.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Get WordPress Site
|
||||
|
||||
**GET /api/v1/sites/{id}**
|
||||
|
||||
Retrieve detailed information about a specific WordPress site.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | Site ID |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/sites/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"tenant_id": "t1t2t3t4-t5t6-7890-tttt-tttttttttttt",
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com",
|
||||
"api_key_prefix": "live_sk_a1b2",
|
||||
"api_key_last_four": "s9t0",
|
||||
"status": "active",
|
||||
"is_verified": true,
|
||||
"search_index_name": "site_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"total_pages_indexed": 145,
|
||||
"last_indexed_at": "2024-10-27T14:30:00Z",
|
||||
"plugin_version": "1.0.0",
|
||||
"storage_used_bytes": 52428800,
|
||||
"search_requests_count": 234,
|
||||
"monthly_pages_indexed": 50,
|
||||
"last_reset_at": "2024-10-01T00:00:00Z",
|
||||
"created_at": "2024-10-27T10:00:00Z",
|
||||
"updated_at": "2024-10-27T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Returns full site details including usage tracking statistics
|
||||
- API key is never returned (only prefix and last 4 chars for identification)
|
||||
- Useful for dashboard display and usage monitoring
|
||||
- Usage-based billing: No quotas or limits, only usage tracking
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Site not found or doesn't belong to your tenant"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to retrieve site"
|
||||
}
|
||||
```
|
||||
72
cloud/maplepress-backend/docs/API/get-tenant-by-id.md
Normal file
72
cloud/maplepress-backend/docs/API/get-tenant-by-id.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Get Tenant by ID
|
||||
|
||||
**GET /api/v1/tenants/{id}**
|
||||
|
||||
Retrieve tenant information by tenant ID.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | Tenant ID |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/tenants/850e8400-e29b-41d4-a716-446655440000 \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "850e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "TechStart Inc",
|
||||
"slug": "techstart",
|
||||
"status": "active",
|
||||
"created_at": "2024-10-24T00:00:00Z",
|
||||
"updated_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Tenant not found"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to retrieve tenant"
|
||||
}
|
||||
```
|
||||
72
cloud/maplepress-backend/docs/API/get-tenant-by-slug.md
Normal file
72
cloud/maplepress-backend/docs/API/get-tenant-by-slug.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Get Tenant by Slug
|
||||
|
||||
**GET /api/v1/tenants/slug/{slug}**
|
||||
|
||||
Retrieve tenant information by tenant slug.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| slug | string | Yes | Tenant slug |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/tenants/slug/techstart \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "850e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "TechStart Inc",
|
||||
"slug": "techstart",
|
||||
"status": "active",
|
||||
"created_at": "2024-10-24T00:00:00Z",
|
||||
"updated_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Tenant not found"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to retrieve tenant"
|
||||
}
|
||||
```
|
||||
85
cloud/maplepress-backend/docs/API/get-user-by-id.md
Normal file
85
cloud/maplepress-backend/docs/API/get-user-by-id.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Get User by ID
|
||||
|
||||
**GET /api/v1/users/{id}**
|
||||
|
||||
Retrieve user information by user ID within a tenant context.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Tenant Context**: Required
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
- `X-Tenant-ID: {tenant_id}` (required in development mode)
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | User ID |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/users/950e8400-e29b-41d4-a716-446655440000 \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||
-H "X-Tenant-ID: 850e8400-e29b-41d4-a716-446655440000"
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "950e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "jane@techstart.com",
|
||||
"name": "Jane Smith",
|
||||
"created_at": "2024-10-24T00:00:00Z",
|
||||
"updated_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Missing tenant context:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Tenant context required"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "User not found in this tenant"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to retrieve user"
|
||||
}
|
||||
```
|
||||
51
cloud/maplepress-backend/docs/API/get-user-profile.md
Normal file
51
cloud/maplepress-backend/docs/API/get-user-profile.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Get User Profile
|
||||
|
||||
**GET /api/v1/me**
|
||||
|
||||
Get the authenticated user's profile information from the JWT token.
|
||||
|
||||
**Authentication**: Required (JWT token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/me \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "john@example.com",
|
||||
"name": "John Doe",
|
||||
"role": "owner",
|
||||
"tenant_id": "650e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized** - Missing or invalid JWT token:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Returns user information extracted from the JWT token claims
|
||||
- No database query required - all data comes from the token
|
||||
- Useful for displaying user information in the dashboard
|
||||
- Can be used to verify the current authenticated user's identity
|
||||
23
cloud/maplepress-backend/docs/API/health-check.md
Normal file
23
cloud/maplepress-backend/docs/API/health-check.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Health Check
|
||||
|
||||
## GET /health
|
||||
|
||||
Check if the service is running and healthy.
|
||||
|
||||
**Authentication**: None required
|
||||
|
||||
**Headers**: None required
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/health
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy"
|
||||
}
|
||||
```
|
||||
66
cloud/maplepress-backend/docs/API/hello.md
Normal file
66
cloud/maplepress-backend/docs/API/hello.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Hello (Authenticated)
|
||||
|
||||
**POST /api/v1/hello**
|
||||
|
||||
A simple authenticated endpoint that returns a personalized greeting message. This endpoint demonstrates JWT authentication and can be used to verify that your access token is working correctly.
|
||||
|
||||
**Authentication**: Required (JWT token)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| name | string | Yes | Name to include in greeting |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/hello \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||
-d '{"name": "Alice"}'
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello, Alice! Welcome to MaplePress Backend."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Missing name field:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Name is required"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Missing or invalid JWT token:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- This endpoint requires a valid JWT access token
|
||||
- The name field is required in the request body
|
||||
- Useful for testing authentication and verifying token validity
|
||||
- Returns a personalized greeting with the provided name
|
||||
79
cloud/maplepress-backend/docs/API/list-sites.md
Normal file
79
cloud/maplepress-backend/docs/API/list-sites.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# List WordPress Sites
|
||||
|
||||
**GET /api/v1/sites**
|
||||
|
||||
Retrieve all WordPress sites for the authenticated user's tenant.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**Query Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| page_size | integer | No | Number of results per page (default: 20, max: 100) |
|
||||
| page_state | string | No | Pagination token from previous response |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:8000/api/v1/sites?page_size=20' \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"sites": [
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"domain": "example.com",
|
||||
"status": "active",
|
||||
"is_verified": true,
|
||||
"created_at": "2024-10-27T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "b2c3d4e5-f6g7-8901-bcde-f12345678901",
|
||||
"domain": "another-site.com",
|
||||
"status": "pending",
|
||||
"is_verified": false,
|
||||
"created_at": "2024-10-27T11:00:00Z"
|
||||
}
|
||||
],
|
||||
"page_state": "base64_encoded_pagination_token"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Returns a summary view (limited fields) for performance
|
||||
- Use `page_state` for pagination through large result sets
|
||||
- Sites are ordered by creation date (newest first)
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to retrieve sites"
|
||||
}
|
||||
```
|
||||
99
cloud/maplepress-backend/docs/API/login.md
Normal file
99
cloud/maplepress-backend/docs/API/login.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# Login
|
||||
|
||||
**POST /api/v1/login**
|
||||
|
||||
Authenticate an existing user and obtain authentication tokens. This endpoint validates user credentials and creates a new session.
|
||||
|
||||
**Authentication**: None required (public endpoint)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| password | string | Yes | User's password |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "john@example.com",
|
||||
"password": "SecurePassword123!"
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_email": "john@example.com",
|
||||
"user_name": "John Doe",
|
||||
"user_role": "user",
|
||||
"tenant_id": "650e8400-e29b-41d4-a716-446655440000",
|
||||
"session_id": "750e8400-e29b-41d4-a716-446655440000",
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"access_expiry": "2024-10-24T12:15:00Z",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_expiry": "2024-10-31T00:00:00Z",
|
||||
"login_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Invalid input:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Invalid request body format. Please check your JSON syntax."
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Invalid credentials:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Invalid email or password."
|
||||
}
|
||||
```
|
||||
|
||||
**429 Too Many Requests** - Rate limit exceeded:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Too Many Requests",
|
||||
"status": 429,
|
||||
"detail": "Too many login attempts from this IP address. Please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to process login. Please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- The `tenant_id` is required for multi-tenant authentication to ensure user credentials are validated within the correct tenant context
|
||||
- Access tokens expire after 15 minutes
|
||||
- Refresh tokens expire after 7 days
|
||||
- Both tokens are JWT tokens that should be stored securely on the client side
|
||||
- Use the access token in the `Authorization: JWT {token}` header for authenticated requests
|
||||
73
cloud/maplepress-backend/docs/API/plugin-verify-api-key.md
Normal file
73
cloud/maplepress-backend/docs/API/plugin-verify-api-key.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Verify API Key (WordPress Plugin)
|
||||
|
||||
**GET /api/v1/plugin/status**
|
||||
|
||||
Verify that an API key is valid and retrieve site information. This endpoint is used by the WordPress plugin to verify the connection and display quota information.
|
||||
|
||||
**Authentication**: Required (API Key)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: Bearer {api_key}`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/v1/plugin/status \
|
||||
-H "Authorization: Bearer live_sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"site_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"tenant_id": "t1t2t3t4-t5t6-7890-tttt-tttttttttttt",
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com",
|
||||
"status": "active",
|
||||
"is_verified": true,
|
||||
"storage_used_bytes": 52428800,
|
||||
"search_requests_count": 234,
|
||||
"monthly_pages_indexed": 50,
|
||||
"total_pages_indexed": 145,
|
||||
"search_index_name": "site_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"api_key_prefix": "live_sk_a1b2",
|
||||
"api_key_last_four": "s9t0",
|
||||
"plugin_version": "1.0.0",
|
||||
"message": "API key is valid"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Used by WordPress plugin to verify connection on plugin activation
|
||||
- Returns site information and usage tracking statistics
|
||||
- If the API key is invalid or missing, returns 401 Unauthorized
|
||||
- Usage-based billing: No quotas or limits, only usage tracking for billing
|
||||
- If the request reaches this handler, the API key has already been validated by the middleware
|
||||
- API key must start with `live_sk_` or `test_sk_` prefix
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized** - Invalid or missing API key:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Invalid or missing API key"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to verify API key"
|
||||
}
|
||||
```
|
||||
131
cloud/maplepress-backend/docs/API/refresh-token.md
Normal file
131
cloud/maplepress-backend/docs/API/refresh-token.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Refresh Token
|
||||
|
||||
**POST /api/v1/refresh**
|
||||
|
||||
Obtain a new access token and refresh token using an existing valid refresh token. This endpoint should be called when the access token expires (after 15 minutes) to maintain the user's session without requiring them to log in again.
|
||||
|
||||
**Authentication**: None required (public endpoint, but requires valid refresh token)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| refresh_token | string | Yes | Valid refresh token from login or previous refresh |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/refresh \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_email": "john@example.com",
|
||||
"user_name": "John Doe",
|
||||
"user_role": "user",
|
||||
"tenant_id": "650e8400-e29b-41d4-a716-446655440000",
|
||||
"session_id": "750e8400-e29b-41d4-a716-446655440000",
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"access_expiry": "2024-10-24T12:30:00Z",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_expiry": "2024-10-31T00:15:00Z",
|
||||
"refreshed_at": "2024-10-24T12:15:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - Missing refresh token:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Refresh token is required"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Invalid or expired refresh token:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Invalid or expired refresh token. Please log in again."
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Session invalidated:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Session has expired or been invalidated. Please log in again."
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to refresh token. Please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
**Token Refresh Flow**:
|
||||
|
||||
1. **Initial Authentication**: User logs in via `/api/v1/login` and receives:
|
||||
- Access token (expires in 15 minutes)
|
||||
- Refresh token (expires in 7 days)
|
||||
|
||||
2. **Token Usage**: Client uses the access token for API requests
|
||||
|
||||
3. **Token Expiration**: When access token expires (after 15 minutes):
|
||||
- Client detects 401 Unauthorized response
|
||||
- Client calls `/api/v1/refresh` with the refresh token
|
||||
- Server validates refresh token and session
|
||||
- Server returns new access token and new refresh token
|
||||
|
||||
4. **Token Rotation**: Both tokens are regenerated on refresh:
|
||||
- New access token (valid for 15 minutes from refresh time)
|
||||
- New refresh token (valid for 7 days from refresh time)
|
||||
- Old tokens become invalid
|
||||
|
||||
5. **Session Validation**: The refresh token is validated against the active session:
|
||||
- If the session has been deleted (e.g., user logged out), refresh will fail
|
||||
- If the session has expired (after 14 days of inactivity), refresh will fail
|
||||
- This prevents using refresh tokens after logout
|
||||
|
||||
**Best Practices**:
|
||||
|
||||
- Store both access and refresh tokens securely on the client (e.g., secure HTTP-only cookies or encrypted storage)
|
||||
- Implement automatic token refresh when access token expires (don't wait for 401 errors)
|
||||
- Consider refreshing tokens proactively before expiration (e.g., 1 minute before)
|
||||
- Handle refresh failures by redirecting user to login
|
||||
- Never share refresh tokens across devices or sessions
|
||||
- Clear tokens on logout
|
||||
|
||||
**Security Notes**:
|
||||
|
||||
- Refresh tokens are single-use in practice due to token rotation
|
||||
- Each refresh generates a new token pair and invalidates the old one
|
||||
- Session validation prevents token reuse after logout (CWE-613)
|
||||
- Refresh tokens have a longer lifetime but are still time-limited (7 days)
|
||||
- Sessions expire after 14 days of inactivity regardless of token refresh
|
||||
149
cloud/maplepress-backend/docs/API/register.md
Normal file
149
cloud/maplepress-backend/docs/API/register.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# Register User & Tenant
|
||||
|
||||
**POST /api/v1/register**
|
||||
|
||||
Register a new user and create a new tenant (organization) in a single request. This is the primary onboarding endpoint that returns authentication tokens.
|
||||
|
||||
**Authentication**: None required (public endpoint)
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| email | string | Yes | User's email address |
|
||||
| password | string | Yes | User's password (min 8 characters) |
|
||||
| confirm_password | string | Yes | Password confirmation (must match password) |
|
||||
| first_name | string | Yes | User's first name |
|
||||
| last_name | string | Yes | User's last name |
|
||||
| tenant_name | string | Yes | Organization/tenant name (slug auto-generated from this) |
|
||||
| timezone | string | No | User's timezone (e.g., "America/New_York", defaults to "UTC" if not provided) |
|
||||
| agree_terms_of_service | boolean | Yes | Must be true - user agreement to Terms of Service |
|
||||
| agree_promotions | boolean | No | Optional - user agreement to receive promotional emails (default: false) |
|
||||
| agree_to_tracking_across_third_party_apps_and_services | boolean | No | Optional - user agreement to cross-platform tracking (default: false) |
|
||||
|
||||
**Example Request (with timezone)**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "john@example.com",
|
||||
"password": "SecurePassword123!",
|
||||
"confirm_password": "SecurePassword123!",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"tenant_name": "Acme Corporation",
|
||||
"timezone": "America/New_York",
|
||||
"agree_terms_of_service": true,
|
||||
"agree_promotions": false,
|
||||
"agree_to_tracking_across_third_party_apps_and_services": false
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Request (without timezone - defaults to UTC)**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "jane@example.com",
|
||||
"password": "SecurePassword456!",
|
||||
"confirm_password": "SecurePassword456!",
|
||||
"first_name": "Jane",
|
||||
"last_name": "Smith",
|
||||
"tenant_name": "Beta Inc",
|
||||
"agree_terms_of_service": true
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"user_email": "john@example.com",
|
||||
"user_name": "John Doe",
|
||||
"user_role": "manager",
|
||||
"tenant_id": "650e8400-e29b-41d4-a716-446655440000",
|
||||
"tenant_name": "Acme Corporation",
|
||||
"tenant_slug": "acme-corp",
|
||||
"session_id": "750e8400-e29b-41d4-a716-446655440000",
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"access_expiry": "2024-10-24T12:00:00Z",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_expiry": "2024-11-24T00:00:00Z",
|
||||
"created_at": "2024-10-24T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Validation Error Response** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Validation Error",
|
||||
"status": 400,
|
||||
"detail": "One or more validation errors occurred",
|
||||
"errors": {
|
||||
"email": ["Invalid email format"],
|
||||
"password": ["Field is required", "Password must be at least 8 characters"],
|
||||
"confirm_password": ["Field is required", "Passwords do not match"],
|
||||
"first_name": ["Field is required"],
|
||||
"last_name": ["Field is required"],
|
||||
"tenant_name": ["Field is required"],
|
||||
"agree_terms_of_service": ["Must agree to terms of service"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**Common Validation Error Messages**:
|
||||
|
||||
| Field | Error Messages |
|
||||
|-------|---------------|
|
||||
| email | "Invalid email format", "Field is required" |
|
||||
| password | "Field is required", "Password must be at least 8 characters", "Password must contain at least one uppercase letter (A-Z)", "Password must contain at least one lowercase letter (a-z)", "Password must contain at least one number (0-9)", "Password must contain at least one special character" |
|
||||
| confirm_password | "Field is required", "Passwords do not match" |
|
||||
| first_name | "Field is required", "First_name must be between 1 and 100 characters" |
|
||||
| last_name | "Field is required", "Last_name must be between 1 and 100 characters" |
|
||||
| tenant_name | "Field is required", "Tenant_name must be between 1 and 100 characters" |
|
||||
| agree_terms_of_service | "Must agree to terms of service" |
|
||||
|
||||
**Other Error Responses**:
|
||||
|
||||
- `409 Conflict`: Email or tenant slug already exists
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Conflict",
|
||||
"status": 409,
|
||||
"detail": "Registration failed. The provided information is already in use"
|
||||
}
|
||||
```
|
||||
|
||||
- `500 Internal Server Error`: Server error
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to register user"
|
||||
}
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- `agree_terms_of_service` must be `true` or the request will fail with 400 Bad Request
|
||||
- `first_name` and `last_name` are required fields
|
||||
- `timezone` is optional and defaults to "UTC" if not provided
|
||||
- Password must be at least 8 characters long
|
||||
- **Tenant slug is automatically generated** from `tenant_name` (converted to lowercase, special chars replaced with hyphens)
|
||||
- The IP address of the request is automatically captured for audit trail purposes
|
||||
- User role for registration is always "manager" (tenant creator)
|
||||
79
cloud/maplepress-backend/docs/API/rotate-site-api-key.md
Normal file
79
cloud/maplepress-backend/docs/API/rotate-site-api-key.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Rotate Site API Key
|
||||
|
||||
**POST /api/v1/sites/{id}/rotate-api-key**
|
||||
|
||||
Rotate a site's API key (use when the key is compromised).
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | Site ID |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/sites/a1b2c3d4-e5f6-7890-abcd-ef1234567890/rotate-api-key \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"new_api_key": "live_sk_z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0",
|
||||
"old_key_last_four": "s9t0",
|
||||
"rotated_at": "2024-10-27T15:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**🚨 CRITICAL Notes**:
|
||||
- The `new_api_key` is shown **only once** - save it immediately!
|
||||
- The old API key is **immediately invalidated** - no grace period!
|
||||
- Your WordPress site will stop working until you update the plugin with the new key
|
||||
- Update the WordPress plugin settings **RIGHT NOW** to restore functionality
|
||||
- The rotation happens atomically:
|
||||
- Old key is deleted from the database
|
||||
- New key is inserted into the database
|
||||
- Both operations complete instantly
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "Site not found or doesn't belong to your tenant"
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to rotate API key"
|
||||
}
|
||||
```
|
||||
148
cloud/maplepress-backend/docs/API/verify-site.md
Normal file
148
cloud/maplepress-backend/docs/API/verify-site.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Verify WordPress Site
|
||||
|
||||
**POST /api/v1/sites/{id}/verify**
|
||||
|
||||
Verify a WordPress site by checking DNS TXT records to prove domain ownership. This transitions the site from `pending` to `active` status.
|
||||
|
||||
**Authentication**: Required (JWT Bearer token)
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: JWT {access_token}`
|
||||
|
||||
**URL Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| id | UUID | Yes | Site ID |
|
||||
|
||||
**Request Body**:
|
||||
|
||||
No request body required. Verification is done automatically by checking DNS TXT records.
|
||||
|
||||
**DNS TXT Record Setup**:
|
||||
|
||||
Before calling this endpoint, you must add a DNS TXT record to your domain:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Host/Name | Your domain (e.g., `example.com`) |
|
||||
| Type | TXT |
|
||||
| Value | `maplepress-verify={verification_token}` |
|
||||
|
||||
The verification token is provided when you create the site. DNS propagation typically takes 5-10 minutes but can take up to 48 hours.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/sites/a1b2c3d4-e5f6-7890-abcd-ef1234567890/verify \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
**Example Response** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "active",
|
||||
"message": "Domain ownership verified successfully via DNS TXT record"
|
||||
}
|
||||
```
|
||||
|
||||
**Important Notes**:
|
||||
- The verification token is provided when the site is created (POST /api/v1/sites)
|
||||
- You must add the DNS TXT record to your domain before calling this endpoint
|
||||
- DNS propagation typically takes 5-10 minutes but can take up to 48 hours
|
||||
- Once verified, the site status changes from `pending` to `active`
|
||||
- After verification, the site can sync pages and use search functionality
|
||||
- Test mode sites (`test_sk_` API keys) skip DNS verification automatically
|
||||
- Already verified sites return success without error
|
||||
|
||||
**Verification Flow**:
|
||||
1. User creates site via dashboard → receives `verification_token` and DNS instructions
|
||||
2. User adds DNS TXT record to domain registrar: `maplepress-verify={token}`
|
||||
3. User waits 5-10 minutes for DNS propagation
|
||||
4. User clicks "Verify Site" in plugin → calls this endpoint
|
||||
5. Backend performs DNS TXT lookup to verify domain ownership
|
||||
6. Site transitions to `active` status → full functionality enabled
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
This endpoint returns errors in **RFC 9457 (Problem Details for HTTP APIs)** format.
|
||||
|
||||
**Content-Type**: `application/problem+json`
|
||||
|
||||
**400 Bad Request** - DNS TXT record not found:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "DNS TXT record not found. Please add the verification record to your domain's DNS settings and wait 5-10 minutes for propagation."
|
||||
}
|
||||
```
|
||||
|
||||
**400 Bad Request** - DNS lookup timed out:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "DNS lookup timed out. Please check that your domain's DNS is properly configured."
|
||||
}
|
||||
```
|
||||
|
||||
**400 Bad Request** - Domain not found:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Domain not found. Please check that your domain is properly registered and DNS is active."
|
||||
}
|
||||
```
|
||||
|
||||
**400 Bad Request** - Invalid site ID:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Invalid site ID format. Please provide a valid site ID."
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized** - Missing or invalid JWT:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unauthorized",
|
||||
"status": 401,
|
||||
"detail": "Tenant context is required to access this resource."
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found** - Site not found or doesn't belong to tenant:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Not Found",
|
||||
"status": 404,
|
||||
"detail": "The requested site could not be found. It may have been deleted or you may not have access to it."
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error** - Server error:
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Internal Server Error",
|
||||
"status": 500,
|
||||
"detail": "Failed to verify site. Please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
**Related Endpoints**:
|
||||
- [Create Site](./create-site.md) - Initial site creation (provides verification token)
|
||||
- [Get Site](./get-site.md) - Check verification status
|
||||
- [Plugin Status](./plugin-verify-api-key.md) - Check verification status from plugin
|
||||
- [Sync Pages](./plugin-sync-pages.md) - Requires verification
|
||||
3126
cloud/maplepress-backend/docs/Architecture/BACKEND_BLUEPRINT.md
Normal file
3126
cloud/maplepress-backend/docs/Architecture/BACKEND_BLUEPRINT.md
Normal file
File diff suppressed because it is too large
Load diff
2823
cloud/maplepress-backend/docs/DEVELOPER_GUIDE.md
Normal file
2823
cloud/maplepress-backend/docs/DEVELOPER_GUIDE.md
Normal file
File diff suppressed because it is too large
Load diff
333
cloud/maplepress-backend/docs/GETTING-STARTED.md
Normal file
333
cloud/maplepress-backend/docs/GETTING-STARTED.md
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
# MaplePress Backend - Getting Started
|
||||
|
||||
Complete guide for local development in under 5 minutes.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Go 1.21+ installed
|
||||
- Task (Taskfile) installed: `brew install go-task/tap/go-task`
|
||||
|
||||
### Start Development (3 steps)
|
||||
|
||||
```bash
|
||||
# 1. Start infrastructure (in separate terminal)
|
||||
cd ../infrastructure/development
|
||||
task dev:start
|
||||
# Wait ~1 minute for services to be ready
|
||||
|
||||
# 2. Start backend (in this directory)
|
||||
cd ../maplepress-backend
|
||||
task dev
|
||||
# Backend runs at http://localhost:8000
|
||||
# Press Ctrl+C to stop
|
||||
# Auto-migration and hot-reload are enabled
|
||||
|
||||
# 3. Verify it's running
|
||||
curl http://localhost:8000/health
|
||||
# Should return: {"status":"healthy"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create Test Data
|
||||
|
||||
### 1. Register a User
|
||||
|
||||
```bash
|
||||
# Create user and tenant
|
||||
curl -X POST http://localhost:8000/api/v1/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@example.com",
|
||||
"password": "TestPassword123!",
|
||||
"name": "Test User",
|
||||
"tenant_name": "Test Organization",
|
||||
"tenant_slug": "test-org"
|
||||
}'
|
||||
```
|
||||
|
||||
**Save the `access_token` from the response!**
|
||||
|
||||
```bash
|
||||
# Export your token for subsequent requests
|
||||
export TOKEN="eyJhbGci..."
|
||||
```
|
||||
|
||||
### 2. Create a WordPress Site
|
||||
|
||||
```bash
|
||||
# Tenant is automatically determined from JWT token
|
||||
curl -X POST http://localhost:8000/api/v1/sites \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: JWT $TOKEN" \
|
||||
-d '{
|
||||
"domain": "localhost:8081",
|
||||
"site_url": "http://localhost:8081"
|
||||
}'
|
||||
```
|
||||
|
||||
**Save the `api_key` from the response!** (Shown only once)
|
||||
|
||||
### 3. Test Plugin Authentication
|
||||
|
||||
```bash
|
||||
# Test the API key (what WordPress plugin uses)
|
||||
curl -X GET http://localhost:8000/api/v1/plugin/status \
|
||||
-H "Authorization: Bearer YOUR_API_KEY_HERE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Start backend (foreground, see logs)
|
||||
task dev
|
||||
|
||||
# Restart after code changes
|
||||
# CompileDaemon auto-rebuilds on file changes
|
||||
# Only manually restart if needed:
|
||||
# Press Ctrl+C, then:
|
||||
task dev
|
||||
|
||||
# Stop backend
|
||||
# Press Ctrl+C (or task dev:down in another terminal)
|
||||
```
|
||||
|
||||
### Database
|
||||
|
||||
```bash
|
||||
# Clear database (WARNING: deletes all data!)
|
||||
task db:clear
|
||||
|
||||
# Manual migration (only if auto-migrate disabled)
|
||||
task migrate:up
|
||||
|
||||
# View database
|
||||
cd ../infrastructure/development
|
||||
task cql
|
||||
# Inside cqlsh:
|
||||
USE maplepress;
|
||||
SELECT * FROM sites_by_id;
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
task test
|
||||
|
||||
# Format code
|
||||
task format
|
||||
|
||||
# Run linters
|
||||
task lint
|
||||
```
|
||||
|
||||
### API Operations
|
||||
|
||||
```bash
|
||||
# Export your token first
|
||||
export TOKEN="your_jwt_token_here"
|
||||
|
||||
# Get your profile
|
||||
curl http://localhost:8000/api/v1/me \
|
||||
-H "Authorization: JWT $TOKEN"
|
||||
|
||||
# List sites
|
||||
curl http://localhost:8000/api/v1/sites \
|
||||
-H "Authorization: JWT $TOKEN"
|
||||
|
||||
# Get specific site
|
||||
curl http://localhost:8000/api/v1/sites/SITE_ID \
|
||||
-H "Authorization: JWT $TOKEN"
|
||||
|
||||
# Delete site
|
||||
curl -X DELETE http://localhost:8000/api/v1/sites/SITE_ID \
|
||||
-H "Authorization: JWT $TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WordPress Plugin Setup
|
||||
|
||||
### 1. Access WordPress Admin
|
||||
|
||||
```bash
|
||||
# WordPress is running at:
|
||||
http://localhost:8081/wp-admin
|
||||
# Default credentials: admin / admin
|
||||
```
|
||||
|
||||
### 2. Configure MaplePress Plugin
|
||||
|
||||
1. Navigate to **Settings → MaplePress**
|
||||
2. Enter:
|
||||
- **API URL**: `http://maplepress-backend-dev:8000`
|
||||
- **API Key**: Your site API key from step 2 above
|
||||
3. Click **Save Settings & Verify Connection**
|
||||
|
||||
**Note**: Use `http://maplepress-backend-dev:8000` (not `localhost`) because WordPress runs in Docker and needs the container name.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Backend won't start
|
||||
|
||||
**Error**: "Infrastructure not running!"
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
cd ../infrastructure/development
|
||||
task dev:start
|
||||
# Wait for services to be healthy (~1 minute)
|
||||
```
|
||||
|
||||
### Token expired (401 Unauthorized)
|
||||
|
||||
Tokens expire after 60 minutes. Login again:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@example.com",
|
||||
"password": "TestPassword123!"
|
||||
}'
|
||||
```
|
||||
|
||||
### WordPress can't connect to backend
|
||||
|
||||
**Error**: "Could not resolve host"
|
||||
|
||||
**Solution**: Make sure you're using `http://maplepress-backend-dev:8000` (not `localhost:8000`) in WordPress settings.
|
||||
|
||||
**Verify from WordPress container**:
|
||||
```bash
|
||||
docker exec maple-wordpress-dev curl http://maplepress-backend-dev:8000/health
|
||||
# Should return: {"status":"healthy"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
Backend (Port 8000)
|
||||
├── HTTP Server
|
||||
├── JWT Authentication (user access)
|
||||
├── API Key Authentication (plugin access)
|
||||
├── Domain Layer (business logic)
|
||||
├── Repository Layer (data access)
|
||||
└── Infrastructure
|
||||
├── Cassandra (primary database)
|
||||
├── Redis (caching)
|
||||
├── Meilisearch (search indexing)
|
||||
└── SeaweedFS (S3-compatible storage)
|
||||
```
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **Tenant**: Organization/account that can have multiple users and sites
|
||||
- **User**: Person who logs in with email/password (gets JWT token)
|
||||
- **Site**: WordPress installation (gets API key for plugin authentication)
|
||||
- **Multi-tenancy**: All data is scoped to a tenant_id
|
||||
- **JWT Token**: Used by dashboard/admin users (Authorization: JWT ...)
|
||||
- **API Key**: Used by WordPress plugins (Authorization: Bearer ...)
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public (No Auth)
|
||||
- `GET /health` - Health check
|
||||
- `POST /api/v1/register` - Register user + tenant
|
||||
- `POST /api/v1/login` - Login
|
||||
|
||||
### Authenticated (JWT Required)
|
||||
- `GET /api/v1/me` - Get user profile
|
||||
- `POST /api/v1/sites` - Create site
|
||||
- `GET /api/v1/sites` - List sites
|
||||
- `GET /api/v1/sites/{id}` - Get site
|
||||
- `DELETE /api/v1/sites/{id}` - Delete site
|
||||
- `POST /api/v1/sites/{id}/rotate-key` - Rotate API key
|
||||
|
||||
### Plugin (API Key Required)
|
||||
- `GET /api/v1/plugin/status` - Verify API key and get site info
|
||||
|
||||
**Full API documentation**: See `API.md`
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The backend uses `.env` for configuration. Copy from sample:
|
||||
|
||||
```bash
|
||||
cp .env.sample .env
|
||||
```
|
||||
|
||||
**Key variables**:
|
||||
|
||||
```bash
|
||||
# Application
|
||||
APP_JWT_SECRET=change-me-in-production
|
||||
SERVER_PORT=8000
|
||||
|
||||
# Database (Cassandra)
|
||||
DATABASE_HOSTS=localhost
|
||||
DATABASE_KEYSPACE=maplepress
|
||||
|
||||
# Cache (Redis)
|
||||
CACHE_HOST=localhost
|
||||
CACHE_PORT=6379
|
||||
```
|
||||
|
||||
For Docker development, the `docker-compose.dev.yml` sets these automatically.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **API Documentation**: See `API.md` for complete endpoint reference
|
||||
- **Architecture**: See `DEVELOPER_GUIDE.md` for code structure
|
||||
- **WordPress Plugin**: See `native/wordpress/maplepress-plugin/README.md`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Infrastructure
|
||||
cd ../infrastructure/development
|
||||
task dev:start # Start all services
|
||||
task dev:stop # Stop all services
|
||||
task cql # Open Cassandra shell
|
||||
|
||||
# Backend
|
||||
cd ../maplepress-backend
|
||||
task dev # Start backend (auto-migrate + hot-reload)
|
||||
task dev:down # Stop backend
|
||||
task db:clear # Clear database
|
||||
task test # Run tests
|
||||
task build # Build binary (only for manual operations)
|
||||
task migrate:up # Manual migration (only if needed)
|
||||
|
||||
# View infrastructure logs
|
||||
docker logs maple-cassandra-1-dev # Cassandra logs
|
||||
docker logs maple-redis-dev # Redis logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Happy coding!** 🚀
|
||||
|
||||
For questions or issues, see the full documentation or check the [GitHub repository](https://codeberg.org/mapleopentech/monorepo).
|
||||
555
cloud/maplepress-backend/docs/SITE_VERIFICATION.md
Normal file
555
cloud/maplepress-backend/docs/SITE_VERIFICATION.md
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
# Site Verification System
|
||||
|
||||
## Overview
|
||||
|
||||
MaplePress implements **DNS-based domain ownership verification** to ensure users actually own the domains they register. Sites start in "pending" status and remain there until verified through DNS TXT record validation.
|
||||
|
||||
## Verification Method: DNS TXT Records
|
||||
|
||||
MaplePress uses **DNS TXT record verification** - the industry standard used by Google, Cloudflare, and other major services. This proves domain ownership, not just dashboard access.
|
||||
|
||||
### Why DNS Verification?
|
||||
|
||||
- **Proves domain ownership**: Only someone with DNS access can add TXT records
|
||||
- **Industry standard**: Same method used by Google Search Console, Cloudflare, etc.
|
||||
- **Secure**: Cannot be spoofed or bypassed without actual domain control
|
||||
- **Automatic**: Backend performs verification via DNS lookup
|
||||
|
||||
## Site Status Lifecycle
|
||||
|
||||
### Status Constants
|
||||
|
||||
**File**: `internal/domain/site/site.go:61-67`
|
||||
|
||||
```go
|
||||
const (
|
||||
StatusPending = "pending" // Site created, awaiting DNS verification
|
||||
StatusActive = "active" // Site verified via DNS and operational
|
||||
StatusInactive = "inactive" // User temporarily disabled
|
||||
StatusSuspended = "suspended" // Suspended due to violation or non-payment
|
||||
StatusArchived = "archived" // Soft deleted
|
||||
)
|
||||
```
|
||||
|
||||
## 1. Site Creation (Pending State)
|
||||
|
||||
**File**: `internal/usecase/site/create.go`
|
||||
|
||||
When a site is created via **POST /api/v1/sites**:
|
||||
|
||||
### What Gets Generated
|
||||
|
||||
1. **API Key** (test or live mode)
|
||||
- Test mode: `test_sk_...` (skips DNS verification)
|
||||
- Live mode: `live_sk_...` (requires DNS verification)
|
||||
|
||||
2. **Verification Token** (lines 88-92)
|
||||
- Format: `mvp_` + 128-bit random token (base64-encoded)
|
||||
- Example: `mvp_xyz789abc123`
|
||||
- Used in DNS TXT record: `maplepress-verify={token}`
|
||||
|
||||
3. **DNS Verification Instructions**
|
||||
- Provides step-by-step DNS setup guide
|
||||
- Includes domain registrar examples (GoDaddy, Namecheap, Cloudflare, etc.)
|
||||
- Explains DNS propagation timing (5-10 minutes typical)
|
||||
|
||||
4. **Site Entity** (lines 104-113)
|
||||
- Initial status: `StatusPending`
|
||||
- `IsVerified`: `false`
|
||||
- `VerificationToken`: Set to generated token
|
||||
|
||||
### Response Example
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"domain": "example.com",
|
||||
"site_url": "https://example.com",
|
||||
"api_key": "live_sk_a1b2...", // ⚠️ SHOWN ONLY ONCE
|
||||
"verification_token": "mvp_xyz789abc123",
|
||||
"status": "pending",
|
||||
"search_index_name": "site_...",
|
||||
"verification_instructions": "To verify ownership of example.com, add this DNS TXT record:\n\nHost/Name: example.com\nType: TXT\nValue: maplepress-verify=mvp_xyz789abc123\n\nInstructions:\n1. Log in to your domain registrar...\n2. Find DNS settings...\n3. Add a new TXT record...\n4. Wait 5-10 minutes for DNS propagation\n5. Click 'Verify Domain' in MaplePress"
|
||||
}
|
||||
```
|
||||
|
||||
**Documentation**: `docs/API/create-site.md`
|
||||
|
||||
## 2. Test Mode Bypass
|
||||
|
||||
**File**: `internal/domain/site/site.go:115-125`
|
||||
|
||||
### Test Mode Detection
|
||||
|
||||
```go
|
||||
func (s *Site) IsTestMode() bool {
|
||||
return len(s.APIKeyPrefix) >= 7 && s.APIKeyPrefix[:7] == "test_sk"
|
||||
}
|
||||
```
|
||||
|
||||
### Verification Requirement Check
|
||||
|
||||
```go
|
||||
func (s *Site) RequiresVerification() bool {
|
||||
return !s.IsTestMode() // Test mode sites skip verification
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Sites with `test_sk_` API keys **skip verification** entirely
|
||||
- Useful for development and testing
|
||||
- Test mode sites can sync pages immediately
|
||||
|
||||
## 3. API Access Control
|
||||
|
||||
**File**: `internal/domain/site/site.go:127-140`
|
||||
|
||||
### CanAccessAPI() Method
|
||||
|
||||
```go
|
||||
func (s *Site) CanAccessAPI() bool {
|
||||
// Allow active sites (fully verified)
|
||||
if s.Status == StatusActive {
|
||||
return true
|
||||
}
|
||||
// Allow pending sites (waiting for verification) for initial setup
|
||||
if s.Status == StatusPending {
|
||||
return true
|
||||
}
|
||||
// Block inactive, suspended, or archived sites
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: Pending sites **CAN access the API** for:
|
||||
- Status checks (`GET /api/v1/plugin/status`)
|
||||
- Initial plugin setup
|
||||
- Retrieving site information
|
||||
|
||||
## 4. Verification Enforcement
|
||||
|
||||
### Where Verification is Required
|
||||
|
||||
**File**: `internal/usecase/page/sync.go:85-89`
|
||||
|
||||
When syncing pages (**POST /api/v1/plugin/sync**):
|
||||
|
||||
```go
|
||||
// Verify site is verified (skip for test mode)
|
||||
if site.RequiresVerification() && !site.IsVerified {
|
||||
uc.logger.Warn("site not verified", zap.String("site_id", siteID.String()))
|
||||
return nil, domainsite.ErrSiteNotVerified
|
||||
}
|
||||
```
|
||||
|
||||
**Error**: `internal/domain/site/errors.go:22`
|
||||
```go
|
||||
ErrSiteNotVerified = errors.New("site is not verified")
|
||||
```
|
||||
|
||||
### HTTP Response
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Forbidden",
|
||||
"status": 403,
|
||||
"detail": "site is not verified"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. DNS Verification Implementation
|
||||
|
||||
### DNS Verifier Package
|
||||
|
||||
**File**: `pkg/dns/verifier.go`
|
||||
|
||||
```go
|
||||
type Verifier struct {
|
||||
resolver *net.Resolver
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (v *Verifier) VerifyDomainOwnership(ctx context.Context, domain string, expectedToken string) (bool, error) {
|
||||
// Create context with 10-second timeout
|
||||
lookupCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Look up TXT records for the domain
|
||||
txtRecords, err := v.resolver.LookupTXT(lookupCtx, domain)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("DNS lookup failed: %w", err)
|
||||
}
|
||||
|
||||
// Expected format: "maplepress-verify=TOKEN"
|
||||
expectedRecord := fmt.Sprintf("maplepress-verify=%s", expectedToken)
|
||||
|
||||
for _, record := range txtRecords {
|
||||
if strings.TrimSpace(record) == expectedRecord {
|
||||
return true, nil // Domain ownership verified!
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil // TXT record not found
|
||||
}
|
||||
```
|
||||
|
||||
### DNS Verification Use Case
|
||||
|
||||
**File**: `internal/usecase/site/verify.go`
|
||||
|
||||
The verification use case performs DNS lookup:
|
||||
|
||||
```go
|
||||
func (uc *VerifySiteUseCase) Execute(ctx context.Context, tenantID gocql.UUID, siteID gocql.UUID, input *VerifySiteInput) (*VerifySiteOutput, error) {
|
||||
// Get site from repository
|
||||
site, err := uc.repo.GetByID(ctx, tenantID, siteID)
|
||||
if err != nil {
|
||||
return nil, domainsite.ErrSiteNotFound
|
||||
}
|
||||
|
||||
// Check if already verified
|
||||
if site.IsVerified {
|
||||
return &VerifySiteOutput{Success: true, Status: site.Status, Message: "Site is already verified"}, nil
|
||||
}
|
||||
|
||||
// Test mode sites skip DNS verification
|
||||
if site.IsTestMode() {
|
||||
site.Verify()
|
||||
if err := uc.repo.Update(ctx, site); err != nil {
|
||||
return nil, fmt.Errorf("failed to update site: %w", err)
|
||||
}
|
||||
return &VerifySiteOutput{Success: true, Status: site.Status, Message: "Test mode site verified successfully"}, nil
|
||||
}
|
||||
|
||||
// Perform DNS TXT record verification
|
||||
verified, err := uc.dnsVerifier.VerifyDomainOwnership(ctx, site.Domain, site.VerificationToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS verification failed: %w", err)
|
||||
}
|
||||
|
||||
if !verified {
|
||||
return nil, fmt.Errorf("DNS TXT record not found. Please add the verification record to your domain's DNS settings")
|
||||
}
|
||||
|
||||
// DNS verification successful - mark site as verified
|
||||
site.Verify()
|
||||
if err := uc.repo.Update(ctx, site); err != nil {
|
||||
return nil, fmt.Errorf("failed to update site: %w", err)
|
||||
}
|
||||
|
||||
return &VerifySiteOutput{Success: true, Status: site.Status, Message: "Domain ownership verified successfully via DNS TXT record"}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Verify Method
|
||||
|
||||
**File**: `internal/domain/site/site.go:169-175`
|
||||
|
||||
```go
|
||||
// Verify marks the site as verified
|
||||
func (s *Site) Verify() {
|
||||
s.IsVerified = true
|
||||
s.Status = StatusActive
|
||||
s.VerificationToken = "" // Clear token after verification
|
||||
s.UpdatedAt = time.Now()
|
||||
}
|
||||
```
|
||||
|
||||
## 6. What Pending Sites Can Do
|
||||
|
||||
**File**: `internal/interface/http/handler/plugin/status_handler.go`
|
||||
|
||||
### Allowed Operations
|
||||
|
||||
✅ **GET /api/v1/plugin/status** - Check site status and quotas
|
||||
- Returns full site details
|
||||
- Shows `is_verified: false`
|
||||
- Shows `status: "pending"`
|
||||
|
||||
### Blocked Operations
|
||||
|
||||
❌ **POST /api/v1/plugin/sync** - Sync pages to search index
|
||||
- Returns 403 Forbidden
|
||||
- Error: "site is not verified"
|
||||
|
||||
❌ **POST /api/v1/plugin/search** - Perform searches
|
||||
- Blocked for unverified sites
|
||||
|
||||
❌ **DELETE /api/v1/plugin/pages** - Delete pages
|
||||
- Blocked for unverified sites
|
||||
|
||||
## 7. Verification Token Details
|
||||
|
||||
**File**: `internal/usecase/site/generate_verification_token.go`
|
||||
|
||||
### Token Generation
|
||||
|
||||
```go
|
||||
func (uc *GenerateVerificationTokenUseCase) Execute() (string, error) {
|
||||
b := make([]byte, 16) // 16 bytes = 128 bits
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
uc.logger.Error("failed to generate random bytes", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := base64.RawURLEncoding.EncodeToString(b)
|
||||
verificationToken := "mvp_" + token // mvp = maplepress verify
|
||||
|
||||
uc.logger.Info("verification token generated")
|
||||
return verificationToken, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Token Format:**
|
||||
- Prefix: `mvp_` (MaplePress Verify)
|
||||
- Encoding: Base64 URL-safe (no padding)
|
||||
- Strength: 128-bit cryptographic randomness
|
||||
- Example: `mvp_dGhpc2lzYXRlc3Q`
|
||||
|
||||
**Security:**
|
||||
- Never exposed in JSON responses (marked with `json:"-"`)
|
||||
- Stored in database only
|
||||
- Cleared after verification
|
||||
|
||||
## 8. DNS Verification Flow
|
||||
|
||||
### Step-by-Step Process
|
||||
|
||||
1. **User creates site** via dashboard (POST /api/v1/sites)
|
||||
- Backend generates API key and verification token
|
||||
- Site status: `pending`
|
||||
- Response includes DNS setup instructions
|
||||
- User receives: API key (once), verification token, DNS TXT record format
|
||||
|
||||
2. **User adds DNS TXT record** to domain registrar
|
||||
- Logs in to domain registrar (GoDaddy, Namecheap, Cloudflare, etc.)
|
||||
- Navigates to DNS management
|
||||
- Adds TXT record: `maplepress-verify={verification_token}`
|
||||
- Waits 5-10 minutes for DNS propagation (can take up to 48 hours)
|
||||
|
||||
3. **User installs WordPress plugin**
|
||||
- Plugin activation screen shows
|
||||
- User enters API key
|
||||
- Plugin connects to backend
|
||||
|
||||
4. **Plugin checks status** (GET /api/v1/plugin/status)
|
||||
- Backend returns site status: `pending`
|
||||
- Plugin shows "Site not verified" message
|
||||
- Plugin displays DNS instructions if not verified
|
||||
|
||||
5. **User verifies site** (POST /api/v1/sites/{id}/verify)
|
||||
- User clicks "Verify Domain" in plugin or dashboard
|
||||
- No request body needed (empty POST)
|
||||
- Backend performs DNS TXT lookup for domain
|
||||
- Backend checks for record: `maplepress-verify={verification_token}`
|
||||
- If found: Site transitions `pending` → `active`, `IsVerified` set to `true`
|
||||
- If not found: Returns error with DNS troubleshooting instructions
|
||||
|
||||
6. **Plugin can now sync** (POST /api/v1/plugin/sync)
|
||||
- Verification check passes
|
||||
- Pages are synced and indexed
|
||||
- Search functionality enabled
|
||||
|
||||
## 9. Architectural Design Decisions
|
||||
|
||||
### Why Pending Sites Can Access API
|
||||
|
||||
From `site.go:127-140`, the design allows pending sites to:
|
||||
- Check their status
|
||||
- View usage statistics
|
||||
- Prepare for verification
|
||||
|
||||
This is a **deliberate UX decision** to allow:
|
||||
1. Plugin to be activated immediately
|
||||
2. Admin to see connection status
|
||||
3. Admin to complete verification steps
|
||||
4. Smoother onboarding experience
|
||||
|
||||
### Why DNS Verification is Required
|
||||
|
||||
DNS verification prevents:
|
||||
- **Domain squatting**: Claiming domains you don't own
|
||||
- **Abuse**: Indexing content from sites you don't control
|
||||
- **Impersonation**: Pretending to be another site
|
||||
- **Unauthorized access**: Using the service without permission
|
||||
|
||||
DNS TXT record verification is the industry standard because:
|
||||
- **Proves domain control**: Only someone with DNS access can add TXT records
|
||||
- **Widely recognized**: Same method used by Google Search Console, Cloudflare, etc.
|
||||
- **Cannot be spoofed**: Requires actual access to domain registrar
|
||||
- **Automatic verification**: Backend can verify ownership without manual review
|
||||
|
||||
### Test Mode Rationale
|
||||
|
||||
Test mode (`test_sk_` keys) bypasses verification to enable:
|
||||
- Local development without DNS
|
||||
- Integration testing in CI/CD
|
||||
- Staging environments
|
||||
- Development workflows
|
||||
|
||||
## 10. Security Considerations
|
||||
|
||||
### Token Security
|
||||
|
||||
1. **Generation**:
|
||||
- Cryptographically secure random generation
|
||||
- 128-bit entropy (sufficient for this use case)
|
||||
- Base64 URL-safe encoding
|
||||
|
||||
2. **Storage**:
|
||||
- Stored in database as plain text (used in DNS TXT record)
|
||||
- Cleared after successful verification
|
||||
- Only accessible to authenticated tenant
|
||||
|
||||
3. **DNS Verification Security**:
|
||||
- DNS TXT records are public (as intended)
|
||||
- Token is meaningless without backend verification
|
||||
- 10-second timeout on DNS lookups prevents DoS
|
||||
- Token cleared after verification prevents reuse
|
||||
|
||||
### Attack Vectors Mitigated
|
||||
|
||||
1. **Domain Squatting**: DNS verification proves domain ownership
|
||||
2. **Token Guessing**: 128-bit entropy makes brute force infeasible
|
||||
3. **Token Reuse**: Token cleared after successful verification
|
||||
4. **Man-in-the-Middle**: HTTPS required for all API calls
|
||||
5. **DNS Spoofing**: Uses multiple DNS resolvers and validates responses
|
||||
6. **DNS Cache Poisoning**: 10-second timeout limits attack window
|
||||
|
||||
## 11. API Documentation
|
||||
|
||||
See individual endpoint documentation:
|
||||
- [Create Site](./API/create-site.md) - Initial site creation
|
||||
- [Verify Site](./API/verify-site.md) - Site verification endpoint
|
||||
- [Plugin Status](./API/plugin-verify-api-key.md) - Check verification status
|
||||
- [Sync Pages](./API/plugin-sync-pages.md) - Requires verification
|
||||
|
||||
## 12. WordPress Plugin Integration
|
||||
|
||||
The WordPress plugin should:
|
||||
|
||||
1. **On Activation**:
|
||||
- Prompt user for API key
|
||||
- Connect to backend
|
||||
- Check verification status
|
||||
|
||||
2. **If Not Verified**:
|
||||
- Display DNS TXT record instructions
|
||||
- Show the exact TXT record to add: `maplepress-verify={token}`
|
||||
- Provide domain registrar examples (GoDaddy, Namecheap, Cloudflare)
|
||||
- Explain DNS propagation timing (5-10 minutes)
|
||||
- Provide "Verify Domain" button
|
||||
- Disable sync/search features
|
||||
|
||||
3. **Verification Process**:
|
||||
- User clicks "Verify Domain"
|
||||
- Plugin calls POST /api/v1/sites/{id}/verify (no body)
|
||||
- Backend performs DNS TXT lookup
|
||||
- If successful: Enable all features
|
||||
- If failed: Show specific DNS error (record not found, timeout, etc.)
|
||||
|
||||
4. **After Verification**:
|
||||
- Enable all features
|
||||
- Allow page synchronization
|
||||
- Enable search functionality
|
||||
- Hide verification prompts
|
||||
|
||||
5. **Error Handling**:
|
||||
- Handle 403 "site is not verified" gracefully
|
||||
- Guide user to DNS verification process
|
||||
- Show DNS troubleshooting tips (check propagation, verify record format)
|
||||
- Retry verification status check
|
||||
|
||||
## 13. Database Schema
|
||||
|
||||
### Site Table Fields
|
||||
|
||||
```
|
||||
sites_by_id:
|
||||
- id (UUID, primary key)
|
||||
- tenant_id (UUID)
|
||||
- status (text: pending|active|inactive|suspended|archived)
|
||||
- is_verified (boolean)
|
||||
- verification_token (text, sensitive)
|
||||
- ...
|
||||
```
|
||||
|
||||
### Indexes Required
|
||||
|
||||
No special indexes needed for DNS verification - uses existing site_id and tenant_id lookups.
|
||||
|
||||
## 14. Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **DNS TXT record not found**:
|
||||
- Check DNS propagation status (use dig or nslookup)
|
||||
- Verify record format: `maplepress-verify={exact_token}`
|
||||
- Wait 5-10 minutes for DNS propagation
|
||||
- Check that TXT record was added to correct domain/subdomain
|
||||
- Verify no typos in the verification token
|
||||
|
||||
2. **DNS lookup timeout**:
|
||||
- Check domain's DNS servers are responding
|
||||
- Verify domain is properly registered
|
||||
- Check for DNS configuration issues
|
||||
- Try again after DNS stabilizes
|
||||
|
||||
3. **Site stuck in pending**:
|
||||
- Verify DNS TXT record is correctly set
|
||||
- Call verification endpoint: POST /api/v1/sites/{id}/verify
|
||||
- Check logs for DNS lookup errors
|
||||
- Use DNS checking tools (dig, nslookup) to verify record
|
||||
|
||||
4. **Test mode not working**:
|
||||
- Verify API key starts with `test_sk_`
|
||||
- Check `IsTestMode()` logic in site.go:115-125
|
||||
- Test mode sites skip DNS verification entirely
|
||||
|
||||
5. **DNS verification fails**:
|
||||
- Token may have been cleared (already verified)
|
||||
- DNS record format incorrect
|
||||
- Wrong domain or subdomain
|
||||
- Check error logs for specific DNS errors
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check DNS TXT record manually
|
||||
dig TXT example.com
|
||||
nslookup -type=TXT example.com
|
||||
|
||||
# Check site status
|
||||
curl -X GET http://localhost:8000/api/v1/sites/{id} \
|
||||
-H "Authorization: JWT {token}"
|
||||
|
||||
# Verify site via DNS
|
||||
curl -X POST http://localhost:8000/api/v1/sites/{id}/verify \
|
||||
-H "Authorization: JWT {token}"
|
||||
```
|
||||
|
||||
## 15. Future Enhancements
|
||||
|
||||
Potential improvements to the verification system:
|
||||
|
||||
1. **Token Expiration**:
|
||||
- Add 24-48 hour expiration for verification tokens
|
||||
- Allow token regeneration
|
||||
- Email token to site admin
|
||||
|
||||
2. **Alternative Verification Methods**:
|
||||
- Meta tag verification (alternative to DNS)
|
||||
- File upload verification (.well-known/maplepress-verify.txt)
|
||||
- WordPress plugin automatic verification (callback endpoint)
|
||||
|
||||
3. **Automatic Re-verification**:
|
||||
- Periodic DNS checks to ensure domain ownership hasn't changed
|
||||
- Alert if DNS record is removed
|
||||
- Grace period before disabling site
|
||||
|
||||
4. **Verification Audit Log**:
|
||||
- Track when site was verified
|
||||
- Record who performed verification
|
||||
- Log IP address and timestamp
|
||||
- DNS lookup results and timing
|
||||
Loading…
Add table
Add a link
Reference in a new issue