package main import ( "crypto/rand" "crypto/subtle" "encoding/hex" "net/http" "strings" "time" "github.com/labstack/echo/v4" ) func (a *App) isAdminRequest(c echo.Context) bool { header := strings.TrimSpace(c.Request().Header.Get(echo.HeaderAuthorization)) if header == "" || !strings.HasPrefix(strings.ToLower(header), "bearer ") { return false } token := strings.TrimSpace(header[7:]) if token == "" { return false } return a.sessions.ValidateToken(token) } func (a *App) adminOnly(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { a.sessions.PurgeExpired() header := strings.TrimSpace(c.Request().Header.Get(echo.HeaderAuthorization)) if header == "" || !strings.HasPrefix(strings.ToLower(header), "bearer ") { return writeError(c, http.StatusUnauthorized, "missing admin token") } token := strings.TrimSpace(header[7:]) if token == "" || !a.sessions.ValidateToken(token) { return writeError(c, http.StatusUnauthorized, "invalid or expired admin token") } return next(c) } } func (a *App) verifyAdmin(username, password string) bool { u := strings.TrimSpace(username) p := strings.TrimSpace(password) if u == "" || p == "" { return false } userMatch := subtle.ConstantTimeCompare([]byte(u), []byte(a.cfg.AdminUser)) == 1 passMatch := subtle.ConstantTimeCompare([]byte(p), []byte(a.cfg.AdminPass)) == 1 return userMatch && passMatch } func (s *SessionStore) CreateToken() (string, time.Time, error) { raw := make([]byte, 32) if _, err := rand.Read(raw); err != nil { return "", time.Time{}, err } token := hex.EncodeToString(raw) expires := time.Now().UTC().Add(s.duration) s.mu.Lock() s.tokens[token] = expires s.mu.Unlock() return token, expires, nil } func (s *SessionStore) ValidateToken(token string) bool { s.mu.RLock() expires, ok := s.tokens[token] s.mu.RUnlock() if !ok { return false } return time.Now().UTC().Before(expires) } func (s *SessionStore) DeleteToken(token string) { s.mu.Lock() delete(s.tokens, token) s.mu.Unlock() } func (s *SessionStore) PurgeExpired() { now := time.Now().UTC() s.mu.Lock() for token, expiry := range s.tokens { if now.After(expiry) { delete(s.tokens, token) } } s.mu.Unlock() }