Files
2026-07-20 21:41:51 +02:00

130 lines
3.9 KiB
Go

package platform
import (
"errors"
"net"
"net/http"
"strings"
"time"
)
const (
sessionCookie = "lp_session"
loginCSRFCookie = "lp_login_csrf"
)
func (s *Server) currentUser(r *http.Request) (User, Session, bool) {
cookie, err := r.Cookie(sessionCookie)
if err != nil || strings.TrimSpace(cookie.Value) == "" {
return User{}, Session{}, false
}
session, ok := s.store.GetSession(tokenHash(cookie.Value), time.Now().UTC())
if !ok {
return User{}, Session{}, false
}
user, ok := s.store.GetUser(session.UserID)
if !ok || !user.Active {
return User{}, Session{}, false
}
return user, session, true
}
func (s *Server) requireUser(w http.ResponseWriter, r *http.Request) (User, Session, bool) {
user, session, ok := s.currentUser(r)
if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return User{}, Session{}, false
}
return user, session, true
}
func (s *Server) requireRole(w http.ResponseWriter, r *http.Request, roles ...Role) (User, Session, bool) {
user, session, ok := s.requireUser(w, r)
if !ok {
return User{}, Session{}, false
}
for _, role := range roles {
if user.Role == role {
return user, session, true
}
}
http.Error(w, "forbidden", http.StatusForbidden)
return User{}, Session{}, false
}
func (s *Server) verifyCSRF(r *http.Request, session Session) bool {
if err := r.ParseForm(); err != nil {
return false
}
return constantTokenEqual(session.CSRFToken, r.FormValue("csrf"))
}
func (s *Server) newSession(user User) (string, Session, error) {
raw, err := randomToken(32)
if err != nil {
return "", Session{}, err
}
csrf, err := randomToken(24)
if err != nil {
return "", Session{}, err
}
now := time.Now().UTC()
session := Session{IDHash: tokenHash(raw), UserID: user.ID, CSRFToken: csrf, CreatedAt: now.Unix(), ExpiresAt: now.Add(s.cfg.SessionTTL).Unix()}
if err := s.store.CreateSession(session); err != nil {
return "", Session{}, err
}
return raw, session, nil
}
func (s *Server) setSessionCookie(w http.ResponseWriter, value string, expires time.Time) {
http.SetCookie(w, &http.Cookie{Name: sessionCookie, Value: value, Path: "/", HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteStrictMode, Expires: expires, MaxAge: int(time.Until(expires).Seconds())})
}
func (s *Server) clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{Name: sessionCookie, Value: "", Path: "/", HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteStrictMode, MaxAge: -1, Expires: time.Unix(0, 0)})
}
func (s *Server) loginCSRF(w http.ResponseWriter, r *http.Request) string {
if cookie, err := r.Cookie(loginCSRFCookie); err == nil && len(cookie.Value) >= 20 {
return cookie.Value
}
value, _ := randomToken(24)
http.SetCookie(w, &http.Cookie{Name: loginCSRFCookie, Value: value, Path: "/login", HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteStrictMode, MaxAge: 600})
return value
}
func (s *Server) checkLoginCSRF(r *http.Request) bool {
cookie, err := r.Cookie(loginCSRFCookie)
if err != nil {
return false
}
return constantTokenEqual(cookie.Value, r.FormValue("csrf"))
}
func remoteIP(r *http.Request) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err == nil {
return host
}
return r.RemoteAddr
}
func bearerToken(r *http.Request) string {
value := strings.TrimSpace(r.Header.Get("Authorization"))
if len(value) < 8 || !strings.EqualFold(value[:7], "Bearer ") {
return ""
}
return strings.TrimSpace(value[7:])
}
func (s *Server) apiActor(r *http.Request) (User, error) {
if token := bearerToken(r); token != "" && constantTokenEqual(s.cfg.AdminAPIToken, token) {
return User{ID: "api_admin", Username: "api", DisplayName: "Management API", Role: RoleAdmin, Active: true}, nil
}
user, _, ok := s.currentUser(r)
if ok && (user.Role == RoleAdmin || user.Role == RoleReseller) {
return user, nil
}
return User{}, errors.New("unauthorized")
}