claude-web-chat/auth.go
djuka 3283888738
All checks were successful
Tests / unit-tests (push) Successful in 51s
Inicijalna implementacija Claude Web Chat (Faza 1 - CLI mod)
- Login sa session cookie autentifikacijom
- Lista projekata iz filesystem-a
- Chat sa Claude CLI preko WebSocket-a
- Streaming NDJSON parsiranje iz CLI stdout-a
- Sesija zivi nezavisno od browsera (reconnect replay)
- Sidebar sa .md fajlovima i markdown renderovanjem
- Dark tema, htmx + Go templates
- 47 unit testova

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 05:03:40 +00:00

128 lines
2.5 KiB
Go

package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"net/http"
"sync"
"time"
)
const (
sessionCookieName = "cwc_session"
sessionMaxAge = 24 * time.Hour
)
type Session struct {
Token string
Username string
CreatedAt time.Time
}
type SessionManager struct {
sessions map[string]*Session
secret string
mu sync.RWMutex
}
func NewSessionManager(secret string) *SessionManager {
return &SessionManager{
sessions: make(map[string]*Session),
secret: secret,
}
}
func (sm *SessionManager) Create(username string) *Session {
token := generateToken()
sig := sm.sign(token)
signedToken := token + "." + sig
sess := &Session{
Token: signedToken,
Username: username,
CreatedAt: time.Now(),
}
sm.mu.Lock()
sm.sessions[signedToken] = sess
sm.mu.Unlock()
return sess
}
func (sm *SessionManager) Get(token string) *Session {
sm.mu.RLock()
sess, ok := sm.sessions[token]
sm.mu.RUnlock()
if !ok {
return nil
}
if time.Since(sess.CreatedAt) > sessionMaxAge {
sm.Delete(token)
return nil
}
return sess
}
func (sm *SessionManager) Delete(token string) {
sm.mu.Lock()
delete(sm.sessions, token)
sm.mu.Unlock()
}
func (sm *SessionManager) sign(token string) string {
h := hmac.New(sha256.New, []byte(sm.secret))
h.Write([]byte(token))
return hex.EncodeToString(h.Sum(nil))
}
func generateToken() string {
b := make([]byte, 32)
rand.Read(b)
return hex.EncodeToString(b)
}
// SetSessionCookie sets the session cookie on the response.
func SetSessionCookie(w http.ResponseWriter, sess *Session) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: sess.Token,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
MaxAge: int(sessionMaxAge.Seconds()),
})
}
// ClearSessionCookie removes the session cookie.
func ClearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
HttpOnly: true,
MaxAge: -1,
})
}
// AuthMiddleware protects routes that require authentication.
func AuthMiddleware(sm *SessionManager, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(sessionCookieName)
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
sess := sm.Get(cookie.Value)
if sess == nil {
ClearSessionCookie(w)
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
})
}