diff --git a/CLAUDE.md b/CLAUDE.md index ec48c3a..87583ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -132,7 +132,7 @@ pytest services/pii/tests/test_file.py::test_function **Config override:** Any config key can be overridden via env var with the `VEYLANT_` prefix and `.` → `_` replacement. Example: `VEYLANT_SERVER_PORT=9090` overrides `server.port`. -**Auth config:** `auth.jwt_secret` (env: `VEYLANT_AUTH_JWT_SECRET`) and `auth.jwt_ttl_hours`. Login endpoint: `POST /v1/auth/login` (public). Dev credentials: `admin@veylant.dev` / `admin123`. Tokens are HS256-signed JWTs; users stored in `users` table with bcrypt password hashes (migration 000010). +**Auth config:** `auth.jwt_secret` (env: `VEYLANT_AUTH_JWT_SECRET`) and `auth.jwt_ttl_hours`. Login endpoint: `POST /v1/auth/login` — public (no JWT required), CORS applied. Dev credentials: `admin@veylant.dev` / `admin123`. Tokens are HS256-signed JWTs; users stored in `users` table with bcrypt password hashes (migration 000010) and a `name TEXT` column (migration 000012). Login response includes `{token, user: {id, email, name, role, tenant_id, department}}`. **Provider configs:** LLM provider API keys are stored encrypted (AES-256-GCM) in the `provider_configs` table (migration 000011). CRUD via `GET|POST /v1/admin/providers`, `PUT|DELETE|POST-test /v1/admin/providers/{id}`. Adapters hot-reload on save/update without proxy restart (`router.UpdateAdapter()` / `RemoveAdapter()`). diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 5b7e958..e3b979c 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -293,53 +293,59 @@ func main() { r.Get(cfg.Metrics.Path, promhttp.Handler().ServeHTTP) } - // Public login endpoint — must be registered before the auth middleware below. loginHandler := auth.NewLoginHandler(db, cfg.Auth.JWTSecret, cfg.Auth.JWTTTLHours, logger) - r.Post("/v1/auth/login", loginHandler.ServeHTTP) r.Route("/v1", func(r chi.Router) { r.Use(middleware.CORS(cfg.Server.AllowedOrigins)) - r.Use(middleware.Auth(jwtVerifier)) - r.Use(middleware.RateLimit(rateLimiter)) - r.Post("/chat/completions", proxyHandler.ServeHTTP) - // PII analyze endpoint for Playground (E8-11, Sprint 8). - piiAnalyzeHandler := pii.NewAnalyzeHandler(piiClient, logger) - r.Post("/pii/analyze", piiAnalyzeHandler.ServeHTTP) + // Public — CORS applied, no auth required. + r.Post("/auth/login", loginHandler.ServeHTTP) - // Admin API — routing policies + audit logs (Sprint 5 + Sprint 6) - // + user management + provider status (Sprint 8). - if routingEngine != nil { - var adminHandler *admin.Handler - if auditLogger != nil { - adminHandler = admin.NewWithAudit( - routing.NewPgStore(db, logger), - routingEngine.Cache(), - auditLogger, - logger, - ) - } else { - adminHandler = admin.New( - routing.NewPgStore(db, logger), - routingEngine.Cache(), - logger, - ) + // Protected — JWT auth + tenant rate limit. + r.Group(func(r chi.Router) { + r.Use(middleware.Auth(jwtVerifier)) + r.Use(middleware.RateLimit(rateLimiter)) + + r.Post("/chat/completions", proxyHandler.ServeHTTP) + + // PII analyze endpoint for Playground (E8-11, Sprint 8). + piiAnalyzeHandler := pii.NewAnalyzeHandler(piiClient, logger) + r.Post("/pii/analyze", piiAnalyzeHandler.ServeHTTP) + + // Admin API — routing policies + audit logs (Sprint 5 + Sprint 6) + // + user management + provider status (Sprint 8). + if routingEngine != nil { + var adminHandler *admin.Handler + if auditLogger != nil { + adminHandler = admin.NewWithAudit( + routing.NewPgStore(db, logger), + routingEngine.Cache(), + auditLogger, + logger, + ) + } else { + adminHandler = admin.New( + routing.NewPgStore(db, logger), + routingEngine.Cache(), + logger, + ) + } + // Wire db, router, rate limiter, feature flags, and encryptor. + adminHandler.WithDB(db).WithRouter(providerRouter).WithRateLimiter(rateLimiter).WithFlagStore(flagStore).WithEncryptor(encryptor) + r.Route("/admin", adminHandler.Routes) } - // Wire db, router, rate limiter, feature flags, and encryptor. - adminHandler.WithDB(db).WithRouter(providerRouter).WithRateLimiter(rateLimiter).WithFlagStore(flagStore).WithEncryptor(encryptor) - r.Route("/admin", adminHandler.Routes) - } - // Compliance module — GDPR Art. 30 registry + AI Act classification + PDF reports (Sprint 9). - if db != nil { - compStore := compliance.NewPgStore(db, logger) - compHandler := compliance.New(compStore, logger). - WithAudit(auditLogger). - WithDB(db). - WithTenantName(cfg.Server.TenantName) - r.Route("/admin/compliance", compHandler.Routes) - logger.Info("compliance module started") - } + // Compliance module — GDPR Art. 30 registry + AI Act classification + PDF reports (Sprint 9). + if db != nil { + compStore := compliance.NewPgStore(db, logger) + compHandler := compliance.New(compStore, logger). + WithAudit(auditLogger). + WithDB(db). + WithTenantName(cfg.Server.TenantName) + r.Route("/admin/compliance", compHandler.Routes) + logger.Info("compliance module started") + } + }) }) // ── HTTP server ─────────────────────────────────────────────────────────── diff --git a/internal/auth/login.go b/internal/auth/login.go index e203066..5d0b308 100644 --- a/internal/auth/login.go +++ b/internal/auth/login.go @@ -70,7 +70,7 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var u userInfo var passwordHash string err := h.db.QueryRowContext(r.Context(), - `SELECT id, tenant_id, email, email, role, COALESCE(department,''), password_hash + `SELECT id, tenant_id, email, COALESCE(name,''), role, COALESCE(department,''), password_hash FROM users WHERE email = $1 AND is_active = TRUE LIMIT 1`, diff --git a/migrations/000012_users_add_name.up.sql b/migrations/000012_users_add_name.up.sql index 9ca02ab..b356875 100644 --- a/migrations/000012_users_add_name.up.sql +++ b/migrations/000012_users_add_name.up.sql @@ -1,6 +1,9 @@ -- Migration 000012: Add name column to users table. -- Migration 000001 created users without a name column; migration 000006 used -- CREATE TABLE IF NOT EXISTS which was a no-op since the table already existed. --- This migration adds the missing column retroactively. +-- This migration adds the missing column retroactively and seeds existing users. ALTER TABLE users ADD COLUMN IF NOT EXISTS name TEXT NOT NULL DEFAULT ''; + +-- Seed name for the dev admin user created in migration 000001. +UPDATE users SET name = 'Admin Veylant' WHERE email = 'admin@veylant.dev' AND name = ''; diff --git a/proxy b/proxy index ba90b4f..24ccb2b 100755 Binary files a/proxy and b/proxy differ