Files
synapbus/internal/auth/provider.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.7 KiB
Go

package auth
import (
"crypto/rand"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/ory/fosite/token/hmac"
)
// NewOAuthProvider creates a configured fosite OAuth 2.1 provider.
// It supports authorization code with PKCE (S256 only), client credentials, and refresh token rotation.
func NewOAuthProvider(cfg Config, store *FositeStore) fosite.OAuth2Provider {
secret := cfg.Secret
if len(secret) < 32 {
// Generate a random secret if not configured
secret = make([]byte, 32)
rand.Read(secret)
}
config := &fosite.Config{
AccessTokenLifespan: cfg.AccessTokenTTL,
RefreshTokenLifespan: cfg.RefreshTokenLifetime,
AuthorizeCodeLifespan: 10 * time.Minute,
GlobalSecret: secret,
SendDebugMessagesToClients: cfg.DevMode,
EnforcePKCE: true,
EnforcePKCEForPublicClients: true,
EnablePKCEPlainChallengeMethod: false,
TokenURL: cfg.IssuerURL + "/oauth/token",
HashCost: cfg.BcryptCost,
}
// HMACSHAStrategy for token generation
hmacStrategy := &hmac.HMACStrategy{
Config: config,
}
_ = hmacStrategy
return compose.Compose(
config,
store,
&compose.CommonStrategy{
CoreStrategy: compose.NewOAuth2HMACStrategy(config),
},
compose.OAuth2AuthorizeExplicitFactory,
compose.OAuth2ClientCredentialsGrantFactory,
compose.OAuth2RefreshTokenGrantFactory,
compose.OAuth2PKCEFactory,
compose.OAuth2TokenIntrospectionFactory,
)
}
// NewSession creates a new fosite session for a user.
func NewSession(user *User) fosite.Session {
return &fositeSession{
UserID: user.ID,
Username: user.Username,
Subject: user.Username,
}
}