109 lines
3.0 KiB
Go
109 lines
3.0 KiB
Go
package api
|
|
|
|
import (
|
|
"linkport/internal/alerts"
|
|
"linkport/internal/gateway"
|
|
"linkport/internal/security"
|
|
"linkport/internal/store"
|
|
"linkport/internal/syncjob"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type session struct {
|
|
User, CSRF, Revision string
|
|
Expires time.Time
|
|
}
|
|
type attempt struct {
|
|
Count int
|
|
Until time.Time
|
|
}
|
|
type probe struct {
|
|
Key, Fingerprint, Host string
|
|
Port int
|
|
Until time.Time
|
|
Session string
|
|
}
|
|
type Server struct {
|
|
alerts *alerts.Engine
|
|
syncer *syncjob.Engine
|
|
store *store.Store
|
|
manager *gateway.Manager
|
|
mu sync.Mutex
|
|
ops sync.Mutex
|
|
sessions map[string]session
|
|
attempts map[string]attempt
|
|
probes map[string]probe
|
|
secure bool
|
|
authEnabled bool
|
|
devCSRF string
|
|
}
|
|
|
|
func New(s *store.Store, m *gateway.Manager, secure bool, authentication ...bool) *Server {
|
|
enabled := true
|
|
if len(authentication) > 0 {
|
|
enabled = authentication[0]
|
|
}
|
|
server := &Server{store: s, manager: m, sessions: map[string]session{}, attempts: map[string]attempt{}, probes: map[string]probe{}, secure: secure, authEnabled: enabled, devCSRF: security.Token(), alerts: alerts.New(s, m)}
|
|
server.alerts.Start()
|
|
m.OnShutdown(server.alerts.Close)
|
|
server.syncer = syncjob.New(s, func() func() { server.ops.Lock(); return server.ops.Unlock }, func() bool {
|
|
gateways, err := s.List()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, p := range gateways {
|
|
if m.Status(p.ID).Running {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
server.syncer.Start()
|
|
m.OnShutdown(server.syncer.Close)
|
|
return server
|
|
}
|
|
func (s *Server) Handler(static http.Handler) http.Handler {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /readyz", s.readiness)
|
|
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { respond(w, 200, map[string]string{"status": "ok"}) })
|
|
mux.HandleFunc("/api/v1/", s.serve)
|
|
mux.Handle("/", static)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'")
|
|
mux.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
func (s *Server) serve(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
path := strings.Trim(strings.TrimPrefix(r.URL.Path, "/api/v1/"), "/")
|
|
if r.Method != "GET" {
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
u, e := url.Parse(origin)
|
|
if e != nil || u.Host != r.Host || (u.Scheme != "http" && u.Scheme != "https") {
|
|
fail(w, 403, "origin rejected")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if path == "login" && r.Method == "POST" {
|
|
if !s.authEnabled {
|
|
fail(w, 400, "login is disabled in development mode")
|
|
return
|
|
}
|
|
s.login(w, r)
|
|
return
|
|
}
|
|
w, identity, ok := s.authorize(w, r, path)
|
|
if !ok {
|
|
return
|
|
}
|
|
s.dispatch(w, r, path, identity)
|
|
}
|