Something went wrong. Try again.
An event companion and onboarding experience for the ATmosphere (alpha) atmo.quest
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129package api
import ( "net/http" "strings"
"github.com/bluesky-social/indigo/atproto/auth/oauth" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax")
// requireAuth extracts the authenticated DID from the request.// Supports both cookie-based sessions (web) and Bearer token auth (mobile).// Returns 401 JSON error if not authenticated.func (h *Handlers) requireAuth(w http.ResponseWriter, r *http.Request) (syntax.DID, bool) { // Try cookie-based session first (web browser). didStr, _ := h.Auth.Sessions.Get(r)
// Fall back to Bearer token (mobile app). if didStr == "" { if authHeader := r.Header.Get("Authorization"); strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") if d, _, err := h.Tokens.Validate(token); err == nil { didStr = d } } }
if didStr == "" { writeError(w, http.StatusUnauthorized, "not authenticated") return "", false } did, err := syntax.ParseDID(didStr) if err != nil { writeError(w, http.StatusUnauthorized, "invalid session") return "", false } return did, true}
// requireSession extracts both the DID and the full OAuth session.// Needed for handlers that must talk to the user's PDS.// Supports both cookie and Bearer token auth.// Returns 401 JSON error if not authenticated.func (h *Handlers) requireSession(w http.ResponseWriter, r *http.Request) (syntax.DID, *oauth.ClientSession, bool) { // Try cookie-based session first. did, sess, err := h.Auth.ResumeSession(r) if err == nil { return did, sess, true }
// Fall back to Bearer token — extract DID + session ID and resume. if authHeader := r.Header.Get("Authorization"); strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") if d, sid, err := h.Tokens.Validate(token); err == nil { parsedDID, err := syntax.ParseDID(d) if err == nil { sess, err := h.Auth.OAuth.ResumeSession(r.Context(), parsedDID, sid) if err == nil { return parsedDID, sess, true } } } }
writeError(w, http.StatusUnauthorized, "not authenticated") return "", nil, false}
// JSONMiddleware sets common headers for all API responses and handles CORS// for web-based clients (Expo web dev server runs on a different origin).func JSONMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff")
// CORS: allow requests from any origin (the mobile app and Expo web // dev server both need cross-origin access). origin := r.Header.Get("Origin") if origin != "" { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Max-Age", "86400") }
// Handle preflight. if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return }
next.ServeHTTP(w, r) })}
// lookupPDS resolves a user's PDS host from cached OAuth session data.func (h *Handlers) lookupPDS(r *http.Request, did syntax.DID) string { if h.DB == nil { return "https://bsky.social" } var host string err := h.DB.QueryRowContext(r.Context(), ` SELECT data ->> 'host_url' FROM oauth_sessions WHERE did = ? ORDER BY updated_at DESC LIMIT 1 `, did.String()).Scan(&host) if err == nil && host != "" { return host } return "https://bsky.social"}
// resolveHandle returns the best-known handle for a DID (from users table or// passed-through as the DID string).func (h *Handlers) resolveHandle(r *http.Request, did string) string { var handle string _ = h.DB.QueryRowContext(r.Context(), `SELECT handle FROM users WHERE did = ?`, did).Scan(&handle) if handle != "" { return handle } // Check if identity can resolve it. dir := identity.DefaultDirectory() ident, err := dir.LookupDID(r.Context(), syntax.DID(did)) if err == nil && ident.Handle != "" { return ident.Handle.String() } return did}