Files
fn_registry/functions/infra/http_session_cookie_middleware.go
T
egutierrez 2a3d780347 feat(doctor): add fn doctor CLI + 14 functions for system management
Adds `fn doctor` read-only diagnostic command with subcommands artefacts,
services, sync, uses-functions, unused, and --json flag for agents.
Each subcommand wraps a registry function in functions/infra/.

New functions:
- artefact_doctor, services_status, pc_locations_drift,
  audit_uses_functions, find_unused_functions (Go diagnostics)
- backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port,
  port_kill, tail_journal, pre_commit_hook_install (bash utilities)
- notify_telegram (Go HTTP)
- backup_all pipeline (tag launcher)

Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry,
git utilities, http_session_cookie_middleware, compile/full-git pipelines).

Fixes pc_locations_drift filepath.Join bug with absolute dir_path.
Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23),
docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry.

First fn doctor uses-functions run found drift in 7/12 apps (deuda
para sincronizar app.md con imports reales).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:42:10 +02:00

79 lines
2.3 KiB
Go

package infra
import (
"context"
"database/sql"
"net/http"
"strings"
)
// SessionCookieConfig configura el middleware de autenticacion por cookie/Bearer.
type SessionCookieConfig struct {
DB *sql.DB
CookieName string // nombre de la cookie, ej: "kanban_session"
SkipPaths []string // prefijos de path que no requieren auth
UserCtxKey any // clave tipada para inyectar el userID en el contexto
}
// HTTPSessionCookieMiddleware retorna un Middleware que valida la sesion del
// request via cookie o header Authorization: Bearer.
// Si el path esta en SkipPaths delega sin validar.
// Si el token es valido inyecta el userID en r.Context() con cfg.UserCtxKey.
// Responde 401 JSON si falta el token o la sesion es invalida.
func HTTPSessionCookieMiddleware(cfg SessionCookieConfig) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Skip paths
for _, prefix := range cfg.SkipPaths {
if strings.HasPrefix(r.URL.Path, prefix) {
next.ServeHTTP(w, r)
return
}
}
// 2. Extraer token: primero cookie, luego Authorization header
token := ""
if c, err := r.Cookie(cfg.CookieName); err == nil {
token = c.Value
} else {
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, "Bearer ") {
token = strings.TrimPrefix(auth, "Bearer ")
}
}
// 3. Sin token → 401
if token == "" {
HTTPErrorResponse(w, HTTPError{
Status: http.StatusUnauthorized,
Code: "unauthorized",
Message: "session required",
})
return
}
// 4. Validar sesion
session, err := SessionValidate(cfg.DB, token)
if err != nil {
HTTPErrorResponse(w, HTTPError{
Status: http.StatusUnauthorized,
Code: "invalid_session",
Message: err.Error(),
})
return
}
// 5. Inyectar userID en contexto y delegar
ctx := context.WithValue(r.Context(), cfg.UserCtxKey, session.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// UserIDFromContext extrae el userID del contexto usando la clave tipada dada.
// Retorna ("", false) si no esta presente o el tipo no coincide.
func UserIDFromContext(ctx context.Context, key any) (string, bool) {
v, ok := ctx.Value(key).(string)
return v, ok
}