Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cmd/hotplex/gateway_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type GatewayDeps struct {
CronScheduler *cron.Scheduler
WebhookHandler *gateway.WebhookHandler // non-nil when webhook is enabled
CookieAuth *security.CookieAuth // non-nil when webchat is enabled
OAuthManager *security.OAuthManager // non-nil when SSO providers are configured
ChatAccessStore messaging.ChatAccessStorer
DB *sql.DB
DBResolver *security.DBResolver
Expand Down Expand Up @@ -274,6 +275,10 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er
WSStore: stores.wsStore,
})

// One-time validation sweep: surface stale/invalid agent_config_overrides
// written before spec ② write-time validation (#749). Non-blocking.
gateway.ScanWorkspaceOverrides(ctx, stores.wsStore, log)

skillsLocator := skills.NewLocator(log, cfg.Skills.CacheTTL)

handler := gateway.NewHandler(gateway.HandlerDeps{
Expand Down Expand Up @@ -422,6 +427,22 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er
log.Info("gateway: webchat cookie auth enabled")
}

// OAuth manager: created when SSO providers are configured (spec ④).
// Requires cookieAuth for signing state cookies.
var oauthManager *security.OAuthManager
if cookieAuth != nil {
oauthManager = security.NewOAuthManager(cookieAuth)
if err := cfg.OAuth.Validate(); err != nil {
log.Warn("oauth config validation failed", "error", err)
} else if len(cfg.OAuth.Providers) > 0 {
count, err := oauthManager.Reload(ctx, cfg.OAuth)
if err != nil {
log.Error("oauth manager init failed", "error", err)
}
log.Info("gateway: oauth SSO providers loaded", "count", count)
}
}

mux := http.NewServeMux()
deps := &GatewayDeps{
Log: log,
Expand All @@ -438,6 +459,7 @@ func runGateway(configPath string, devMode bool, stopCh <-chan struct{}) (err er
ConfigWatcher: configWatcher,
CronScheduler: cronScheduler,
CookieAuth: cookieAuth,
OAuthManager: oauthManager,
ChatAccessStore: stores.chatAccessOrNew(stores.sqlDB, log),
DB: stores.sqlDB,
DBResolver: dbResolver,
Expand Down
30 changes: 23 additions & 7 deletions cmd/hotplex/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,29 @@ func setupRoutes(
}
}

// TODO(spec ⑥): Register WebChat multi-tenant HTTP endpoints when login UI ships.
// - POST /api/auth/login, /api/auth/logout, GET /api/auth/me
// - POST /api/auth/accept-invite
// - POST/GET/PUT/DELETE /api/workspaces/*
// - POST/GET/DELETE /api/admin/invitations
// Handlers are implemented in internal/gateway/{auth,workspace}_handlers.go
// but intentionally not wired until the frontend login flow is ready.
// WebChat multi-tenant auth endpoints (spec ① + spec ④).
// Wired when cookieAuth is available (webchat enabled).
if deps.CookieAuth != nil && deps.WorkspaceStore != nil {
// Account-login handlers (spec ①): requires LocalAccountProvider.
// LocalAccountProvider is created lazily from WorkspaceStore + bcrypt cost.
lap := security.NewLocalAccountProvider(deps.WorkspaceStore, security.BcryptCostDefault)
authHandlers := gateway.NewAuthHandlers(auth, deps.CookieAuth, deps.WorkspaceStore, lap)
mux.Handle("POST /api/auth/login", corsMw(http.HandlerFunc(authHandlers.Login)))
mux.Handle("POST /api/auth/logout", corsMw(http.HandlerFunc(authHandlers.Logout)))
mux.Handle("GET /api/auth/me", corsMw(http.HandlerFunc(authHandlers.Me)))
mux.Handle("POST /api/auth/accept-invite", corsMw(http.HandlerFunc(authHandlers.AcceptInvite)))
log.Info("auth endpoints registered", "channels", "login,logout,me,accept-invite")

// OAuth SSO handlers (spec ④): requires OAuthManager with providers.
if deps.OAuthManager != nil && deps.OAuthManager.HasProviders() {
oauthHandlers := gateway.NewOAuthHandlers(deps.OAuthManager, deps.CookieAuth, deps.WorkspaceStore, log)
mux.Handle("GET /api/auth/oauth/providers", corsMw(http.HandlerFunc(oauthHandlers.Providers)))
mux.Handle("GET /api/auth/oauth/{provider}/login", http.HandlerFunc(oauthHandlers.Login))
mux.Handle("GET /api/auth/oauth/{provider}/callback", http.HandlerFunc(oauthHandlers.Callback))
// Note: login/callback are redirect flows, CORS not needed (browser navigates directly).
log.Info("oauth SSO endpoints registered", "providers", deps.OAuthManager.List())
}
}

// Global favicon fallback using docs logo
mux.HandleFunc("GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading