This commit replaces the Axum/Rust backend with a Gin/Go implementation. The original Rust code has been archived in the 'rust' branch.
152 lines
3.2 KiB
Go
152 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Session struct {
|
|
Username string `json:"username"`
|
|
Role string `json:"role"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
SessionID string `json:"session_id"`
|
|
}
|
|
|
|
type SessionManager struct {
|
|
sessions map[string]Session
|
|
mu sync.RWMutex
|
|
secret []byte
|
|
ttl time.Duration
|
|
}
|
|
|
|
type sessionPayload struct {
|
|
SessionID string `json:"session_id"`
|
|
Username string `json:"username"`
|
|
Role string `json:"role"`
|
|
Exp int64 `json:"exp"`
|
|
}
|
|
|
|
func NewSessionManager(secret []byte, ttl time.Duration) *SessionManager {
|
|
return &SessionManager{
|
|
sessions: make(map[string]Session),
|
|
secret: secret,
|
|
ttl: ttl,
|
|
}
|
|
}
|
|
|
|
func (m *SessionManager) CreateSession(username, role string) (string, error) {
|
|
sessionID := uuid.New().String()
|
|
now := time.Now()
|
|
expiresAt := now.Add(m.ttl)
|
|
|
|
m.mu.Lock()
|
|
m.sessions[sessionID] = Session{
|
|
Username: username,
|
|
Role: role,
|
|
CreatedAt: now,
|
|
ExpiresAt: expiresAt,
|
|
SessionID: sessionID,
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
return m.createSignedToken(sessionID, username, role, expiresAt.Unix())
|
|
}
|
|
|
|
func (m *SessionManager) createSignedToken(sessionID, username, role string, exp int64) (string, error) {
|
|
payload := sessionPayload{
|
|
SessionID: sessionID,
|
|
Username: username,
|
|
Role: role,
|
|
Exp: exp,
|
|
}
|
|
|
|
payloadJSON, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
|
|
|
|
h := hmac.New(sha256.New, m.secret)
|
|
h.Write(payloadJSON)
|
|
signature := h.Sum(nil)
|
|
signatureB64 := base64.RawURLEncoding.EncodeToString(signature)
|
|
|
|
return fmt.Sprintf("%s.%s", payloadB64, signatureB64), nil
|
|
}
|
|
|
|
func (m *SessionManager) ValidateSession(token string) (*Session, string, error) {
|
|
parts := strings.Split(token, ".")
|
|
if len(parts) != 2 {
|
|
return nil, "", fmt.Errorf("invalid token format")
|
|
}
|
|
|
|
payloadB64 := parts[0]
|
|
signatureB64 := parts[1]
|
|
|
|
payloadJSON, err := base64.RawURLEncoding.DecodeString(payloadB64)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
signature, err := base64.RawURLEncoding.DecodeString(signatureB64)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
h := hmac.New(sha256.New, m.secret)
|
|
h.Write(payloadJSON)
|
|
if !hmac.Equal(signature, h.Sum(nil)) {
|
|
return nil, "", fmt.Errorf("invalid signature")
|
|
}
|
|
|
|
var payload sessionPayload
|
|
if err := json.Unmarshal(payloadJSON, &payload); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if time.Now().Unix() > payload.Exp {
|
|
return nil, "", fmt.Errorf("token expired")
|
|
}
|
|
|
|
m.mu.RLock()
|
|
session, ok := m.sessions[payload.SessionID]
|
|
m.mu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, "", fmt.Errorf("session not found")
|
|
}
|
|
|
|
return &session, "", nil
|
|
}
|
|
|
|
func (m *SessionManager) RevokeSession(token string) {
|
|
parts := strings.Split(token, ".")
|
|
if len(parts) != 2 {
|
|
return
|
|
}
|
|
|
|
payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[0])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var payload sessionPayload
|
|
if err := json.Unmarshal(payloadJSON, &payload); err != nil {
|
|
return
|
|
}
|
|
|
|
m.mu.Lock()
|
|
delete(m.sessions, payload.SessionID)
|
|
m.mu.Unlock()
|
|
}
|