@saulwade/swl-ses 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +238 -0
- package/README.md +560 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/.evolved.json +9 -0
- package/agentes/accesibilidad-wcag-swl.md +692 -0
- package/agentes/arquitecto-swl.md +238 -0
- package/agentes/auto-evolucion-swl.md +854 -0
- package/agentes/backend-api-swl.md +470 -0
- package/agentes/backend-csharp-swl.md +418 -0
- package/agentes/backend-go-swl.md +388 -0
- package/agentes/backend-java-swl.md +279 -0
- package/agentes/backend-node-swl.md +477 -0
- package/agentes/backend-python-swl.md +608 -0
- package/agentes/backend-rust-swl.md +362 -0
- package/agentes/backend-workers-swl.md +480 -0
- package/agentes/cloud-infra-swl.md +485 -0
- package/agentes/consolidador-swl.md +539 -0
- package/agentes/datos-swl.md +584 -0
- package/agentes/depurador-swl.md +349 -0
- package/agentes/devops-ci-swl.md +374 -0
- package/agentes/disenador-ui-swl.md +558 -0
- package/agentes/documentador-swl.md +343 -0
- package/agentes/evals/arquitecto-swl.evals.json +56 -0
- package/agentes/evals/auto-evolucion-swl.evals.json +68 -0
- package/agentes/evals/implementador-swl.evals.json +56 -0
- package/agentes/evals/orquestador-swl.evals.json +60 -0
- package/agentes/evals/perfilador-usuario-swl.evals.json +60 -0
- package/agentes/evals/red-team-swl.evals.json +59 -0
- package/agentes/evals/revisor-codigo-swl.evals.json +59 -0
- package/agentes/frontend-angular-swl.md +627 -0
- package/agentes/frontend-css-swl.md +720 -0
- package/agentes/frontend-react-swl.md +696 -0
- package/agentes/frontend-swl.md +500 -0
- package/agentes/frontend-tailwind-swl.md +830 -0
- package/agentes/implementador-swl.md +328 -0
- package/agentes/investigador-swl.md +430 -0
- package/agentes/investigador-ux-swl.md +500 -0
- package/agentes/llm-apps-swl.md +276 -0
- package/agentes/migrador-swl.md +417 -0
- package/agentes/mobile-android-swl.md +509 -0
- package/agentes/mobile-cross-swl.md +539 -0
- package/agentes/mobile-ios-swl.md +500 -0
- package/agentes/mobile-testing-swl.md +300 -0
- package/agentes/notificador-swl.md +916 -0
- package/agentes/observabilidad-swl.md +436 -0
- package/agentes/orquestador-swl.md +884 -0
- package/agentes/pagos-swl.md +283 -0
- package/agentes/perfilador-usuario-swl.md +306 -0
- package/agentes/planificador-swl.md +402 -0
- package/agentes/producto-prd-swl.md +587 -0
- package/agentes/red-team-swl.md +216 -0
- package/agentes/release-manager-swl.md +568 -0
- package/agentes/rendimiento-swl.md +714 -0
- package/agentes/resolutor-build-swl.md +243 -0
- package/agentes/revisor-angular-swl.md +276 -0
- package/agentes/revisor-codigo-swl.md +348 -0
- package/agentes/revisor-csharp-swl.md +262 -0
- package/agentes/revisor-go-swl.md +257 -0
- package/agentes/revisor-java-swl.md +255 -0
- package/agentes/revisor-kotlin-swl.md +271 -0
- package/agentes/revisor-nextjs-swl.md +279 -0
- package/agentes/revisor-php-swl.md +269 -0
- package/agentes/revisor-react-swl.md +276 -0
- package/agentes/revisor-rust-swl.md +344 -0
- package/agentes/revisor-seguridad-swl.md +390 -0
- package/agentes/revisor-swift-swl.md +266 -0
- package/agentes/revisor-typescript-swl.md +344 -0
- package/agentes/sre-swl.md +265 -0
- package/agentes/tdd-qa-swl.md +354 -0
- package/agentes/ux-disenador-swl.md +501 -0
- package/bin/lib/bot-comandos.js +1030 -0
- package/bin/lib/bot-discovery.js +182 -0
- package/bin/lib/bot-git.js +142 -0
- package/bin/swl-ses.js +325 -0
- package/bin/swl-telegram-bot.js +442 -0
- package/bin/swl-telegram-bot.plist +21 -0
- package/bin/swl-telegram-bot.service +14 -0
- package/comandos/swl/.evolved.json +23 -0
- package/comandos/swl/actualizar.md +174 -0
- package/comandos/swl/adoptar-proyecto.md +207 -0
- package/comandos/swl/aprender.md +701 -0
- package/comandos/swl/auditar-deps.md +134 -0
- package/comandos/swl/autoresearch.md +170 -0
- package/comandos/swl/ayuda.md +224 -0
- package/comandos/swl/brainstorm.md +50 -0
- package/comandos/swl/checkpoint.md +330 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/configurar-ci.md +227 -0
- package/comandos/swl/contexto.md +112 -0
- package/comandos/swl/contribuir.md +233 -0
- package/comandos/swl/crear-skill.md +292 -0
- package/comandos/swl/cron.md +196 -0
- package/comandos/swl/dashboard.md +146 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +135 -0
- package/comandos/swl/evaluar-skill.md +487 -0
- package/comandos/swl/evolucion-estado.md +142 -0
- package/comandos/swl/evolucionar.md +259 -0
- package/comandos/swl/exportar-vault.md +189 -0
- package/comandos/swl/gateway.md +158 -0
- package/comandos/swl/inbox.md +116 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/instintos.md +86 -0
- package/comandos/swl/mapear-codebase.md +312 -0
- package/comandos/swl/mcp-status.md +175 -0
- package/comandos/swl/metricas.md +270 -0
- package/comandos/swl/modelo.md +102 -0
- package/comandos/swl/notificaciones.md +396 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/plugins.md +256 -0
- package/comandos/swl/reflect-skills.md +125 -0
- package/comandos/swl/release.md +217 -0
- package/comandos/swl/revisar-impacto.md +206 -0
- package/comandos/swl/revisar.md +330 -0
- package/comandos/swl/salud.md +363 -0
- package/comandos/swl/sesiones.md +200 -0
- package/comandos/swl/skill-search.md +113 -0
- package/comandos/swl/verificar.md +585 -0
- package/comandos/swl/wiki.md +620 -0
- package/contextos/dev.md +32 -0
- package/contextos/research.md +30 -0
- package/contextos/review.md +31 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +201 -0
- package/habilidades/accesibilidad-a11y/evals/evals.json +56 -0
- package/habilidades/accesibilidad-a11y/recursos/ejemplos-y-checklist-completo.md +441 -0
- package/habilidades/agent-browser/SKILL.md +218 -0
- package/habilidades/agentes-como-servicio/SKILL.md +218 -0
- package/habilidades/ai-runtime-security/SKILL.md +273 -0
- package/habilidades/angular-avanzado/SKILL.md +164 -0
- package/habilidades/angular-avanzado/recursos/ejemplos-avanzados.md +219 -0
- package/habilidades/angular-moderno/SKILL.md +186 -0
- package/habilidades/angular-moderno/evals/evals.json +45 -0
- package/habilidades/angular-moderno/recursos/ejemplos-avanzados.md +106 -0
- package/habilidades/api-rest-diseno/SKILL.md +191 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/api-rest-diseno/recursos/referencia-api.md +140 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +151 -0
- package/habilidades/aprendizaje-continuo/evals/evals.json +53 -0
- package/habilidades/aprendizaje-continuo/recursos/referencia-instintos.md +290 -0
- package/habilidades/async-python/SKILL.md +149 -0
- package/habilidades/async-python/evals/evals.json +47 -0
- package/habilidades/async-python/recursos/patrones-y-ejemplos-completos.md +292 -0
- package/habilidades/auth-patrones/.evolved.json +9 -0
- package/habilidades/auth-patrones/SKILL.md +413 -0
- package/habilidades/auth-patrones/recursos/implementaciones-completas.md +229 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -0
- package/habilidades/auto-evolucion-protocolo/evals/evals.json +55 -0
- package/habilidades/auto-evolucion-protocolo/recursos/referencia-completa.md +145 -0
- package/habilidades/autoresearch/SKILL.md +268 -0
- package/habilidades/autoresearch/evals/evals.json +41 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/azure-cloud/SKILL.md +308 -0
- package/habilidades/azure-cloud/recursos/aks.md +327 -0
- package/habilidades/backend-mcp-servidor/SKILL.md +270 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -0
- package/habilidades/brainstorming/SKILL.md +295 -0
- package/habilidades/brainstorming/recursos/componentes-html.md +247 -0
- package/habilidades/build-errors-cpp/SKILL.md +270 -0
- package/habilidades/build-errors-csharp/SKILL.md +265 -0
- package/habilidades/build-errors-go/SKILL.md +306 -0
- package/habilidades/build-errors-java/SKILL.md +278 -0
- package/habilidades/build-errors-kotlin/SKILL.md +303 -0
- package/habilidades/build-errors-nextjs/SKILL.md +312 -0
- package/habilidades/build-errors-php/SKILL.md +270 -0
- package/habilidades/build-errors-python/SKILL.md +292 -0
- package/habilidades/build-errors-rust/SKILL.md +284 -0
- package/habilidades/build-errors-swift/SKILL.md +272 -0
- package/habilidades/build-errors-typescript/SKILL.md +369 -0
- package/habilidades/checklist-calidad/SKILL.md +271 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +285 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +298 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +157 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/ci-cd-pipelines/recursos/pipelines-completos.md +487 -0
- package/habilidades/cloud-aws/SKILL.md +142 -0
- package/habilidades/cloud-aws/recursos/servicios-aws-referencia.md +321 -0
- package/habilidades/compactacion-contexto/SKILL.md +247 -0
- package/habilidades/contenedores-docker/SKILL.md +137 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/contenedores-docker/recursos/ejemplos-y-configuraciones.md +327 -0
- package/habilidades/context-builder/SKILL.md +170 -0
- package/habilidades/control-profundidad/SKILL.md +128 -0
- package/habilidades/csharp-experto/SKILL.md +322 -0
- package/habilidades/csharp-patrones/SKILL.md +316 -0
- package/habilidades/csharp-testing/SKILL.md +286 -0
- package/habilidades/css-moderno/SKILL.md +166 -0
- package/habilidades/css-moderno/evals/evals.json +43 -0
- package/habilidades/css-moderno/recursos/ejemplos-y-patrones-completos.md +337 -0
- package/habilidades/datos-etl/SKILL.md +129 -0
- package/habilidades/datos-etl/recursos/implementaciones-completas.md +322 -0
- package/habilidades/dbml-experto/SKILL.md +339 -0
- package/habilidades/dbml-experto/evals/evals.json +56 -0
- package/habilidades/dependencias-auditoria/SKILL.md +320 -0
- package/habilidades/deprecacion-migracion/SKILL.md +169 -0
- package/habilidades/deprecacion-migracion/recursos/implementaciones-completas.md +220 -0
- package/habilidades/design-tokens/SKILL.md +158 -0
- package/habilidades/design-tokens/recursos/tokens-y-configuracion.md +363 -0
- package/habilidades/devsecops-pipeline-security/SKILL.md +309 -0
- package/habilidades/diagrama-arquitectura/SKILL.md +165 -0
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -0
- package/habilidades/discutir-fase/SKILL.md +188 -0
- package/habilidades/diseno-herramientas-agente/SKILL.md +199 -0
- package/habilidades/diseno-responsivo/SKILL.md +186 -0
- package/habilidades/diseno-responsivo/recursos/ejemplos-layouts.md +156 -0
- package/habilidades/django-experto/SKILL.md +205 -0
- package/habilidades/django-experto/recursos/async-django.md +390 -0
- package/habilidades/django-experto/recursos/drf-patrones.md +438 -0
- package/habilidades/django-experto/recursos/orm-avanzado.md +382 -0
- package/habilidades/django-experto/recursos/referencia-completa.md +188 -0
- package/habilidades/django-experto/recursos/testing-django.md +415 -0
- package/habilidades/doc-sync/SKILL.md +280 -0
- package/habilidades/drift-detection/SKILL.md +179 -0
- package/habilidades/ejecutar-fase/SKILL.md +468 -0
- package/habilidades/estilo-sin-ai-isms/SKILL.md +775 -0
- package/habilidades/estilo-sin-ai-isms/evals/evals.json +63 -0
- package/habilidades/estilo-sin-ai-isms/scripts/detectar_aiisms.py +500 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +215 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +176 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +289 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/evaluacion-agentes/SKILL.md +314 -0
- package/habilidades/event-driven/SKILL.md +153 -0
- package/habilidades/event-driven/recursos/implementaciones-completas.md +423 -0
- package/habilidades/extraccion-documentos/SKILL.md +221 -0
- package/habilidades/extractor-de-aprendizajes/.evolved.json +9 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +311 -0
- package/habilidades/extractor-de-aprendizajes/evals/evals.json +55 -0
- package/habilidades/fastapi-experto/SKILL.md +221 -0
- package/habilidades/fastapi-experto/recursos/async-patterns.md +438 -0
- package/habilidades/fastapi-experto/recursos/dependency-injection.md +330 -0
- package/habilidades/fastapi-experto/recursos/referencia-completa.md +79 -0
- package/habilidades/fastapi-experto/recursos/testing-httpx.md +420 -0
- package/habilidades/filament-admin/SKILL.md +290 -0
- package/habilidades/frontend-avanzado/SKILL.md +257 -0
- package/habilidades/frontend-avanzado/recursos/apis-nativas-ejemplos.md +341 -0
- package/habilidades/gcp-cloud/SKILL.md +260 -0
- package/habilidades/gcp-cloud/recursos/gke.md +234 -0
- package/habilidades/gcp-cloud/recursos/terraform-gcp.md +307 -0
- package/habilidades/generacion-mermaid/SKILL.md +229 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +270 -0
- package/habilidades/go-experto/SKILL.md +305 -0
- package/habilidades/go-patrones/SKILL.md +299 -0
- package/habilidades/go-testing/SKILL.md +291 -0
- package/habilidades/graphql-experto/SKILL.md +323 -0
- package/habilidades/guardrail-semantico/SKILL.md +282 -0
- package/habilidades/harness-claude-code/SKILL.md +299 -0
- package/habilidades/iam-secretos/SKILL.md +265 -0
- package/habilidades/iam-secretos/recursos/implementaciones-completas.md +356 -0
- package/habilidades/infra-github-actions/SKILL.md +166 -0
- package/habilidades/instalar-sistema/.evolved.json +9 -0
- package/habilidades/instalar-sistema/SKILL.md +221 -0
- package/habilidades/java-experto/SKILL.md +290 -0
- package/habilidades/java-patrones/SKILL.md +275 -0
- package/habilidades/java-testing/SKILL.md +288 -0
- package/habilidades/kotlin-compose/SKILL.md +278 -0
- package/habilidades/kotlin-compose/recursos/animaciones-performance.md +93 -0
- package/habilidades/kotlin-experto/SKILL.md +318 -0
- package/habilidades/kotlin-testing/SKILL.md +267 -0
- package/habilidades/kotlin-testing/recursos/testing-avanzado.md +74 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +152 -0
- package/habilidades/kubernetes-orquestacion/recursos/manifiestos-completos.md +452 -0
- package/habilidades/langchain-langraph/SKILL.md +386 -0
- package/habilidades/langchain-langraph/recursos/evaluacion-rag.md +321 -0
- package/habilidades/langchain-langraph/recursos/rag-maturity-model.md +225 -0
- package/habilidades/langchain-langraph/recursos/vectorstores.md +306 -0
- package/habilidades/legacy-code-rescue/SKILL.md +267 -0
- package/habilidades/likec4-experto/SKILL.md +412 -0
- package/habilidades/likec4-experto/evals/evals.json +69 -0
- package/habilidades/manejo-errores/.evolved.json +9 -0
- package/habilidades/manejo-errores/SKILL.md +407 -0
- package/habilidades/manejo-errores/recursos/implementaciones-completas.md +248 -0
- package/habilidades/mapear-codebase/SKILL.md +275 -0
- package/habilidades/memoria-busqueda/SKILL.md +194 -0
- package/habilidades/memoria-busqueda/evals/evals.json +44 -0
- package/habilidades/meta-skills-estandar/SKILL.md +298 -0
- package/habilidades/meta-skills-estandar/recursos/anti-patrones-y-leyes.md +205 -0
- package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +107 -0
- package/habilidades/meta-skills-estandar/recursos/idiomas-framework.md +60 -0
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -0
- package/habilidades/microservicios/SKILL.md +155 -0
- package/habilidades/microservicios/recursos/patrones-y-ejemplos-completos.md +325 -0
- package/habilidades/mobile-flutter/SKILL.md +199 -0
- package/habilidades/mobile-flutter/recursos/ejemplos-completos.md +319 -0
- package/habilidades/mobile-react-native/SKILL.md +176 -0
- package/habilidades/mobile-react-native/recursos/ejemplos-completos.md +216 -0
- package/habilidades/mongodb-experto/SKILL.md +302 -0
- package/habilidades/monitoring-alertas/SKILL.md +201 -0
- package/habilidades/monitoring-alertas/recursos/instrumentacion-y-alertas.md +301 -0
- package/habilidades/nestjs-experto/SKILL.md +307 -0
- package/habilidades/nestjs-experto/recursos/guards-interceptors.md +339 -0
- package/habilidades/nestjs-experto/recursos/modulos-di.md +287 -0
- package/habilidades/nestjs-experto/recursos/testing-nestjs.md +354 -0
- package/habilidades/nextjs-experto/SKILL.md +335 -0
- package/habilidades/nextjs-patrones/SKILL.md +303 -0
- package/habilidades/nextjs-testing/SKILL.md +331 -0
- package/habilidades/node-experto/.evolved.json +9 -0
- package/habilidades/node-experto/SKILL.md +266 -0
- package/habilidades/node-experto/recursos/patrones-completos.md +283 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +159 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/notificaciones-multicanal/recursos/configuracion-y-templates.md +303 -0
- package/habilidades/nuevo-proyecto/SKILL.md +204 -0
- package/habilidades/orquestacion-async/SKILL.md +303 -0
- package/habilidades/paid-media-tracking/SKILL.md +269 -0
- package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +220 -0
- package/habilidades/paid-media-tracking/recursos/google-ads-api.md +215 -0
- package/habilidades/patrones-python/SKILL.md +228 -0
- package/habilidades/patrones-python/evals/evals.json +56 -0
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -0
- package/habilidades/patrones-python/recursos/referencia-completa.md +202 -0
- package/habilidades/perfil-usuario/SKILL.md +200 -0
- package/habilidades/perfil-usuario/evals/evals.json +55 -0
- package/habilidades/performance-baseline/SKILL.md +297 -0
- package/habilidades/php-experto/SKILL.md +291 -0
- package/habilidades/php-patrones/SKILL.md +306 -0
- package/habilidades/php-testing/SKILL.md +280 -0
- package/habilidades/planear-fase/SKILL.md +269 -0
- package/habilidades/postgresql-experto/SKILL.md +151 -0
- package/habilidades/postgresql-experto/evals/evals.json +53 -0
- package/habilidades/postgresql-experto/recursos/referencia-completa.md +215 -0
- package/habilidades/prevencion-racionalizacion/SKILL.md +175 -0
- package/habilidades/prevencion-sobreingenieria/SKILL.md +323 -0
- package/habilidades/privacy-memoria/SKILL.md +141 -0
- package/habilidades/privacy-memoria/evals/evals.json +43 -0
- package/habilidades/prompt-engineering/SKILL.md +518 -0
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +467 -0
- package/habilidades/rag-arquitectura/SKILL.md +338 -0
- package/habilidades/rails-experto/SKILL.md +237 -0
- package/habilidades/rails-experto/recursos/active-record.md +260 -0
- package/habilidades/rails-experto/recursos/hotwire-turbo.md +293 -0
- package/habilidades/rails-experto/recursos/testing-rspec.md +362 -0
- package/habilidades/react-experto/SKILL.md +209 -0
- package/habilidades/react-experto/evals/evals.json +55 -0
- package/habilidades/react-experto/recursos/patrones-y-ejemplos-completos.md +240 -0
- package/habilidades/react-optimizacion/SKILL.md +174 -0
- package/habilidades/react-optimizacion/recursos/patrones-avanzados.md +138 -0
- package/habilidades/redis-experto/SKILL.md +305 -0
- package/habilidades/release-semver/.evolved.json +9 -0
- package/habilidades/release-semver/SKILL.md +248 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/rust-experto/SKILL.md +400 -0
- package/habilidades/rust-patrones/SKILL.md +296 -0
- package/habilidades/rust-testing/SKILL.md +311 -0
- package/habilidades/seguridad-skills-ia/SKILL.md +262 -0
- package/habilidades/sql-optimizacion/SKILL.md +200 -0
- package/habilidades/sql-optimizacion/evals/evals.json +54 -0
- package/habilidades/sql-optimizacion/recursos/patrones-sql-avanzados.md +131 -0
- package/habilidades/sre-patrones/SKILL.md +333 -0
- package/habilidades/sre-patrones/recursos/chaos-engineering.md +241 -0
- package/habilidades/sre-patrones/recursos/oncall-design.md +236 -0
- package/habilidades/stripe-pagos/SKILL.md +550 -0
- package/habilidades/stripe-pagos/recursos/errores-reintentos.md +390 -0
- package/habilidades/stripe-pagos/recursos/stripe-connect.md +290 -0
- package/habilidades/structured-outputs/SKILL.md +343 -0
- package/habilidades/swift-experto/SKILL.md +320 -0
- package/habilidades/swift-experto/recursos/keychain-y-wrappers.md +110 -0
- package/habilidades/swift-patrones/SKILL.md +313 -0
- package/habilidades/swift-patrones/recursos/tca-ejemplo-completo.md +113 -0
- package/habilidades/swift-testing/SKILL.md +254 -0
- package/habilidades/swift-testing/recursos/xcuitest-planes.md +143 -0
- package/habilidades/swl-dashboard/SKILL.md +370 -0
- package/habilidades/swl-markitdown/SKILL.md +285 -0
- package/habilidades/swl-markitdown/evals/evals.json +52 -0
- package/habilidades/swl-revisar-impacto/SKILL.md +233 -0
- package/habilidades/tailwind-experto/SKILL.md +240 -0
- package/habilidades/tailwind-experto/recursos/referencia-completa.md +184 -0
- package/habilidades/tdd-workflow/SKILL.md +293 -0
- package/habilidades/terraform-experto/SKILL.md +321 -0
- package/habilidades/testing-python/SKILL.md +340 -0
- package/habilidades/testing-python/recursos/ejemplos-completos.md +167 -0
- package/habilidades/threat-model-lite/SKILL.md +246 -0
- package/habilidades/tracing-processor/SKILL.md +212 -0
- package/habilidades/tracking-measurement/SKILL.md +239 -0
- package/habilidades/tracking-measurement/recursos/consent-mode.md +231 -0
- package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +216 -0
- package/habilidades/tracking-measurement/recursos/meta-capi.md +262 -0
- package/habilidades/typescript-avanzado/SKILL.md +144 -0
- package/habilidades/typescript-avanzado/evals/evals.json +55 -0
- package/habilidades/typescript-avanzado/recursos/patrones-y-ejemplos-completos.md +298 -0
- package/habilidades/typescript-diagnosticos/SKILL.md +513 -0
- package/habilidades/ux-diseno/SKILL.md +116 -0
- package/habilidades/ux-diseno/evals/evals.json +43 -0
- package/habilidades/ux-diseno/recursos/patrones-ux-referencia.md +214 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +136 -0
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +369 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificacion-evidencia/SKILL.md +160 -0
- package/habilidades/verificar-trabajo/SKILL.md +303 -0
- package/habilidades/verificar-trabajo/recursos/plantilla-verificacion.md +60 -0
- package/habilidades/wiki-conocimiento/SKILL.md +276 -0
- package/habilidades/wireframes-flujos/SKILL.md +212 -0
- package/habilidades/wireframes-flujos/recursos/referencia-completa.md +192 -0
- package/habilidades/workflow-claude-code/SKILL.md +260 -0
- package/habilidades/workflow-claude-code/recursos/referencia-completa.md +109 -0
- package/hooks/_run-hook.sh +57 -0
- package/hooks/actualizar-perfil-usuario.js +364 -0
- package/hooks/agente-lifecycle.js +71 -0
- package/hooks/aiisms-detector.js +173 -0
- package/hooks/audit-trail.js +204 -0
- package/hooks/auto-background.js +97 -0
- package/hooks/auto-consolidacion.js +178 -0
- package/hooks/auto-evolucion.js +666 -0
- package/hooks/auto-restaurar-settings.js +360 -0
- package/hooks/calidad-pre-commit.js +929 -0
- package/hooks/calidad-typescript.js +511 -0
- package/hooks/captura-feedback-usuario.js +148 -0
- package/hooks/check-update.js +211 -0
- package/hooks/clasificador-mensajes.js +271 -0
- package/hooks/degradacion-instintos.js +272 -0
- package/hooks/escaneo-secretos.js +389 -0
- package/hooks/extraccion-aprendizajes.js +763 -0
- package/hooks/grafo-contexto.js +129 -0
- package/hooks/graph-update.js +67 -0
- package/hooks/guardrail-modelo.js +247 -0
- package/hooks/inbox-aviso.js +75 -0
- package/hooks/inyeccion-contexto.js +246 -0
- package/hooks/lib/abort-registry.js +214 -0
- package/hooks/lib/agent-backend.js +210 -0
- package/hooks/lib/agent-comms.js +263 -0
- package/hooks/lib/agent-issue-codes.js +284 -0
- package/hooks/lib/agent-matcher.js +189 -0
- package/hooks/lib/async-hook-registry.js +252 -0
- package/hooks/lib/atomic-write.js +130 -0
- package/hooks/lib/auto-consolidator.js +335 -0
- package/hooks/lib/canary-skills.js +187 -0
- package/hooks/lib/consolidation-lock.js +291 -0
- package/hooks/lib/context-builder.js +430 -0
- package/hooks/lib/context-compressor.js +657 -0
- package/hooks/lib/convergence-detector.js +105 -0
- package/hooks/lib/delegation-tracker.js +198 -0
- package/hooks/lib/detectar-package-manager.js +423 -0
- package/hooks/lib/edit-accumulator.js +171 -0
- package/hooks/lib/error-classifier.js +308 -0
- package/hooks/lib/event-bus.js +112 -0
- package/hooks/lib/evolution-tracker.js +442 -0
- package/hooks/lib/execution-state.js +316 -0
- package/hooks/lib/fingerprint-id.js +135 -0
- package/hooks/lib/gateway-notify.js +116 -0
- package/hooks/lib/graph-security.js +75 -0
- package/hooks/lib/guardrail-metrics.js +202 -0
- package/hooks/lib/hook-circuit-breaker.js +206 -0
- package/hooks/lib/loop-detector.js +267 -0
- package/hooks/lib/mcp-health.js +184 -0
- package/hooks/lib/mcp-pool.js +436 -0
- package/hooks/lib/memory-search.js +506 -0
- package/hooks/lib/merkle-audit.js +96 -0
- package/hooks/lib/model-router.js +222 -0
- package/hooks/lib/normalize-error.js +324 -0
- package/hooks/lib/normalize-input.js +65 -0
- package/hooks/lib/nudge-tracker.js +306 -0
- package/hooks/lib/otlp-exporter.js +365 -0
- package/hooks/lib/performance-marks.js +239 -0
- package/hooks/lib/privacy-filter.js +128 -0
- package/hooks/lib/prompt-injection-scanner.js +209 -0
- package/hooks/lib/provenance-tracker.js +183 -0
- package/hooks/lib/rate-limit-tracker.js +253 -0
- package/hooks/lib/reflect-classifier.js +164 -0
- package/hooks/lib/resource-quota.js +122 -0
- package/hooks/lib/retry-jitter.js +165 -0
- package/hooks/lib/risk-engine.js +368 -0
- package/hooks/lib/run-log.js +408 -0
- package/hooks/lib/session-fts.js +379 -0
- package/hooks/lib/session-store.js +293 -0
- package/hooks/lib/singleton-guard.js +159 -0
- package/hooks/lib/skill-auditor.js +588 -0
- package/hooks/lib/sync-status.js +228 -0
- package/hooks/lib/taint-tracker.js +107 -0
- package/hooks/lib/task-service.js +295 -0
- package/hooks/lib/tech-skills-map.js +146 -0
- package/hooks/lib/telegram-cliente.js +159 -0
- package/hooks/lib/telegram-config.js +170 -0
- package/hooks/lib/token-budget.js +156 -0
- package/hooks/lib/token-estimator.js +420 -0
- package/hooks/lib/toon-compressor.js +245 -0
- package/hooks/lib/usage-model.js +183 -0
- package/hooks/lib/variable-resolver.js +230 -0
- package/hooks/linea-estado.js +324 -0
- package/hooks/metricas-evolucion.js +209 -0
- package/hooks/monitor-contexto.js +325 -0
- package/hooks/notificacion-sesion-stop.js +198 -0
- package/hooks/notificacion-telegram-notification.js +4 -0
- package/hooks/notificacion-telegram-subagent.js +4 -0
- package/hooks/notificacion-telegram.js +267 -0
- package/hooks/preservar-estado-pre-compact.js +150 -0
- package/hooks/proteccion-rutas.js +366 -0
- package/hooks/registro-turnos.js +209 -0
- package/hooks/resumen-sesion.js +249 -0
- package/hooks/risk-scoring.js +323 -0
- package/hooks/rotar-audit-auto.js +122 -0
- package/hooks/sugerir-regenerar-inventario.js +170 -0
- package/hooks/telemetria-agentes.js +167 -0
- package/hooks/tracking-costos.js +688 -0
- package/instintos/global.yaml +8 -0
- package/instintos/perfil-usuario.yaml +53 -0
- package/instintos/prompt-appendices.yaml +57 -0
- package/instintos/proyecto.yaml +372 -0
- package/manifiestos/gateway-config.json +77 -0
- package/manifiestos/handoff-context.json +223 -0
- package/manifiestos/hook-profiles.json +44 -0
- package/manifiestos/hooks-config.json +360 -0
- package/manifiestos/modulos.json +1173 -0
- package/manifiestos/perfiles.json +404 -0
- package/package.json +86 -0
- package/plantillas/ESTADO.md +109 -0
- package/plantillas/HOJA-RUTA.md +143 -0
- package/plantillas/PROYECTO.md +122 -0
- package/plantillas/REQUISITOS.md +132 -0
- package/plantillas/auditor-veto-template.md +105 -0
- package/plantillas/github-workflows/README.md +47 -0
- package/plantillas/github-workflows/release-please.yml +44 -0
- package/plantillas/github-workflows/swl-ci.yml +107 -0
- package/plantillas/github-workflows/swl-security.yml +51 -0
- package/plantillas/mcp-mineru.json +13 -0
- package/plantillas/research/ARQUITECTURA.md +220 -0
- package/plantillas/research/FUNCIONALIDADES.md +175 -0
- package/plantillas/research/RESUMEN.md +165 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/TRAMPAS.md +299 -0
- package/plantillas/skill-evals-template.json +44 -0
- package/plugin.json +343 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +352 -0
- package/reglas/brevedad-output.md +124 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +201 -0
- package/reglas/git-workflow.md +245 -0
- package/reglas/gobernanza.md +271 -0
- package/reglas/harness-claude-code.md +213 -0
- package/reglas/hooks.md +186 -0
- package/reglas/lenguajes/csharp/estilo-codigo.md +231 -0
- package/reglas/lenguajes/csharp/hooks.md +281 -0
- package/reglas/lenguajes/csharp/patrones.md +226 -0
- package/reglas/lenguajes/csharp/seguridad.md +258 -0
- package/reglas/lenguajes/csharp/testing.md +176 -0
- package/reglas/lenguajes/go/estilo-codigo.md +195 -0
- package/reglas/lenguajes/go/hooks.md +249 -0
- package/reglas/lenguajes/go/patrones.md +249 -0
- package/reglas/lenguajes/go/seguridad.md +225 -0
- package/reglas/lenguajes/go/testing.md +272 -0
- package/reglas/lenguajes/java/estilo-codigo.md +217 -0
- package/reglas/lenguajes/java/hooks.md +251 -0
- package/reglas/lenguajes/java/patrones.md +226 -0
- package/reglas/lenguajes/java/seguridad.md +233 -0
- package/reglas/lenguajes/java/testing.md +238 -0
- package/reglas/lenguajes/kotlin/estilo-codigo.md +208 -0
- package/reglas/lenguajes/kotlin/hooks.md +245 -0
- package/reglas/lenguajes/kotlin/patrones.md +201 -0
- package/reglas/lenguajes/kotlin/seguridad.md +202 -0
- package/reglas/lenguajes/kotlin/testing.md +236 -0
- package/reglas/lenguajes/nextjs/estilo-codigo.md +175 -0
- package/reglas/lenguajes/nextjs/hooks.md +186 -0
- package/reglas/lenguajes/nextjs/patrones.md +225 -0
- package/reglas/lenguajes/nextjs/seguridad.md +216 -0
- package/reglas/lenguajes/nextjs/testing.md +193 -0
- package/reglas/lenguajes/php/estilo-codigo.md +228 -0
- package/reglas/lenguajes/php/hooks.md +165 -0
- package/reglas/lenguajes/php/patrones.md +233 -0
- package/reglas/lenguajes/php/seguridad.md +186 -0
- package/reglas/lenguajes/php/testing.md +205 -0
- package/reglas/lenguajes/rust/estilo-codigo.md +207 -0
- package/reglas/lenguajes/rust/hooks.md +240 -0
- package/reglas/lenguajes/rust/patrones.md +250 -0
- package/reglas/lenguajes/rust/seguridad.md +221 -0
- package/reglas/lenguajes/rust/testing.md +194 -0
- package/reglas/lenguajes/swift/estilo-codigo.md +238 -0
- package/reglas/lenguajes/swift/hooks.md +257 -0
- package/reglas/lenguajes/swift/patrones.md +235 -0
- package/reglas/lenguajes/swift/seguridad.md +248 -0
- package/reglas/lenguajes/swift/testing.md +242 -0
- package/reglas/markitdown.md +60 -0
- package/reglas/memoria-consolidada.md +209 -0
- package/reglas/patrones.md +225 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad-agentes.md +351 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +373 -0
- package/reglas/testing.md +193 -0
- package/schemas/agent-contract.json +176 -0
- package/schemas/agent-frontmatter.schema.json +149 -0
- package/schemas/agent-message.schema.json +53 -0
- package/schemas/agent-output-implementacion.schema.json +85 -0
- package/schemas/agent-output-planificacion.schema.json +113 -0
- package/schemas/agent-output-review.schema.json +78 -0
- package/schemas/diary-entry.schema.json +80 -0
- package/schemas/hook-profiles.schema.json +39 -0
- package/schemas/hooks-config.schema.json +74 -0
- package/schemas/instinct.schema.json +115 -0
- package/schemas/modulos.schema.json +29 -0
- package/schemas/perfiles.schema.json +28 -0
- package/schemas/plugin.schema.json +64 -0
- package/schemas/skill-evals.schema.json +95 -0
- package/schemas/skill-frontmatter.schema.json +170 -0
- package/scripts/actualizar.js +145 -0
- package/scripts/audit-skills.sh +78 -0
- package/scripts/auditar-agentes-gaps.js +149 -0
- package/scripts/auditar-cobertura-frameworks.js +241 -0
- package/scripts/auditar-skills-gaps.js +206 -0
- package/scripts/bootstrap-instintos.js +259 -0
- package/scripts/check-update.js +109 -0
- package/scripts/comandos/agents.js +105 -0
- package/scripts/comandos/info.js +108 -0
- package/scripts/comandos/install-asistido.js +186 -0
- package/scripts/comandos/skills.js +211 -0
- package/scripts/configurar-branch-protection.js +418 -0
- package/scripts/daemon-swl.py +388 -0
- package/scripts/desinstalar.js +130 -0
- package/scripts/doctor.js +559 -0
- package/scripts/field-report.js +199 -0
- package/scripts/generar-inventario.js +317 -0
- package/scripts/inbox-tmux-inject.js +161 -0
- package/scripts/inferir-herramientas-permitidas.js +586 -0
- package/scripts/inicializar.js +133 -0
- package/scripts/instalador.js +1031 -0
- package/scripts/instalar-git-hook.js +122 -0
- package/scripts/lib/agp-frontmatter.js +222 -0
- package/scripts/lib/append-con-marcadores.js +199 -0
- package/scripts/lib/artefactos-python.js +43 -0
- package/scripts/lib/audit-query.js +221 -0
- package/scripts/lib/autostart-linux.js +347 -0
- package/scripts/lib/autostart-macos.js +360 -0
- package/scripts/lib/autostart-windows.js +307 -0
- package/scripts/lib/budget-enforcer.js +252 -0
- package/scripts/lib/claude-sessions.js +285 -0
- package/scripts/lib/configurar-ci.js +380 -0
- package/scripts/lib/console-span-exporter.js +92 -0
- package/scripts/lib/contadores-inventario.js +217 -0
- package/scripts/lib/dashboard-widgets.js +290 -0
- package/scripts/lib/detectar-runtime.js +279 -0
- package/scripts/lib/detectar-stack.js +187 -0
- package/scripts/lib/diary-entry.js +234 -0
- package/scripts/lib/drift-detector.js +545 -0
- package/scripts/lib/estado.js +124 -0
- package/scripts/lib/gestor-componentes.js +243 -0
- package/scripts/lib/gitignore-manifest.js +305 -0
- package/scripts/lib/graph-analyze.py +556 -0
- package/scripts/lib/graph-builder.py +485 -0
- package/scripts/lib/graph-cluster.py +259 -0
- package/scripts/lib/health-row.js +168 -0
- package/scripts/lib/hooks-settings.js +789 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/mc-client.js +137 -0
- package/scripts/lib/notificaciones-telegram.js +1107 -0
- package/scripts/lib/npm-version.js +261 -0
- package/scripts/lib/paquetes-conocidos.js +50 -0
- package/scripts/lib/preservar-usuario.js +586 -0
- package/scripts/lib/prompt-builder.js +264 -0
- package/scripts/lib/resolver-externo.js +332 -0
- package/scripts/lib/schedule-parser.js +305 -0
- package/scripts/lib/scoring-instintos.js +240 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/lib/selector-interactivo.js +152 -0
- package/scripts/lib/semantic-search.js +242 -0
- package/scripts/lib/skill-discovery.js +234 -0
- package/scripts/lib/skill-metrics.js +246 -0
- package/scripts/lib/skill-normalizer.js +112 -0
- package/scripts/lib/skills-hub.js +340 -0
- package/scripts/lib/span-schema.js +134 -0
- package/scripts/lib/tool-cost-analyzer.js +255 -0
- package/scripts/lib/tracing-processor-interface.js +286 -0
- package/scripts/lib/transformadores/base.js +80 -0
- package/scripts/lib/transformadores/claude.js +124 -0
- package/scripts/lib/transformadores/codex.js +115 -0
- package/scripts/lib/transformadores/copilot.js +106 -0
- package/scripts/lib/transformadores/gemini.js +74 -0
- package/scripts/lib/transformadores/index.js +35 -0
- package/scripts/lib/transformadores/opencode.js +75 -0
- package/scripts/lib/ui.js +259 -0
- package/scripts/limpiar-artefactos-python.js +131 -0
- package/scripts/mcp-orchestrator.py +386 -0
- package/scripts/mcp-pool-manager.py +352 -0
- package/scripts/mcp-telemetry.py +378 -0
- package/scripts/poblar-evolvable.js +226 -0
- package/scripts/publicar.js +287 -0
- package/scripts/reflect-skills.js +403 -0
- package/scripts/rotar-audit-logs.js +185 -0
- package/scripts/run-skill-evals.js +242 -0
- package/scripts/smoke-test.js +374 -0
- package/scripts/token-analysis.py +471 -0
- package/scripts/validar-manifest.js +195 -0
- package/scripts/validar-memoria.js +321 -0
- package/scripts/validar-tests-aislamiento.js +184 -0
- package/scripts/validar-tokens-test.js +208 -0
- package/scripts/validar.js +147 -0
- package/scripts/validate-markdown.py +339 -0
- package/scripts/validate-skills.py +385 -0
- package/scripts/vendor/claude-usage/README.md +116 -0
- package/scripts/vendor/claude-usage/cli.py +334 -0
- package/scripts/vendor/claude-usage/dashboard.py +795 -0
- package/scripts/vendor/claude-usage/scanner.py +467 -0
- package/scripts/vendor/markitdown/cli.py +194 -0
- package/scripts/verificar-evolucion.js +289 -0
- package/scripts/verificar-release.js +494 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auth-patrones
|
|
3
|
+
description: Patrones de autenticación y autorización. JWT, OAuth2, OIDC, RBAC, ABAC, session management, refresh tokens, PKCE, MFA. Implementaciones en FastAPI y Django. Reglas anti-error de seguridad críticas.
|
|
4
|
+
version: "1.2.0"
|
|
5
|
+
evolved: true
|
|
6
|
+
evolved-from: "1.1.0"
|
|
7
|
+
evolved-at: "2026-04-24"
|
|
8
|
+
evolved-by: "aprender"
|
|
9
|
+
evolved-note: "2 secciones nuevas: IDOR defense con 404 (no 403) + consent PII en TODOS los ambientes"
|
|
10
|
+
herramientasPermitidas: [Read, Grep]
|
|
11
|
+
nist_csf: [PR.AA-01, PR.AA-02, PR.AA-03, PR.AA-04, PR.AA-05]
|
|
12
|
+
attack_techniques: [T1078, T1110, T1212]
|
|
13
|
+
d3fend_techniques: [D3-MFA]
|
|
14
|
+
exclusiones:
|
|
15
|
+
- "No cargar para seguridad de infraestructura (firewall, VPN, TLS, certificados) — este skill cubre autenticación de aplicación, no seguridad de red."
|
|
16
|
+
- "No cargar para gestión de secretos en cloud (AWS Secrets Manager, Vault, Azure Key Vault) — para infraestructura de secretos cargar `cloud-infra`."
|
|
17
|
+
- "No cargar para auditorías de seguridad de código o OWASP Top 10 completo — para revisión de seguridad cargar la regla `seguridad.md`."
|
|
18
|
+
- "No cargar para implementaciones de auth en Rails (Devise) o Laravel (Sanctum/Passport) — las implementaciones aquí son para FastAPI/Django; para Rails cargar `rails-experto`."
|
|
19
|
+
evolvable: true # default para skill estandar
|
|
20
|
+
---
|
|
21
|
+
# Autenticación y Autorización — Patrones de Seguridad
|
|
22
|
+
|
|
23
|
+
## Cuándo NO cargar
|
|
24
|
+
|
|
25
|
+
- La tarea es seguridad de infraestructura: firewall, VPN, TLS, configuración de certificados — este skill cubre autenticación en la aplicación, no seguridad de red.
|
|
26
|
+
- El tema es gestión de secretos en cloud: AWS Secrets Manager, HashiCorp Vault, Azure Key Vault — para infraestructura de secretos cargar `cloud-infra`.
|
|
27
|
+
- La tarea es auditoría de seguridad de código o revisión OWASP Top 10 — cargar la regla `seguridad.md`.
|
|
28
|
+
- El framework es Rails (Devise) o Laravel (Sanctum/Passport) — las implementaciones aquí son para Python/FastAPI/Django; para Rails cargar `rails-experto`.
|
|
29
|
+
|
|
30
|
+
La autenticación ("¿quién eres?") y la autorización ("¿qué puedes hacer?") son las
|
|
31
|
+
capas de seguridad más críticas en cualquier sistema. Un error aquí compromete todo lo demás.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 1. JWT — JSON Web Tokens
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import os
|
|
39
|
+
from datetime import datetime, timedelta, timezone
|
|
40
|
+
from jose import JWTError, jwt
|
|
41
|
+
|
|
42
|
+
SECRET_KEY = os.environ["JWT_SECRET_KEY"] # NUNCA hardcodear — min 256 bits
|
|
43
|
+
ALGORITHM = "HS256"
|
|
44
|
+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
|
45
|
+
|
|
46
|
+
def crear_access_token(payload: dict) -> str:
|
|
47
|
+
datos = payload.copy()
|
|
48
|
+
datos["exp"] = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
49
|
+
datos["iat"] = datetime.now(timezone.utc)
|
|
50
|
+
datos["type"] = "access"
|
|
51
|
+
return jwt.encode(datos, SECRET_KEY, algorithm=ALGORITHM)
|
|
52
|
+
|
|
53
|
+
def verificar_token(token: str, tipo_esperado: str = "access") -> dict:
|
|
54
|
+
try:
|
|
55
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
56
|
+
except JWTError as e:
|
|
57
|
+
raise CredencialesInvalidas("Token inválido o expirado") from e
|
|
58
|
+
if payload.get("type") != tipo_esperado:
|
|
59
|
+
raise CredencialesInvalidas(f"Se esperaba token de tipo '{tipo_esperado}'")
|
|
60
|
+
return payload
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Claims OBLIGATORIOS: `sub`, `exp`, `iat`, `type`, `jti` (para revocación)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 2. FastAPI — Auth Completo
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fastapi import Depends, HTTPException, status
|
|
71
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
72
|
+
|
|
73
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
|
74
|
+
|
|
75
|
+
async def get_current_user(
|
|
76
|
+
token: str = Depends(oauth2_scheme),
|
|
77
|
+
db: AsyncSession = Depends(get_db),
|
|
78
|
+
) -> Usuario:
|
|
79
|
+
credenciales_exc = HTTPException(status_code=401, detail="No autenticado")
|
|
80
|
+
try:
|
|
81
|
+
payload = verificar_token(token, tipo_esperado="access")
|
|
82
|
+
user_id = payload.get("sub", "")
|
|
83
|
+
if not user_id:
|
|
84
|
+
raise credenciales_exc
|
|
85
|
+
except CredencialesInvalidas:
|
|
86
|
+
raise credenciales_exc
|
|
87
|
+
usuario = await db.get(Usuario, user_id)
|
|
88
|
+
if usuario is None or not usuario.activo:
|
|
89
|
+
raise credenciales_exc
|
|
90
|
+
return usuario
|
|
91
|
+
|
|
92
|
+
def require_role(roles_permitidos: list[str]):
|
|
93
|
+
async def verificar_rol(usuario: Usuario = Depends(get_current_user)) -> Usuario:
|
|
94
|
+
if not {r.nombre for r in usuario.roles}.intersection(roles_permitidos):
|
|
95
|
+
raise HTTPException(status_code=403, detail="Sin permisos")
|
|
96
|
+
return usuario
|
|
97
|
+
return verificar_rol
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. RBAC — Control de Acceso Basado en Roles
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
PERMISOS: dict[str, list[str]] = {
|
|
106
|
+
"empleados:crear": ["ADMIN", "RRHH"],
|
|
107
|
+
"empleados:editar": ["ADMIN", "RRHH"],
|
|
108
|
+
"empleados:ver": ["ADMIN", "RRHH", "NOMINA", "AUDITOR"],
|
|
109
|
+
"nomina:procesar": ["ADMIN", "NOMINA"],
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ABAC — combinar rol + contexto
|
|
113
|
+
def puede_ver_expediente(usuario: Usuario, expediente: Expediente) -> bool:
|
|
114
|
+
"""Un empleado solo puede ver su propio expediente."""
|
|
115
|
+
if "ADMIN" in {r.nombre for r in usuario.roles}:
|
|
116
|
+
return True
|
|
117
|
+
if "RRHH" in {r.nombre for r in usuario.roles}:
|
|
118
|
+
return expediente.departamento_id == usuario.departamento_id
|
|
119
|
+
return expediente.empleado_id == usuario.id
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 4. IDOR — Insecure Direct Object Reference
|
|
125
|
+
|
|
126
|
+
SIEMPRE verificar que el recurso pertenece al usuario autenticado:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# MAL: sin verificar propiedad
|
|
130
|
+
return await db.get(Expediente, id)
|
|
131
|
+
|
|
132
|
+
# BIEN: verificar autorización
|
|
133
|
+
expediente = await db.get(Expediente, id)
|
|
134
|
+
if not puede_ver_expediente(usuario, expediente):
|
|
135
|
+
raise HTTPException(403, "Sin autorización")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Implementaciones Completas
|
|
141
|
+
|
|
142
|
+
Para Refresh Token con rotación, OAuth2/OIDC con authlib, Passwords (bcrypt + validación),
|
|
143
|
+
MFA con TOTP (pyotp + QR), y Headers de seguridad HTTP, ver
|
|
144
|
+
[recursos/implementaciones-completas.md](recursos/implementaciones-completas.md).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Gotchas / Errores comunes no obvios
|
|
149
|
+
|
|
150
|
+
**JWT con `alg: none` aceptado por librerías mal configuradas permite falsificar tokens sin firma**: algunas versiones de `python-jose` y `PyJWT` sin la opción `algorithms=` explícita aceptaban `alg: none` — cualquier payload sin firma era válido. Causa: el estándar JWT permite `alg: none` para tokens no firmados; las librerías que no restringen el algoritmo aceptan esto. Fix: SIEMPRE especificar `algorithms=["HS256"]` (o el algoritmo usado) al decodificar; NUNCA pasar `options={"verify_signature": False}` en producción.
|
|
151
|
+
|
|
152
|
+
**Refresh token almacenado en `localStorage` es vulnerable a XSS aunque el access token esté en httpOnly cookie**: si el refresh token está en `localStorage` y hay una vulnerabilidad XSS en la app, el atacante puede robar el refresh token y obtener nuevos access tokens indefinidamente. Causa: `localStorage` es accesible por JavaScript — cualquier script en la página puede leerlo. Fix: almacenar tanto el access token como el refresh token en cookies `httpOnly; Secure; SameSite=Strict`; usar un endpoint `/auth/refresh` que lea la cookie httpOnly y retorne un nuevo access token.
|
|
153
|
+
|
|
154
|
+
**`require_role` implementado como dependencia de FastAPI no se aplica a rutas OPTIONS (preflight CORS)**: el decorador de RBAC en FastAPI bloquea las peticiones OPTIONS del preflight si el middleware CORS no intercepta antes. Esto causa errores `403 Forbidden` en peticiones cross-origin aunque el usuario tenga el rol correcto. Causa: FastAPI ejecuta las dependencias antes de que el middleware CORS responda OPTIONS. Fix: configurar el middleware CORS de FastAPI con `CORSMiddleware` ANTES de cualquier middleware de auth, o excluir explícitamente el método `OPTIONS` de las verificaciones de auth.
|
|
155
|
+
|
|
156
|
+
**Tokens JWT con `exp` muy largo (7+ días) no se pueden invalidar después de un compromiso**: si un access token de 7 días es robado, el usuario no tiene forma de invalidarlo porque JWT es stateless — el servidor no puede verificar si fue revocado. Causa: el diseño stateless de JWT. Fix: mantener access tokens con expiración corta (15-30 min) y usar una allowlist/denylist de `jti` en Redis para tokens revocados; invalidar el `jti` al logout o al detectar actividad sospechosa.
|
|
157
|
+
|
|
158
|
+
## Multitenancy: queries auth-aware desde el repositorio
|
|
159
|
+
|
|
160
|
+
La prevención de IDOR capa por capa (middleware + controlador + servicio) es
|
|
161
|
+
frágil — si una nueva ruta olvida el check, el bug pasa a producción
|
|
162
|
+
silenciosamente. **Patrón recomendado (observado en Polar y otros sistemas
|
|
163
|
+
SaaS multitenant): empujar el filtro de `auth_subject` al repositorio**, no
|
|
164
|
+
al servicio. El repositorio expone un método `get_readable_statement(auth_subject)`
|
|
165
|
+
que retorna un `Select` ya filtrado. Cualquier query de lectura parte de ese
|
|
166
|
+
statement base.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# repository.py — SQLAlchemy 2.0 async
|
|
170
|
+
class ResourceRepository:
|
|
171
|
+
model = Resource
|
|
172
|
+
|
|
173
|
+
def get_readable_statement(
|
|
174
|
+
self, auth_subject: AuthSubject[User | Organization]
|
|
175
|
+
) -> Select[tuple[Resource]]:
|
|
176
|
+
statement = select(self.model)
|
|
177
|
+
if is_user(auth_subject):
|
|
178
|
+
statement = statement.where(
|
|
179
|
+
Resource.organization_id.in_(
|
|
180
|
+
select(UserOrganization.organization_id).where(
|
|
181
|
+
UserOrganization.user_id == auth_subject.subject.id
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
elif is_organization(auth_subject):
|
|
186
|
+
statement = statement.where(
|
|
187
|
+
Resource.organization_id == auth_subject.subject.id
|
|
188
|
+
)
|
|
189
|
+
return statement
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# service.py — nunca construye queries; parte del statement readable
|
|
194
|
+
async def get_resource(auth_subject, resource_id):
|
|
195
|
+
repo = ResourceRepository.from_session(session)
|
|
196
|
+
statement = repo.get_readable_statement(auth_subject).where(
|
|
197
|
+
Resource.id == resource_id
|
|
198
|
+
)
|
|
199
|
+
# Si no es accesible, retorna None (indistinguible de "no existe").
|
|
200
|
+
# Indistinguibilidad evita filtrar información sobre la existencia del recurso.
|
|
201
|
+
return await session.scalar(statement)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Ventajas sobre verificación en controlador:**
|
|
205
|
+
- Imposible olvidar el check — la query misma está filtrada.
|
|
206
|
+
- Un solo punto de cambio si la regla de acceso evoluciona.
|
|
207
|
+
- IDOR eliminado por diseño — acceder a recurso ajeno retorna `None`.
|
|
208
|
+
|
|
209
|
+
**Reglas obligatorias:**
|
|
210
|
+
|
|
211
|
+
1. **NUNCA exponer queries directas al modelo desde servicios** — porque es
|
|
212
|
+
donde se pierde el contexto de `auth_subject` y aparecen bugs IDOR; todo
|
|
213
|
+
parte de `get_readable_statement`.
|
|
214
|
+
2. **`get_readable_statement` recibe exactamente un `auth_subject`; no existe
|
|
215
|
+
overload "sin auth"** — porque un overload "admin sin filtro" se convierte
|
|
216
|
+
en la puerta trasera que usa el 80% del código. Para bypass legítimo, crear
|
|
217
|
+
un método explícito `get_statement_as_superuser()` auditable con grep.
|
|
218
|
+
3. **El filtro del repositorio NO sustituye la verificación de scope** — un
|
|
219
|
+
token OAuth puede tener acceso a la organización pero no al scope
|
|
220
|
+
`resources:write`; el scope se verifica en el endpoint, el filtro en el
|
|
221
|
+
repositorio. Son capas complementarias.
|
|
222
|
+
|
|
223
|
+
**Refuerzo opcional con hook:** un PreToolUse que bloquee `select(Model)` en
|
|
224
|
+
archivos `service.py` fuerza el patrón. Ver `hooks/calidad-pre-commit.js` como
|
|
225
|
+
modelo de implementación.
|
|
226
|
+
|
|
227
|
+
Aplica a: FastAPI, Django (custom manager con `.for_user(user)`), Rails
|
|
228
|
+
(`scope :readable_by, ->(user) { ... }`), Laravel (Eloquent scopes globales
|
|
229
|
+
con `Model::booted`), cualquier ORM con query object pattern.
|
|
230
|
+
|
|
231
|
+
## Checklist de Seguridad Auth
|
|
232
|
+
|
|
233
|
+
- [ ] Passwords hasheados con bcrypt (rounds >= 10)
|
|
234
|
+
- [ ] JWT con expiración corta (access <= 30 min)
|
|
235
|
+
- [ ] Refresh tokens rotados en cada uso
|
|
236
|
+
- [ ] Refresh tokens almacenados en BD (para revocación)
|
|
237
|
+
- [ ] Rate limiting en endpoints de login y refresh
|
|
238
|
+
- [ ] HTTPS obligatorio en producción
|
|
239
|
+
- [ ] Headers de seguridad configurados
|
|
240
|
+
- [ ] IDOR verificado en TODOS los endpoints con recursos por ID
|
|
241
|
+
- [ ] RBAC en todos los endpoints de escritura
|
|
242
|
+
- [ ] Logs de auditoría en login, logout, cambio de contraseña
|
|
243
|
+
- [ ] Secrets en variables de entorno, nunca en código
|
|
244
|
+
- [ ] MFA disponible para roles privilegiados (ADMIN, NOMINA)
|
|
245
|
+
- [ ] Tokens no almacenados en localStorage (usar httpOnly cookies)
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## IDOR defense: responder 404 en lugar de 403 cuando el recurso ajeno existe
|
|
250
|
+
|
|
251
|
+
### Regla
|
|
252
|
+
|
|
253
|
+
Cuando un usuario autenticado pero **no-dueño** intenta acceder a un recurso
|
|
254
|
+
ajeno identificable por ID (IDOR, Insecure Direct Object Reference):
|
|
255
|
+
|
|
256
|
+
- Responder **404 Not Found** — idéntico al caso donde el recurso no existe.
|
|
257
|
+
- **Nunca** responder 403 Forbidden distinguiendo "existe pero no puedes verlo".
|
|
258
|
+
|
|
259
|
+
### Por qué
|
|
260
|
+
|
|
261
|
+
`403` revela la existencia del recurso a terceros autenticados. Un atacante
|
|
262
|
+
puede enumerar IDs secuenciales (`/pedidos/1000`, `/pedidos/1001`, ...) y
|
|
263
|
+
distinguir:
|
|
264
|
+
|
|
265
|
+
- **404** → el recurso no existe
|
|
266
|
+
- **403** → el recurso existe pero es de otro usuario (fuga de información)
|
|
267
|
+
|
|
268
|
+
Con 404 uniforme, el atacante no puede mapear el universo de IDs existentes.
|
|
269
|
+
El dueño legítimo del recurso obtiene 200; todos los demás obtienen 404 con
|
|
270
|
+
el mismo body genérico.
|
|
271
|
+
|
|
272
|
+
### Patrón canónico (FastAPI)
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from fastapi import HTTPException, status, Depends
|
|
276
|
+
|
|
277
|
+
async def _verificar_ownership(
|
|
278
|
+
recurso_id: int,
|
|
279
|
+
user: Usuario = Depends(get_current_user),
|
|
280
|
+
db: AsyncSession = Depends(get_db),
|
|
281
|
+
) -> Recurso:
|
|
282
|
+
"""Retorna el recurso solo si existe Y el user es su dueño.
|
|
283
|
+
|
|
284
|
+
Caso no-dueño → 404 idéntico al caso de ID inexistente.
|
|
285
|
+
"""
|
|
286
|
+
stmt = select(Recurso).where(
|
|
287
|
+
Recurso.id == recurso_id,
|
|
288
|
+
Recurso.usuario_id == user.id, # filtro authoritativo server-side
|
|
289
|
+
)
|
|
290
|
+
recurso = (await db.execute(stmt)).scalar_one_or_none()
|
|
291
|
+
|
|
292
|
+
if recurso is None:
|
|
293
|
+
# No distinguir "no existe" de "no es tuyo"
|
|
294
|
+
raise HTTPException(
|
|
295
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
296
|
+
detail="Recurso no encontrado",
|
|
297
|
+
)
|
|
298
|
+
return recurso
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@router.get("/pedidos/{pedido_id}")
|
|
302
|
+
async def obtener_pedido(
|
|
303
|
+
pedido: Pedido = Depends(_verificar_ownership),
|
|
304
|
+
):
|
|
305
|
+
return pedido
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Anti-patrón
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
# MAL — fuga de información
|
|
312
|
+
pedido = await db.get(Pedido, pedido_id)
|
|
313
|
+
if pedido is None:
|
|
314
|
+
raise HTTPException(404)
|
|
315
|
+
if pedido.usuario_id != user.id:
|
|
316
|
+
raise HTTPException(403, "No tienes permiso para ver este pedido") # ← revela existencia
|
|
317
|
+
return pedido
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Excepciones controladas
|
|
321
|
+
|
|
322
|
+
- Si el recurso es **compartido por diseño** (archivo público, post de foro),
|
|
323
|
+
el control de acceso no aplica y la respuesta puede ser 200 legítimamente.
|
|
324
|
+
- En admin panels donde el usuario **debe** saber qué IDs existen para
|
|
325
|
+
moderar, el rol admin puede recibir 403 con detalle; usuarios normales 404.
|
|
326
|
+
- Logs del servidor deben distinguir ambos casos para detección de abuso
|
|
327
|
+
(un mismo usuario acumulando 404s sobre IDs secuenciales es señal de
|
|
328
|
+
enumeración).
|
|
329
|
+
|
|
330
|
+
### Frameworks de seguridad
|
|
331
|
+
|
|
332
|
+
- OWASP Top 10 A01:2021 — Broken Access Control.
|
|
333
|
+
- NIST CSF PR.AA-05 — Access rights and permissions are managed.
|
|
334
|
+
- MITRE ATT&CK T1613 — Container and Resource Discovery.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Consent PII obligatorio en TODOS los ambientes (dev/staging/prod)
|
|
339
|
+
|
|
340
|
+
### Regla
|
|
341
|
+
|
|
342
|
+
Toda feature que procese datos personales identificables (PII) requiere consent
|
|
343
|
+
explícito del titular. El gate de consent **debe activarse en TODOS los
|
|
344
|
+
ambientes**, no solo en producción.
|
|
345
|
+
|
|
346
|
+
### Por qué
|
|
347
|
+
|
|
348
|
+
Las regulaciones de protección de datos (GDPR Art. 6, CCPA, LGPD Brasil,
|
|
349
|
+
LGPDPPSO México) **no distinguen entre ambientes**. Un bug en staging que
|
|
350
|
+
transfiere PII a un LLM externo sin consent es la misma violación que en
|
|
351
|
+
producción, con el agravante de que:
|
|
352
|
+
|
|
353
|
+
- Los datos de staging suelen ser "datos reales de hace 30 días" que siguen
|
|
354
|
+
siendo PII.
|
|
355
|
+
- El pipeline de LLM en staging puede loggear prompts en el proveedor (OpenAI,
|
|
356
|
+
Anthropic) que ya es "transferencia a terceros".
|
|
357
|
+
- El mismo bug detectado en staging se deploya a prod sin fix si el gate no
|
|
358
|
+
se probó activo en staging.
|
|
359
|
+
|
|
360
|
+
### Anti-patrón más común
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
# MAL — gate solo en production deja fugas en dev/staging
|
|
364
|
+
def requiere_consent_transferencia_pii(user: Usuario) -> bool:
|
|
365
|
+
if settings.environment != "production":
|
|
366
|
+
return False # ← aquí está el agujero
|
|
367
|
+
return not user.consent_transferencia_internacional
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Patrón canónico
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
# BIEN — consent obligatorio en todos los ambientes
|
|
374
|
+
def requiere_consent_transferencia_pii(user: Usuario) -> bool:
|
|
375
|
+
"""Gate activo independiente del ambiente.
|
|
376
|
+
|
|
377
|
+
Los ambientes dev/staging suelen tener datos clonados de producción
|
|
378
|
+
o sintéticos-pero-realistas. Procesarlos sin consent viola la misma
|
|
379
|
+
regulación que producción.
|
|
380
|
+
"""
|
|
381
|
+
if not user.consent_transferencia_internacional:
|
|
382
|
+
return True
|
|
383
|
+
|
|
384
|
+
# Verificación adicional: consent no expirado
|
|
385
|
+
if user.consent_fecha + CONSENT_VIGENCIA < datetime.now(UTC):
|
|
386
|
+
return True
|
|
387
|
+
|
|
388
|
+
return False
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# En el resolver/factory del cliente LLM:
|
|
392
|
+
def resolver_llm_client(user: Usuario):
|
|
393
|
+
if requiere_consent_transferencia_pii(user):
|
|
394
|
+
# Usar solo LLM on-premise o rechazar la operación
|
|
395
|
+
return _cliente_local_sin_egreso()
|
|
396
|
+
return _cliente_cloud_con_consent_vigente()
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Checklist de consent
|
|
400
|
+
|
|
401
|
+
- [ ] El modelo `Usuario` tiene campo `consent_transferencia_internacional` con default `False`.
|
|
402
|
+
- [ ] El consent se registra con timestamp y versión del texto legal aceptado (auditable).
|
|
403
|
+
- [ ] El consent expira (típicamente 12-24 meses) y requiere re-confirmación.
|
|
404
|
+
- [ ] El gate se ejecuta en dev/staging/prod sin flag de bypass por ambiente.
|
|
405
|
+
- [ ] Hay test de integración que valida que un user sin consent recibe error en staging (no solo en prod).
|
|
406
|
+
- [ ] El log de decisión del gate incluye `user_id`, `decision`, `ambiente`, `motivo` para auditoría.
|
|
407
|
+
- [ ] Existe endpoint de revocación con efecto inmediato (invalida cualquier transferencia en curso).
|
|
408
|
+
|
|
409
|
+
### Frameworks de seguridad
|
|
410
|
+
|
|
411
|
+
- GDPR Art. 6 (bases de licitud), Art. 44-50 (transferencias internacionales).
|
|
412
|
+
- NIST CSF GV.OC-03 — Legal, regulatory, and contractual requirements regarding cybersecurity are understood and managed.
|
|
413
|
+
- NIST AI RMF GOVERN-6.1 — Policies and procedures are in place to address AI risks.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Autenticación y Autorización — Implementaciones Completas
|
|
2
|
+
|
|
3
|
+
## Refresh Token — Rotación segura
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
# Tabla de refresh tokens en BD (para invalidación)
|
|
7
|
+
class RefreshToken(Base):
|
|
8
|
+
__tablename__ = "refresh_tokens"
|
|
9
|
+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
10
|
+
usuario_id = Column(UUID(as_uuid=True), ForeignKey("usuarios.id"), nullable=False)
|
|
11
|
+
token_hash = Column(String(64), nullable=False, unique=True) # SHA-256
|
|
12
|
+
expires_at = Column(DateTime(timezone=True), nullable=False)
|
|
13
|
+
revocado_en = Column(DateTime(timezone=True), nullable=True)
|
|
14
|
+
ip_address = Column(String(45), nullable=True)
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
|
|
18
|
+
@router.post("/auth/refresh", response_model=TokenResponse)
|
|
19
|
+
async def refresh_token_endpoint(
|
|
20
|
+
body: RefreshRequest,
|
|
21
|
+
db: AsyncSession = Depends(get_db),
|
|
22
|
+
request: Request = None,
|
|
23
|
+
) -> TokenResponse:
|
|
24
|
+
try:
|
|
25
|
+
payload = verificar_token(body.refresh_token, tipo_esperado="refresh")
|
|
26
|
+
except CredencialesInvalidas:
|
|
27
|
+
raise HTTPException(status_code=401, detail="Refresh token inválido")
|
|
28
|
+
|
|
29
|
+
# Verificar contra BD (detección de reuso de tokens robados)
|
|
30
|
+
token_hash = hashlib.sha256(body.refresh_token.encode()).hexdigest()
|
|
31
|
+
rt = await db.execute(
|
|
32
|
+
select(RefreshToken).where(
|
|
33
|
+
RefreshToken.token_hash == token_hash,
|
|
34
|
+
RefreshToken.revocado_en.is_(None),
|
|
35
|
+
RefreshToken.expires_at > datetime.now(timezone.utc),
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
rt = rt.scalar_one_or_none()
|
|
39
|
+
if rt is None:
|
|
40
|
+
# Posible reuso — revocar TODOS los tokens del usuario (token family)
|
|
41
|
+
await revocar_todos_tokens_usuario(db, payload["sub"])
|
|
42
|
+
raise HTTPException(status_code=401, detail="Refresh token inválido o reusado")
|
|
43
|
+
|
|
44
|
+
# Rotar: revocar el anterior, emitir uno nuevo
|
|
45
|
+
rt.revocado_en = datetime.now(timezone.utc)
|
|
46
|
+
nuevo_refresh = crear_refresh_token(payload["sub"])
|
|
47
|
+
db.add(RefreshToken(
|
|
48
|
+
usuario_id=payload["sub"],
|
|
49
|
+
token_hash=hashlib.sha256(nuevo_refresh.encode()).hexdigest(),
|
|
50
|
+
expires_at=datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS),
|
|
51
|
+
ip_address=request.client.host if request else None,
|
|
52
|
+
))
|
|
53
|
+
await db.commit()
|
|
54
|
+
|
|
55
|
+
usuario = await db.get(Usuario, payload["sub"])
|
|
56
|
+
return TokenResponse(
|
|
57
|
+
access_token=crear_access_token({"sub": str(usuario.id)}),
|
|
58
|
+
refresh_token=nuevo_refresh,
|
|
59
|
+
token_type="bearer",
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## OAuth2 / OIDC — Integración con proveedores externos
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Con authlib — pip install authlib
|
|
69
|
+
from authlib.integrations.starlette_client import OAuth
|
|
70
|
+
from starlette.config import Config
|
|
71
|
+
|
|
72
|
+
config = Config(".env")
|
|
73
|
+
oauth = OAuth(config)
|
|
74
|
+
|
|
75
|
+
# Registro de proveedor (ej: Azure AD / Microsoft Entra)
|
|
76
|
+
oauth.register(
|
|
77
|
+
name="microsoft",
|
|
78
|
+
client_id=os.environ["AZURE_CLIENT_ID"],
|
|
79
|
+
client_secret=os.environ["AZURE_CLIENT_SECRET"],
|
|
80
|
+
server_metadata_url=(
|
|
81
|
+
f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}"
|
|
82
|
+
"/v2.0/.well-known/openid-configuration"
|
|
83
|
+
),
|
|
84
|
+
client_kwargs={"scope": "openid email profile"},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@router.get("/auth/microsoft/login")
|
|
88
|
+
async def login_microsoft(request: Request):
|
|
89
|
+
redirect_uri = str(request.url_for("auth_microsoft_callback"))
|
|
90
|
+
return await oauth.microsoft.authorize_redirect(request, redirect_uri)
|
|
91
|
+
|
|
92
|
+
@router.get("/auth/microsoft/callback")
|
|
93
|
+
async def auth_microsoft_callback(request: Request, db: AsyncSession = Depends(get_db)):
|
|
94
|
+
try:
|
|
95
|
+
token = await oauth.microsoft.authorize_access_token(request)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise HTTPException(status_code=400, detail="Autenticación fallida")
|
|
98
|
+
|
|
99
|
+
userinfo = token.get("userinfo") or await oauth.microsoft.userinfo(token=token)
|
|
100
|
+
email = userinfo["email"]
|
|
101
|
+
|
|
102
|
+
usuario = await obtener_o_crear_usuario_oauth(db, email, userinfo)
|
|
103
|
+
await db.commit()
|
|
104
|
+
|
|
105
|
+
access_token = crear_access_token({"sub": str(usuario.id), "email": email})
|
|
106
|
+
return RedirectResponse(
|
|
107
|
+
f"{os.environ['FRONTEND_URL']}/auth/callback?token={access_token}"
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Passwords — Hashing y validación de fortaleza
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from passlib.context import CryptContext
|
|
117
|
+
|
|
118
|
+
pwd_context = CryptContext(
|
|
119
|
+
schemes=["bcrypt"],
|
|
120
|
+
deprecated="auto",
|
|
121
|
+
bcrypt__rounds=12,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def hash_password(password: str) -> str:
|
|
125
|
+
return pwd_context.hash(password)
|
|
126
|
+
|
|
127
|
+
def verificar_password(plain: str, hashed: str) -> bool:
|
|
128
|
+
return pwd_context.verify(plain, hashed)
|
|
129
|
+
|
|
130
|
+
# Validación de fortaleza de password
|
|
131
|
+
import re
|
|
132
|
+
|
|
133
|
+
def validar_fortaleza_password(password: str) -> list[str]:
|
|
134
|
+
"""Retorna lista de errores. Lista vacía = password válido."""
|
|
135
|
+
errores = []
|
|
136
|
+
if len(password) < 12:
|
|
137
|
+
errores.append("Mínimo 12 caracteres")
|
|
138
|
+
if not re.search(r"[A-Z]", password):
|
|
139
|
+
errores.append("Al menos una mayúscula")
|
|
140
|
+
if not re.search(r"[a-z]", password):
|
|
141
|
+
errores.append("Al menos una minúscula")
|
|
142
|
+
if not re.search(r"\d", password):
|
|
143
|
+
errores.append("Al menos un número")
|
|
144
|
+
if not re.search(r"[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]", password):
|
|
145
|
+
errores.append("Al menos un carácter especial")
|
|
146
|
+
return errores
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## MFA — Autenticación Multifactor (TOTP)
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# pip install pyotp qrcode
|
|
155
|
+
import pyotp
|
|
156
|
+
import qrcode
|
|
157
|
+
from io import BytesIO
|
|
158
|
+
import base64
|
|
159
|
+
|
|
160
|
+
def generar_secreto_totp() -> str:
|
|
161
|
+
return pyotp.random_base32()
|
|
162
|
+
|
|
163
|
+
def generar_qr_totp(email: str, secreto: str, emisor: str = "MiEmpresa") -> str:
|
|
164
|
+
"""Genera imagen QR en base64 para escanear con Google Authenticator."""
|
|
165
|
+
totp = pyotp.TOTP(secreto)
|
|
166
|
+
uri = totp.provisioning_uri(name=email, issuer_name=emisor)
|
|
167
|
+
|
|
168
|
+
qr = qrcode.make(uri)
|
|
169
|
+
buffer = BytesIO()
|
|
170
|
+
qr.save(buffer, format="PNG")
|
|
171
|
+
return base64.b64encode(buffer.getvalue()).decode()
|
|
172
|
+
|
|
173
|
+
def verificar_totp(secreto: str, codigo: str) -> bool:
|
|
174
|
+
"""Verifica un código TOTP. Permite ventana de +/-1 intervalo (30s)."""
|
|
175
|
+
totp = pyotp.TOTP(secreto)
|
|
176
|
+
return totp.verify(codigo, valid_window=1)
|
|
177
|
+
|
|
178
|
+
# Endpoint de activación MFA
|
|
179
|
+
@router.post("/auth/mfa/activar")
|
|
180
|
+
async def activar_mfa(
|
|
181
|
+
usuario: Usuario = Depends(get_current_user),
|
|
182
|
+
db: AsyncSession = Depends(get_db),
|
|
183
|
+
):
|
|
184
|
+
secreto = generar_secreto_totp()
|
|
185
|
+
usuario.mfa_secreto_pendiente = secreto
|
|
186
|
+
await db.commit()
|
|
187
|
+
return {
|
|
188
|
+
"secreto": secreto,
|
|
189
|
+
"qr_code": generar_qr_totp(usuario.email, secreto),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@router.post("/auth/mfa/confirmar")
|
|
193
|
+
async def confirmar_mfa(
|
|
194
|
+
body: MFAConfirmRequest,
|
|
195
|
+
usuario: Usuario = Depends(get_current_user),
|
|
196
|
+
db: AsyncSession = Depends(get_db),
|
|
197
|
+
):
|
|
198
|
+
if not usuario.mfa_secreto_pendiente:
|
|
199
|
+
raise HTTPException(400, "No hay MFA pendiente de activar")
|
|
200
|
+
if not verificar_totp(usuario.mfa_secreto_pendiente, body.codigo):
|
|
201
|
+
raise HTTPException(400, "Código TOTP inválido")
|
|
202
|
+
usuario.mfa_secreto = usuario.mfa_secreto_pendiente
|
|
203
|
+
usuario.mfa_secreto_pendiente = None
|
|
204
|
+
usuario.mfa_activo = True
|
|
205
|
+
await db.commit()
|
|
206
|
+
return {"mensaje": "MFA activado correctamente"}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Headers de seguridad
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
215
|
+
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
|
|
216
|
+
|
|
217
|
+
app.add_middleware(HTTPSRedirectMiddleware)
|
|
218
|
+
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["miapp.com.mx", "*.miapp.com.mx"])
|
|
219
|
+
|
|
220
|
+
@app.middleware("http")
|
|
221
|
+
async def security_headers(request, call_next):
|
|
222
|
+
response = await call_next(request)
|
|
223
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
224
|
+
response.headers["X-Frame-Options"] = "DENY"
|
|
225
|
+
response.headers["X-XSS-Protection"] = "1; mode=block"
|
|
226
|
+
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
|
227
|
+
response.headers["Content-Security-Policy"] = "default-src 'self'"
|
|
228
|
+
return response
|
|
229
|
+
```
|