mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-14 14:21:59 +01:00
350 lines
9.4 KiB
Markdown
350 lines
9.4 KiB
Markdown
# Authentication
|
|
|
|
Authentication providers supporting OIDC and username/password authentication with JWT-based sessions.
|
|
|
|
## Overview
|
|
|
|
The auth package implements authentication middleware and login handlers that integrate with GoDoxy's HTTP routing system. It provides flexible authentication that can be enabled/disabled based on configuration and supports multiple authentication providers.
|
|
|
|
### Primary consumers
|
|
|
|
- `internal/route/rules` - Authentication middleware for routes
|
|
- `internal/api/v1/auth` - Login and session management endpoints
|
|
- `internal/homepage` - WebUI login page
|
|
|
|
### Non-goals
|
|
|
|
- ACL or authorization (see `internal/acl`)
|
|
- User management database
|
|
- Multi-factor authentication
|
|
- Rate limiting (basic OIDC rate limiting only)
|
|
|
|
### Stability
|
|
|
|
Stable internal package. Public API consists of the `Provider` interface and initialization functions.
|
|
|
|
## Public API
|
|
|
|
### Exported types
|
|
|
|
```go
|
|
type Provider interface {
|
|
CheckToken(r *http.Request) error
|
|
LoginHandler(w http.ResponseWriter, r *http.Request)
|
|
PostAuthCallbackHandler(w http.ResponseWriter, r *http.Request)
|
|
LogoutHandler(w http.ResponseWriter, r *http.Request)
|
|
}
|
|
```
|
|
|
|
### OIDC Provider
|
|
|
|
```go
|
|
type OIDCProvider struct {
|
|
oauthConfig *oauth2.Config
|
|
oidcProvider *oidc.Provider
|
|
oidcVerifier *oidc.IDTokenVerifier
|
|
endSessionURL *url.URL
|
|
allowedUsers []string
|
|
allowedGroups []string
|
|
rateLimit *rate.Limiter
|
|
}
|
|
```
|
|
|
|
### Username/Password Provider
|
|
|
|
```go
|
|
type UserPassAuth struct {
|
|
username string
|
|
pwdHash []byte
|
|
secret []byte
|
|
tokenTTL time.Duration
|
|
}
|
|
```
|
|
|
|
### Exported functions
|
|
|
|
```go
|
|
func Initialize() error
|
|
```
|
|
|
|
Sets up authentication providers based on environment configuration. Returns error if OIDC issuer is configured but cannot be reached.
|
|
|
|
```go
|
|
func IsEnabled() bool
|
|
```
|
|
|
|
Returns whether authentication is enabled. Checks `DEBUG_DISABLE_AUTH`, `API_JWT_SECRET`, and `OIDC_ISSUER_URL`.
|
|
|
|
```go
|
|
func IsOIDCEnabled() bool
|
|
```
|
|
|
|
Returns whether OIDC authentication is configured.
|
|
|
|
```go
|
|
func GetDefaultAuth() Provider
|
|
```
|
|
|
|
Returns the configured authentication provider.
|
|
|
|
```go
|
|
func AuthCheckHandler(w http.ResponseWriter, r *http.Request)
|
|
```
|
|
|
|
HTTP handler that checks if the request has a valid token. Returns 200 if valid, invokes login handler otherwise.
|
|
|
|
```go
|
|
func AuthOrProceed(w http.ResponseWriter, r *http.Request) bool
|
|
```
|
|
|
|
Authenticates request or proceeds if valid. Returns `false` if login handler was invoked, `true` if authenticated.
|
|
|
|
```go
|
|
func ProceedNext(w http.ResponseWriter, r *http.Request)
|
|
```
|
|
|
|
Continues to the next handler after successful authentication.
|
|
|
|
```go
|
|
func NewUserPassAuth(username, password string, secret []byte, tokenTTL time.Duration) (*UserPassAuth, error)
|
|
```
|
|
|
|
Creates a new username/password auth provider with bcrypt password hashing.
|
|
|
|
```go
|
|
func NewUserPassAuthFromEnv() (*UserPassAuth, error)
|
|
```
|
|
|
|
Creates username/password auth from environment variables `API_USER`, `API_PASSWORD`, `API_JWT_SECRET`.
|
|
|
|
```go
|
|
func NewOIDCProvider(issuerURL, clientID, clientSecret string, allowedUsers, allowedGroups []string) (*OIDCProvider, error)
|
|
```
|
|
|
|
Creates a new OIDC provider. Returns error if issuer cannot be reached or no allowed users/groups are configured.
|
|
|
|
```go
|
|
func NewOIDCProviderFromEnv() (*OIDCProvider, error)
|
|
```
|
|
|
|
Creates OIDC provider from environment variables `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, etc.
|
|
|
|
## Architecture
|
|
|
|
### Core components
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[HTTP Request] --> B{Auth Enabled?}
|
|
B -->|No| C[Proceed Direct]
|
|
B -->|Yes| D[Check Token]
|
|
D -->|Valid| E[Proceed]
|
|
D -->|Invalid| F[Login Handler]
|
|
|
|
G[OIDC Provider] --> H[Token Validation]
|
|
I[UserPass Provider] --> J[Credential Check]
|
|
|
|
F --> K{OIDC Configured?}
|
|
K -->|Yes| G
|
|
K -->|No| I
|
|
|
|
subgraph Cookie Management
|
|
L[Token Cookie]
|
|
M[State Cookie]
|
|
N[Session Cookie]
|
|
end
|
|
```
|
|
|
|
### OIDC authentication flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant App
|
|
participant IdP
|
|
|
|
User->>App: Access Protected Resource
|
|
App->>App: Check Token
|
|
alt No valid token
|
|
App-->>User: Redirect to /auth/
|
|
User->>IdP: Login & Authorize
|
|
IdP-->>User: Redirect with Code
|
|
User->>App: /auth/callback?code=...
|
|
App->>IdP: Exchange Code for Token
|
|
IdP-->>App: Access Token + ID Token
|
|
App->>App: Validate Token
|
|
App->>App: Check allowed users/groups
|
|
App-->>User: Protected Resource
|
|
else Valid token exists
|
|
App-->>User: Protected Resource
|
|
end
|
|
```
|
|
|
|
### Username/password flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant App
|
|
|
|
User->>App: POST /auth/callback
|
|
App->>App: Validate credentials
|
|
alt Valid
|
|
App->>App: Generate JWT
|
|
App-->>User: Set token cookie, redirect to /
|
|
else Invalid
|
|
App-->>User: 401 Unauthorized
|
|
end
|
|
```
|
|
|
|
## Configuration Surface
|
|
|
|
### Environment variables
|
|
|
|
| Variable | Description |
|
|
| ------------------------ | ----------------------------------------------------------- |
|
|
| `DEBUG_DISABLE_AUTH` | Set to "true" to disable auth for debugging |
|
|
| `API_JWT_SECRET` | Secret key for JWT token validation (enables userpass auth) |
|
|
| `API_USER` | Username for userpass authentication |
|
|
| `API_PASSWORD` | Password for userpass authentication |
|
|
| `API_JWT_TOKEN_TTL` | Token TTL duration (default: 24h) |
|
|
| `OIDC_ISSUER_URL` | OIDC provider URL (enables OIDC) |
|
|
| `OIDC_CLIENT_ID` | OIDC client ID |
|
|
| `OIDC_CLIENT_SECRET` | OIDC client secret |
|
|
| `OIDC_REDIRECT_URL` | OIDC redirect URL |
|
|
| `OIDC_ALLOWED_USERS` | Comma-separated list of allowed users |
|
|
| `OIDC_ALLOWED_GROUPS` | Comma-separated list of allowed groups |
|
|
| `OIDC_SCOPES` | Comma-separated OIDC scopes (default: openid,profile,email) |
|
|
| `OIDC_RATE_LIMIT` | Rate limit requests (default: 10) |
|
|
| `OIDC_RATE_LIMIT_PERIOD` | Rate limit period (default: 1m) |
|
|
|
|
### Hot-reloading
|
|
|
|
Authentication configuration requires restart. No dynamic reconfiguration is supported.
|
|
|
|
## Dependency and Integration Map
|
|
|
|
### Internal dependencies
|
|
|
|
- `internal/common` - Environment variable access
|
|
|
|
### External dependencies
|
|
|
|
- `golang.org/x/crypto/bcrypt` - Password hashing
|
|
- `github.com/coreos/go-oidc/v3/oidc` - OIDC protocol
|
|
- `golang.org/x/oauth2` - OAuth2/OIDC implementation
|
|
- `github.com/golang-jwt/jwt/v5` - JWT token handling
|
|
- `golang.org/x/time/rate` - OIDC rate limiting
|
|
|
|
### Integration points
|
|
|
|
```go
|
|
// Route middleware uses AuthOrProceed
|
|
routeHandler := func(w http.ResponseWriter, r *http.Request) {
|
|
if !auth.AuthOrProceed(w, r) {
|
|
return // Auth failed, login handler was invoked
|
|
}
|
|
// Continue with authenticated request
|
|
}
|
|
```
|
|
|
|
## Observability
|
|
|
|
### Logs
|
|
|
|
- OIDC provider initialization errors
|
|
- Token validation failures
|
|
- Rate limit exceeded events
|
|
|
|
### Metrics
|
|
|
|
No metrics are currently exposed.
|
|
|
|
## Security Considerations
|
|
|
|
- JWT tokens use HS512 signing for userpass auth
|
|
- OIDC tokens are validated against the issuer
|
|
- Session tokens are scoped by client ID to prevent conflicts
|
|
- Passwords are hashed with bcrypt (cost 10)
|
|
- OIDC rate limiting prevents brute-force attacks
|
|
- State parameter prevents CSRF attacks
|
|
- Refresh tokens are stored and invalidated on logout
|
|
|
|
## Failure Modes and Recovery
|
|
|
|
| Failure | Behavior | Recovery |
|
|
| ------------------------ | ------------------------------ | ----------------------------- |
|
|
| OIDC issuer unreachable | Initialize returns error | Fix network/URL configuration |
|
|
| Invalid JWT secret | Initialize uses API_JWT_SECRET | Provide correct secret |
|
|
| Token expired | CheckToken returns error | User must re-authenticate |
|
|
| User not in allowed list | Returns ErrUserNotAllowed | Add user to allowed list |
|
|
| Rate limit exceeded | Returns 429 Too Many Requests | Wait for rate limit reset |
|
|
|
|
## Usage Examples
|
|
|
|
### Basic setup
|
|
|
|
```go
|
|
// Initialize authentication during startup
|
|
err := auth.Initialize()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Check if auth is enabled
|
|
if auth.IsEnabled() {
|
|
log.Println("Authentication is enabled")
|
|
}
|
|
|
|
// Check OIDC status
|
|
if auth.IsOIDCEnabled() {
|
|
log.Println("OIDC authentication configured")
|
|
}
|
|
```
|
|
|
|
### Using AuthOrProceed middleware
|
|
|
|
```go
|
|
func protectedHandler(w http.ResponseWriter, r *http.Request) {
|
|
if !auth.AuthOrProceed(w, r) {
|
|
return // Auth failed, login handler was invoked
|
|
}
|
|
// Continue with authenticated request
|
|
}
|
|
```
|
|
|
|
### Using AuthCheckHandler
|
|
|
|
```go
|
|
http.HandleFunc("/api/", auth.AuthCheckHandler(apiHandler))
|
|
```
|
|
|
|
### Custom OIDC provider
|
|
|
|
```go
|
|
provider, err := auth.NewOIDCProvider(
|
|
"https://your-idp.com",
|
|
"your-client-id",
|
|
"your-client-secret",
|
|
[]string{"user1", "user2"},
|
|
[]string{"group1"},
|
|
)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
```
|
|
|
|
### Custom userpass provider
|
|
|
|
```go
|
|
provider, err := auth.NewUserPassAuth(
|
|
"admin",
|
|
"password123",
|
|
[]byte("jwt-secret-key"),
|
|
24*time.Hour,
|
|
)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
```
|