Files
at-container-registry/pkg/labeler/handlers.go
T
2026-05-03 20:49:56 -05:00

118 lines
3.4 KiB
Go

package labeler
import (
"fmt"
"html/template"
"log/slog"
"net/http"
"strings"
"atcr.io/pkg/atproto"
)
// Auth handlers
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
if token, ok := getSessionCookie(r); ok {
if session := s.auth.GetSession(token); session != nil && session.DID == s.config.Labeler.OwnerDID {
http.Redirect(w, r, "/", http.StatusFound)
return
}
}
errorMsg := r.URL.Query().Get("error")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head><title>%s Labeler - Login</title>
<style>body{font-family:system-ui;max-width:400px;margin:100px auto;padding:0 20px}
.error{color:red;margin-bottom:1em}
input{width:100%%;padding:8px;margin:8px 0;box-sizing:border-box}
button{padding:10px 20px;cursor:pointer}</style>
</head>
<body>
<h1>%s Labeler</h1>
<p>Sign in with your AT Protocol identity.</p>
%s
<form action="/auth/oauth/authorize" method="GET">
<input name="handle" placeholder="your.handle.com" required>
<button type="submit">Sign In</button>
</form>
</body></html>`,
s.config.Labeler.ClientShortName,
s.config.Labeler.ClientShortName,
func() string {
if errorMsg != "" {
return fmt.Sprintf(`<div class="error">%s</div>`, template.HTMLEscapeString(errorMsg))
}
return ""
}(),
)
}
func (s *Server) handleAuthorize(w http.ResponseWriter, r *http.Request) {
handle := strings.TrimSpace(r.URL.Query().Get("handle"))
if handle == "" {
http.Redirect(w, r, "/auth/login?error=Handle+is+required", http.StatusFound)
return
}
handle = strings.TrimPrefix(handle, "@")
did, _, _, err := atproto.ResolveIdentity(r.Context(), handle)
if err != nil {
slog.Warn("Failed to resolve handle for labeler login", "handle", handle, "error", err)
http.Redirect(w, r, "/auth/login?error=Could+not+resolve+handle", http.StatusFound)
return
}
authURL, err := s.clientApp.StartAuthFlow(r.Context(), did)
if err != nil {
slog.Error("Failed to start OAuth flow", "error", err)
http.Redirect(w, r, "/auth/login?error=OAuth+initialization+failed", http.StatusFound)
return
}
http.Redirect(w, r, authURL, http.StatusFound)
}
func (s *Server) handleCallback(w http.ResponseWriter, r *http.Request) {
sessionData, err := s.clientApp.ProcessCallback(r.Context(), r.URL.Query())
if err != nil {
slog.Error("OAuth callback failed", "error", err)
http.Redirect(w, r, "/auth/login?error=OAuth+authentication+failed", http.StatusFound)
return
}
did := sessionData.AccountDID.String()
_, handle, _, err := atproto.ResolveIdentity(r.Context(), did)
if err != nil {
handle = did
}
// Only allow the owner
if did != s.config.Labeler.OwnerDID {
slog.Warn("Non-owner attempted labeler access", "did", did, "handle", handle, "owner", s.config.Labeler.OwnerDID)
http.Redirect(w, r, "/auth/login?error=Access+denied:+Only+the+labeler+owner+can+access+the+admin+panel", http.StatusFound)
return
}
token, _, err := s.auth.CreateSession(did, handle, r.UserAgent(), clientIPPrefix(r))
if err != nil {
http.Error(w, "Failed to create session", http.StatusInternalServerError)
return
}
setSessionCookie(w, r, token)
http.Redirect(w, r, "/", http.StatusFound)
}
func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
if token, ok := getSessionCookie(r); ok {
s.auth.DeleteSession(token)
}
clearSessionCookie(w)
http.Redirect(w, r, "/auth/login", http.StatusFound)
}