package main import ( "bytes" "encoding/json" "io" "net/http" "net/url" "strings" "time" "github.com/gorilla/websocket" ) const ( cookieToken = "dji_token" cookieEmail = "dji_email" cookieApi = "dji_api" ) var client = &http.Client{Timeout: 15 * time.Second} // apiBaseFor returns the API Server base for this request: the per-session value // chosen at login (cookie), falling back to the server's configured default. func (a *App) apiBaseFor(r *http.Request) string { if c, err := r.Cookie(cookieApi); err == nil && c.Value != "" { return c.Value } return a.apiBase } func normalizeURL(u string) string { u = strings.TrimSpace(u) if u == "" { return "" } if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") { u = "http://" + u } return strings.TrimRight(u, "/") } var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // requireAuth gates a handler on the presence of a session cookie. func (a *App) requireAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := r.Cookie(cookieToken); err != nil { writeJSON(w, http.StatusUnauthorized, map[string]any{"error": "not signed in"}) return } next(w, r) } } // POST /bff/login {"email","password"} → proxies to API Server /api/auth/login. func (a *App) handleLogin(w http.ResponseWriter, r *http.Request) { var body struct { Email string `json:"email"` Password string `json:"password"` ApiBase string `json:"apiBase"` // optional API Server override (per session) } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid json"}) return } base := a.apiBase if b := normalizeURL(body.ApiBase); b != "" { base = b } payload, _ := json.Marshal(map[string]string{"email": body.Email, "password": body.Password}) resp, err := client.Post(base+"/api/auth/login", "application/json", bytes.NewReader(payload)) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { // Relay PocketBase's error (e.g. invalid credentials). w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) _, _ = w.Write(data) return } var auth struct { Token string `json:"token"` Record struct { Email string `json:"email"` } `json:"record"` } if err := json.Unmarshal(data, &auth); err != nil || auth.Token == "" { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "unexpected auth response"}) return } setCookie(w, cookieToken, auth.Token, true) setCookie(w, cookieEmail, auth.Record.Email, false) setCookie(w, cookieApi, base, true) // remember the chosen API Server for this session writeJSON(w, http.StatusOK, map[string]any{"email": auth.Record.Email}) } // GET /bff/config → the server's default API Server address (to prefill the field). func (a *App) handleConfig(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"apiBase": a.apiBase}) } // POST /bff/logout func (a *App) handleLogout(w http.ResponseWriter, r *http.Request) { clearCookie(w, cookieToken) clearCookie(w, cookieEmail) clearCookie(w, cookieApi) writeJSON(w, http.StatusOK, map[string]any{"ok": true}) } // GET /bff/me → current session info incl. role (or 401). Proxies to the API // Server /api/me so the role is always fresh (reflects admin promotions). func (a *App) handleMe(w http.ResponseWriter, r *http.Request) { token := tokenOf(r) if token == "" { writeJSON(w, http.StatusUnauthorized, map[string]any{"error": "not signed in"}) return } req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/me", nil) req.Header.Set("Authorization", token) resp, err := client.Do(req) if err != nil { // Fall back to the cookie email so the session survives a brief API blip. email := "" if c, err := r.Cookie(cookieEmail); err == nil { email = c.Value } writeJSON(w, http.StatusOK, map[string]any{"email": email, "role": "user"}) return } defer resp.Body.Close() relay(w, resp) } // GET /bff/devices → API Server /api/devices func (a *App) handleDevices(w http.ResponseWriter, r *http.Request) { a.proxyGET(w, a.apiBaseFor(r)+"/api/devices") } // GET /bff/devices/{id}/track → API Server /api/devices/{id}/track func (a *App) handleTrack(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") a.proxyGET(w, a.apiBaseFor(r)+"/api/devices/"+id+"/track") } // POST /bff/devices/{id}/command → API Server /api/devices/{id}/command func (a *App) handleCommand(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) resp, err := client.Post(a.apiBaseFor(r)+"/api/devices/"+id+"/command", "application/json", bytes.NewReader(body)) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() relay(w, resp) } // tokenOf returns the PocketBase auth token stored in the session cookie. func tokenOf(r *http.Request) string { if c, err := r.Cookie(cookieToken); err == nil { return c.Value } return "" } // GET /bff/preferences → API Server /api/preferences (Authorization: session token) func (a *App) handleGetPrefs(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/preferences", nil) req.Header.Set("Authorization", tokenOf(r)) resp, err := client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() relay(w, resp) } // PUT /bff/preferences → API Server /api/preferences (Authorization: session token) func (a *App) handlePutPrefs(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPut, a.apiBaseFor(r)+"/api/preferences", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() relay(w, resp) } // GET /bff/integrations/opensky → API Server /api/integrations/opensky func (a *App) handleGetOpenSky(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/integrations/opensky", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // PUT /bff/integrations/opensky → API Server /api/integrations/opensky func (a *App) handlePutOpenSky(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPut, a.apiBaseFor(r)+"/api/integrations/opensky", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // POST /bff/integrations/opensky/health → API Server /api/integrations/opensky/health func (a *App) handleOpenSkyHealth(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/integrations/opensky/health", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/integrations/opensky/states → API Server /api/integrations/opensky/states. // Live aircraft positions for the Live map. func (a *App) handleOpenSkyStates(w http.ResponseWriter, r *http.Request) { target := a.apiBaseFor(r) + "/api/integrations/opensky/states" // Forward the optional ?bbox= override the Live map sends in auto mode. if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } req, _ := http.NewRequest(http.MethodGet, target, nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/integrations/filetransfer → API Server /api/integrations/filetransfer func (a *App) handleGetFileTransfer(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/integrations/filetransfer", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // PUT /bff/integrations/filetransfer → API Server /api/integrations/filetransfer func (a *App) handlePutFileTransfer(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPut, a.apiBaseFor(r)+"/api/integrations/filetransfer", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // POST /bff/integrations/filetransfer/health → API Server /api/integrations/filetransfer/health func (a *App) handleFileTransferHealth(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/integrations/filetransfer/health", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/integrations/localstorage → API Server /api/integrations/localstorage func (a *App) handleGetLocalStorage(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/integrations/localstorage", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // PUT /bff/integrations/localstorage → API Server /api/integrations/localstorage func (a *App) handlePutLocalStorage(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPut, a.apiBaseFor(r)+"/api/integrations/localstorage", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // POST /bff/integrations/localstorage/health → API Server /api/integrations/localstorage/health func (a *App) handleLocalStorageHealth(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/integrations/localstorage/health", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/integrations/webdav → API Server /api/integrations/webdav func (a *App) handleGetWebDav(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/integrations/webdav", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // PUT /bff/integrations/webdav → API Server /api/integrations/webdav func (a *App) handlePutWebDav(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPut, a.apiBaseFor(r)+"/api/integrations/webdav", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // POST /bff/integrations/webdav/health → API Server /api/integrations/webdav/health func (a *App) handleWebDavHealth(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/integrations/webdav/health", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/users → API Server /api/users (admin only, enforced upstream) func (a *App) handleListUsers(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/users", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // POST /bff/users → API Server /api/users func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/users", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // PATCH /bff/users/{id} → API Server /api/users/{id} func (a *App) handleUpdateUser(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPatch, a.apiBaseFor(r)+"/api/users/"+url.PathEscape(id), bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // DELETE /bff/users/{id} → API Server /api/users/{id} func (a *App) handleDeleteUser(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") req, _ := http.NewRequest(http.MethodDelete, a.apiBaseFor(r)+"/api/users/"+url.PathEscape(id), nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/orgs → API Server /api/orgs (manager only, enforced upstream) func (a *App) handleListOrgs(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/orgs", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // POST /bff/orgs → API Server /api/orgs func (a *App) handleCreateOrg(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/orgs", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // PATCH /bff/orgs/{id} → API Server /api/orgs/{id} func (a *App) handleUpdateOrg(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPatch, a.apiBaseFor(r)+"/api/orgs/"+url.PathEscape(id), bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // DELETE /bff/orgs/{id} → API Server /api/orgs/{id} func (a *App) handleDeleteOrg(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") req, _ := http.NewRequest(http.MethodDelete, a.apiBaseFor(r)+"/api/orgs/"+url.PathEscape(id), nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } /* ---------- Logbook: drones ---------- */ // GET /bff/drones → API Server /api/drones func (a *App) handleListDrones(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/drones", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // POST /bff/drones → API Server /api/drones func (a *App) handleCreateDrone(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/drones", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // PATCH /bff/drones/{id} → API Server /api/drones/{id} func (a *App) handleUpdateDrone(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPatch, a.apiBaseFor(r)+"/api/drones/"+url.PathEscape(id), bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // DELETE /bff/drones/{id} → API Server /api/drones/{id} func (a *App) handleDeleteDrone(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") req, _ := http.NewRequest(http.MethodDelete, a.apiBaseFor(r)+"/api/drones/"+url.PathEscape(id), nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } /* ---------- Logbook: flights ---------- */ // GET /bff/flights → API Server /api/flights func (a *App) handleListFlights(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/flights", nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // POST /bff/flights → API Server /api/flights func (a *App) handleCreateFlight(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/flights", bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // PATCH /bff/flights/{id} → API Server /api/flights/{id} func (a *App) handleUpdateFlight(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPatch, a.apiBaseFor(r)+"/api/flights/"+url.PathEscape(id), bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // DELETE /bff/flights/{id} → API Server /api/flights/{id} func (a *App) handleDeleteFlight(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") req, _ := http.NewRequest(http.MethodDelete, a.apiBaseFor(r)+"/api/flights/"+url.PathEscape(id), nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/logbook/export → API Server /api/logbook/export. Unlike the JSON // endpoints this streams a CSV download, so it preserves the upstream // Content-Type + Content-Disposition instead of forcing application/json. func (a *App) handleExportLogbook(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodGet, a.apiBaseFor(r)+"/api/logbook/export", nil) req.Header.Set("Authorization", tokenOf(r)) resp, err := client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() if ct := resp.Header.Get("Content-Type"); ct != "" { w.Header().Set("Content-Type", ct) } if cd := resp.Header.Get("Content-Disposition"); cd != "" { w.Header().Set("Content-Disposition", cd) } w.WriteHeader(resp.StatusCode) _, _ = io.Copy(w, resp.Body) } /* ---------- Documents ---------- */ // GET /bff/documents → API Server /api/documents (preserves ?expiring=N). func (a *App) handleListDocuments(w http.ResponseWriter, r *http.Request) { target := a.apiBaseFor(r) + "/api/documents" if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } req, _ := http.NewRequest(http.MethodGet, target, nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // POST /bff/documents → API Server /api/documents. Streams the multipart body // through unchanged (metadata fields + the optional file blob). func (a *App) handleCreateDocument(w http.ResponseWriter, r *http.Request) { req, _ := http.NewRequest(http.MethodPost, a.apiBaseFor(r)+"/api/documents", r.Body) req.Header.Set("Authorization", tokenOf(r)) if ct := r.Header.Get("Content-Type"); ct != "" { req.Header.Set("Content-Type", ct) } req.ContentLength = r.ContentLength a.doRelay(w, req) } // PATCH /bff/documents/{id} → API Server /api/documents/{id} (JSON metadata). func (a *App) handleUpdateDocument(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") body, _ := io.ReadAll(r.Body) req, _ := http.NewRequest(http.MethodPatch, a.apiBaseFor(r)+"/api/documents/"+url.PathEscape(id), bytes.NewReader(body)) req.Header.Set("Authorization", tokenOf(r)) req.Header.Set("Content-Type", "application/json") a.doRelay(w, req) } // DELETE /bff/documents/{id} → API Server /api/documents/{id} func (a *App) handleDeleteDocument(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") req, _ := http.NewRequest(http.MethodDelete, a.apiBaseFor(r)+"/api/documents/"+url.PathEscape(id), nil) req.Header.Set("Authorization", tokenOf(r)) a.doRelay(w, req) } // GET /bff/documents/{id}/file → API Server /api/documents/{id}/file. Streams // the blob, preserving the upstream Content-Type + Content-Disposition. The // `?inline=1` flag (preview vs. download) is forwarded unchanged. func (a *App) handleDownloadDocument(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") target := a.apiBaseFor(r) + "/api/documents/" + url.PathEscape(id) + "/file" if r.URL.RawQuery != "" { target += "?" + r.URL.RawQuery } req, _ := http.NewRequest(http.MethodGet, target, nil) req.Header.Set("Authorization", tokenOf(r)) resp, err := client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() for _, h := range []string{"Content-Type", "Content-Disposition", "Content-Length"} { if v := resp.Header.Get(h); v != "" { w.Header().Set(h, v) } } w.WriteHeader(resp.StatusCode) _, _ = io.Copy(w, resp.Body) } // doRelay executes an outbound request and relays the response verbatim. func (a *App) doRelay(w http.ResponseWriter, req *http.Request) { resp, err := client.Do(req) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() relay(w, resp) } func (a *App) proxyGET(w http.ResponseWriter, url string) { resp, err := client.Get(url) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": "cannot reach API server"}) return } defer resp.Body.Close() relay(w, resp) } 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) } // GET /bff/ws → relays the API Server's /ws/ui websocket to the browser. func (a *App) handleWS(w http.ResponseWriter, r *http.Request) { if _, err := r.Cookie(cookieToken); err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } wsBase := strings.Replace(a.apiBaseFor(r), "http", "ws", 1) // http→ws, https→wss upstream, _, err := websocket.DefaultDialer.Dial(wsBase+"/ws/ui", nil) if err != nil { http.Error(w, "upstream websocket unavailable", http.StatusBadGateway) return } downstream, err := upgrader.Upgrade(w, r, nil) if err != nil { upstream.Close() return } go pipe(upstream, downstream) pipe(downstream, upstream) } func pipe(src, dst *websocket.Conn) { defer src.Close() defer dst.Close() for { mt, msg, err := src.ReadMessage() if err != nil { return } if err := dst.WriteMessage(mt, msg); err != nil { return } } } func setCookie(w http.ResponseWriter, name, value string, httpOnly bool) { http.SetCookie(w, &http.Cookie{ Name: name, Value: value, Path: "/", HttpOnly: httpOnly, SameSite: http.SameSiteLaxMode, MaxAge: 7 * 24 * 3600, }) } func clearCookie(w http.ResponseWriter, name string) { http.SetCookie(w, &http.Cookie{ Name: name, Value: "", Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: -1, }) }