94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
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()
|
|
}
|