This updates gallery handling to support video playback with generated poster thumbnails, adds authenticated admin upload/delete flows, and improves dev/runtime behavior including reliable thumbnail generation and media-safe response handling. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
2.1 KiB
Go
100 lines
2.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"errors"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const sessionCookie = "tk_admin_session"
|
|
|
|
// Config holds admin credentials from the environment.
|
|
type Config struct {
|
|
Username string
|
|
Password string
|
|
Enabled bool
|
|
}
|
|
|
|
// Load reads admin auth settings. Admin is enabled when both user and password are set.
|
|
func Load() Config {
|
|
user := os.Getenv("ADMIN_USER")
|
|
pass := os.Getenv("ADMIN_PASSWORD")
|
|
return Config{
|
|
Username: user,
|
|
Password: pass,
|
|
Enabled: user != "" && pass != "",
|
|
}
|
|
}
|
|
|
|
// Sessions tracks active login tokens in memory.
|
|
type Sessions struct {
|
|
mu sync.RWMutex
|
|
tokens map[string]time.Time
|
|
ttl time.Duration
|
|
}
|
|
|
|
// NewSessions creates a session store.
|
|
func NewSessions() *Sessions {
|
|
return &Sessions{
|
|
tokens: make(map[string]time.Time),
|
|
ttl: 24 * time.Hour,
|
|
}
|
|
}
|
|
|
|
// Create issues a new session token.
|
|
func (s *Sessions) Create() (string, error) {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", err
|
|
}
|
|
token := base64.RawURLEncoding.EncodeToString(b)
|
|
exp := time.Now().Add(s.ttl)
|
|
s.mu.Lock()
|
|
s.tokens[token] = exp
|
|
s.mu.Unlock()
|
|
return token, nil
|
|
}
|
|
|
|
// Valid reports whether a session token is still active.
|
|
func (s *Sessions) Valid(token string) bool {
|
|
if token == "" {
|
|
return false
|
|
}
|
|
s.mu.RLock()
|
|
exp, ok := s.tokens[token]
|
|
s.mu.RUnlock()
|
|
if !ok || time.Now().After(exp) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Delete removes a session token.
|
|
func (s *Sessions) Delete(token string) {
|
|
s.mu.Lock()
|
|
delete(s.tokens, token)
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
// CookieName returns the session cookie name.
|
|
func CookieName() string {
|
|
return sessionCookie
|
|
}
|
|
|
|
// CheckCredentials compares username and password to config using constant-time compare.
|
|
func CheckCredentials(cfg Config, username, password string) bool {
|
|
if !cfg.Enabled {
|
|
return false
|
|
}
|
|
uOK := subtle.ConstantTimeCompare([]byte(username), []byte(cfg.Username)) == 1
|
|
pOK := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Password)) == 1
|
|
return uOK && pOK
|
|
}
|
|
|
|
// ErrDisabled is returned when admin auth is not configured.
|
|
var ErrDisabled = errors.New("admin auth is not configured")
|