Files
vasyansk 38005c0618 fix(api): add snake_case json tags to Endpoint/Task/request bodies for frontend contract
Go's encoding/json does not bridge snake_case <-> PascalCase field names,
so store.Endpoint, store.Task and the anonymous request bodies in
accounts.go/auth.go were silently decoding empty/zero values from the
frontend's snake_case JSON contract (tls_mode, role_label,
src_endpoint_id, dst_endpoint_id, src_login/pass, dst_login/pass).
Adds explicit json tags; DB layer is unaffected since pgx binds by
positional params, not struct-tag reflection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
2026-07-01 19:04:46 +07:00

72 lines
2.1 KiB
Go

package httpapi
import (
"crypto/subtle"
"encoding/json"
"net/http"
"time"
"github.com/vasyansk/imap-copier/internal/config"
"github.com/vasyansk/imap-copier/internal/crypto"
"github.com/vasyansk/imap-copier/internal/orchestrator"
"github.com/vasyansk/imap-copier/internal/store"
"github.com/vasyansk/imap-copier/internal/wshub"
)
const cookieName = "session"
type Server struct {
cfg config.Config
store *store.Store
orch *orchestrator.Orchestrator
hub *wshub.Hub
}
func NewServer(cfg config.Config, s *store.Store, orch *orchestrator.Orchestrator, hub *wshub.Hub) *Server {
return &Server{cfg: cfg, store: s, orch: orch, hub: hub}
}
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
var body struct {
User string `json:"user"`
Pass string `json:"pass"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
uOK := subtle.ConstantTimeCompare([]byte(body.User), []byte(s.cfg.AuthUser)) == 1
pOK := subtle.ConstantTimeCompare([]byte(body.Pass), []byte(s.cfg.AuthPass)) == 1
if !uOK || !pOK {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
tok := crypto.SignSession(s.cfg.SessionSecret, body.User, time.Now().Add(24*time.Hour))
http.SetCookie(w, &http.Cookie{
Name: cookieName, Value: tok, Path: "/",
HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: 86400,
})
w.WriteHeader(http.StatusOK)
}
func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: cookieName, Value: "", Path: "/", MaxAge: -1})
w.WriteHeader(http.StatusOK)
}
func (s *Server) requireAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie(cookieName)
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
user, ok := crypto.VerifySession(s.cfg.SessionSecret, c.Value, time.Now())
if !ok || user != s.cfg.AuthUser {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}