Files
synapbus/internal/auth/logging.go
T
Algis DumbrisandClaude Opus 4.6 8a1c096355 feat: implement human auth with OAuth 2.1 (fosite)
Add complete auth subsystem with OAuth 2.1 authorization server using
ory/fosite, local user accounts with bcrypt password hashing, session
management, and HTTP handlers for the Web UI.

Components:
- User store with bcrypt hashing (configurable cost, default 12), CRUD,
  validation (username 3-64 chars alphanumeric+underscore, password 8-72 bytes)
- Session store with secure random IDs, configurable lifetime (default 24h),
  expiration cleanup, and per-user invalidation
- OAuth client store with client_id/secret generation and bcrypt verification
- Fosite storage adapter implementing CoreStorage, TokenRevocationStorage,
  and PKCERequestStorage backed by SQLite
- OAuth provider configured with authorization code (PKCE S256 mandatory),
  client credentials, refresh token rotation, and token introspection
- HTTP handlers: POST /auth/register, POST /auth/login, POST /auth/logout,
  GET /auth/me, PUT /auth/password, GET /oauth/authorize, POST /oauth/token,
  POST /oauth/introspect
- Middleware: RequireSession (cookie), RequireBearer (access token),
  RequireAuth (either), RequireAdmin (role check)
- Structured auth event logging (login, token issuance, session lifecycle)
- Schema migration 002_auth.sql extending users, oauth_clients, oauth_tokens
  tables and adding sessions, oauth_authorization_codes tables
- Initial admin user auto-created on first run with random password printed
  to stdout
- All tests pass with CGO_ENABLED=0, zero external runtime dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:59:37 +02:00

64 lines
1.5 KiB
Go

package auth
import (
"context"
"log/slog"
"net/http"
)
// Auth event type constants.
const (
EventLoginSuccess = "login_success"
EventLoginFailure = "login_failure"
EventTokenIssued = "token_issued"
EventTokenRefreshed = "token_refreshed"
EventTokenRevoked = "token_revoked"
EventSessionCreated = "session_created"
EventSessionDestroyed = "session_destroyed"
EventUserCreated = "user_created"
EventPasswordChanged = "password_changed"
)
// AuthEvent represents a structured auth event for logging.
type AuthEvent struct {
Type string
UserID int64
Username string
ClientID string
RemoteIP string
Details map[string]any
}
// LogAuthEvent logs an authentication event with structured fields.
func LogAuthEvent(ctx context.Context, logger *slog.Logger, event AuthEvent) {
attrs := []any{
"event", event.Type,
}
if event.UserID > 0 {
attrs = append(attrs, "user_id", event.UserID)
}
if event.Username != "" {
attrs = append(attrs, "username", event.Username)
}
if event.ClientID != "" {
attrs = append(attrs, "client_id", event.ClientID)
}
if event.RemoteIP != "" {
attrs = append(attrs, "remote_ip", event.RemoteIP)
}
for k, v := range event.Details {
attrs = append(attrs, k, v)
}
logger.InfoContext(ctx, "auth event", attrs...)
}
// remoteIP extracts the remote IP from an HTTP request.
func remoteIP(r *http.Request) string {
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
return forwarded
}
return r.RemoteAddr
}