refactor: modularize chat backend, update docs for .m4s segments

This commit is contained in:
zwitschi committed 2026-08-07 13:16:44 +02:00
1 parent 46e878bef6
commit b3a10ba3ef
24 files changed
+3771 -2781

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
package auth
import (
"crypto/rand"
"encoding/hex"
"net/http"
"strings"
"sync"
"time"
)
// SessionStore manages admin authentication sessions.
type SessionStore struct {
mu sync.Mutex
sessions map[string]time.Time
}
// NewSessionStore creates a new session store.
func NewSessionStore() *SessionStore {
return &SessionStore{
sessions: make(map[string]time.Time),
}
}
// GenerateToken creates a cryptographically random 64-char hex session token.
func GenerateToken() (string, error) {
buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return hex.EncodeToString(buf), nil
}
// Validate checks if a session token is valid and not expired.
// Returns true if valid, false otherwise. Expired sessions are cleaned up.
func (s *SessionStore) Validate(token string) bool {
if token == "" {
return false
}
s.mu.Lock()
defer s.mu.Unlock()
expiresAt, ok := s.sessions[token]
if !ok {
return false
}
if time.Now().After(expiresAt) {
delete(s.sessions, token)
return false
}
return true
}
// Add stores a session token with expiry.
func (s *SessionStore) Add(token string, expiresAt time.Time) {
s.mu.Lock()
defer s.mu.Unlock()
s.sessions[token] = expiresAt
}
// Remove deletes a session token.
func (s *SessionStore) Remove(token string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.sessions, token)
}
// CountActive returns the number of unexpired sessions.
func (s *SessionStore) CountActive() int {
now := time.Now()
s.mu.Lock()
defer s.mu.Unlock()
for token, expiresAt := range s.sessions {
if now.After(expiresAt) {
delete(s.sessions, token)
}
}
return len(s.sessions)
}
// CookieSecure determines if the session cookie should have Secure flag.
func CookieSecure(r *http.Request) bool {
if forwardedProto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); forwardedProto != "" {
return strings.EqualFold(forwardedProto, "https")
}
return r.TLS != nil
}
// SetCookie writes the admin session cookie.
func SetCookie(w http.ResponseWriter, r *http.Request, cookieName, token string, expiresAt time.Time) {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: token,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: CookieSecure(r),
Expires: expiresAt,
MaxAge: int(time.Until(expiresAt).Seconds()),
})
}
// ClearCookie removes the admin session cookie.
func ClearCookie(w http.ResponseWriter, r *http.Request, cookieName string) {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: "",
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
Secure: CookieSecure(r),
Expires: time.Unix(0, 0),
MaxAge: -1,
})
}
// GetTokenFromCookie extracts the session token from the request cookie.
func GetTokenFromCookie(r *http.Request, cookieName string) (string, bool) {
cookie, err := r.Cookie(cookieName)
if err != nil || cookie.Value == "" {
return "", false
}
return cookie.Value, true
}
// RequireAdmin returns middleware that checks for a valid admin session.
func RequireAdmin(store *SessionStore, cookieName string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token, ok := GetTokenFromCookie(r, cookieName)
if !ok || !store.Validate(token) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
}