38005c0618
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
72 lines
2.1 KiB
Go
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)
|
|
})
|
|
}
|