Files

107 lines
2.3 KiB
Go

package labeler
import (
"crypto/rand"
"encoding/base64"
"net/http"
"sync"
)
// Session represents an authenticated admin session.
type Session struct {
DID string
Handle string
}
// Auth manages admin authentication.
type Auth struct {
ownerDID string
sessions map[string]*Session
sessionsMu sync.RWMutex
}
// NewAuth creates a new Auth manager.
func NewAuth(ownerDID string) *Auth {
return &Auth{
ownerDID: ownerDID,
sessions: make(map[string]*Session),
}
}
func (a *Auth) createSession(did, handle string) (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
token := base64.URLEncoding.EncodeToString(b)
a.sessionsMu.Lock()
a.sessions[token] = &Session{DID: did, Handle: handle}
a.sessionsMu.Unlock()
return token, nil
}
func (a *Auth) getSession(token string) *Session {
a.sessionsMu.RLock()
defer a.sessionsMu.RUnlock()
return a.sessions[token]
}
func (a *Auth) deleteSession(token string) {
a.sessionsMu.Lock()
delete(a.sessions, token)
a.sessionsMu.Unlock()
}
const sessionCookieName = "labeler_session"
func setSessionCookie(w http.ResponseWriter, r *http.Request, token string) {
secure := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: token,
Path: "/",
MaxAge: 86400, // 24 hours
HttpOnly: true,
Secure: secure,
SameSite: http.SameSiteLaxMode,
})
}
func clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}
func getSessionCookie(r *http.Request) (string, bool) {
cookie, err := r.Cookie(sessionCookieName)
if err != nil {
return "", false
}
return cookie.Value, true
}
// RequireOwner is middleware that checks the session belongs to the owner DID.
func (a *Auth) RequireOwner(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, ok := getSessionCookie(r)
if !ok {
http.Redirect(w, r, "/auth/login", http.StatusFound)
return
}
session := a.getSession(token)
if session == nil || session.DID != a.ownerDID {
http.Redirect(w, r, "/auth/login", http.StatusFound)
return
}
next.ServeHTTP(w, r)
})
}