mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-19 00:34:16 +00:00
fix oauth login on admin panel for production
This commit is contained in:
+46
-23
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/rand"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
@@ -83,45 +84,50 @@ func NewAdminUI(ctx context.Context, holdPDS *pds.HoldPDS, quotaMgr *quota.Manag
|
||||
return nil, fmt.Errorf("PublicURL is required for admin panel")
|
||||
}
|
||||
|
||||
// Determine OAuth base URL
|
||||
// - For IP addresses (Docker network IPs), substitute 127.0.0.1 (ATProto requires localhost for public clients)
|
||||
// - For real domains, use as-is (works with ATProto OAuth)
|
||||
oauthBaseURL := cfg.PublicURL
|
||||
// Determine OAuth configuration based on URL type
|
||||
u, err := url.Parse(cfg.PublicURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PublicURL: %w", err)
|
||||
}
|
||||
|
||||
host := u.Hostname()
|
||||
if isIPAddress(host) {
|
||||
// IP address (e.g., 172.28.0.3) - substitute localhost
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
oauthBaseURL = "http://127.0.0.1:" + port
|
||||
}
|
||||
|
||||
// Use in-memory store for OAuth sessions
|
||||
oauthStore := indigooauth.NewMemStore()
|
||||
|
||||
// Use minimal scopes for admin (only need basic auth, no blob access)
|
||||
adminScopes := []string{"atproto"}
|
||||
|
||||
// Admin panel uses localhost callback for OAuth (ATProto requirement for public clients)
|
||||
redirectURI := oauthBaseURL + "/admin/auth/oauth/callback"
|
||||
var oauthConfig indigooauth.ClientConfig
|
||||
var redirectURI string
|
||||
|
||||
// Use simple public client - no need for confidential client complexity
|
||||
oauthConfig := indigooauth.NewLocalhostConfig(redirectURI, adminScopes)
|
||||
host := u.Hostname()
|
||||
if isIPAddress(host) || host == "localhost" || host == "127.0.0.1" {
|
||||
// Development mode: IP address or localhost - use localhost OAuth config
|
||||
// Substitute 127.0.0.1 for Docker network IPs
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
oauthBaseURL := "http://127.0.0.1:" + port
|
||||
redirectURI = oauthBaseURL + "/admin/auth/oauth/callback"
|
||||
oauthConfig = indigooauth.NewLocalhostConfig(redirectURI, adminScopes)
|
||||
|
||||
slog.Info("Admin OAuth configured (localhost mode)",
|
||||
"redirect_uri", redirectURI,
|
||||
"public_url", cfg.PublicURL)
|
||||
} else {
|
||||
// Production mode: real domain - use public client with metadata endpoint
|
||||
clientID := cfg.PublicURL + "/admin/oauth-client-metadata.json"
|
||||
redirectURI = cfg.PublicURL + "/admin/auth/oauth/callback"
|
||||
oauthConfig = indigooauth.NewPublicConfig(clientID, redirectURI, adminScopes)
|
||||
|
||||
slog.Info("Admin OAuth configured (production mode)",
|
||||
"client_id", clientID,
|
||||
"redirect_uri", redirectURI)
|
||||
}
|
||||
|
||||
clientApp := indigooauth.NewClientApp(&oauthConfig, oauthStore)
|
||||
clientApp.Dir = atproto.GetDirectory()
|
||||
|
||||
slog.Info("Admin OAuth configured",
|
||||
"redirect_uri", redirectURI,
|
||||
"public_url", cfg.PublicURL,
|
||||
"oauth_base_url", oauthBaseURL)
|
||||
|
||||
// Parse templates
|
||||
templates, err := parseTemplates()
|
||||
if err != nil {
|
||||
@@ -286,6 +292,9 @@ func (ui *AdminUI) RegisterRoutes(r chi.Router) {
|
||||
staticSub, _ := fs.Sub(staticFS, "static")
|
||||
r.Handle("/admin/static/*", http.StripPrefix("/admin/static/", http.FileServer(http.FS(staticSub))))
|
||||
|
||||
// OAuth client metadata endpoint (required for production OAuth)
|
||||
r.Get("/admin/oauth-client-metadata.json", ui.handleClientMetadata)
|
||||
|
||||
// Public auth routes
|
||||
r.Get("/admin/auth/login", ui.handleLogin)
|
||||
r.Get("/admin/auth/oauth/authorize", ui.handleAuthorize)
|
||||
@@ -320,6 +329,20 @@ func (ui *AdminUI) RegisterRoutes(r chi.Router) {
|
||||
})
|
||||
}
|
||||
|
||||
// handleClientMetadata serves the OAuth client metadata for production deployments
|
||||
func (ui *AdminUI) handleClientMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
metadata := ui.clientApp.Config.ClientMetadata()
|
||||
|
||||
// Set client name for display in OAuth consent screen
|
||||
clientName := "Hold Admin Panel"
|
||||
metadata.ClientName = &clientName
|
||||
metadata.ClientURI = &ui.config.PublicURL
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
json.NewEncoder(w).Encode(metadata)
|
||||
}
|
||||
|
||||
// Close cleans up resources (no-op now, but keeps interface consistent)
|
||||
func (ui *AdminUI) Close() error {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user