2a3d780347
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>
79 lines
2.3 KiB
Go
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
|
|
}
|