package api import ( "bytes" "encoding/json" "io" "net/http" "sync" "time" ) // authProxy forwards login / token-validation to the PocketBase kept behind the // API Server. PocketBase's address lives only here — it is never exposed to or // configurable by clients. The base URL is guarded by a mutex so it can be // retargeted at runtime from the panel's PocketBase settings. type authProxy struct { mu sync.RWMutex baseURL string client *http.Client } func newAuthProxy(baseURL string) *authProxy { return &authProxy{ baseURL: baseURL, client: &http.Client{Timeout: 15 * time.Second}, } } // url returns the current PocketBase base URL. func (a *authProxy) url() string { a.mu.RLock() defer a.mu.RUnlock() return a.baseURL } // setBaseURL retargets the proxy at a new PocketBase address. func (a *authProxy) setBaseURL(u string) { a.mu.Lock() a.baseURL = u a.mu.Unlock() } // POST /api/auth/login // Body: {"email"|"identity":"...","password":"..."} // Proxies to PocketBase users auth-with-password and returns its response verbatim. func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) { var body struct { Email string `json:"email"` Identity string `json:"identity"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "invalid json") return } identity := body.Identity if identity == "" { identity = body.Email } payload, _ := json.Marshal(map[string]string{"identity": identity, "password": body.Password}) req, _ := http.NewRequestWithContext(r.Context(), http.MethodPost, s.auth.url()+"/api/collections/users/auth-with-password", bytes.NewReader(payload)) req.Header.Set("Content-Type", "application/json") resp, err := s.auth.client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach PocketBase", "detail": err.Error()}) return } defer resp.Body.Close() relay(w, resp) } // GET /api/auth/validate (Authorization: ) // Proxies to PocketBase auth-refresh to confirm a token is still valid. func (s *Server) handleAuthValidate(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token == "" { writeJSON(w, http.StatusUnauthorized, map[string]any{"valid": false}) return } req, _ := http.NewRequestWithContext(r.Context(), http.MethodPost, s.auth.url()+"/api/collections/users/auth-refresh", nil) req.Header.Set("Authorization", token) resp, err := s.auth.client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"valid": false, "detail": err.Error()}) return } defer resp.Body.Close() relay(w, resp) } // relay copies an upstream PocketBase response (status + JSON body) to the client. func relay(w http.ResponseWriter, resp *http.Response) { data, _ := io.ReadAll(resp.Body) w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) _, _ = w.Write(data) }