Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# GPTKit Configuration
# Copy this file to .env and set your values
# DO NOT commit .env to version control!

# Bearer token for API authentication (required in production)
GPTKIT_BEARER_TOKEN=your-secret-token-here

# Disable authentication in development (set to 1, true, or yes)
# GPTKIT_DISABLE_AUTH=1
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@

GPTKit is a unified backend designed to provide tools via HTTP Actions for Custom GPTs.

## Authentication

All endpoints require Bearer token authentication. The `GPTKIT_BEARER_TOKEN` environment variable **must** be set for the API to function (unless disabled in development mode).

### Usage

When calling the API, include the Bearer token in the `Authorization` header:

```bash
curl -H "Authorization: Bearer your-token-here" \
"https://gptkit.guillaumeduveau.com/domain/whois?domain=example.com"
```

### Configuration

#### Production (Docker)

Use a `.env` file with Docker Compose (see [Deployment](#deployment) section):

```bash
# .env
GPTKIT_BEARER_TOKEN=your-secret-token-here
```

#### Local Development

For local development, you can disable authentication:

```bash
export GPTKIT_DISABLE_AUTH=1
uvicorn app.main:app --reload
```

Or set the token normally:

```bash
export GPTKIT_BEARER_TOKEN="your-secret-token-here"
uvicorn app.main:app --reload
```

## Tools

### WHOIS (`/domain/whois`)
Expand All @@ -11,7 +51,7 @@ Allows checking domain name availability and retrieving WHOIS information.
- **Endpoint**: `GET /domain/whois`
- **Parameters**:
- `domain` (required): The domain name to check (e.g., `google.com`).
- `force` (optional): `1` to force a fresh WHOIS lookup (ignores cache).
- `refresh` (optional): `1` to force a fresh WHOIS lookup (ignores cache).
- **Features**:
- Persistent cache (SQLite).
- Rate limiting (global and per domain).
Expand All @@ -32,6 +72,8 @@ services:
restart: unless-stopped
ports:
- "8000:8000"
environment:
- GPTKIT_BEARER_TOKEN=${GPTKIT_BEARER_TOKEN}
volumes:
# Data persistence (WHOIS cache stored in /app/data/whois_cache.db)
- gptkit_data:/app/data
Expand All @@ -40,6 +82,17 @@ volumes:
gptkit_data:
```

Create a `.env` file in the same directory as `docker-compose.yml` (see `.env.example` for reference):

```bash
# .env (do not commit this file!)
GPTKIT_BEARER_TOKEN=your-secret-token-here
```

Docker Compose will automatically load variables from the `.env` file or from the host environment.

> **Security**: Never commit the `.env` file to version control. It's already in `.gitignore`. Copy `.env.example` to `.env` and set your values.

## Development

1. **Installation**:
Expand All @@ -55,7 +108,12 @@ volumes:

- Quick API smoke test (curl):
```bash
# Without authentication (if GPTKIT_BEARER_TOKEN is not set)
curl "http://localhost:8000/domain/whois?domain=example.com"

# With authentication
curl -H "Authorization: Bearer your-token-here" \
"http://localhost:8000/domain/whois?domain=example.com"
```

- Run the unit test suite with pytest (from the project root):
Expand Down
57 changes: 57 additions & 0 deletions app/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from fastapi import Security, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import Optional
import os
import logging

logger = logging.getLogger(__name__)

# HTTPBearer scheme for extracting Bearer token
security = HTTPBearer(auto_error=False)

def get_bearer_token() -> Optional[str]:
"""Get the expected Bearer token from environment variable."""
# Allow disabling auth in local/dev mode
if os.getenv("GPTKIT_DISABLE_AUTH", "").lower() in ("1", "true", "yes"):
logger.warning("Authentication is DISABLED (GPTKIT_DISABLE_AUTH is set). Not recommended for production!")
return None

token = os.getenv("GPTKIT_BEARER_TOKEN")
if not token:
raise ValueError(
"GPTKIT_BEARER_TOKEN environment variable must be set. "
"Authentication is required for all endpoints. "
"Set GPTKIT_DISABLE_AUTH=1 to disable auth in development."
)
return token

def verify_token(credentials: Optional[HTTPAuthorizationCredentials] = Security(security)) -> Optional[str]:
"""
Verify the Bearer token from the Authorization header.

Raises HTTPException if token is invalid or missing (unless auth is disabled).
Returns the token if valid, or None if authentication is disabled.
"""
expected_token = get_bearer_token()

# If auth is disabled, allow access
if expected_token is None:
return None

if not credentials:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing Authorization header",
headers={"WWW-Authenticate": "Bearer"},
)

if credentials.credentials != expected_token:
logger.warning(f"Invalid token attempt from client")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)

return credentials.credentials

37 changes: 35 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from fastapi import FastAPI
from fastapi import FastAPI, Depends
from app.routers import domain
from app.auth import verify_token
import logging
import logging.handlers
import os
Expand All @@ -25,8 +26,40 @@
version="1.0.0"
)

# Add security scheme to OpenAPI
from fastapi.openapi.utils import get_openapi

def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title=app.title,
version=app.version,
description=app.description,
routes=app.routes,
)
# Add security scheme
openapi_schema["components"]["securitySchemes"] = {
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Bearer token authentication. Set GPTKIT_BEARER_TOKEN environment variable."
}
}
# Apply security to all endpoints
for path in openapi_schema["paths"].values():
for method in path.values():
if isinstance(method, dict) and "security" not in method:
method["security"] = [{"BearerAuth": []}]

app.openapi_schema = openapi_schema
return app.openapi_schema

app.openapi = custom_openapi

app.include_router(domain.router)

@app.get("/")
@app.get("/", dependencies=[Depends(verify_token)])
async def root():
return {"message": "GPTKit is running"}
Loading