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") }