130 lines
3.9 KiB
Go
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")
|
|
}
|