573 lines
20 KiB
Text
573 lines
20 KiB
Text
# Configurable Mock API with Admin Interface and OAuth2 Provider
|
||
|
||
A lightweight, configurable mock API application in Python that allows dynamic endpoint management via an admin interface. The API serves customizable responses stored in a SQLite database with template variable support. Includes a full OAuth2 provider for securing endpoints with token-based authentication.
|
||
|
||
## Features
|
||
|
||
- **Dynamic Endpoint Configuration**: Create, read, update, and delete API endpoints through a web-based admin interface.
|
||
- **Template Variable Support**: Response bodies can include Jinja2 template variables (e.g., `{{ user_id }}`, `{{ timestamp }}`) populated from path parameters, query strings, headers, request body, system variables, and endpoint defaults.
|
||
- **Dynamic Route Registration**: Endpoints are registered/unregistered at runtime without restarting the server.
|
||
- **Admin Interface**: Secure web UI with session-based authentication for managing endpoints.
|
||
- **OAuth2 Provider**: Full OAuth2 implementation supporting authorization code, client credentials, and refresh token grant types.
|
||
- **Endpoint‑Level OAuth Protection**: Individual endpoints can require OAuth2 tokens with configurable scopes.
|
||
- **Admin OAuth2 Management**: Web UI for managing OAuth clients, tokens, and users.
|
||
- **OpenID Connect Discovery**: Standards‑compliant discovery endpoint.
|
||
- **Production Ready**: Uses Waitress WSGI server, SQLAlchemy async, and FastAPI with proper error handling and security measures.
|
||
|
||
## Technology Stack
|
||
|
||
- **Framework**: FastAPI (with automatic OpenAPI documentation)
|
||
- **Server**: Waitress (production WSGI server)
|
||
- **Database**: SQLite with SQLAlchemy 2.0 async ORM
|
||
- **Templating**: Jinja2 with sandboxed environment
|
||
- **Authentication**: Session‑based admin authentication with bcrypt password hashing
|
||
- **OAuth2**: JWT‑based tokens with configurable scopes, client validation, and token revocation
|
||
- **Frontend**: Bootstrap 5 (CDN) for admin UI
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
mockapi/
|
||
├── app.py # FastAPI application factory & lifespan
|
||
├── config.py # Configuration (Pydantic Settings)
|
||
├── database.py # SQLAlchemy async database setup
|
||
├── dependencies.py # FastAPI dependencies
|
||
├── example_usage.py # Integration test & demonstration script
|
||
├── middleware/
|
||
│ └── auth_middleware.py # Admin authentication middleware
|
||
├── models/
|
||
│ ├── endpoint_model.py # Endpoint SQLAlchemy model
|
||
│ └── oauth_models.py # OAuth2 client, token, and user models
|
||
├── observers/
|
||
│ └── __init__.py # Observer pattern placeholder
|
||
├── repositories/
|
||
│ ├── endpoint_repository.py # Repository pattern for endpoints
|
||
│ └── oauth2/ # OAuth2 repositories
|
||
├── run.py # Development runner script (with auto-reload)
|
||
├── services/
|
||
│ ├── route_service.py # Dynamic route registration/management
|
||
│ └── template_service.py # Jinja2 template rendering
|
||
├── controllers/
|
||
│ ├── admin_controller.py # Admin UI routes
|
||
│ └── oauth2/ # OAuth2 controllers and services
|
||
├── schemas/
|
||
│ ├── endpoint_schema.py # Pydantic schemas for validation
|
||
│ └── oauth2/ # OAuth2 schemas
|
||
├── templates/ # Jinja2 HTML templates
|
||
│ ├── base.html # Base layout
|
||
│ └── admin/
|
||
│ ├── login.html # Login page
|
||
│ ├── dashboard.html # Admin dashboard
|
||
│ ├── endpoints.html # Endpoint list
|
||
│ ├── endpoint_form.html # Create/edit endpoint
|
||
│ └── oauth/ # OAuth2 management pages
|
||
├── static/
|
||
│ └── css/ # Static CSS (optional)
|
||
├── tests/ # Test suite
|
||
│ ├── test_admin.py # Admin authentication tests
|
||
│ ├── test_endpoint_repository.py
|
||
│ ├── test_route_manager_fix.py
|
||
│ ├── test_oauth2_controller.py
|
||
│ └── integration/ # Integration tests
|
||
├── utils/ # Utility modules
|
||
│ └── __init__.py
|
||
├── requirements.txt # Python dependencies
|
||
├── .env.example # Example environment variables
|
||
├── .env # Local environment variables (create from .env.example)
|
||
├── run_example.sh # Script to run the integration test
|
||
├── LICENSE # MIT License
|
||
└── README.md # This file
|
||
```
|
||
|
||
## Installation
|
||
|
||
1. **Navigate to project directory**:
|
||
```bash
|
||
cd ~/GitLab/customer-engineering/mockapi
|
||
```
|
||
|
||
2. **Create a virtual environment** (recommended):
|
||
```bash
|
||
python3 -m venv venv
|
||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||
```
|
||
|
||
3. **Install dependencies**:
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
4. **Configure environment variables**:
|
||
```bash
|
||
cp .env.example .env
|
||
# Edit .env with your settings
|
||
```
|
||
|
||
Example `.env`:
|
||
```ini
|
||
DATABASE_URL=sqlite+aiosqlite:///./mockapi.db
|
||
ADMIN_USERNAME=admin
|
||
ADMIN_PASSWORD=admin123 # Change this in production!
|
||
SECRET_KEY=your-secret-key-here # Change this!
|
||
DEBUG=True # Set to False in production
|
||
```
|
||
|
||
5. **Initialize the database** (tables are created automatically on first run).
|
||
|
||
## Running the Application
|
||
|
||
### Development (with auto‑reload)
|
||
|
||
Make sure your virtual environment is activated:
|
||
|
||
```bash
|
||
source venv/bin/activate # Linux/macOS
|
||
# venv\Scripts\activate # Windows
|
||
```
|
||
|
||
Then run with auto-reload for development:
|
||
|
||
```bash
|
||
# Using run.py (convenience script)
|
||
python run.py
|
||
|
||
# Or directly with uvicorn
|
||
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||
```
|
||
|
||
### Production (with Waitress)
|
||
|
||
For production deployment, use Waitress WSGI server with the provided WSGI adapter (a2wsgi):
|
||
|
||
```bash
|
||
waitress-serve --host=0.0.0.0 --port=8000 --threads=4 wsgi:wsgi_app
|
||
```
|
||
|
||
The server will start on `http://localhost:8000` (or your configured host/port).
|
||
|
||
**Note:** Waitress is a WSGI server, but FastAPI is an ASGI framework. The `wsgi.py` file uses `a2wsgi` to wrap the ASGI application into a WSGI-compatible interface. Routes are automatically refreshed from the database on server startup.
|
||
|
||
## Quick Start with cURL Examples
|
||
|
||
### 1. Create a Mock Endpoint via Admin API
|
||
|
||
First, log in to the admin interface (default credentials: `admin` / `admin123`):
|
||
|
||
```bash
|
||
# Simulate login and create session (use browser for UI, but you can also use curl)
|
||
curl -c cookies.txt -X POST http://localhost:8000/admin/login \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "username=admin&password=admin123"
|
||
```
|
||
|
||
Then create a mock endpoint:
|
||
|
||
```bash
|
||
curl -b cookies.txt -X POST http://localhost:8000/admin/endpoints \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "route=/api/greeting/{name}&method=GET&response_body={\"message\": \"Hello, {{ name }}!\"}&response_code=200&content_type=application/json&is_active=true"
|
||
```
|
||
|
||
### 2. Call the Mock Endpoint
|
||
|
||
```bash
|
||
curl http://localhost:8000/api/greeting/World
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{ "message": "Hello, World!" }
|
||
```
|
||
|
||
### 3. Use Template Variables
|
||
|
||
Create an endpoint that uses multiple variable sources:
|
||
|
||
```bash
|
||
curl -b cookies.txt -X POST http://localhost:8000/admin/endpoints \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "route=/api/user/{id}&method=GET&response_body={\"id\": {{ id }}, \"name\": \"{{ query.name }}\", \"timestamp\": \"{{ timestamp }}\"}&response_code=200&content_type=application/json&is_active=true"
|
||
```
|
||
|
||
Then call it with query parameters:
|
||
|
||
```bash
|
||
curl "http://localhost:8000/api/user/123?name=John"
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{ "id": 123, "name": "John", "timestamp": "2026-03-16T06:14:12.345678" }
|
||
```
|
||
|
||
### 4. OAuth2 Client Credentials Flow
|
||
|
||
First, create an OAuth client via the admin UI or using the admin API. Then obtain a token:
|
||
|
||
```bash
|
||
# Get an access token using client credentials
|
||
curl -X POST http://localhost:8000/oauth/token \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "grant_type=client_credentials&client_id=your_client_id&client_secret=your_client_secret&scope=api:read"
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||
"token_type": "Bearer",
|
||
"expires_in": 1800,
|
||
"scope": "api:read"
|
||
}
|
||
```
|
||
|
||
### 5. Protect an Endpoint with OAuth2
|
||
|
||
Create an endpoint that requires OAuth2:
|
||
|
||
```bash
|
||
curl -b cookies.txt -X POST http://localhost:8000/admin/endpoints \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "route=/api/protected&method=GET&response_body={\"status\": \"authorized\"}&response_code=200&content_type=application/json&is_active=true&requires_oauth=true&oauth_scopes=[\"api:read\"]"
|
||
```
|
||
|
||
Call it with a valid token:
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
|
||
http://localhost:8000/api/protected
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{ "status": "authorized" }
|
||
```
|
||
> **Note**: The `requires_oauth` and `oauth_scopes` fields are not yet exposed in the admin UI. To set OAuth protection, update the endpoint directly in the database or use the repository API.
|
||
|
||
### 6. OAuth2 Authorization Code Flow
|
||
|
||
For interactive applications:
|
||
|
||
1. **Authorization request** (user redirects to):
|
||
```
|
||
http://localhost:8000/oauth/authorize?response_type=code&client_id=your_client_id&redirect_uri=http://localhost:8080/callback&scope=api:read&state=xyz123
|
||
```
|
||
|
||
2. **Exchange code for token**:
|
||
```bash
|
||
curl -X POST http://localhost:8000/oauth/token \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "grant_type=authorization_code&code=AUTH_CODE_HERE&redirect_uri=http://localhost:8080/callback&client_id=your_client_id&client_secret=your_client_secret"
|
||
```
|
||
|
||
### 7. OAuth2 Token Introspection
|
||
|
||
```bash
|
||
curl -u client_id:client_secret -X POST http://localhost:8000/oauth/introspect \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
|
||
```
|
||
|
||
### 8. OpenID Connect Discovery
|
||
|
||
```bash
|
||
curl http://localhost:8000/.well-known/openid-configuration
|
||
```
|
||
|
||
## OAuth2 Authentication
|
||
|
||
The application includes a full OAuth2 provider implementing RFC 6749 and OpenID Connect Discovery.
|
||
|
||
### Supported Grant Types
|
||
|
||
- **Authorization Code**: For web applications with server‑side components.
|
||
- **Client Credentials**: For machine‑to‑machine communication.
|
||
- **Refresh Token**: To obtain new access tokens without user interaction.
|
||
|
||
### Endpoints
|
||
|
||
| Endpoint | Method | Description |
|
||
|----------|--------|-------------|
|
||
| `/oauth/authorize` | GET, POST | Authorization endpoint (interactive user consent) |
|
||
| `/oauth/token` | POST | Token issuance and refresh |
|
||
| `/oauth/userinfo` | GET | OpenID Connect user claims |
|
||
| `/oauth/introspect` | POST | Token introspection (RFC 7662) |
|
||
| `/oauth/revoke` | POST | Token revocation (RFC 7009) |
|
||
| `/.well‑known/openid‑configuration` | GET | OpenID Connect discovery document |
|
||
|
||
### Scope Management
|
||
|
||
Default scopes include:
|
||
- `openid` – OpenID Connect support
|
||
- `profile` – Basic profile information
|
||
- `email` – Email address claim
|
||
- `api:read` – Read access to protected endpoints
|
||
- `api:write` – Write access to protected endpoints
|
||
|
||
### Admin OAuth2 Management
|
||
|
||
Access OAuth2 management via the admin interface:
|
||
|
||
- **Clients**: `http://localhost:8000/admin/oauth/clients` – Register and manage OAuth clients
|
||
- **Tokens**: `http://localhost:8000/admin/oauth/tokens` – View and revoke issued tokens
|
||
- **Users**: `http://localhost:8000/admin/oauth/users` – Manage OAuth user records
|
||
|
||
## Production Deployment Considerations
|
||
|
||
### 1. **Environment Configuration**
|
||
- Set `DEBUG=False` in production
|
||
- Use strong, unique values for `ADMIN_PASSWORD` and `SECRET_KEY`
|
||
- Consider using a more robust database (PostgreSQL) by changing `DATABASE_URL`
|
||
- Store sensitive values in environment variables or a secrets manager
|
||
|
||
### 2. **Process Management**
|
||
Use a process manager like systemd (Linux) or Supervisor to keep the application running:
|
||
|
||
**Example systemd service (`/etc/systemd/system/mockapi.service`)**:
|
||
```ini
|
||
[Unit]
|
||
Description=Mock API Service
|
||
After=network.target
|
||
|
||
[Service]
|
||
User=www-data
|
||
Group=www-data
|
||
WorkingDirectory=/path/to/mockapi
|
||
Environment="PATH=/path/to/mockapi/venv/bin"
|
||
ExecStart=/path/to/mockapi/venv/bin/waitress-serve --host=0.0.0.0 --port=8000 wsgi:wsgi_app
|
||
Restart=always
|
||
RestartSec=10
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
### 3. **Reverse Proxy (Recommended)**
|
||
Use Nginx or Apache as a reverse proxy for SSL termination, load balancing, and static file serving:
|
||
|
||
**Example Nginx configuration**:
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.yourdomain.com;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8000;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. **Database Backups**
|
||
For SQLite, regularly backup the `mockapi.db` file. For production, consider migrating to PostgreSQL.
|
||
|
||
## Usage
|
||
|
||
### 1. Access the Admin Interface
|
||
- Open `http://localhost:8000/admin/login`
|
||
- Log in with the credentials set in `.env` (default: `admin` / `admin123`)
|
||
|
||
### 2. Create a Mock Endpoint
|
||
1. Navigate to **Endpoints** → **Create New**.
|
||
2. Fill in the form:
|
||
- **Route**: `/api/greeting/{name}` (supports path parameters)
|
||
- **Method**: GET
|
||
- **Response Body**: `{ "message": "Hello, {{ name }}!" }`
|
||
- **Response Code**: 200
|
||
- **Content-Type**: `application/json`
|
||
- **Variables**: `{ "server": "mock-api" }` (optional defaults)
|
||
3. Click **Create**.
|
||
|
||
### 3. Call the Mock Endpoint
|
||
```bash
|
||
curl http://localhost:8000/api/greeting/World
|
||
```
|
||
Response:
|
||
```json
|
||
{ "message": "Hello, World!" }
|
||
```
|
||
|
||
### 4. Template Variables
|
||
The following variable sources are available in response templates:
|
||
|
||
| Source | Example variable | Usage in template |
|
||
|--------|------------------|-------------------|
|
||
| Path parameters | `{{ name }}` | `/users/{id}` → `{{ id }}` |
|
||
| Query parameters | `{{ query.page }}` | `?page=1` → `{{ page }}` |
|
||
| Request headers | `{{ header.authorization }}` | `Authorization: Bearer token` |
|
||
| Request body | `{{ body.user.email }}` | JSON request body |
|
||
| System variables | `{{ timestamp }}`, `{{ request_id }}` | Automatically injected |
|
||
| Endpoint defaults | `{{ server }}` | Defined in endpoint variables |
|
||
|
||
### 5. Admin Functions
|
||
- **List endpoints** with pagination and filtering
|
||
- **Edit** existing endpoints (changes take effect immediately)
|
||
- **Activate/deactivate** endpoints without deletion
|
||
- **Delete** endpoints (removes route)
|
||
- **Dashboard** with statistics (total endpoints, active routes, etc.)
|
||
- **OAuth2 management** – clients, tokens, users
|
||
|
||
## Security Considerations
|
||
|
||
- **Admin authentication**: Uses bcrypt password hashing. Store a strong password hash in production.
|
||
- **Session management**: Signed cookies with configurable secret key.
|
||
- **Template sandboxing**: Jinja2 environment restricted with `SandboxedEnvironment` and `StrictUndefined`.
|
||
- **Request size limits**: Maximum body size of 1MB to prevent DoS.
|
||
- **Route validation**: Prevents path traversal (`..`) and other unsafe patterns.
|
||
- **SQL injection protection**: All queries use SQLAlchemy ORM.
|
||
- **OAuth2 security**: Client secret hashing, token revocation, scope validation, secure token storage.
|
||
|
||
## Configuration Options
|
||
|
||
See `config.py` for all available settings. Key environment variables:
|
||
|
||
| Variable | Default | Description |
|
||
|----------|---------|-------------|
|
||
| `DATABASE_URL` | `sqlite+aiosqlite:///./mockapi.db` | SQLAlchemy database URL |
|
||
| `ADMIN_USERNAME` | `admin` | Admin login username |
|
||
| `ADMIN_PASSWORD` | `admin123` | Admin login password (plaintext) |
|
||
| `SECRET_KEY` | `your‑secret‑key‑here‑change‑me` | Session signing secret |
|
||
| `DEBUG` | `False` | Enable debug mode (more logging, relaxed validation) |
|
||
| `OAUTH2_ISSUER` | `http://localhost:8000` | OAuth2 issuer URL for discovery |
|
||
| `OAUTH2_ACCESS_TOKEN_EXPIRE_MINUTES` | `30` | Access token lifetime |
|
||
| `OAUTH2_REFRESH_TOKEN_EXPIRE_DAYS` | `7` | Refresh token lifetime |
|
||
| `OAUTH2_SUPPORTED_SCOPES` | `["openid","profile","email","api:read","api:write"]` | Available OAuth2 scopes |
|
||
|
||
**Warning**: In production (`DEBUG=False`), the default `ADMIN_PASSWORD` and `SECRET_KEY` will cause validation errors. You must set unique values via environment variables.
|
||
|
||
## API Documentation
|
||
|
||
FastAPI automatically provides OpenAPI documentation at:
|
||
- Swagger UI: `http://localhost:8000/docs`
|
||
- ReDoc: `http://localhost:8000/redoc`
|
||
|
||
The root URL (/) automatically redirects to the Swagger documentation at /docs.
|
||
|
||
The dynamic mock endpoints are not listed in the OpenAPI schema (they are registered at runtime).
|
||
|
||
## Development & Testing
|
||
|
||
## API Testing with Bruno
|
||
|
||
A ready-to-use [Bruno](https://www.usebruno.com/) API collection is available in the `examples/` directory:
|
||
|
||
```bash
|
||
# Set up test OAuth client and view instructions
|
||
./examples/setup.sh
|
||
|
||
# Or import directly:
|
||
# examples/mockapi-collection.bru
|
||
```
|
||
|
||
The collection includes:
|
||
- Global variables for base URL and credentials
|
||
- Pre-configured requests for all endpoints
|
||
- OAuth2 flow examples (client credentials, authorization code)
|
||
- Admin authentication
|
||
- Mock endpoint creation and testing
|
||
- Protected endpoint examples
|
||
|
||
See [examples/README.md](examples/README.md) for detailed usage instructions.
|
||
|
||
### Running Tests
|
||
|
||
Run tests with pytest:
|
||
```bash
|
||
pytest tests/
|
||
```
|
||
|
||
The test suite includes:
|
||
- Unit tests for repository and service layers
|
||
- Integration tests for admin authentication
|
||
- Template rendering tests
|
||
- OAuth2 unit and integration tests (21+ tests)
|
||
|
||
### Example Integration Test
|
||
|
||
A ready‑to‑run integration test demonstrates the core functionality:
|
||
|
||
```bash
|
||
# Make the script executable (Linux/macOS)
|
||
chmod +x run_example.sh
|
||
|
||
# Run the example
|
||
./run_example.sh
|
||
```
|
||
|
||
Or directly with Python:
|
||
```bash
|
||
python example_usage.py
|
||
```
|
||
|
||
The example script will:
|
||
1. Start the FastAPI app (via TestClient)
|
||
2. Log in as admin
|
||
3. Create a mock endpoint with template variables
|
||
4. Call the endpoint and verify the response
|
||
5. Report success or failure
|
||
|
||
This is a great way to verify that the API is working correctly after installation.
|
||
|
||
## Troubleshooting
|
||
|
||
### Common Issues
|
||
|
||
1. **"no such table: endpoints" error**
|
||
- The database hasn't been initialized
|
||
- Restart the application - tables are created on first startup
|
||
- Or run `python -c "from database import init_db; import asyncio; asyncio.run(init_db())"`
|
||
|
||
2. **Login fails even with correct credentials**
|
||
- Check that `DEBUG=True` is set in `.env` (or provide unique credentials)
|
||
- The default credentials only work when `DEBUG=True`
|
||
- In production, you must set unique `ADMIN_PASSWORD` and `SECRET_KEY`
|
||
|
||
3. **Routes not being registered**
|
||
- Check that the endpoint is marked as active (`is_active=True`)
|
||
- Refresh the page - routes are registered immediately after creation
|
||
- Check application logs for errors
|
||
|
||
4. **Template variables not rendering**
|
||
- Ensure you're using double curly braces: `{{ variable }}`
|
||
- Check variable names match the context (use path_, query_, header_ prefixes as needed)
|
||
- View the rendered template in the admin edit form preview
|
||
|
||
5. **OAuth2 token validation fails**
|
||
- Verify the token hasn't expired
|
||
- Check that the client is active
|
||
- Confirm the token has required scopes for the endpoint
|
||
- Ensure the token hasn't been revoked
|
||
|
||
### Logging
|
||
Enable debug logging by setting `DEBUG=True` in `.env`. Check the console output for detailed error messages.
|
||
|
||
## Limitations & Future Enhancements
|
||
|
||
- **Current limitations**:
|
||
- SQLite only (but can be extended to PostgreSQL via `DATABASE_URL`)
|
||
- Single admin user (no multi‑user support)
|
||
- No request logging/history
|
||
- OAuth2 user authentication uses placeholder user IDs (integration with external identity providers pending)
|
||
|
||
- **Possible extensions**:
|
||
- Import/export endpoints as JSON/YAML
|
||
- Request logging and analytics
|
||
- WebSocket notifications for admin actions
|
||
- Multiple admin users with roles
|
||
- Rate limiting per endpoint
|
||
- CORS configuration
|
||
- PKCE support for public OAuth2 clients
|
||
- Integration with external identity providers (SAML, LDAP)
|
||
|
||
## License
|
||
|
||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||
|
||
## Acknowledgments
|
||
|
||
- Built with [FastAPI](https://fastapi.tiangolo.com/), [SQLAlchemy](https://www.sqlalchemy.org/), and [Jinja2](https://jinja.palletsprojects.com/).
|
||
- Admin UI uses [Bootstrap 5](https://getbootstrap.com/) via CDN.
|
||
- OAuth2 implementation follows RFC 6749, RFC 7662, and OpenID Connect standards.
|