mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-26 04:04:15 +00:00
118 lines
3.4 KiB
Go
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)
|
|
}
|