Initial commit: PilotVault multi-service project
Add API Server (Go/PocketBase), Web App (Go BFF + Vue), Fly App (Flutter/DJI MSDK), Adobe Plugin, and Docker/Docker AIO deployment configs. Design assets and build artifacts are gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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: <pb token>)
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user