9153a20384
Fase 3 del issue 0010 — sesiones SQLite como alternativa a JWT. - Tabla sessions creada con CREATE TABLE IF NOT EXISTS (autosetup) - Tokens de 32 bytes aleatorios (crypto/rand) codificados en hex (256 bits) - Indices en user_id y expires_at - Prepared statements para evitar SQL injection - SessionCleanup para eliminar expiradas periodicamente
84 lines
2.2 KiB
Go
84 lines
2.2 KiB
Go
package infra
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// sessionEnsureTable crea la tabla sessions si no existe.
|
|
// Columnas: token (PK), user_id, expires_at, created_at, metadata (JSON).
|
|
func sessionEnsureTable(db *sql.DB) error {
|
|
const stmt = `
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
token TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL,
|
|
expires_at INTEGER NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
metadata TEXT NOT NULL DEFAULT '{}'
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
|
`
|
|
_, err := db.Exec(stmt)
|
|
return err
|
|
}
|
|
|
|
// SessionCreate genera un token aleatorio de 32 bytes (64 hex chars) e
|
|
// inserta la sesion en la tabla sessions. Crea la tabla si no existe.
|
|
// Retorna la Session completa lista para devolver al cliente.
|
|
func SessionCreate(db *sql.DB, userID string, ttl time.Duration, metadata map[string]any) (Session, error) {
|
|
var zero Session
|
|
if db == nil {
|
|
return zero, fmt.Errorf("session_create: db nil")
|
|
}
|
|
if userID == "" {
|
|
return zero, fmt.Errorf("session_create: user_id vacio")
|
|
}
|
|
if ttl <= 0 {
|
|
return zero, fmt.Errorf("session_create: ttl debe ser positivo")
|
|
}
|
|
if err := sessionEnsureTable(db); err != nil {
|
|
return zero, fmt.Errorf("session_create: crear tabla: %w", err)
|
|
}
|
|
|
|
raw := make([]byte, 32)
|
|
if _, err := rand.Read(raw); err != nil {
|
|
return zero, fmt.Errorf("session_create: rand: %w", err)
|
|
}
|
|
token := hex.EncodeToString(raw)
|
|
|
|
now := time.Now().Unix()
|
|
exp := now + int64(ttl.Seconds())
|
|
|
|
var metaJSON []byte
|
|
if metadata == nil {
|
|
metaJSON = []byte("{}")
|
|
} else {
|
|
b, err := json.Marshal(metadata)
|
|
if err != nil {
|
|
return zero, fmt.Errorf("session_create: marshal metadata: %w", err)
|
|
}
|
|
metaJSON = b
|
|
}
|
|
|
|
_, err := db.Exec(
|
|
"INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)",
|
|
token, userID, exp, now, string(metaJSON),
|
|
)
|
|
if err != nil {
|
|
return zero, fmt.Errorf("session_create: insert: %w", err)
|
|
}
|
|
|
|
return Session{
|
|
Token: token,
|
|
UserID: userID,
|
|
ExpiresAt: exp,
|
|
CreatedAt: now,
|
|
Metadata: metadata,
|
|
}, nil
|
|
}
|