feat: update configuration and README for improved authentication and database support
This commit is contained in:
62
.env.example
62
.env.example
@@ -2,82 +2,64 @@
|
|||||||
# K-Template Configuration
|
# K-Template Configuration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Copy this file to .env and adjust values for your environment.
|
# Copy this file to .env and adjust values for your environment.
|
||||||
# All values shown are defaults or examples.
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Server Configuration
|
# Server
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
HOST=127.0.0.1
|
HOST=127.0.0.1
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Database Configuration
|
# Database
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SQLite (default)
|
# SQLite (default)
|
||||||
DATABASE_URL=sqlite:data.db?mode=rwc
|
DATABASE_URL=sqlite:data.db?mode=rwc
|
||||||
|
|
||||||
# PostgreSQL (alternative - requires postgres feature)
|
# PostgreSQL (requires postgres feature flag)
|
||||||
# DATABASE_URL=postgres://user:password@localhost:5432/mydb
|
# DATABASE_URL=postgres://user:password@localhost:5432/mydb
|
||||||
|
|
||||||
# Connection pool settings
|
|
||||||
DB_MAX_CONNECTIONS=5
|
DB_MAX_CONNECTIONS=5
|
||||||
DB_MIN_CONNECTIONS=1
|
DB_MIN_CONNECTIONS=1
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Authentication Mode
|
# Cookie Secret
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Options: session, jwt, both
|
# Used to encrypt the OIDC state cookie (CSRF token, PKCE verifier, nonce).
|
||||||
# - session: Cookie-based sessions (requires auth-axum-login feature)
|
# Must be at least 64 characters in production.
|
||||||
# - jwt: Bearer token authentication (requires auth-jwt feature)
|
COOKIE_SECRET=your-cookie-secret-key-must-be-at-least-64-characters-long-for-security!!
|
||||||
# - both: Support both methods (try JWT first, fall back to session)
|
|
||||||
AUTH_MODE=jwt
|
|
||||||
|
|
||||||
# ============================================================================
|
# Set to true when serving over HTTPS
|
||||||
# Session Configuration (for session/both modes)
|
|
||||||
# ============================================================================
|
|
||||||
# Must be at least 64 characters in production
|
|
||||||
SESSION_SECRET=your-super-secret-key-must-be-at-least-64-characters-long-for-security
|
|
||||||
|
|
||||||
# Set to true in production for HTTPS-only cookies
|
|
||||||
SECURE_COOKIE=false
|
SECURE_COOKIE=false
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# JWT Configuration (for jwt/both modes)
|
# JWT
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Must be at least 32 characters in production
|
# Must be at least 32 characters in production.
|
||||||
JWT_SECRET=your-jwt-secret-key-at-least-32-chars
|
JWT_SECRET=your-jwt-secret-key-at-least-32-chars
|
||||||
|
|
||||||
# Optional: JWT issuer and audience for token validation
|
# Optional: embed issuer/audience claims in tokens
|
||||||
JWT_ISSUER=your-app-name
|
# JWT_ISSUER=your-app-name
|
||||||
JWT_AUDIENCE=your-app-audience
|
# JWT_AUDIENCE=your-app-audience
|
||||||
|
|
||||||
# Token expiry in hours (default: 24)
|
# Token lifetime in hours (default: 24)
|
||||||
JWT_EXPIRY_HOURS=24
|
JWT_EXPIRY_HOURS=24
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# OIDC Configuration (optional - requires auth-oidc feature)
|
# OIDC (optional — requires auth-oidc feature flag)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Your OIDC provider's issuer URL (e.g., Keycloak, Auth0, Zitadel)
|
# OIDC_ISSUER=https://your-oidc-provider.com
|
||||||
OIDC_ISSUER=https://your-oidc-provider.com
|
# OIDC_CLIENT_ID=your-client-id
|
||||||
|
# OIDC_CLIENT_SECRET=your-client-secret
|
||||||
# Client credentials from your OIDC provider
|
# OIDC_REDIRECT_URL=http://localhost:3000/api/v1/auth/callback
|
||||||
OIDC_CLIENT_ID=your-client-id
|
# OIDC_RESOURCE_ID=your-resource-id # optional audience claim to verify
|
||||||
OIDC_CLIENT_SECRET=your-client-secret
|
|
||||||
|
|
||||||
# Callback URL (must match what's configured in your OIDC provider)
|
|
||||||
OIDC_REDIRECT_URL=http://localhost:3000/api/v1/auth/callback
|
|
||||||
|
|
||||||
# Optional: Resource ID for audience verification
|
|
||||||
# OIDC_RESOURCE_ID=your-resource-id
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CORS Configuration
|
# CORS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Comma-separated list of allowed origins
|
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
|
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Production Mode
|
# Production Mode
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Set to true/production/1 to enable production checks (secret length, etc.)
|
# Set to true/production/1 to enforce minimum secret lengths and other checks.
|
||||||
PRODUCTION=false
|
PRODUCTION=false
|
||||||
|
|||||||
128
Cargo.lock
generated
128
Cargo.lock
generated
@@ -803,20 +803,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures"
|
|
||||||
version = "0.3.31"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -1395,8 +1381,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "k-core"
|
name = "k-core"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
source = "git+https://git.gabrielkaszewski.dev/GKaszewski/k-core#7a72f5f54ad45ba82f451e90c44c0581d13194d9"
|
source = "git+https://git.gabrielkaszewski.dev/GKaszewski/k-core#0ea9aa7870d73b5f665241a4183ffd899e628b9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
@@ -1408,12 +1394,9 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-sessions",
|
|
||||||
"tower-sessions-sqlx-store",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -1475,7 +1458,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1639,7 +1621,7 @@ version = "5.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.21.7",
|
||||||
"chrono",
|
"chrono",
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"http",
|
"http",
|
||||||
@@ -2189,25 +2171,6 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rmp"
|
|
||||||
version = "0.8.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rmp-serde"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
|
|
||||||
dependencies = [
|
|
||||||
"rmp",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@@ -2707,7 +2670,6 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2792,7 +2754,6 @@ dependencies = [
|
|||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
@@ -2832,7 +2793,6 @@ dependencies = [
|
|||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
@@ -2859,7 +2819,6 @@ dependencies = [
|
|||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"time",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -3129,22 +3088,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-cookies"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
|
||||||
dependencies = [
|
|
||||||
"axum-core",
|
|
||||||
"cookie",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
@@ -3176,71 +3119,6 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-sessions"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"http",
|
|
||||||
"time",
|
|
||||||
"tokio",
|
|
||||||
"tower-cookies",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
"tower-sessions-core",
|
|
||||||
"tower-sessions-memory-store",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-sessions-core"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"axum-core",
|
|
||||||
"base64 0.22.1",
|
|
||||||
"futures",
|
|
||||||
"http",
|
|
||||||
"parking_lot",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 2.0.17",
|
|
||||||
"time",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-sessions-memory-store"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"time",
|
|
||||||
"tokio",
|
|
||||||
"tower-sessions-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-sessions-sqlx-store"
|
|
||||||
version = "0.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e054622079f57fc1a7d6a6089c9334f963d62028fe21dc9eddd58af9a78480b3"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"rmp-serde",
|
|
||||||
"sqlx",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"time",
|
|
||||||
"tower-sessions-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
|||||||
148
README.md
148
README.md
@@ -5,10 +5,10 @@ A production-ready, modular Rust API template for K-Suite applications, followin
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Hexagonal Architecture**: Clear separation between Domain, Infrastructure, and API layers
|
- **Hexagonal Architecture**: Clear separation between Domain, Infrastructure, and API layers
|
||||||
- **Multiple Auth Modes**: Session-based, JWT, or both - fully configurable
|
- **JWT-Only Authentication**: Stateless Bearer token auth — no server-side sessions
|
||||||
- **OIDC Integration**: Connect to any OpenID Connect provider (Keycloak, Auth0, Zitadel, etc.)
|
- **OIDC Integration**: Connect to any OpenID Connect provider (Keycloak, Auth0, Zitadel, etc.) with stateless cookie-based flow state
|
||||||
- **Database Flexibility**: SQLite (default) or PostgreSQL via feature flags
|
- **Database Flexibility**: SQLite (default) or PostgreSQL via feature flags
|
||||||
- **Type-Safe Configuration**: Newtypes with built-in validation for all security-sensitive values
|
- **Type-Safe Domain**: Newtypes with built-in validation for emails, passwords, secrets, and OIDC values
|
||||||
- **Cargo Generate Ready**: Pre-configured for scaffolding new services
|
- **Cargo Generate Ready**: Pre-configured for scaffolding new services
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -22,7 +22,6 @@ cargo generate --git https://github.com/GKaszewski/k-template.git
|
|||||||
You'll be prompted to choose:
|
You'll be prompted to choose:
|
||||||
- **Project name**: Your new service name
|
- **Project name**: Your new service name
|
||||||
- **Database**: `sqlite` or `postgres`
|
- **Database**: `sqlite` or `postgres`
|
||||||
- **Session auth**: Enable cookie-based sessions
|
|
||||||
- **JWT auth**: Enable Bearer token authentication
|
- **JWT auth**: Enable Bearer token authentication
|
||||||
- **OIDC**: Enable OpenID Connect integration
|
- **OIDC**: Enable OpenID Connect integration
|
||||||
|
|
||||||
@@ -33,15 +32,6 @@ git clone https://github.com/GKaszewski/k-template.git my-api
|
|||||||
cd my-api
|
cd my-api
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env with your configuration
|
# Edit .env with your configuration
|
||||||
```
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Development (with hot reload via cargo-watch)
|
|
||||||
cargo watch -x run
|
|
||||||
|
|
||||||
# Or simply
|
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -51,51 +41,51 @@ The API will be available at `http://localhost:3000/api/v1/`.
|
|||||||
|
|
||||||
All configuration is done via environment variables. See [.env.example](.env.example) for all options.
|
All configuration is done via environment variables. See [.env.example](.env.example) for all options.
|
||||||
|
|
||||||
### Authentication Modes
|
### Key Variables
|
||||||
|
|
||||||
Set `AUTH_MODE` to one of:
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
| Mode | Description | Required Features |
|
| `DATABASE_URL` | `sqlite:data.db?mode=rwc` | Database connection string |
|
||||||
|------|-------------|-------------------|
|
| `COOKIE_SECRET` | *(insecure dev default)* | Secret for encrypting OIDC state cookie (≥64 bytes in production) |
|
||||||
| `session` | Cookie-based sessions | `auth-axum-login` |
|
| `JWT_SECRET` | *(insecure dev default)* | Secret for signing JWT tokens (≥32 bytes in production) |
|
||||||
| `jwt` | Bearer token authentication | `auth-jwt` |
|
| `JWT_EXPIRY_HOURS` | `24` | Token lifetime in hours |
|
||||||
| `both` | Try JWT first, fall back to session | `auth-axum-login`, `auth-jwt` |
|
| `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins |
|
||||||
|
| `SECURE_COOKIE` | `false` | Set `true` when serving over HTTPS |
|
||||||
|
| `PRODUCTION` | `false` | Enforces minimum secret lengths |
|
||||||
|
|
||||||
### OIDC Integration
|
### OIDC Integration
|
||||||
|
|
||||||
To enable OIDC login (e.g., "Login with Google"):
|
To enable "Login with Google/Keycloak/etc.":
|
||||||
|
|
||||||
1. Enable the `auth-oidc` feature (enabled by default)
|
1. Enable the `auth-oidc` feature (on by default in cargo-generate)
|
||||||
2. Configure your OIDC provider in `.env`:
|
2. Set these environment variables:
|
||||||
```env
|
```env
|
||||||
OIDC_ISSUER=https://your-provider.com
|
OIDC_ISSUER=https://your-provider.com
|
||||||
OIDC_CLIENT_ID=your-client-id
|
OIDC_CLIENT_ID=your-client-id
|
||||||
OIDC_CLIENT_SECRET=your-secret
|
OIDC_CLIENT_SECRET=your-secret
|
||||||
OIDC_REDIRECT_URL=http://localhost:3000/api/v1/auth/callback
|
OIDC_REDIRECT_URL=http://localhost:3000/api/v1/auth/callback
|
||||||
```
|
```
|
||||||
3. Users can login via `GET /api/v1/auth/login/oidc`
|
3. Users start the flow at `GET /api/v1/auth/login/oidc`
|
||||||
|
|
||||||
|
OIDC state (CSRF token, PKCE verifier, nonce) is stored in a short-lived encrypted cookie — no database session table required.
|
||||||
|
|
||||||
## Feature Flags
|
## Feature Flags
|
||||||
|
|
||||||
Features are configured in `api/Cargo.toml`:
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite", "auth-axum-login", "auth-oidc", "auth-jwt"]
|
default = ["sqlite", "auth-jwt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `sqlite` | SQLite database support (default) |
|
| `sqlite` | SQLite database (default) |
|
||||||
| `postgres` | PostgreSQL database support |
|
| `postgres` | PostgreSQL database |
|
||||||
| `auth-axum-login` | Session-based authentication |
|
| `auth-jwt` | JWT Bearer token authentication |
|
||||||
| `auth-oidc` | OpenID Connect integration |
|
| `auth-oidc` | OpenID Connect integration |
|
||||||
| `auth-jwt` | JWT token authentication |
|
|
||||||
| `auth-full` | All auth features combined |
|
|
||||||
|
|
||||||
### Common Configurations
|
### Common Configurations
|
||||||
|
|
||||||
**JWT-only API (stateless)**:
|
**JWT-only (minimal, default)**:
|
||||||
```toml
|
```toml
|
||||||
default = ["sqlite", "auth-jwt"]
|
default = ["sqlite", "auth-jwt"]
|
||||||
```
|
```
|
||||||
@@ -105,48 +95,74 @@ default = ["sqlite", "auth-jwt"]
|
|||||||
default = ["sqlite", "auth-oidc", "auth-jwt"]
|
default = ["sqlite", "auth-oidc", "auth-jwt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Full-featured (all auth methods)**:
|
**PostgreSQL + OIDC + JWT**:
|
||||||
```toml
|
```toml
|
||||||
default = ["sqlite", "auth-full"]
|
default = ["postgres", "auth-oidc", "auth-jwt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
| Method | Endpoint | Description |
|
| Method | Endpoint | Auth | Description |
|
||||||
|--------|----------|-------------|
|
|--------|----------|------|-------------|
|
||||||
| `POST` | `/api/v1/auth/login` | Login with email/password |
|
| `POST` | `/api/v1/auth/register` | — | Register with email + password → JWT |
|
||||||
| `POST` | `/api/v1/auth/register` | Register new user |
|
| `POST` | `/api/v1/auth/login` | — | Login with email + password → JWT |
|
||||||
| `POST` | `/api/v1/auth/logout` | Logout (session mode) |
|
| `POST` | `/api/v1/auth/logout` | — | Returns 200; client drops the token |
|
||||||
| `GET` | `/api/v1/auth/me` | Get current user |
|
| `GET` | `/api/v1/auth/me` | Bearer | Current user info |
|
||||||
| `POST` | `/api/v1/auth/token` | Get JWT for session user |
|
| `POST` | `/api/v1/auth/token` | Bearer | Issue a fresh JWT (`auth-jwt`) |
|
||||||
| `GET` | `/api/v1/auth/login/oidc` | Start OIDC login flow |
|
| `GET` | `/api/v1/auth/login/oidc` | — | Start OIDC flow, sets encrypted state cookie (`auth-oidc`) |
|
||||||
| `GET` | `/api/v1/auth/callback` | OIDC callback |
|
| `GET` | `/api/v1/auth/callback` | — | Complete OIDC flow → JWT, clears cookie (`auth-oidc`) |
|
||||||
|
|
||||||
|
### Example: Register and use a token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Register
|
||||||
|
curl -X POST http://localhost:3000/api/v1/auth/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"email": "user@example.com", "password": "mypassword"}'
|
||||||
|
# → {"access_token": "eyJ...", "token_type": "Bearer", "expires_in": 86400}
|
||||||
|
|
||||||
|
# Use the token
|
||||||
|
curl http://localhost:3000/api/v1/auth/me \
|
||||||
|
-H "Authorization: Bearer eyJ..."
|
||||||
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
k-template/
|
k-template/
|
||||||
├── domain/ # Core business logic (no I/O dependencies)
|
├── domain/ # Pure business logic — zero I/O dependencies
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── entities.rs # User entity
|
│ ├── entities.rs # User entity
|
||||||
│ ├── value_objects.rs # Email, Password, OIDC newtypes
|
│ ├── value_objects.rs # Email, Password, JwtSecret, OIDC newtypes
|
||||||
│ ├── repositories.rs # Repository interfaces (ports)
|
│ ├── repositories.rs # Repository interfaces (ports)
|
||||||
│ └── services.rs # Domain services
|
│ ├── services.rs # Domain services
|
||||||
|
│ └── errors.rs # DomainError (Unauthenticated 401, Forbidden 403, …)
|
||||||
│
|
│
|
||||||
├── infra/ # Infrastructure adapters
|
├── infra/ # Infrastructure adapters
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── auth/ # Auth backends (OIDC, JWT)
|
│ ├── auth/
|
||||||
│ └── user_repository.rs
|
│ │ ├── jwt.rs # JwtValidator — create + verify tokens
|
||||||
|
│ │ └── oidc.rs # OidcService + OidcState (cookie-serializable)
|
||||||
|
│ ├── user_repository.rs # SQLite / PostgreSQL adapter
|
||||||
|
│ ├── db.rs # DatabasePool re-export
|
||||||
|
│ └── factory.rs # build_user_repository()
|
||||||
│
|
│
|
||||||
├── api/ # HTTP API layer
|
├── api/ # HTTP layer
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── routes/ # API endpoints
|
│ ├── routes/
|
||||||
│ ├── config.rs # Configuration
|
│ │ ├── auth.rs # Login, register, logout, me, OIDC flow
|
||||||
│ └── state.rs # Application state
|
│ │ └── config.rs # /config endpoint
|
||||||
|
│ ├── config.rs # Config::from_env()
|
||||||
|
│ ├── state.rs # AppState (user_service, cookie_key, jwt_validator, …)
|
||||||
|
│ ├── extractors.rs # CurrentUser (JWT Bearer extractor)
|
||||||
|
│ ├── error.rs # ApiError → HTTP status mapping
|
||||||
|
│ └── dto.rs # LoginRequest, RegisterRequest, TokenResponse, …
|
||||||
│
|
│
|
||||||
├── .env.example # Configuration template
|
├── migrations_sqlite/
|
||||||
|
├── migrations_postgres/
|
||||||
|
├── .env.example
|
||||||
└── compose.yml # Docker Compose for local dev
|
└── compose.yml # Docker Compose for local dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -156,10 +172,13 @@ k-template/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All tests
|
# All tests
|
||||||
cargo test --all-features
|
cargo test
|
||||||
|
|
||||||
# Domain tests only
|
# Domain only
|
||||||
cargo test -p domain
|
cargo test -p domain
|
||||||
|
|
||||||
|
# Infra only (SQLite integration tests)
|
||||||
|
cargo test -p infra
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database Migrations
|
### Database Migrations
|
||||||
@@ -168,10 +187,23 @@ cargo test -p domain
|
|||||||
# SQLite
|
# SQLite
|
||||||
sqlx migrate run --source migrations_sqlite
|
sqlx migrate run --source migrations_sqlite
|
||||||
|
|
||||||
# PostgreSQL
|
# PostgreSQL
|
||||||
sqlx migrate run --source migrations_postgres
|
sqlx migrate run --source migrations_postgres
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Building with specific features
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Minimal: SQLite + JWT only
|
||||||
|
cargo build -F sqlite,auth-jwt
|
||||||
|
|
||||||
|
# Full: SQLite + JWT + OIDC
|
||||||
|
cargo build -F sqlite,auth-jwt,auth-oidc
|
||||||
|
|
||||||
|
# PostgreSQL variant
|
||||||
|
cargo build --no-default-features -F postgres,auth-jwt,auth-oidc
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -34,7 +34,23 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Setup database
|
// Setup database
|
||||||
tracing::info!("Connecting to database: {}", config.database_url);
|
tracing::info!("Connecting to database: {}", config.database_url);
|
||||||
|
|
||||||
|
#[cfg(all(feature = "sqlite", not(feature = "postgres")))]
|
||||||
|
let db_type = k_core::db::DbType::Sqlite;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
|
||||||
|
let db_type = k_core::db::DbType::Postgres;
|
||||||
|
|
||||||
|
// Both features enabled: fall back to URL inspection at runtime
|
||||||
|
#[cfg(all(feature = "sqlite", feature = "postgres"))]
|
||||||
|
let db_type = if config.database_url.starts_with("postgres") {
|
||||||
|
k_core::db::DbType::Postgres
|
||||||
|
} else {
|
||||||
|
k_core::db::DbType::Sqlite
|
||||||
|
};
|
||||||
|
|
||||||
let db_config = k_core::db::DatabaseConfig {
|
let db_config = k_core::db::DatabaseConfig {
|
||||||
|
db_type,
|
||||||
url: config.database_url.clone(),
|
url: config.database_url.clone(),
|
||||||
max_connections: config.db_max_connections,
|
max_connections: config.db_max_connections,
|
||||||
min_connections: config.db_min_connections,
|
min_connections: config.db_min_connections,
|
||||||
@@ -51,8 +67,6 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let server_config = ServerConfig {
|
let server_config = ServerConfig {
|
||||||
cors_origins: config.cors_allowed_origins.clone(),
|
cors_origins: config.cors_allowed_origins.clone(),
|
||||||
// session_secret is unused (sessions removed); kept for k-core API compat
|
|
||||||
session_secret: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|||||||
Reference in New Issue
Block a user