Files
fn_registry/functions/infra/session_create.go
T
egutierrez 9153a20384 feat: session_create, session_validate, session_cleanup
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
2026-04-18 17:40:13 +02:00

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
}