Something went wrong. Try again.
An event companion and onboarding experience for the ATmosphere (alpha) atmo.quest
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119package api
import ( "log/slog" "net/http" "strings"
"atmoquest/internal/connection" "atmoquest/internal/users")
// sessionResponse is the JSON shape for GET /api/v1/auth/session.type sessionResponse struct { DID string `json:"did"` Handle string `json:"handle"` IsAdmin bool `json:"isAdmin"`}
// GetSession returns the current authenticated user's session info.// GET /api/v1/auth/sessionfunc (h *Handlers) GetSession(w http.ResponseWriter, r *http.Request) { did, ok := h.requireAuth(w, r) if !ok { return }
handle := h.resolveHandle(r, did.String())
isAdmin, _ := users.IsAdmin(r.Context(), h.DB, did)
writeJSON(w, http.StatusOK, sessionResponse{ DID: did.String(), Handle: handle, IsAdmin: isAdmin, })}
// OAuthLogin starts the OAuth flow for mobile clients.// POST /api/v1/oauth/login//// Request: {"handle": "alice.bsky.social"}// Response: {"authUrl": "https://...", "state": "..."}func (h *Handlers) OAuthLogin(w http.ResponseWriter, r *http.Request) { var body struct { Handle string `json:"handle"` } if err := decodeBody(r, &body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return }
identifier := strings.TrimSpace(body.Handle) if identifier == "" { writeError(w, http.StatusBadRequest, "handle is required") return }
redirectURL, err := h.Auth.OAuth.StartAuthFlow(r.Context(), identifier) if err != nil { slog.Warn("api: oauth start", "identifier", identifier, "err", err, ) writeError(w, http.StatusBadRequest, "couldn't start sign-in") return }
writeJSON(w, http.StatusOK, map[string]string{ "authUrl": redirectURL, })}
// OAuthCallback completes the OAuth flow for mobile clients.// GET /api/v1/oauth/callback?code=...&state=...//// Response: {"token": "...", "did": "...", "handle": "..."}func (h *Handlers) OAuthCallback(w http.ResponseWriter, r *http.Request) { sessData, err := h.Auth.OAuth.ProcessCallback(r.Context(), r.URL.Query()) if err != nil { slog.Warn("api: oauth callback", "err", err) writeError(w, http.StatusBadRequest, "sign-in failed") return }
// Issue a Bearer token for the mobile app. token := h.Tokens.Issue(sessData.AccountDID.String(), sessData.SessionID)
// Best-effort: record login in users table. go h.Auth.RecordUserLogin(sessData)
// Best-effort: drain pending connections. if h.ConnQueue != nil { sess, err := h.Auth.OAuth.ResumeSession(r.Context(), sessData.AccountDID, sessData.SessionID) if err == nil { res, err := connection.Drain(r.Context(), h.ConnQueue, sess, sess.Data.HostURL, slog.Default(), nil) if err != nil { slog.Warn("api: connect drain", "did", sessData.AccountDID.String(), "err", err) } else if res.Written > 0 || res.Skipped > 0 { slog.Info("api: connect drain", "did", sessData.AccountDID.String(), "written", res.Written, "skipped", res.Skipped) } } }
handle := h.resolveHandle(r, sessData.AccountDID.String())
writeJSON(w, http.StatusOK, map[string]string{ "token": token, "did": sessData.AccountDID.String(), "handle": handle, })}
// Logout revokes the current session.// POST /api/v1/oauth/logoutfunc (h *Handlers) Logout(w http.ResponseWriter, r *http.Request) { h.Auth.Sessions.Clear(w, r) writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})}