initital commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const sessionKey contextKey = "userID"
|
||||
|
||||
// SessionStore is a simple in-memory session store.
|
||||
// Swap for Redis in production.
|
||||
type SessionStore struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]sessionEntry
|
||||
}
|
||||
|
||||
type sessionEntry struct {
|
||||
userID string
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func NewSessionStore() *SessionStore {
|
||||
s := &SessionStore{sessions: make(map[string]sessionEntry)}
|
||||
go s.cleanup()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SessionStore) Create(userID string) string {
|
||||
b := make([]byte, 32)
|
||||
rand.Read(b)
|
||||
token := hex.EncodeToString(b)
|
||||
s.mu.Lock()
|
||||
s.sessions[token] = sessionEntry{userID: userID, expiresAt: time.Now().Add(24 * time.Hour)}
|
||||
s.mu.Unlock()
|
||||
return token
|
||||
}
|
||||
|
||||
func (s *SessionStore) Get(token string) (string, bool) {
|
||||
s.mu.RLock()
|
||||
entry, ok := s.sessions[token]
|
||||
s.mu.RUnlock()
|
||||
if !ok || time.Now().After(entry.expiresAt) {
|
||||
return "", false
|
||||
}
|
||||
return entry.userID, true
|
||||
}
|
||||
|
||||
func (s *SessionStore) Delete(token string) {
|
||||
s.mu.Lock()
|
||||
delete(s.sessions, token)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *SessionStore) cleanup() {
|
||||
for range time.Tick(time.Hour) {
|
||||
s.mu.Lock()
|
||||
for k, v := range s.sessions {
|
||||
if time.Now().After(v.expiresAt) {
|
||||
delete(s.sessions, k)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAuth middleware redirects unauthenticated requests to /login.
|
||||
func (s *SessionStore) RequireAuth(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
userID, ok := s.Get(cookie.Value)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), sessionKey, userID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// SetSession writes the session cookie.
|
||||
func SetSession(w http.ResponseWriter, token string) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 86400,
|
||||
})
|
||||
}
|
||||
|
||||
// ClearSession removes the session cookie.
|
||||
func ClearSession(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user