moai-adk 0.34.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- moai_adk/__main__.py +136 -5
- moai_adk/astgrep/__init__.py +37 -0
- moai_adk/astgrep/analyzer.py +522 -0
- moai_adk/astgrep/models.py +124 -0
- moai_adk/astgrep/rules.py +179 -0
- moai_adk/cli/commands/analyze.py +11 -2
- moai_adk/cli/commands/doctor.py +7 -1
- moai_adk/cli/commands/init.py +321 -11
- moai_adk/cli/commands/language.py +7 -1
- moai_adk/cli/commands/rank.py +449 -0
- moai_adk/cli/commands/status.py +7 -1
- moai_adk/cli/commands/switch.py +325 -0
- moai_adk/cli/commands/update.py +296 -23
- moai_adk/cli/prompts/init_prompts.py +362 -66
- moai_adk/cli/prompts/translations/__init__.py +573 -0
- moai_adk/cli/ui/prompts.py +61 -2
- moai_adk/cli/worktree/cli.py +106 -1
- moai_adk/cli/worktree/manager.py +155 -0
- moai_adk/core/config/unified.py +244 -63
- moai_adk/core/credentials.py +264 -0
- moai_adk/core/error_recovery_system.py +22 -4
- moai_adk/core/git/conflict_detector.py +10 -1
- moai_adk/core/git/event_detector.py +16 -5
- moai_adk/core/integration/engine.py +2 -2
- moai_adk/core/integration/integration_tester.py +5 -5
- moai_adk/core/language_config_resolver.py +9 -3
- moai_adk/core/merge/analyzer.py +509 -324
- moai_adk/core/migration/alfred_to_moai_migrator.py +7 -1
- moai_adk/core/migration/backup_manager.py +54 -4
- moai_adk/core/migration/file_migrator.py +174 -2
- moai_adk/core/migration/interactive_checkbox_ui.py +42 -31
- moai_adk/core/migration/version_detector.py +123 -19
- moai_adk/core/migration/version_migrator.py +44 -9
- moai_adk/core/model_allocator.py +241 -0
- moai_adk/core/project/backup_utils.py +12 -2
- moai_adk/core/project/initializer.py +44 -87
- moai_adk/core/project/phase_executor.py +95 -33
- moai_adk/core/project/validator.py +16 -1
- moai_adk/core/quality/trust_checker.py +30 -10
- moai_adk/core/rollback_manager.py +60 -25
- moai_adk/core/template/backup.py +88 -6
- moai_adk/core/template/config.py +33 -9
- moai_adk/core/template/merger.py +34 -8
- moai_adk/core/template/processor.py +334 -11
- moai_adk/core/template_engine.py +10 -1
- moai_adk/core/template_variable_synchronizer.py +16 -2
- moai_adk/core/version_sync.py +54 -6
- moai_adk/foundation/__init__.py +1 -20
- moai_adk/foundation/testing.py +1 -1
- moai_adk/loop/__init__.py +54 -0
- moai_adk/loop/controller.py +305 -0
- moai_adk/loop/feedback.py +230 -0
- moai_adk/loop/state.py +209 -0
- moai_adk/loop/storage.py +220 -0
- moai_adk/lsp/__init__.py +70 -0
- moai_adk/lsp/client.py +320 -0
- moai_adk/lsp/models.py +261 -0
- moai_adk/lsp/protocol.py +404 -0
- moai_adk/lsp/server_manager.py +248 -0
- moai_adk/project/configuration.py +8 -1
- moai_adk/py.typed +0 -0
- moai_adk/ralph/__init__.py +37 -0
- moai_adk/ralph/engine.py +307 -0
- moai_adk/rank/__init__.py +21 -0
- moai_adk/rank/auth.py +425 -0
- moai_adk/rank/client.py +557 -0
- moai_adk/rank/config.py +147 -0
- moai_adk/rank/hook.py +1503 -0
- moai_adk/rank/py.typed +0 -0
- moai_adk/statusline/__init__.py +3 -0
- moai_adk/statusline/enhanced_output_style_detector.py +5 -5
- moai_adk/statusline/main.py +20 -1
- moai_adk/statusline/memory_collector.py +268 -0
- moai_adk/statusline/renderer.py +54 -38
- moai_adk/tag_system/__init__.py +48 -0
- moai_adk/tag_system/atomic_ops.py +117 -0
- moai_adk/tag_system/linkage.py +335 -0
- moai_adk/tag_system/parser.py +176 -0
- moai_adk/tag_system/validator.py +200 -0
- moai_adk/templates/.claude/agents/moai/builder-agent.md +19 -3
- moai_adk/templates/.claude/agents/moai/builder-command.md +62 -16
- moai_adk/templates/.claude/agents/moai/builder-plugin.md +763 -0
- moai_adk/templates/.claude/agents/moai/builder-skill.md +21 -5
- moai_adk/templates/.claude/agents/moai/expert-backend.md +103 -39
- moai_adk/templates/.claude/agents/moai/expert-debug.md +9 -3
- moai_adk/templates/.claude/agents/moai/expert-devops.md +16 -14
- moai_adk/templates/.claude/agents/moai/expert-frontend.md +45 -31
- moai_adk/templates/.claude/agents/moai/expert-performance.md +13 -9
- moai_adk/templates/.claude/agents/moai/expert-refactoring.md +228 -0
- moai_adk/templates/.claude/agents/moai/expert-security.md +19 -3
- moai_adk/templates/.claude/agents/moai/expert-testing.md +13 -9
- moai_adk/templates/.claude/agents/moai/manager-claude-code.md +8 -2
- moai_adk/templates/.claude/agents/moai/manager-docs.md +10 -5
- moai_adk/templates/.claude/agents/moai/manager-git.md +99 -27
- moai_adk/templates/.claude/agents/moai/manager-project.md +87 -7
- moai_adk/templates/.claude/agents/moai/manager-quality.md +22 -5
- moai_adk/templates/.claude/agents/moai/manager-spec.md +8 -2
- moai_adk/templates/.claude/agents/moai/manager-strategy.md +45 -14
- moai_adk/templates/.claude/agents/moai/manager-tdd.md +16 -3
- moai_adk/templates/.claude/commands/moai/0-project.md +239 -1185
- moai_adk/templates/.claude/commands/moai/1-plan.md +383 -363
- moai_adk/templates/.claude/commands/moai/2-run.md +254 -347
- moai_adk/templates/.claude/commands/moai/3-sync.md +174 -100
- moai_adk/templates/.claude/commands/moai/9-feedback.md +49 -33
- moai_adk/templates/.claude/commands/moai/alfred.md +339 -0
- moai_adk/templates/.claude/commands/moai/cancel-loop.md +163 -0
- moai_adk/templates/.claude/commands/moai/fix.md +264 -0
- moai_adk/templates/.claude/commands/moai/loop.md +363 -0
- moai_adk/templates/.claude/hooks/moai/lib/README.md +143 -0
- moai_adk/templates/.claude/hooks/moai/lib/__init__.py +37 -81
- moai_adk/templates/.claude/hooks/moai/lib/alfred_detector.py +105 -0
- moai_adk/templates/.claude/hooks/moai/lib/atomic_write.py +122 -0
- moai_adk/templates/.claude/hooks/moai/lib/checkpoint.py +4 -1
- moai_adk/templates/.claude/hooks/moai/lib/common.py +35 -5
- moai_adk/templates/.claude/hooks/moai/lib/config.py +376 -0
- moai_adk/templates/.claude/hooks/moai/lib/config_manager.py +24 -28
- moai_adk/templates/.claude/hooks/moai/lib/config_validator.py +14 -14
- moai_adk/templates/.claude/hooks/moai/lib/enhanced_output_style_detector.py +372 -0
- moai_adk/templates/.claude/hooks/moai/lib/exceptions.py +171 -0
- moai_adk/templates/.claude/hooks/moai/lib/file_utils.py +95 -0
- moai_adk/templates/.claude/hooks/moai/lib/git_collector.py +190 -0
- moai_adk/templates/.claude/hooks/moai/lib/git_operations_manager.py +15 -13
- moai_adk/templates/.claude/hooks/moai/lib/language_detector.py +298 -0
- moai_adk/templates/.claude/hooks/moai/lib/language_validator.py +125 -25
- moai_adk/templates/.claude/hooks/moai/lib/main.py +341 -0
- moai_adk/templates/.claude/hooks/moai/lib/memory_collector.py +268 -0
- moai_adk/templates/.claude/hooks/moai/lib/metrics_tracker.py +78 -0
- moai_adk/templates/.claude/hooks/moai/lib/models.py +9 -7
- moai_adk/templates/.claude/hooks/moai/lib/path_utils.py +204 -13
- moai_adk/templates/.claude/hooks/moai/lib/project.py +23 -14
- moai_adk/templates/.claude/hooks/moai/lib/renderer.py +359 -0
- moai_adk/templates/.claude/hooks/moai/lib/tag_linkage.py +333 -0
- moai_adk/templates/.claude/hooks/moai/lib/tag_parser.py +176 -0
- moai_adk/templates/.claude/hooks/moai/lib/tag_validator.py +200 -0
- moai_adk/templates/.claude/hooks/moai/lib/timeout.py +5 -5
- moai_adk/templates/.claude/hooks/moai/lib/tool_registry.py +896 -0
- moai_adk/templates/.claude/hooks/moai/lib/unified_timeout_manager.py +30 -18
- moai_adk/templates/.claude/hooks/moai/lib/update_checker.py +129 -0
- moai_adk/templates/.claude/hooks/moai/lib/version_reader.py +741 -0
- moai_adk/templates/.claude/hooks/moai/post_tool__ast_grep_scan.py +276 -0
- moai_adk/templates/.claude/hooks/moai/post_tool__code_formatter.py +255 -0
- moai_adk/templates/.claude/hooks/moai/post_tool__coverage_guard.py +325 -0
- moai_adk/templates/.claude/hooks/moai/post_tool__linter.py +315 -0
- moai_adk/templates/.claude/hooks/moai/post_tool__lsp_diagnostic.py +508 -0
- moai_adk/templates/.claude/hooks/moai/pre_commit__tag_validator.py +287 -0
- moai_adk/templates/.claude/hooks/moai/pre_tool__security_guard.py +268 -0
- moai_adk/templates/.claude/hooks/moai/pre_tool__tdd_enforcer.py +208 -0
- moai_adk/templates/.claude/hooks/moai/session_end__auto_cleanup.py +93 -61
- moai_adk/templates/.claude/hooks/moai/session_end__rank_submit.py +69 -0
- moai_adk/templates/.claude/hooks/moai/session_start__show_project_info.py +165 -70
- moai_adk/templates/.claude/hooks/moai/shared/utils/announcement_translator.py +206 -0
- moai_adk/templates/.claude/hooks/moai/stop__loop_controller.py +621 -0
- moai_adk/templates/.claude/output-styles/moai/alfred.md +758 -0
- moai_adk/templates/.claude/output-styles/moai/r2d2.md +86 -3
- moai_adk/templates/.claude/output-styles/moai/yoda.md +2 -2
- moai_adk/templates/.claude/settings.json +154 -77
- moai_adk/templates/.claude/skills/moai-docs-generation/SKILL.md +252 -198
- moai_adk/templates/.claude/skills/moai-docs-generation/examples.md +169 -323
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/README.md +39 -27
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/api-documentation.md +115 -125
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/code-documentation.md +150 -150
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/multi-format-output.md +182 -175
- moai_adk/templates/.claude/skills/moai-docs-generation/modules/user-guides.md +198 -138
- moai_adk/templates/.claude/skills/moai-docs-generation/reference.md +226 -320
- moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +43 -222
- moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +75 -219
- moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +103 -463
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/component-architecture.md +723 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/nextjs16-patterns.md +713 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/performance-optimization.md +694 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/react19-patterns.md +591 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/state-management.md +680 -0
- moai_adk/templates/.claude/skills/moai-domain-frontend/modules/vue35-patterns.md +802 -0
- moai_adk/templates/.claude/skills/moai-domain-uiux/SKILL.md +118 -339
- moai_adk/templates/.claude/skills/moai-formats-data/SKILL.md +74 -377
- moai_adk/templates/.claude/skills/moai-formats-data/modules/README.md +299 -70
- moai_adk/templates/.claude/skills/moai-foundation-claude/SKILL.md +205 -182
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/advanced-agent-patterns.md +370 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-cli-reference-official.md +420 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-custom-slash-commands-official.md +32 -22
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-devcontainers-official.md +381 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-discover-plugins-official.md +379 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-headless-official.md +378 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-hooks-official.md +110 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-plugin-marketplaces-official.md +308 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-plugins-official.md +640 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sandboxing-official.md +282 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-skills-official.md +425 -71
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-statusline-official.md +293 -0
- moai_adk/templates/.claude/skills/moai-foundation-claude/reference/claude-code-sub-agents-official.md +325 -143
- moai_adk/templates/.claude/skills/moai-foundation-context/SKILL.md +96 -316
- moai_adk/templates/.claude/skills/moai-foundation-core/SKILL.md +116 -294
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-advanced.md +279 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-implementation.md +267 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/delegation-patterns.md +121 -650
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/patterns.md +22 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-ears-format.md +200 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-first-tdd.md +37 -730
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/spec-tdd-implementation.md +275 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-framework.md +77 -819
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-implementation.md +244 -0
- moai_adk/templates/.claude/skills/moai-foundation-core/modules/trust-5-validation.md +219 -0
- moai_adk/templates/.claude/skills/moai-foundation-philosopher/SKILL.md +14 -18
- moai_adk/templates/.claude/skills/moai-foundation-quality/SKILL.md +86 -270
- moai_adk/templates/.claude/skills/moai-framework-electron/SKILL.md +288 -0
- moai_adk/templates/.claude/skills/moai-framework-electron/examples.md +2082 -0
- moai_adk/templates/.claude/skills/moai-framework-electron/reference.md +1649 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +76 -582
- moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +1239 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/modules/advanced-patterns.md +401 -0
- moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +1136 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +82 -436
- moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +585 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/modules/aspnet-core.md +627 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/modules/blazor-components.md +767 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/modules/cqrs-validation.md +626 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/modules/csharp12-features.md +580 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/modules/efcore-patterns.md +622 -0
- moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +403 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +65 -542
- moai_adk/templates/.claude/skills/moai-lang-elixir/examples.md +1171 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/modules/advanced-patterns.md +531 -0
- moai_adk/templates/.claude/skills/moai-lang-elixir/reference.md +889 -0
- moai_adk/templates/.claude/skills/moai-lang-flutter/SKILL.md +32 -405
- moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +114 -293
- moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +83 -307
- moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +179 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +973 -0
- moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +1543 -0
- moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +42 -279
- moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +94 -556
- moai_adk/templates/.claude/skills/moai-lang-php/examples.md +1608 -0
- moai_adk/templates/.claude/skills/moai-lang-php/modules/advanced-patterns.md +538 -0
- moai_adk/templates/.claude/skills/moai-lang-php/reference.md +1323 -0
- moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +108 -358
- moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +84 -482
- moai_adk/templates/.claude/skills/moai-lang-r/examples.md +1154 -0
- moai_adk/templates/.claude/skills/moai-lang-r/modules/advanced-patterns.md +489 -0
- moai_adk/templates/.claude/skills/moai-lang-r/reference.md +1087 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +106 -610
- moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +1106 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/modules/advanced-patterns.md +309 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/modules/testing-patterns.md +306 -0
- moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +1024 -0
- moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +51 -265
- moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +106 -442
- moai_adk/templates/.claude/skills/moai-lang-scala/modules/akka-actors.md +479 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/modules/cats-effect.md +489 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/modules/functional-programming.md +460 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/modules/spark-data.md +498 -0
- moai_adk/templates/.claude/skills/moai-lang-scala/modules/zio-patterns.md +541 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +88 -457
- moai_adk/templates/.claude/skills/moai-lang-swift/modules/combine-reactive.md +256 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/modules/concurrency.md +270 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/modules/swift6-features.md +265 -0
- moai_adk/templates/.claude/skills/moai-lang-swift/modules/swiftui-patterns.md +314 -0
- moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +75 -283
- moai_adk/templates/.claude/skills/moai-library-mermaid/SKILL.md +97 -252
- moai_adk/templates/.claude/skills/moai-library-nextra/SKILL.md +64 -240
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/advanced-patterns.md +331 -12
- moai_adk/templates/.claude/skills/moai-library-nextra/modules/configuration.md +330 -37
- moai_adk/templates/.claude/skills/moai-library-shadcn/SKILL.md +90 -287
- moai_adk/templates/.claude/skills/moai-platform-auth0/SKILL.md +200 -206
- moai_adk/templates/.claude/skills/moai-platform-auth0/examples.md +2446 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/adaptive-mfa.md +233 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/akamai-integration.md +214 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/application-credentials.md +280 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/attack-protection-log-events.md +224 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/attack-protection-overview.md +140 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/bot-detection.md +144 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/breached-password-detection.md +187 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/brute-force-protection.md +189 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/certifications.md +282 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/compliance-overview.md +263 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/continuous-session-protection.md +307 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/customize-mfa.md +177 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/dpop-implementation.md +283 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/fapi-implementation.md +259 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/gdpr-compliance.md +313 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/guardian-configuration.md +269 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/highly-regulated-identity.md +272 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/jwt-fundamentals.md +248 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mdl-verification.md +210 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-api-management.md +278 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-factors.md +226 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mfa-overview.md +174 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/mtls-sender-constraining.md +316 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/ropg-flow-mfa.md +216 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/security-center.md +325 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/security-guidance.md +277 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/state-parameters.md +177 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/step-up-authentication.md +251 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/suspicious-ip-throttling.md +240 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/tenant-access-control.md +179 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/modules/webauthn-fido.md +235 -0
- moai_adk/templates/.claude/skills/moai-platform-auth0/reference.md +224 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/SKILL.md +75 -330
- moai_adk/templates/.claude/skills/moai-platform-clerk/examples.md +1426 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/modules/advanced-patterns.md +417 -0
- moai_adk/templates/.claude/skills/moai-platform-clerk/reference.md +273 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/SKILL.md +100 -340
- moai_adk/templates/.claude/skills/moai-platform-convex/examples.md +506 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/modules/auth-integration.md +421 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/modules/file-storage.md +474 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/modules/reactive-queries.md +302 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/modules/server-functions.md +452 -0
- moai_adk/templates/.claude/skills/moai-platform-convex/reference.md +385 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/SKILL.md +113 -326
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/examples.md +514 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/custom-claims.md +374 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/phone-auth.md +372 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/modules/social-auth.md +339 -0
- moai_adk/templates/.claude/skills/moai-platform-firebase-auth/reference.md +382 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/SKILL.md +71 -302
- moai_adk/templates/.claude/skills/moai-platform-firestore/examples.md +445 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/modules/offline-cache.md +392 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/modules/realtime-listeners.md +441 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/modules/security-rules.md +352 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/modules/transactions.md +452 -0
- moai_adk/templates/.claude/skills/moai-platform-firestore/reference.md +322 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/SKILL.md +101 -412
- moai_adk/templates/.claude/skills/moai-platform-neon/examples.md +470 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/modules/auto-scaling.md +349 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/modules/branching-workflows.md +354 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/modules/connection-pooling.md +412 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/modules/pitr-backups.md +458 -0
- moai_adk/templates/.claude/skills/moai-platform-neon/reference.md +272 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/SKILL.md +96 -327
- moai_adk/templates/.claude/skills/moai-platform-railway/examples.md +539 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/modules/docker-deployment.md +261 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/modules/multi-service.md +291 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/modules/networking-domains.md +338 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/modules/volumes-storage.md +353 -0
- moai_adk/templates/.claude/skills/moai-platform-railway/reference.md +374 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/SKILL.md +103 -428
- moai_adk/templates/.claude/skills/moai-platform-supabase/examples.md +502 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/auth-integration.md +384 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/edge-functions.md +371 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/postgresql-pgvector.md +231 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/realtime-presence.md +354 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/row-level-security.md +286 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/storage-cdn.md +319 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/modules/typescript-patterns.md +453 -0
- moai_adk/templates/.claude/skills/moai-platform-supabase/reference.md +284 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/SKILL.md +96 -446
- moai_adk/templates/.claude/skills/moai-platform-vercel/examples.md +502 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/modules/analytics-speed.md +348 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/modules/deployment-config.md +344 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/modules/edge-functions.md +222 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/modules/isr-caching.md +306 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/modules/kv-storage.md +399 -0
- moai_adk/templates/.claude/skills/moai-platform-vercel/reference.md +360 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/SKILL.md +193 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/examples.md +1099 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/language-specific.md +307 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/pattern-syntax.md +237 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/refactoring-patterns.md +260 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/modules/security-rules.md +239 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/reference.md +288 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/go.yml +90 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/python.yml +101 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/languages/typescript.yml +83 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/quality/complexity-check.yml +94 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/quality/deprecated-apis.yml +84 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/secrets-detection.yml +89 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/sql-injection.yml +45 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/security/xss-prevention.yml +50 -0
- moai_adk/templates/.claude/skills/moai-tool-ast-grep/rules/sgconfig.yml +54 -0
- moai_adk/templates/.claude/skills/moai-workflow-jit-docs/SKILL.md +225 -423
- moai_adk/templates/.claude/skills/moai-workflow-loop/SKILL.md +197 -0
- moai_adk/templates/.claude/skills/moai-workflow-loop/examples.md +1063 -0
- moai_adk/templates/.claude/skills/moai-workflow-loop/reference.md +1414 -0
- moai_adk/templates/.claude/skills/moai-workflow-project/SKILL.md +211 -314
- moai_adk/templates/.claude/skills/moai-workflow-project/schemas/tab_schema.json +15 -43
- moai_adk/templates/.claude/skills/moai-workflow-spec/SKILL.md +119 -316
- moai_adk/templates/.claude/skills/moai-workflow-spec/modules/advanced-patterns.md +237 -0
- moai_adk/templates/.claude/skills/moai-workflow-templates/SKILL.md +96 -203
- moai_adk/templates/.claude/skills/moai-workflow-testing/SKILL.md +201 -388
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/README.md +52 -3
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/ai-debugging.md +263 -806
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/context7-integration.md +286 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/review-workflows.md +500 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/relevance-analysis.md +154 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/safety-analysis.md +148 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/scoring-algorithms.md +196 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/timeliness-analysis.md +168 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/truthfulness-analysis.md +136 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework/usability-analysis.md +153 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review/trust5-framework.md +257 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/automated-code-review.md +191 -1344
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/analysis-patterns.md +340 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/core-classes.md +299 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/code-review/tool-integration.md +380 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/debugging/debugging-workflows.md +451 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/debugging/error-analysis.md +442 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance/optimization-patterns.md +473 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance/profiling-techniques.md +481 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/ai-optimization.md +241 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/bottleneck-detection.md +397 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/optimization-plan.md +315 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/profiler-core.md +277 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization/real-time-monitoring.md +187 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/performance-optimization.md +287 -1194
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/quality-metrics.md +415 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/refactoring/ai-workflows.md +620 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/refactoring/patterns.md +692 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/security-analysis.md +429 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/smart-refactoring.md +262 -1192
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/static-analysis.md +438 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd/core-classes.md +397 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/advanced-features.md +494 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/red-green-refactor.md +316 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/test-generation.md +471 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7/test-patterns.md +371 -0
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/tdd-context7.md +227 -1222
- moai_adk/templates/.claude/skills/moai-workflow-testing/modules/trust5-validation.md +428 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/SKILL.md +228 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/integration-patterns.md +149 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/moai-adk-integration.md +245 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-advanced.md +310 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-development.md +202 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/parallel-workflows.md +302 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/registry-architecture.md +271 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/resource-optimization.md +300 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/tools-integration.md +280 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/troubleshooting.md +397 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/worktree-commands.md +296 -0
- moai_adk/templates/.claude/skills/moai-workflow-worktree/modules/worktree-management.md +217 -0
- moai_adk/templates/.git-hooks/pre-push +162 -59
- moai_adk/templates/.github/workflows/ci-universal.yml +934 -133
- moai_adk/templates/.gitignore +65 -107
- moai_adk/templates/.lsp.json +152 -0
- moai_adk/templates/.mcp.json +2 -20
- moai_adk/templates/.moai/announcements/en.json +18 -0
- moai_adk/templates/.moai/announcements/ja.json +18 -0
- moai_adk/templates/.moai/announcements/ko.json +18 -0
- moai_adk/templates/.moai/announcements/zh.json +18 -0
- moai_adk/templates/.moai/config/config.yaml +8 -2
- moai_adk/templates/.moai/config/multilingual-triggers.yaml +213 -0
- moai_adk/templates/.moai/config/sections/language.yaml +2 -2
- moai_adk/templates/.moai/config/sections/llm.yaml +41 -0
- moai_adk/templates/.moai/config/sections/pricing.yaml +30 -0
- moai_adk/templates/.moai/config/sections/project.yaml +2 -2
- moai_adk/templates/.moai/config/sections/quality.yaml +43 -5
- moai_adk/templates/.moai/config/sections/ralph.yaml +55 -0
- moai_adk/templates/.moai/config/sections/system.yaml +46 -1
- moai_adk/templates/.moai/config/sections/user.yaml +1 -1
- moai_adk/templates/.moai/config/statusline-config.yaml +2 -2
- moai_adk/templates/.moai/llm-configs/glm.json +22 -0
- moai_adk/templates/CLAUDE.ja.md +343 -0
- moai_adk/templates/CLAUDE.ko.md +343 -0
- moai_adk/templates/CLAUDE.md +200 -499
- moai_adk/templates/CLAUDE.zh.md +343 -0
- moai_adk/utils/common.py +37 -0
- moai_adk/version.py +1 -1
- moai_adk-1.1.0.dist-info/METADATA +2443 -0
- moai_adk-1.1.0.dist-info/RECORD +701 -0
- {moai_adk-0.34.0.dist-info → moai_adk-1.1.0.dist-info}/entry_points.txt +2 -0
- moai_adk-1.1.0.dist-info/licenses/LICENSE +99 -0
- moai_adk/core/config/auto_spec_config.py +0 -340
- moai_adk/core/hooks/post_tool_auto_spec_completion.py +0 -901
- moai_adk/core/spec/confidence_scoring.py +0 -680
- moai_adk/core/spec/ears_template_engine.py +0 -1247
- moai_adk/core/spec/quality_validator.py +0 -687
- moai_adk/templates/.claude/agents/moai/ai-nano-banana.md +0 -670
- moai_adk/templates/.claude/agents/moai/expert-database.md +0 -777
- moai_adk/templates/.claude/agents/moai/expert-uiux.md +0 -1041
- moai_adk/templates/.claude/agents/moai/mcp-context7.md +0 -458
- moai_adk/templates/.claude/agents/moai/mcp-figma.md +0 -1607
- moai_adk/templates/.claude/agents/moai/mcp-notion.md +0 -789
- moai_adk/templates/.claude/agents/moai/mcp-playwright.md +0 -469
- moai_adk/templates/.claude/agents/moai/mcp-sequential-thinking.md +0 -1032
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/SKILL.md +0 -438
- moai_adk/templates/.claude/skills/moai-ai-nano-banana/examples.md +0 -431
- moai_adk/templates/.claude/skills/moai-domain-uiux/modules/design-system-tokens.md +0 -405
- moai_adk/templates/.claude/skills/moai-library-nextra/advanced-patterns.md +0 -336
- moai_adk/templates/.claude/skills/moai-mcp-figma/SKILL.md +0 -402
- moai_adk/templates/.claude/skills/moai-mcp-figma/advanced-patterns.md +0 -607
- moai_adk/templates/.claude/skills/moai-mcp-notion/SKILL.md +0 -300
- moai_adk/templates/.claude/skills/moai-mcp-notion/advanced-patterns.md +0 -537
- moai_adk/templates/.claude/skills/moai-workflow-project/__init__.py +0 -520
- moai_adk/templates/.claude/skills/moai-workflow-project/complete_workflow_demo_fixed.py +0 -574
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_project_setup.py +0 -317
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/complete_workflow_demo.py +0 -663
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/config-migration-example.json +0 -190
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/question-examples.json +0 -175
- moai_adk/templates/.claude/skills/moai-workflow-project/examples/quick_start.py +0 -196
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/__init__.py +0 -17
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/advanced-patterns.md +0 -158
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/ask_user_integration.py +0 -340
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/batch_questions.py +0 -713
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/config_manager.py +0 -538
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/documentation_manager.py +0 -1336
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/language_initializer.py +0 -730
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/migration_manager.py +0 -608
- moai_adk/templates/.claude/skills/moai-workflow-project/modules/template_optimizer.py +0 -1005
- moai_adk/templates/.claude/skills/moai-workflow-project/test_integration_simple.py +0 -436
- moai_adk/templates/.claude/skills/moai-worktree/SKILL.md +0 -411
- moai_adk/templates/.claude/skills/moai-worktree/modules/integration-patterns.md +0 -982
- moai_adk/templates/.claude/skills/moai-worktree/modules/parallel-development.md +0 -778
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-commands.md +0 -646
- moai_adk/templates/.claude/skills/moai-worktree/modules/worktree-management.md +0 -782
- moai_adk/templates/.moai/config/questions/_schema.yaml +0 -151
- moai_adk/templates/.moai/config/questions/tab0-init.yaml +0 -251
- moai_adk/templates/.moai/config/questions/tab1-user.yaml +0 -108
- moai_adk/templates/.moai/config/questions/tab2-project.yaml +0 -81
- moai_adk/templates/.moai/config/questions/tab3-git.yaml +0 -634
- moai_adk/templates/.moai/config/questions/tab4-quality.yaml +0 -170
- moai_adk/templates/.moai/config/questions/tab5-system.yaml +0 -87
- moai_adk/templates/.moai/scripts/setup-glm.py +0 -136
- moai_adk-0.34.0.dist-info/METADATA +0 -2999
- moai_adk-0.34.0.dist-info/RECORD +0 -463
- moai_adk-0.34.0.dist-info/licenses/LICENSE +0 -21
- /moai_adk/foundation/{git.py → git/__init__.py} +0 -0
- /moai_adk/templates/.claude/skills/moai-library-mermaid/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-library-mermaid/{optimization.md → modules/optimization.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-library-nextra/{optimization.md → modules/optimization.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-workflow-jit-docs/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-workflow-jit-docs/{optimization.md → modules/optimization.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-workflow-testing/{advanced-patterns.md → modules/advanced-patterns.md} +0 -0
- /moai_adk/templates/.claude/skills/moai-workflow-testing/{optimization.md → modules/optimization.md} +0 -0
- /moai_adk/templates/.claude/skills/{moai-worktree → moai-workflow-worktree}/examples.md +0 -0
- /moai_adk/templates/.claude/skills/{moai-worktree → moai-workflow-worktree}/reference.md +0 -0
- {moai_adk-0.34.0.dist-info → moai_adk-1.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,2446 @@
|
|
|
1
|
+
# Auth0 Platform Security - Production Examples
|
|
2
|
+
|
|
3
|
+
Comprehensive, production-ready code examples for implementing Auth0 security features including attack protection, MFA, token management, sender constraining, and compliance.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Environment Setup](#1-environment-setup)
|
|
8
|
+
2. [Client Initialization and Authentication](#2-client-initialization-and-authentication)
|
|
9
|
+
3. [MFA Implementation](#3-mfa-implementation)
|
|
10
|
+
4. [Attack Protection Configuration](#4-attack-protection-configuration)
|
|
11
|
+
5. [Token Management](#5-token-management)
|
|
12
|
+
6. [DPoP Implementation](#6-dpop-implementation)
|
|
13
|
+
7. [mTLS Implementation](#7-mtls-implementation)
|
|
14
|
+
8. [GDPR Compliance](#8-gdpr-compliance)
|
|
15
|
+
9. [Error Handling and Retry Logic](#9-error-handling-and-retry-logic)
|
|
16
|
+
10. [Security Monitoring](#10-security-monitoring)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Environment Setup
|
|
21
|
+
|
|
22
|
+
### 1.1 Environment Variables (.env)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Auth0 Configuration
|
|
26
|
+
AUTH0_DOMAIN=your-tenant.auth0.com
|
|
27
|
+
AUTH0_CLIENT_ID=your-client-id
|
|
28
|
+
AUTH0_CLIENT_SECRET=your-client-secret
|
|
29
|
+
AUTH0_AUDIENCE=https://api.yourapp.com
|
|
30
|
+
|
|
31
|
+
# Token Configuration
|
|
32
|
+
TOKEN_ISSUER=https://your-tenant.auth0.com/
|
|
33
|
+
TOKEN_ALGORITHM=RS256
|
|
34
|
+
TOKEN_LEEWAY=30
|
|
35
|
+
|
|
36
|
+
# MFA Configuration
|
|
37
|
+
MFA_ENABLED=true
|
|
38
|
+
ADAPTIVE_MFA_ENABLED=true
|
|
39
|
+
MFA_FACTORS=otp,push,webauthn
|
|
40
|
+
|
|
41
|
+
# Attack Protection
|
|
42
|
+
BOT_DETECTION_SENSITIVITY=medium
|
|
43
|
+
BRUTE_FORCE_THRESHOLD=10
|
|
44
|
+
SUSPICIOUS_IP_THROTTLE_ENABLED=true
|
|
45
|
+
|
|
46
|
+
# DPoP Configuration (if enabled)
|
|
47
|
+
DPOP_ENABLED=false
|
|
48
|
+
DPOP_PRIVATE_KEY_PATH=/path/to/private_key.pem
|
|
49
|
+
|
|
50
|
+
# Compliance
|
|
51
|
+
GDPR_ENABLED=true
|
|
52
|
+
DATA_RETENTION_DAYS=365
|
|
53
|
+
CONSENT_MANAGEMENT_ENABLED=true
|
|
54
|
+
|
|
55
|
+
# Application URLs
|
|
56
|
+
APP_BASE_URL=https://yourapp.com
|
|
57
|
+
APP_CALLBACK_URL=https://yourapp.com/callback
|
|
58
|
+
APP_LOGOUT_URL=https://yourapp.com/logout
|
|
59
|
+
|
|
60
|
+
# API Configuration
|
|
61
|
+
API_BASE_URL=https://api.yourapp.com
|
|
62
|
+
API_TIMEOUT=30000
|
|
63
|
+
API_MAX_RETRIES=3
|
|
64
|
+
|
|
65
|
+
# Logging
|
|
66
|
+
LOG_LEVEL=info
|
|
67
|
+
SECURITY_LOG_ENABLED=true
|
|
68
|
+
AUDIT_LOG_RETENTION_DAYS=90
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 1.2 Configuration Loader (Python)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# config.py
|
|
75
|
+
import os
|
|
76
|
+
from dataclasses import dataclass
|
|
77
|
+
from typing import Optional
|
|
78
|
+
from dotenv import load_dotenv
|
|
79
|
+
|
|
80
|
+
load_dotenv()
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class Auth0Config:
|
|
84
|
+
"""Auth0 configuration from environment variables"""
|
|
85
|
+
|
|
86
|
+
domain: str
|
|
87
|
+
client_id: str
|
|
88
|
+
client_secret: str
|
|
89
|
+
audience: str
|
|
90
|
+
token_issuer: str
|
|
91
|
+
token_algorithm: str = "RS256"
|
|
92
|
+
token_leeway: int = 30
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_env(cls) -> "Auth0Config":
|
|
96
|
+
"""Load configuration from environment variables"""
|
|
97
|
+
domain = os.getenv("AUTH0_DOMAIN")
|
|
98
|
+
if not domain:
|
|
99
|
+
raise ValueError("AUTH0_DOMAIN is required")
|
|
100
|
+
|
|
101
|
+
client_id = os.getenv("AUTH0_CLIENT_ID")
|
|
102
|
+
if not client_id:
|
|
103
|
+
raise ValueError("AUTH0_CLIENT_ID is required")
|
|
104
|
+
|
|
105
|
+
client_secret = os.getenv("AUTH0_CLIENT_SECRET")
|
|
106
|
+
if not client_secret:
|
|
107
|
+
raise ValueError("AUTH0_CLIENT_SECRET is required")
|
|
108
|
+
|
|
109
|
+
audience = os.getenv("AUTH0_AUDIENCE")
|
|
110
|
+
if not audience:
|
|
111
|
+
raise ValueError("AUTH0_AUDIENCE is required")
|
|
112
|
+
|
|
113
|
+
return cls(
|
|
114
|
+
domain=domain,
|
|
115
|
+
client_id=client_id,
|
|
116
|
+
client_secret=client_secret,
|
|
117
|
+
audience=audience,
|
|
118
|
+
token_issuer=os.getenv("TOKEN_ISSUER", f"https://{domain}/"),
|
|
119
|
+
token_algorithm=os.getenv("TOKEN_ALGORITHM", "RS256"),
|
|
120
|
+
token_leeway=int(os.getenv("TOKEN_LEEWAY", "30"))
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@dataclass
|
|
124
|
+
class SecurityConfig:
|
|
125
|
+
"""Security configuration from environment variables"""
|
|
126
|
+
|
|
127
|
+
mfa_enabled: bool = False
|
|
128
|
+
adaptive_mfa_enabled: bool = False
|
|
129
|
+
mfa_factors: list = None
|
|
130
|
+
|
|
131
|
+
bot_detection_sensitivity: str = "medium"
|
|
132
|
+
brute_force_threshold: int = 10
|
|
133
|
+
suspicious_ip_throttle_enabled: bool = False
|
|
134
|
+
|
|
135
|
+
dpop_enabled: bool = False
|
|
136
|
+
dpop_private_key_path: Optional[str] = None
|
|
137
|
+
|
|
138
|
+
gdpr_enabled: bool = False
|
|
139
|
+
data_retention_days: int = 365
|
|
140
|
+
consent_management_enabled: bool = False
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_env(cls) -> "SecurityConfig":
|
|
144
|
+
"""Load security configuration from environment variables"""
|
|
145
|
+
return cls(
|
|
146
|
+
mfa_enabled=os.getenv("MFA_ENABLED", "false").lower() == "true",
|
|
147
|
+
adaptive_mfa_enabled=os.getenv("ADAPTIVE_MFA_ENABLED", "false").lower() == "true",
|
|
148
|
+
mfa_factors=os.getenv("MFA_FACTORS", "").split(",") if os.getenv("MFA_FACTORS") else [],
|
|
149
|
+
bot_detection_sensitivity=os.getenv("BOT_DETECTION_SENSITIVITY", "medium"),
|
|
150
|
+
brute_force_threshold=int(os.getenv("BRUTE_FORCE_THRESHOLD", "10")),
|
|
151
|
+
suspicious_ip_throttle_enabled=os.getenv("SUSPICIOUS_IP_THROTTLE_ENABLED", "false").lower() == "true",
|
|
152
|
+
dpop_enabled=os.getenv("DPOP_ENABLED", "false").lower() == "true",
|
|
153
|
+
dpop_private_key_path=os.getenv("DPOP_PRIVATE_KEY_PATH"),
|
|
154
|
+
gdpr_enabled=os.getenv("GDPR_ENABLED", "false").lower() == "true",
|
|
155
|
+
data_retention_days=int(os.getenv("DATA_RETENTION_DAYS", "365")),
|
|
156
|
+
consent_management_enabled=os.getenv("CONSENT_MANAGEMENT_ENABLED", "false").lower() == "true"
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 2. Client Initialization and Authentication
|
|
163
|
+
|
|
164
|
+
### 2.1 Auth0 Client Factory (Python)
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# auth0_client.py
|
|
168
|
+
import httpx
|
|
169
|
+
from typing import Optional, Dict, Any
|
|
170
|
+
from config import Auth0Config, SecurityConfig
|
|
171
|
+
|
|
172
|
+
class Auth0Client:
|
|
173
|
+
"""
|
|
174
|
+
Auth0 authentication client with comprehensive security features
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def __init__(self, auth0_config: Auth0Config, security_config: SecurityConfig):
|
|
178
|
+
self.config = auth0_config
|
|
179
|
+
self.security = security_config
|
|
180
|
+
self.domain = auth0_config.domain
|
|
181
|
+
self.client_id = auth0_config.client_id
|
|
182
|
+
self.client_secret = auth0_config.client_secret
|
|
183
|
+
self.audience = auth0_config.audience
|
|
184
|
+
|
|
185
|
+
# HTTP client with security settings
|
|
186
|
+
self.http_client = httpx.AsyncClient(
|
|
187
|
+
base_url=f"https://{self.domain}",
|
|
188
|
+
timeout=30.0,
|
|
189
|
+
headers={
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"User-Agent": f"Auth0PythonClient/1.0"
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def close(self):
|
|
196
|
+
"""Close HTTP client"""
|
|
197
|
+
await self.http_client.aclose()
|
|
198
|
+
|
|
199
|
+
async def get_token(self, code: str, redirect_uri: str, code_verifier: Optional[str] = None) -> Dict[str, Any]:
|
|
200
|
+
"""
|
|
201
|
+
Exchange authorization code for tokens
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
code: Authorization code from login
|
|
205
|
+
redirect_uri: Redirect URI used in authorization
|
|
206
|
+
code_verifier: PKCE code verifier (for public clients)
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Dictionary with access_token, refresh_token, id_token, etc.
|
|
210
|
+
"""
|
|
211
|
+
token_endpoint = f"https://{self.domain}/oauth/token"
|
|
212
|
+
|
|
213
|
+
payload = {
|
|
214
|
+
"grant_type": "authorization_code",
|
|
215
|
+
"client_id": self.client_id,
|
|
216
|
+
"code": code,
|
|
217
|
+
"redirect_uri": redirect_uri
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Add client secret for confidential clients
|
|
221
|
+
if self.client_secret:
|
|
222
|
+
payload["client_secret"] = self.client_secret
|
|
223
|
+
|
|
224
|
+
# Add PKCE verifier for public clients
|
|
225
|
+
if code_verifier:
|
|
226
|
+
payload["code_verifier"] = code_verifier
|
|
227
|
+
|
|
228
|
+
response = await self.http_client.post(token_endpoint, json=payload)
|
|
229
|
+
response.raise_for_status()
|
|
230
|
+
|
|
231
|
+
tokens = response.json()
|
|
232
|
+
|
|
233
|
+
# Validate token response
|
|
234
|
+
if "access_token" not in tokens:
|
|
235
|
+
raise ValueError("Token response missing access_token")
|
|
236
|
+
|
|
237
|
+
return tokens
|
|
238
|
+
|
|
239
|
+
async def refresh_token(self, refresh_token: str) -> Dict[str, Any]:
|
|
240
|
+
"""
|
|
241
|
+
Refresh access token using refresh token
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
refresh_token: Refresh token from initial authentication
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Dictionary with new access_token and refresh_token (if rotation enabled)
|
|
248
|
+
"""
|
|
249
|
+
token_endpoint = f"https://{self.domain}/oauth/token"
|
|
250
|
+
|
|
251
|
+
payload = {
|
|
252
|
+
"grant_type": "refresh_token",
|
|
253
|
+
"client_id": self.client_id,
|
|
254
|
+
"refresh_token": refresh_token
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if self.client_secret:
|
|
258
|
+
payload["client_secret"] = self.client_secret
|
|
259
|
+
|
|
260
|
+
response = await self.http_client.post(token_endpoint, json=payload)
|
|
261
|
+
response.raise_for_status()
|
|
262
|
+
|
|
263
|
+
return response.json()
|
|
264
|
+
|
|
265
|
+
async def get_user_info(self, access_token: str) -> Dict[str, Any]:
|
|
266
|
+
"""
|
|
267
|
+
Get user information from Auth0
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
access_token: Valid access token
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dictionary with user profile information
|
|
274
|
+
"""
|
|
275
|
+
userinfo_endpoint = f"https://{self.domain}/userinfo"
|
|
276
|
+
|
|
277
|
+
response = await self.http_client.get(
|
|
278
|
+
userinfo_endpoint,
|
|
279
|
+
headers={"Authorization": f"Bearer {access_token}"}
|
|
280
|
+
)
|
|
281
|
+
response.raise_for_status()
|
|
282
|
+
|
|
283
|
+
return response.json()
|
|
284
|
+
|
|
285
|
+
async def revoke_token(self, token: str) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Revoke a token (requires Management API)
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
token: Token to revoke
|
|
291
|
+
"""
|
|
292
|
+
# Note: This requires Management API credentials
|
|
293
|
+
# Implementation depends on your setup
|
|
294
|
+
pass
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 2.2 JWT Token Validator (Python)
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
# token_validator.py
|
|
301
|
+
import jwt
|
|
302
|
+
import httpx
|
|
303
|
+
from typing import Dict, Any, Optional
|
|
304
|
+
from datetime import datetime, timedelta
|
|
305
|
+
from config import Auth0Config
|
|
306
|
+
|
|
307
|
+
class TokenValidator:
|
|
308
|
+
"""
|
|
309
|
+
JWT token validation with comprehensive security checks
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
def __init__(self, config: Auth0Config):
|
|
313
|
+
self.config = config
|
|
314
|
+
self.jwks_url = f"https://{config.domain}/.well-known/jwks.json"
|
|
315
|
+
self.jwks_cache: Dict[str, Any] = {}
|
|
316
|
+
self.jwks_cache_expiry: Optional[datetime] = None
|
|
317
|
+
|
|
318
|
+
async def get_jwks(self) -> Dict[str, Any]:
|
|
319
|
+
"""
|
|
320
|
+
Fetch JSON Web Key Set from Auth0
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Dictionary with keys
|
|
324
|
+
"""
|
|
325
|
+
# Check cache
|
|
326
|
+
if self.jwks_cache and self.jwks_cache_expiry and datetime.now() < self.jwks_cache_expiry:
|
|
327
|
+
return self.jwks_cache
|
|
328
|
+
|
|
329
|
+
async with httpx.AsyncClient() as client:
|
|
330
|
+
response = await client.get(self.jwks_url)
|
|
331
|
+
response.raise_for_status()
|
|
332
|
+
jwks = response.json()
|
|
333
|
+
|
|
334
|
+
# Cache for 1 hour
|
|
335
|
+
self.jwks_cache = jwks
|
|
336
|
+
self.jwks_cache_expiry = datetime.now() + timedelta(hours=1)
|
|
337
|
+
|
|
338
|
+
return jwks
|
|
339
|
+
|
|
340
|
+
async def validate_access_token(self, token: str) -> Dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Validate access token with comprehensive checks
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
token: JWT access token
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
Decoded token payload
|
|
349
|
+
|
|
350
|
+
Raises:
|
|
351
|
+
ValueError: If token is invalid
|
|
352
|
+
"""
|
|
353
|
+
try:
|
|
354
|
+
# Get JWKS for signature verification
|
|
355
|
+
jwks = await self.get_jwks()
|
|
356
|
+
|
|
357
|
+
# Decode and validate token
|
|
358
|
+
payload = jwt.decode(
|
|
359
|
+
token,
|
|
360
|
+
key=self._get_public_key(jwks, token),
|
|
361
|
+
algorithms=[self.config.token_algorithm],
|
|
362
|
+
audience=self.config.audience,
|
|
363
|
+
issuer=self.config.token_issuer,
|
|
364
|
+
leeway=self.config.token_leeway
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Additional validation checks
|
|
368
|
+
self._validate_token_claims(payload)
|
|
369
|
+
|
|
370
|
+
return payload
|
|
371
|
+
|
|
372
|
+
except jwt.ExpiredSignatureError:
|
|
373
|
+
raise ValueError("Token has expired")
|
|
374
|
+
except jwt.InvalidTokenError as e:
|
|
375
|
+
raise ValueError(f"Invalid token: {str(e)}")
|
|
376
|
+
|
|
377
|
+
def _get_public_key(self, jwks: Dict[str, Any], token: str) -> str:
|
|
378
|
+
"""
|
|
379
|
+
Extract public key from JWKS using token header
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
jwks: JSON Web Key Set
|
|
383
|
+
token: JWT token
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Public key string
|
|
387
|
+
"""
|
|
388
|
+
unverified_header = jwt.get_unverified_header(token)
|
|
389
|
+
kid = unverified_header.get("kid")
|
|
390
|
+
|
|
391
|
+
if not kid:
|
|
392
|
+
raise ValueError("Token missing 'kid' in header")
|
|
393
|
+
|
|
394
|
+
# Find matching key
|
|
395
|
+
for key in jwks.get("keys", []):
|
|
396
|
+
if key.get("kid") == kid:
|
|
397
|
+
return jwt.algorithms.RSAAlgorithm.from_jwk(key)
|
|
398
|
+
|
|
399
|
+
raise ValueError(f"Unable to find key with kid: {kid}")
|
|
400
|
+
|
|
401
|
+
def _validate_token_claims(self, payload: Dict[str, Any]) -> None:
|
|
402
|
+
"""
|
|
403
|
+
Validate token claims for security
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
payload: Decoded token payload
|
|
407
|
+
|
|
408
|
+
Raises:
|
|
409
|
+
ValueError: If claims are invalid
|
|
410
|
+
"""
|
|
411
|
+
# Required claims
|
|
412
|
+
required_claims = ["iss", "sub", "aud", "exp", "iat"]
|
|
413
|
+
for claim in required_claims:
|
|
414
|
+
if claim not in payload:
|
|
415
|
+
raise ValueError(f"Token missing required claim: {claim}")
|
|
416
|
+
|
|
417
|
+
# Validate scope (if present)
|
|
418
|
+
if "scope" in payload:
|
|
419
|
+
scopes = payload["scope"].split()
|
|
420
|
+
# Add custom scope validation here
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
# Validate token type
|
|
424
|
+
if payload.get("typ") and payload["typ"] != "Bearer":
|
|
425
|
+
raise ValueError(f"Invalid token type: {payload['typ']}")
|
|
426
|
+
|
|
427
|
+
def validate_id_token(self, token: str, nonce: Optional[str] = None) -> Dict[str, Any]:
|
|
428
|
+
"""
|
|
429
|
+
Validate ID token with nonce verification
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
token: JWT ID token
|
|
433
|
+
nonce: Original nonce from authorization request
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Decoded token payload
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
ValueError: If token is invalid
|
|
440
|
+
"""
|
|
441
|
+
payload = await self.validate_access_token(token)
|
|
442
|
+
|
|
443
|
+
# Validate nonce if provided
|
|
444
|
+
if nonce and payload.get("nonce") != nonce:
|
|
445
|
+
raise ValueError("Token nonce does not match")
|
|
446
|
+
|
|
447
|
+
return payload
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 3. MFA Implementation
|
|
453
|
+
|
|
454
|
+
### 3.1 MFA Challenge Handler (Python)
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
# mfa_handler.py
|
|
458
|
+
from typing import Dict, Any, Optional
|
|
459
|
+
from enum import Enum
|
|
460
|
+
from auth0_client import Auth0Client
|
|
461
|
+
|
|
462
|
+
class MFAMethod(Enum):
|
|
463
|
+
"""Supported MFA methods"""
|
|
464
|
+
OTP = "otp"
|
|
465
|
+
PUSH = "push"
|
|
466
|
+
WEBAUTHN = "webauthn"
|
|
467
|
+
SMS = "sms"
|
|
468
|
+
VOICE = "voice"
|
|
469
|
+
|
|
470
|
+
class MFAHandler:
|
|
471
|
+
"""
|
|
472
|
+
Multi-Factor Authentication handler for Auth0
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
476
|
+
self.client = auth0_client
|
|
477
|
+
|
|
478
|
+
async def initiate_mfa_challenge(
|
|
479
|
+
self,
|
|
480
|
+
access_token: str,
|
|
481
|
+
mfa_token: str,
|
|
482
|
+
method: MFAMethod = MFAMethod.OTP
|
|
483
|
+
) -> Dict[str, Any]:
|
|
484
|
+
"""
|
|
485
|
+
Initiate MFA challenge
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
access_token: Valid access token
|
|
489
|
+
mfa_token: MFA token from authentication
|
|
490
|
+
method: MFA method to use
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Challenge response with challenge_id or oob_code
|
|
494
|
+
"""
|
|
495
|
+
mfa_endpoint = f"https://{self.client.domain}/mfa/challenge"
|
|
496
|
+
|
|
497
|
+
payload = {
|
|
498
|
+
"mfa_token": mfa_token,
|
|
499
|
+
"client_id": self.client.client_id,
|
|
500
|
+
"challenge_type": method.value
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if self.client.client_secret:
|
|
504
|
+
payload["client_secret"] = self.client.client_secret
|
|
505
|
+
|
|
506
|
+
response = await self.client.http_client.post(
|
|
507
|
+
mfa_endpoint,
|
|
508
|
+
json=payload,
|
|
509
|
+
headers={"Authorization": f"Bearer {access_token}"}
|
|
510
|
+
)
|
|
511
|
+
response.raise_for_status()
|
|
512
|
+
|
|
513
|
+
return response.json()
|
|
514
|
+
|
|
515
|
+
async def verify_mfa_challenge(
|
|
516
|
+
self,
|
|
517
|
+
access_token: str,
|
|
518
|
+
challenge_id: str,
|
|
519
|
+
code: str,
|
|
520
|
+
oob_code: Optional[str] = None
|
|
521
|
+
) -> Dict[str, Any]:
|
|
522
|
+
"""
|
|
523
|
+
Verify MFA challenge response
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
access_token: Valid access token
|
|
527
|
+
challenge_id: Challenge ID from initiate_mfa_challenge
|
|
528
|
+
code: OTP code from user
|
|
529
|
+
oob_code: Out-of-band code (for push notifications)
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Verification result with tokens
|
|
533
|
+
"""
|
|
534
|
+
verify_endpoint = f"https://{self.client.domain}/mfa/verify"
|
|
535
|
+
|
|
536
|
+
payload = {
|
|
537
|
+
"mfa_token": challenge_id,
|
|
538
|
+
"client_id": self.client.client_id,
|
|
539
|
+
"code": code
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if oob_code:
|
|
543
|
+
payload["oob_code"] = oob_code
|
|
544
|
+
|
|
545
|
+
if self.client.client_secret:
|
|
546
|
+
payload["client_secret"] = self.client.client_secret
|
|
547
|
+
|
|
548
|
+
response = await self.client.http_client.post(
|
|
549
|
+
verify_endpoint,
|
|
550
|
+
json=payload,
|
|
551
|
+
headers={"Authorization": f"Bearer {access_token}"}
|
|
552
|
+
)
|
|
553
|
+
response.raise_for_status()
|
|
554
|
+
|
|
555
|
+
return response.json()
|
|
556
|
+
|
|
557
|
+
async def associate_mfa_factor(
|
|
558
|
+
self,
|
|
559
|
+
access_token: str,
|
|
560
|
+
factor_type: MFAMethod,
|
|
561
|
+
factor_data: Dict[str, Any]
|
|
562
|
+
) -> Dict[str, Any]:
|
|
563
|
+
"""
|
|
564
|
+
Associate MFA factor with user account
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
access_token: Valid access token
|
|
568
|
+
factor_type: Type of MFA factor
|
|
569
|
+
factor_data: Factor-specific data (e.g., phone number for SMS)
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Association response
|
|
573
|
+
"""
|
|
574
|
+
associate_endpoint = f"https://{self.client.domain}/mfa/associate"
|
|
575
|
+
|
|
576
|
+
payload = {
|
|
577
|
+
"client_id": self.client.client_id,
|
|
578
|
+
"factor_type": factor_type.value,
|
|
579
|
+
**factor_data
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
response = await self.client.http_client.post(
|
|
583
|
+
associate_endpoint,
|
|
584
|
+
json=payload,
|
|
585
|
+
headers={"Authorization": f"Bearer {access_token}"}
|
|
586
|
+
)
|
|
587
|
+
response.raise_for_status()
|
|
588
|
+
|
|
589
|
+
return response.json()
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### 3.2 Adaptive MFA Integration (Python)
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
# adaptive_mfa.py
|
|
596
|
+
from typing import Dict, Any, Optional
|
|
597
|
+
from datetime import datetime
|
|
598
|
+
from auth0_client import Auth0Client
|
|
599
|
+
|
|
600
|
+
class AdaptiveMFAEvaluator:
|
|
601
|
+
"""
|
|
602
|
+
Adaptive MFA risk evaluation and challenge logic
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
606
|
+
self.client = auth0_client
|
|
607
|
+
|
|
608
|
+
async def evaluate_login_risk(
|
|
609
|
+
self,
|
|
610
|
+
user_id: str,
|
|
611
|
+
ip_address: str,
|
|
612
|
+
user_agent: str,
|
|
613
|
+
location: Optional[Dict[str, float]] = None
|
|
614
|
+
) -> Dict[str, Any]:
|
|
615
|
+
"""
|
|
616
|
+
Evaluate login risk using Auth0 Adaptive MFA signals
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
user_id: User ID
|
|
620
|
+
ip_address: IP address of login attempt
|
|
621
|
+
user_agent: User agent string
|
|
622
|
+
location: Optional location data (lat, lon)
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Risk assessment with score and recommended action
|
|
626
|
+
"""
|
|
627
|
+
# Collect risk signals
|
|
628
|
+
signals = await self._collect_risk_signals(
|
|
629
|
+
user_id, ip_address, user_agent, location
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# Calculate risk score
|
|
633
|
+
risk_score = self._calculate_risk_score(signals)
|
|
634
|
+
|
|
635
|
+
# Determine action
|
|
636
|
+
action = self._determine_action(risk_score, signals)
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
"risk_score": risk_score,
|
|
640
|
+
"action": action,
|
|
641
|
+
"signals": signals,
|
|
642
|
+
"timestamp": datetime.utcnow().isoformat()
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async def _collect_risk_signals(
|
|
646
|
+
self,
|
|
647
|
+
user_id: str,
|
|
648
|
+
ip_address: str,
|
|
649
|
+
user_agent: str,
|
|
650
|
+
location: Optional[Dict[str, float]]
|
|
651
|
+
) -> Dict[str, Any]:
|
|
652
|
+
"""Collect risk signals for evaluation"""
|
|
653
|
+
|
|
654
|
+
signals = {
|
|
655
|
+
"new_device": await self._is_new_device(user_id, user_agent),
|
|
656
|
+
"impossible_travel": await self._check_impossible_travel(
|
|
657
|
+
user_id, location
|
|
658
|
+
) if location else False,
|
|
659
|
+
"untrusted_ip": await self._is_untrusted_ip(ip_address),
|
|
660
|
+
"anomalous_location": await self._is_anomalous_location(
|
|
661
|
+
user_id, location
|
|
662
|
+
) if location else False
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return signals
|
|
666
|
+
|
|
667
|
+
async def _is_new_device(self, user_id: str, user_agent: str) -> bool:
|
|
668
|
+
"""Check if device is new (not seen in 30 days)"""
|
|
669
|
+
# Implementation: Check user agent against recent logins
|
|
670
|
+
# This would typically query your user device tracking database
|
|
671
|
+
return False # Placeholder
|
|
672
|
+
|
|
673
|
+
async def _check_impossible_travel(
|
|
674
|
+
self,
|
|
675
|
+
user_id: str,
|
|
676
|
+
location: Dict[str, float]
|
|
677
|
+
) -> bool:
|
|
678
|
+
"""Check for impossible travel (geographic anomalies)"""
|
|
679
|
+
# Implementation: Compare with last known location
|
|
680
|
+
# Calculate travel velocity and assess feasibility
|
|
681
|
+
return False # Placeholder
|
|
682
|
+
|
|
683
|
+
async def _is_untrusted_ip(self, ip_address: str) -> bool:
|
|
684
|
+
"""Check if IP is untrusted (has suspicious activity)"""
|
|
685
|
+
# Implementation: Check against IP reputation databases
|
|
686
|
+
# Could use Auth0's Suspicious IP Throttling data
|
|
687
|
+
return False # Placeholder
|
|
688
|
+
|
|
689
|
+
async def _is_anomalous_location(
|
|
690
|
+
self,
|
|
691
|
+
user_id: str,
|
|
692
|
+
location: Dict[str, float]
|
|
693
|
+
) -> bool:
|
|
694
|
+
"""Check if location is anomalous for user"""
|
|
695
|
+
# Implementation: Compare with user's typical locations
|
|
696
|
+
return False # Placeholder
|
|
697
|
+
|
|
698
|
+
def _calculate_risk_score(self, signals: Dict[str, Any]) -> float:
|
|
699
|
+
"""
|
|
700
|
+
Calculate risk score from signals
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Risk score (0.0 = low risk, 1.0 = high risk)
|
|
704
|
+
"""
|
|
705
|
+
risk_score = 0.0
|
|
706
|
+
|
|
707
|
+
# Weighted risk factors
|
|
708
|
+
if signals["new_device"]:
|
|
709
|
+
risk_score += 0.3
|
|
710
|
+
|
|
711
|
+
if signals["impossible_travel"]:
|
|
712
|
+
risk_score += 0.4
|
|
713
|
+
|
|
714
|
+
if signals["untrusted_ip"]:
|
|
715
|
+
risk_score += 0.5
|
|
716
|
+
|
|
717
|
+
if signals["anomalous_location"]:
|
|
718
|
+
risk_score += 0.2
|
|
719
|
+
|
|
720
|
+
return min(risk_score, 1.0)
|
|
721
|
+
|
|
722
|
+
def _determine_action(self, risk_score: float, signals: Dict[str, Any]) -> str:
|
|
723
|
+
"""
|
|
724
|
+
Determine action based on risk score
|
|
725
|
+
|
|
726
|
+
Returns:
|
|
727
|
+
Action: "allow", "mfa", "block"
|
|
728
|
+
"""
|
|
729
|
+
if risk_score >= 0.7:
|
|
730
|
+
return "block"
|
|
731
|
+
|
|
732
|
+
if risk_score >= 0.4:
|
|
733
|
+
return "mfa"
|
|
734
|
+
|
|
735
|
+
return "allow"
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## 4. Attack Protection Configuration
|
|
741
|
+
|
|
742
|
+
### 4.1 Attack Protection Monitor (Python)
|
|
743
|
+
|
|
744
|
+
```python
|
|
745
|
+
# attack_protection.py
|
|
746
|
+
from typing import Dict, Any, List
|
|
747
|
+
from datetime import datetime, timedelta
|
|
748
|
+
from auth0_client import Auth0Client
|
|
749
|
+
from enum import Enum
|
|
750
|
+
|
|
751
|
+
class AttackType(Enum):
|
|
752
|
+
"""Attack types to monitor"""
|
|
753
|
+
CREDENTIAL_STUFFING = "credential_stuffing"
|
|
754
|
+
BRUTE_FORCE = "brute_force"
|
|
755
|
+
BOT_ATTACK = "bot_attack"
|
|
756
|
+
SUSPICIOUS_IP = "suspicious_ip"
|
|
757
|
+
|
|
758
|
+
class AttackProtectionMonitor:
|
|
759
|
+
"""
|
|
760
|
+
Monitor and respond to Auth0 attack protection events
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
764
|
+
self.client = auth0_client
|
|
765
|
+
self.base_url = f"https://{auth0_client.domain}/api/v2"
|
|
766
|
+
|
|
767
|
+
async def get_attack_events(
|
|
768
|
+
self,
|
|
769
|
+
attack_type: Optional[AttackType] = None,
|
|
770
|
+
from_date: Optional[datetime] = None,
|
|
771
|
+
to_date: Optional[datetime] = None
|
|
772
|
+
) -> List[Dict[str, Any]]:
|
|
773
|
+
"""
|
|
774
|
+
Get attack protection events from Auth0 logs
|
|
775
|
+
|
|
776
|
+
Args:
|
|
777
|
+
attack_type: Filter by attack type
|
|
778
|
+
from_date: Start date for events (default: 24 hours ago)
|
|
779
|
+
to_date: End date for events (default: now)
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
List of attack events
|
|
783
|
+
"""
|
|
784
|
+
# Default time range: last 24 hours
|
|
785
|
+
if not from_date:
|
|
786
|
+
from_date = datetime.utcnow() - timedelta(hours=24)
|
|
787
|
+
if not to_date:
|
|
788
|
+
to_date = datetime.utcnow()
|
|
789
|
+
|
|
790
|
+
# Query Auth0 logs for attack events
|
|
791
|
+
# This requires Management API access
|
|
792
|
+
logs_endpoint = f"{self.base_url}/logs"
|
|
793
|
+
|
|
794
|
+
params = {
|
|
795
|
+
"q": f"type:{attack_type.value} if attack_type else 'type:*attack*'",
|
|
796
|
+
"from": from_date.isoformat(),
|
|
797
|
+
"to": to_date.isoformat(),
|
|
798
|
+
"per_page": 100
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
response = await self.client.http_client.get(
|
|
802
|
+
logs_endpoint,
|
|
803
|
+
params=params,
|
|
804
|
+
headers={"Authorization": f"Bearer {await self._get_management_token()}"}
|
|
805
|
+
)
|
|
806
|
+
response.raise_for_status()
|
|
807
|
+
|
|
808
|
+
return response.json().get("logs", [])
|
|
809
|
+
|
|
810
|
+
async def get_suspicious_ip_throttling(self) -> Dict[str, Any]:
|
|
811
|
+
"""
|
|
812
|
+
Get current suspicious IP throttling status
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Throttling configuration and statistics
|
|
816
|
+
"""
|
|
817
|
+
# This would query Auth0 Management API
|
|
818
|
+
# Implementation depends on API availability
|
|
819
|
+
return {
|
|
820
|
+
"enabled": True,
|
|
821
|
+
"thresholds": {
|
|
822
|
+
"login_per_day": 100,
|
|
823
|
+
"signup_per_minute": 10
|
|
824
|
+
},
|
|
825
|
+
"blocked_ips": []
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async def is_ip_blocked(self, ip_address: str) -> bool:
|
|
829
|
+
"""
|
|
830
|
+
Check if IP address is currently blocked
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
ip_address: IP address to check
|
|
834
|
+
|
|
835
|
+
Returns:
|
|
836
|
+
True if IP is blocked
|
|
837
|
+
"""
|
|
838
|
+
# Implementation: Check against Auth0's blocked IP list
|
|
839
|
+
# Could also check local cache of blocked IPs
|
|
840
|
+
return False
|
|
841
|
+
|
|
842
|
+
async def handle_brute_force_protection(
|
|
843
|
+
self,
|
|
844
|
+
user_id: str,
|
|
845
|
+
ip_address: str
|
|
846
|
+
) -> Dict[str, Any]:
|
|
847
|
+
"""
|
|
848
|
+
Handle brute force protection event
|
|
849
|
+
|
|
850
|
+
Args:
|
|
851
|
+
user_id: User ID being targeted
|
|
852
|
+
ip_address: IP address of attack
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
Action taken
|
|
856
|
+
"""
|
|
857
|
+
# Check if account is locked
|
|
858
|
+
is_locked = await self._is_account_locked(user_id)
|
|
859
|
+
|
|
860
|
+
if is_locked:
|
|
861
|
+
return {
|
|
862
|
+
"action": "account_locked",
|
|
863
|
+
"message": "Account has been locked due to suspicious activity"
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
# Check if IP is blocked
|
|
867
|
+
ip_blocked = await self.is_ip_blocked(ip_address)
|
|
868
|
+
|
|
869
|
+
if ip_blocked:
|
|
870
|
+
return {
|
|
871
|
+
"action": "ip_blocked",
|
|
872
|
+
"message": "IP address has been blocked"
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return {
|
|
876
|
+
"action": "monitoring",
|
|
877
|
+
"message": "Monitoring suspicious activity"
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async def _is_account_locked(self, user_id: str) -> bool:
|
|
881
|
+
"""Check if user account is locked"""
|
|
882
|
+
# Implementation: Query Auth0 user status
|
|
883
|
+
return False
|
|
884
|
+
|
|
885
|
+
async def _get_management_token(self) -> str:
|
|
886
|
+
"""Get Management API token"""
|
|
887
|
+
# Implementation: Use client credentials flow
|
|
888
|
+
# to get Management API access token
|
|
889
|
+
return ""
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### 4.2 Breached Password Detection (Python)
|
|
893
|
+
|
|
894
|
+
```python
|
|
895
|
+
# breached_password.py
|
|
896
|
+
from typing import Dict, Any, Optional
|
|
897
|
+
from auth0_client import Auth0Client
|
|
898
|
+
|
|
899
|
+
class BreachedPasswordDetector:
|
|
900
|
+
"""
|
|
901
|
+
Detect and block breached passwords using Auth0's Breached Password Detection
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
905
|
+
self.client = auth0_client
|
|
906
|
+
|
|
907
|
+
async def check_password_breached(
|
|
908
|
+
self,
|
|
909
|
+
password: str,
|
|
910
|
+
user_id: Optional[str] = None
|
|
911
|
+
) -> Dict[str, Any]:
|
|
912
|
+
"""
|
|
913
|
+
Check if password has been breached
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
password: Password to check
|
|
917
|
+
user_id: Optional user ID for logging
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
Breach status and action required
|
|
921
|
+
"""
|
|
922
|
+
# Note: Actual breached password checking happens on Auth0 side
|
|
923
|
+
# during authentication. This is a client-side check using
|
|
924
|
+
# Have I Been Pwned API (optional enhancement).
|
|
925
|
+
|
|
926
|
+
# For production, rely on Auth0's built-in breached password detection
|
|
927
|
+
|
|
928
|
+
return {
|
|
929
|
+
"breached": False,
|
|
930
|
+
"action": "allow"
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async def handle_breached_password(self, user_id: str) -> Dict[str, Any]:
|
|
934
|
+
"""
|
|
935
|
+
Handle breached password detection
|
|
936
|
+
|
|
937
|
+
Args:
|
|
938
|
+
user_id: User ID
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
Action taken (password reset required)
|
|
942
|
+
"""
|
|
943
|
+
# Implementation:
|
|
944
|
+
# 1. Block login attempt
|
|
945
|
+
# 2. Trigger password reset email
|
|
946
|
+
# 3. Log security event
|
|
947
|
+
# 4. Notify user and admin
|
|
948
|
+
|
|
949
|
+
return {
|
|
950
|
+
"action": "password_reset_required",
|
|
951
|
+
"message": "Your password has been detected in a data breach. Please reset your password."
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
def test_breached_password(self) -> str:
|
|
955
|
+
"""
|
|
956
|
+
Generate test password for breached password detection
|
|
957
|
+
|
|
958
|
+
Returns:
|
|
959
|
+
Test password that will trigger breached password detection
|
|
960
|
+
"""
|
|
961
|
+
# Auth0 provides test passwords that start with AUTH0-TEST-
|
|
962
|
+
# These will always trigger breached password detection
|
|
963
|
+
return "AUTH0-TEST-breached123"
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## 5. Token Management
|
|
969
|
+
|
|
970
|
+
### 5.1 Refresh Token Rotation (Python)
|
|
971
|
+
|
|
972
|
+
```python
|
|
973
|
+
# token_rotation.py
|
|
974
|
+
from typing import Dict, Any, Optional
|
|
975
|
+
from datetime import datetime, timedelta
|
|
976
|
+
from auth0_client import Auth0Client
|
|
977
|
+
from token_validator import TokenValidator
|
|
978
|
+
|
|
979
|
+
class TokenRotationManager:
|
|
980
|
+
"""
|
|
981
|
+
Manage refresh token rotation with security monitoring
|
|
982
|
+
"""
|
|
983
|
+
|
|
984
|
+
def __init__(self, auth0_client: Auth0Client, validator: TokenValidator):
|
|
985
|
+
self.client = auth0_client
|
|
986
|
+
self.validator = validator
|
|
987
|
+
|
|
988
|
+
async def rotate_token(
|
|
989
|
+
self,
|
|
990
|
+
refresh_token: str,
|
|
991
|
+
detect_reuse: bool = True
|
|
992
|
+
) -> Dict[str, Any]:
|
|
993
|
+
"""
|
|
994
|
+
Rotate refresh token and detect reuse attacks
|
|
995
|
+
|
|
996
|
+
Args:
|
|
997
|
+
refresh_token: Current refresh token
|
|
998
|
+
detect_reuse: Enable reuse detection
|
|
999
|
+
|
|
1000
|
+
Returns:
|
|
1001
|
+
New token pair
|
|
1002
|
+
"""
|
|
1003
|
+
# Store old token for reuse detection
|
|
1004
|
+
old_token = refresh_token
|
|
1005
|
+
|
|
1006
|
+
# Get new tokens
|
|
1007
|
+
new_tokens = await self.client.refresh_token(refresh_token)
|
|
1008
|
+
|
|
1009
|
+
# If reuse detection enabled, store old token hash
|
|
1010
|
+
if detect_reuse:
|
|
1011
|
+
await self._store_used_token(old_token)
|
|
1012
|
+
|
|
1013
|
+
# Validate new tokens
|
|
1014
|
+
access_token_payload = await self.validator.validate_access_token(
|
|
1015
|
+
new_tokens["access_token"]
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
"access_token": new_tokens["access_token"],
|
|
1020
|
+
"refresh_token": new_tokens.get("refresh_token"),
|
|
1021
|
+
"id_token": new_tokens.get("id_token"),
|
|
1022
|
+
"token_type": new_tokens.get("token_type", "Bearer"),
|
|
1023
|
+
"expires_in": new_tokens.get("expires_in", 86400)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
async def check_token_reuse(self, refresh_token: str) -> bool:
|
|
1027
|
+
"""
|
|
1028
|
+
Check if refresh token has been reused (potential attack)
|
|
1029
|
+
|
|
1030
|
+
Args:
|
|
1031
|
+
refresh_token: Refresh token to check
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
True if token has been reused
|
|
1035
|
+
"""
|
|
1036
|
+
# Implementation: Check against database of used tokens
|
|
1037
|
+
# If token is found in used tokens database, it's a reuse
|
|
1038
|
+
return False
|
|
1039
|
+
|
|
1040
|
+
async def revoke_all_user_tokens(self, user_id: str) -> None:
|
|
1041
|
+
"""
|
|
1042
|
+
Revoke all tokens for a user (after reuse attack detected)
|
|
1043
|
+
|
|
1044
|
+
Args:
|
|
1045
|
+
user_id: User ID
|
|
1046
|
+
"""
|
|
1047
|
+
# Implementation: Use Auth0 Management API to revoke all tokens
|
|
1048
|
+
# Also clear from local database
|
|
1049
|
+
pass
|
|
1050
|
+
|
|
1051
|
+
async def _store_used_token(self, token: str) -> None:
|
|
1052
|
+
"""Store used refresh token for reuse detection"""
|
|
1053
|
+
# Implementation: Store token hash in database
|
|
1054
|
+
# with expiration date matching token lifetime
|
|
1055
|
+
pass
|
|
1056
|
+
|
|
1057
|
+
async def cleanup_expired_tokens(self) -> int:
|
|
1058
|
+
"""
|
|
1059
|
+
Cleanup expired tokens from database
|
|
1060
|
+
|
|
1061
|
+
Returns:
|
|
1062
|
+
Number of tokens cleaned up
|
|
1063
|
+
"""
|
|
1064
|
+
# Implementation: Remove expired tokens from database
|
|
1065
|
+
return 0
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
### 5.2 Token Storage (Secure Patterns)
|
|
1069
|
+
|
|
1070
|
+
```python
|
|
1071
|
+
# token_storage.py
|
|
1072
|
+
from typing import Optional, Dict, Any
|
|
1073
|
+
from datetime import datetime, timedelta
|
|
1074
|
+
import jwt
|
|
1075
|
+
|
|
1076
|
+
class SecureTokenStorage:
|
|
1077
|
+
"""
|
|
1078
|
+
Secure token storage with encryption
|
|
1079
|
+
|
|
1080
|
+
Note: This is a simplified example. For production:
|
|
1081
|
+
- Use proper encryption libraries (cryptography)
|
|
1082
|
+
- Store encryption keys securely (AWS KMS, HashiCorp Vault)
|
|
1083
|
+
- Use secure session storage for SPAs
|
|
1084
|
+
- Use platform keystores for mobile (Keychain, Keystore)
|
|
1085
|
+
"""
|
|
1086
|
+
|
|
1087
|
+
def __init__(self, encryption_key: bytes):
|
|
1088
|
+
self.encryption_key = encryption_key
|
|
1089
|
+
self.storage: Dict[str, Any] = {}
|
|
1090
|
+
|
|
1091
|
+
async def store_tokens(
|
|
1092
|
+
self,
|
|
1093
|
+
user_id: str,
|
|
1094
|
+
access_token: str,
|
|
1095
|
+
refresh_token: Optional[str] = None,
|
|
1096
|
+
id_token: Optional[str] = None
|
|
1097
|
+
) -> None:
|
|
1098
|
+
"""
|
|
1099
|
+
Securely store tokens
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
user_id: User ID
|
|
1103
|
+
access_token: Access token
|
|
1104
|
+
refresh_token: Optional refresh token
|
|
1105
|
+
id_token: Optional ID token
|
|
1106
|
+
"""
|
|
1107
|
+
# Decode access token to get expiration
|
|
1108
|
+
payload = jwt.decode(access_token, options={"verify_signature": False})
|
|
1109
|
+
expires_at = datetime.fromtimestamp(payload["exp"])
|
|
1110
|
+
|
|
1111
|
+
token_data = {
|
|
1112
|
+
"access_token": self._encrypt_token(access_token),
|
|
1113
|
+
"refresh_token": self._encrypt_token(refresh_token) if refresh_token else None,
|
|
1114
|
+
"id_token": self._encrypt_token(id_token) if id_token else None,
|
|
1115
|
+
"expires_at": expires_at.isoformat(),
|
|
1116
|
+
"issued_at": datetime.utcnow().isoformat()
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
self.storage[user_id] = token_data
|
|
1120
|
+
|
|
1121
|
+
async def get_tokens(self, user_id: str) -> Optional[Dict[str, str]]:
|
|
1122
|
+
"""
|
|
1123
|
+
Retrieve stored tokens
|
|
1124
|
+
|
|
1125
|
+
Args:
|
|
1126
|
+
user_id: User ID
|
|
1127
|
+
|
|
1128
|
+
Returns:
|
|
1129
|
+
Dictionary with tokens or None if not found
|
|
1130
|
+
"""
|
|
1131
|
+
token_data = self.storage.get(user_id)
|
|
1132
|
+
|
|
1133
|
+
if not token_data:
|
|
1134
|
+
return None
|
|
1135
|
+
|
|
1136
|
+
# Check if expired
|
|
1137
|
+
expires_at = datetime.fromisoformat(token_data["expires_at"])
|
|
1138
|
+
if datetime.utcnow() >= expires_at:
|
|
1139
|
+
await self.delete_tokens(user_id)
|
|
1140
|
+
return None
|
|
1141
|
+
|
|
1142
|
+
return {
|
|
1143
|
+
"access_token": self._decrypt_token(token_data["access_token"]),
|
|
1144
|
+
"refresh_token": self._decrypt_token(token_data["refresh_token"]) if token_data["refresh_token"] else None,
|
|
1145
|
+
"id_token": self._decrypt_token(token_data["id_token"]) if token_data["id_token"] else None
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
async def delete_tokens(self, user_id: str) -> None:
|
|
1149
|
+
"""
|
|
1150
|
+
Delete stored tokens
|
|
1151
|
+
|
|
1152
|
+
Args:
|
|
1153
|
+
user_id: User ID
|
|
1154
|
+
"""
|
|
1155
|
+
if user_id in self.storage:
|
|
1156
|
+
del self.storage[user_id]
|
|
1157
|
+
|
|
1158
|
+
def _encrypt_token(self, token: str) -> str:
|
|
1159
|
+
"""Encrypt token (placeholder - use proper encryption)"""
|
|
1160
|
+
# In production, use AES-256-GCM or similar
|
|
1161
|
+
return f"encrypted:{token}"
|
|
1162
|
+
|
|
1163
|
+
def _decrypt_token(self, encrypted_token: str) -> str:
|
|
1164
|
+
"""Decrypt token (placeholder - use proper decryption)"""
|
|
1165
|
+
# In production, use AES-256-GCM or similar
|
|
1166
|
+
return encrypted_token.replace("encrypted:", "")
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
---
|
|
1170
|
+
|
|
1171
|
+
## 6. DPoP Implementation
|
|
1172
|
+
|
|
1173
|
+
### 6.1 DPoP Client (Python)
|
|
1174
|
+
|
|
1175
|
+
```python
|
|
1176
|
+
# dpop_client.py
|
|
1177
|
+
import jwt
|
|
1178
|
+
import time
|
|
1179
|
+
import hashlib
|
|
1180
|
+
from typing import Dict, Any, Optional
|
|
1181
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
1182
|
+
from cryptography.hazmat.primitives import serialization, hashes
|
|
1183
|
+
from cryptography.hazmat.backends import default_backend
|
|
1184
|
+
from auth0_client import Auth0Client
|
|
1185
|
+
|
|
1186
|
+
class DPoPClient:
|
|
1187
|
+
"""
|
|
1188
|
+
Demonstrating Proof-of-Possession (DPoP) client implementation
|
|
1189
|
+
"""
|
|
1190
|
+
|
|
1191
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
1192
|
+
self.client = auth0_client
|
|
1193
|
+
self.private_key: Optional[ec.EllipticCurvePrivateKey] = None
|
|
1194
|
+
self.public_key: Optional[ec.EllipticCurvePublicKey] = None
|
|
1195
|
+
self.jwk: Optional[Dict[str, Any]] = None
|
|
1196
|
+
self.nonce_cache: Dict[str, str] = {}
|
|
1197
|
+
|
|
1198
|
+
def generate_key_pair(self) -> None:
|
|
1199
|
+
"""
|
|
1200
|
+
Generate DPoP key pair (ES256)
|
|
1201
|
+
|
|
1202
|
+
In production:
|
|
1203
|
+
- Generate once per installation
|
|
1204
|
+
- Store private key securely
|
|
1205
|
+
- Reuse across sessions
|
|
1206
|
+
"""
|
|
1207
|
+
# Generate ES256 key pair
|
|
1208
|
+
self.private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
|
|
1209
|
+
self.public_key = self.private_key.public_key()
|
|
1210
|
+
|
|
1211
|
+
# Create JWK from public key
|
|
1212
|
+
public_numbers = self.public_key.public_numbers()
|
|
1213
|
+
self.jwk = {
|
|
1214
|
+
"kty": "EC",
|
|
1215
|
+
"crv": "P-256",
|
|
1216
|
+
"x": self._base64url_encode(public_numbers.x.to_bytes(32, 'big')),
|
|
1217
|
+
"y": self._base64url_encode(public_numbers.y.to_bytes(32, 'big'))
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
def create_dpop_proof(
|
|
1221
|
+
self,
|
|
1222
|
+
http_method: str,
|
|
1223
|
+
http_uri: str,
|
|
1224
|
+
access_token: Optional[str] = None,
|
|
1225
|
+
nonce: Optional[str] = None
|
|
1226
|
+
) -> str:
|
|
1227
|
+
"""
|
|
1228
|
+
Create DPoP proof JWT
|
|
1229
|
+
|
|
1230
|
+
Args:
|
|
1231
|
+
http_method: HTTP method (GET, POST, etc.)
|
|
1232
|
+
http_uri: Full HTTP URI
|
|
1233
|
+
access_token: Optional access token for ath claim
|
|
1234
|
+
nonce: Optional nonce from server
|
|
1235
|
+
|
|
1236
|
+
Returns:
|
|
1237
|
+
DPoP proof JWT
|
|
1238
|
+
"""
|
|
1239
|
+
if not self.private_key:
|
|
1240
|
+
raise ValueError("Key pair not generated. Call generate_key_pair() first.")
|
|
1241
|
+
|
|
1242
|
+
# Calculate ath claim if access token provided
|
|
1243
|
+
ath = None
|
|
1244
|
+
if access_token:
|
|
1245
|
+
ath = self._calculate_access_token_hash(access_token)
|
|
1246
|
+
|
|
1247
|
+
# Create payload
|
|
1248
|
+
now = int(time.time())
|
|
1249
|
+
payload = {
|
|
1250
|
+
"jti": self._generate_jti(),
|
|
1251
|
+
"htm": http_method.upper(),
|
|
1252
|
+
"htu": http_uri,
|
|
1253
|
+
"iat": now
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if ath:
|
|
1257
|
+
payload["ath"] = ath
|
|
1258
|
+
|
|
1259
|
+
if nonce:
|
|
1260
|
+
payload["nonce"] = nonce
|
|
1261
|
+
|
|
1262
|
+
# Create header
|
|
1263
|
+
header = {
|
|
1264
|
+
"typ": "dpop+jwt",
|
|
1265
|
+
"alg": "ES256",
|
|
1266
|
+
"jwk": self.jwk
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
# Sign JWT
|
|
1270
|
+
proof = jwt.encode(
|
|
1271
|
+
payload,
|
|
1272
|
+
self.private_key,
|
|
1273
|
+
algorithm="ES256",
|
|
1274
|
+
headers=header
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
return proof
|
|
1278
|
+
|
|
1279
|
+
async def get_token_with_dpop(self, code: str, redirect_uri: str) -> Dict[str, Any]:
|
|
1280
|
+
"""
|
|
1281
|
+
Get tokens with DPoP binding
|
|
1282
|
+
|
|
1283
|
+
Args:
|
|
1284
|
+
code: Authorization code
|
|
1285
|
+
redirect_uri: Redirect URI
|
|
1286
|
+
|
|
1287
|
+
Returns:
|
|
1288
|
+
Token response with DPoP-bound tokens
|
|
1289
|
+
"""
|
|
1290
|
+
token_endpoint = f"https://{self.client.domain}/oauth/token"
|
|
1291
|
+
|
|
1292
|
+
# Create DPoP proof for token request
|
|
1293
|
+
dpop_proof = self.create_dpop_proof("POST", token_endpoint)
|
|
1294
|
+
|
|
1295
|
+
# Request headers
|
|
1296
|
+
headers = {
|
|
1297
|
+
"DPoP": dpop_proof,
|
|
1298
|
+
"Content-Type": "application/json"
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
payload = {
|
|
1302
|
+
"grant_type": "authorization_code",
|
|
1303
|
+
"client_id": self.client.client_id,
|
|
1304
|
+
"code": code,
|
|
1305
|
+
"redirect_uri": redirect_uri
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if self.client.client_secret:
|
|
1309
|
+
payload["client_secret"] = self.client.client_secret
|
|
1310
|
+
|
|
1311
|
+
response = await self.client.http_client.post(token_endpoint, json=payload, headers=headers)
|
|
1312
|
+
|
|
1313
|
+
# Handle nonce requirement
|
|
1314
|
+
if response.status_code == 400 and "use_dpop_nonce" in response.text:
|
|
1315
|
+
nonce = response.headers.get("DPoP-Nonce")
|
|
1316
|
+
|
|
1317
|
+
if nonce:
|
|
1318
|
+
# Retry with nonce
|
|
1319
|
+
dpop_proof = self.create_dpop_proof("POST", token_endpoint, nonce=nonce)
|
|
1320
|
+
headers["DPoP"] = dpop_proof
|
|
1321
|
+
response = await self.client.http_client.post(token_endpoint, json=payload, headers=headers)
|
|
1322
|
+
|
|
1323
|
+
response.raise_for_status()
|
|
1324
|
+
return response.json()
|
|
1325
|
+
|
|
1326
|
+
async def call_api_with_dpop(
|
|
1327
|
+
self,
|
|
1328
|
+
access_token: str,
|
|
1329
|
+
api_endpoint: str,
|
|
1330
|
+
method: str = "GET"
|
|
1331
|
+
) -> Any:
|
|
1332
|
+
"""
|
|
1333
|
+
Call API with DPoP-bound token
|
|
1334
|
+
|
|
1335
|
+
Args:
|
|
1336
|
+
access_token: DPoP-bound access token
|
|
1337
|
+
api_endpoint: API endpoint to call
|
|
1338
|
+
method: HTTP method
|
|
1339
|
+
|
|
1340
|
+
Returns:
|
|
1341
|
+
API response
|
|
1342
|
+
"""
|
|
1343
|
+
# Create DPoP proof with ath claim
|
|
1344
|
+
dpop_proof = self.create_dpop_proof(method, api_endpoint, access_token)
|
|
1345
|
+
|
|
1346
|
+
headers = {
|
|
1347
|
+
"Authorization": f"DPoP {access_token}",
|
|
1348
|
+
"DPoP": dpop_proof
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
response = await self.client.http_client.request(method, api_endpoint, headers=headers)
|
|
1352
|
+
response.raise_for_status()
|
|
1353
|
+
|
|
1354
|
+
return response.json()
|
|
1355
|
+
|
|
1356
|
+
def _calculate_access_token_hash(self, access_token: str) -> str:
|
|
1357
|
+
"""Calculate SHA-256 hash of access token for ath claim"""
|
|
1358
|
+
hash_bytes = hashlib.sha256(access_token.encode()).digest()
|
|
1359
|
+
return self._base64url_encode(hash_bytes)
|
|
1360
|
+
|
|
1361
|
+
def _generate_jti(self) -> str:
|
|
1362
|
+
"""Generate unique JTI for replay prevention"""
|
|
1363
|
+
import uuid
|
|
1364
|
+
return str(uuid.uuid4())
|
|
1365
|
+
|
|
1366
|
+
def _base64url_encode(self, data: bytes) -> str:
|
|
1367
|
+
"""Base64url encode without padding"""
|
|
1368
|
+
import base64
|
|
1369
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
|
|
1370
|
+
|
|
1371
|
+
def export_private_key_pem(self) -> str:
|
|
1372
|
+
"""Export private key as PEM (for storage)"""
|
|
1373
|
+
if not self.private_key:
|
|
1374
|
+
raise ValueError("No private key")
|
|
1375
|
+
|
|
1376
|
+
pem = self.private_key.private_bytes(
|
|
1377
|
+
encoding=serialization.Encoding.PEM,
|
|
1378
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
1379
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
return pem.decode('utf-8')
|
|
1383
|
+
|
|
1384
|
+
def import_private_key_pem(self, pem: str) -> None:
|
|
1385
|
+
"""Import private key from PEM"""
|
|
1386
|
+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
1387
|
+
|
|
1388
|
+
self.private_key = load_pem_private_key(
|
|
1389
|
+
pem.encode(),
|
|
1390
|
+
password=None,
|
|
1391
|
+
backend=default_backend()
|
|
1392
|
+
)
|
|
1393
|
+
self.public_key = self.private_key.public_key()
|
|
1394
|
+
|
|
1395
|
+
# Recreate JWK
|
|
1396
|
+
public_numbers = self.public_key.public_numbers()
|
|
1397
|
+
self.jwk = {
|
|
1398
|
+
"kty": "EC",
|
|
1399
|
+
"crv": "P-256",
|
|
1400
|
+
"x": self._base64url_encode(public_numbers.x.to_bytes(32, 'big')),
|
|
1401
|
+
"y": self._base64url_encode(public_numbers.y.to_bytes(32, 'big'))
|
|
1402
|
+
}
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
---
|
|
1406
|
+
|
|
1407
|
+
## 7. mTLS Implementation
|
|
1408
|
+
|
|
1409
|
+
### 7.1 mTLS Client (Python)
|
|
1410
|
+
|
|
1411
|
+
```python
|
|
1412
|
+
# mtls_client.py
|
|
1413
|
+
import httpx
|
|
1414
|
+
from typing import Dict, Any, Optional
|
|
1415
|
+
from auth0_client import Auth0Client
|
|
1416
|
+
|
|
1417
|
+
class mTLSClient:
|
|
1418
|
+
"""
|
|
1419
|
+
mTLS (mutual TLS) client for Auth0 token binding
|
|
1420
|
+
|
|
1421
|
+
Note: mTLS requires:
|
|
1422
|
+
- Auth0 Enterprise Plan with HRI add-on
|
|
1423
|
+
- X.509 certificates
|
|
1424
|
+
- Confidential client
|
|
1425
|
+
"""
|
|
1426
|
+
|
|
1427
|
+
def __init__(
|
|
1428
|
+
self,
|
|
1429
|
+
auth0_client: Auth0Client,
|
|
1430
|
+
cert_path: str,
|
|
1431
|
+
key_path: str,
|
|
1432
|
+
ca_path: Optional[str] = None
|
|
1433
|
+
):
|
|
1434
|
+
self.client = auth0_client
|
|
1435
|
+
self.cert_path = cert_path
|
|
1436
|
+
self.key_path = key_path
|
|
1437
|
+
self.ca_path = ca_path
|
|
1438
|
+
|
|
1439
|
+
# Create HTTP client with mTLS
|
|
1440
|
+
self.http_client = httpx.AsyncClient(
|
|
1441
|
+
cert=(cert_path, key_path),
|
|
1442
|
+
verify=ca_path if ca_path else True
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
async def get_token_with_mtls(self, code: str, redirect_uri: str) -> Dict[str, Any]:
|
|
1446
|
+
"""
|
|
1447
|
+
Get tokens with mTLS binding
|
|
1448
|
+
|
|
1449
|
+
The certificate thumbprint will be embedded in the token's cnf claim
|
|
1450
|
+
|
|
1451
|
+
Args:
|
|
1452
|
+
code: Authorization code
|
|
1453
|
+
redirect_uri: Redirect URI
|
|
1454
|
+
|
|
1455
|
+
Returns:
|
|
1456
|
+
Token response with mTLS-bound tokens
|
|
1457
|
+
"""
|
|
1458
|
+
token_endpoint = f"https://{self.client.domain}/oauth/token"
|
|
1459
|
+
|
|
1460
|
+
payload = {
|
|
1461
|
+
"grant_type": "authorization_code",
|
|
1462
|
+
"client_id": self.client.client_id,
|
|
1463
|
+
"code": code,
|
|
1464
|
+
"redirect_uri": redirect_uri
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
# mTLS authentication (no client secret needed)
|
|
1468
|
+
# The certificate is used for client authentication
|
|
1469
|
+
|
|
1470
|
+
response = await self.http_client.post(token_endpoint, json=payload)
|
|
1471
|
+
response.raise_for_status()
|
|
1472
|
+
|
|
1473
|
+
tokens = response.json()
|
|
1474
|
+
|
|
1475
|
+
# Token will contain cnf claim with x5t#S256 (certificate thumbprint)
|
|
1476
|
+
return tokens
|
|
1477
|
+
|
|
1478
|
+
async def call_api_with_mtls(
|
|
1479
|
+
self,
|
|
1480
|
+
access_token: str,
|
|
1481
|
+
api_endpoint: str,
|
|
1482
|
+
method: str = "GET"
|
|
1483
|
+
) -> Any:
|
|
1484
|
+
"""
|
|
1485
|
+
Call API with mTLS-bound token
|
|
1486
|
+
|
|
1487
|
+
The resource server will validate:
|
|
1488
|
+
1. Token signature
|
|
1489
|
+
2. Certificate thumbprint in cnf.x5t#S256 matches client certificate
|
|
1490
|
+
|
|
1491
|
+
Args:
|
|
1492
|
+
access_token: mTLS-bound access token
|
|
1493
|
+
api_endpoint: API endpoint
|
|
1494
|
+
method: HTTP method
|
|
1495
|
+
|
|
1496
|
+
Returns:
|
|
1497
|
+
API response
|
|
1498
|
+
"""
|
|
1499
|
+
headers = {
|
|
1500
|
+
"Authorization": f"Bearer {access_token}"
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
response = await self.http_client.request(method, api_endpoint, headers=headers)
|
|
1504
|
+
response.raise_for_status()
|
|
1505
|
+
|
|
1506
|
+
return response.json()
|
|
1507
|
+
|
|
1508
|
+
def calculate_certificate_thumbprint(self) -> str:
|
|
1509
|
+
"""
|
|
1510
|
+
Calculate SHA-256 thumbprint of certificate
|
|
1511
|
+
|
|
1512
|
+
This should match the cnf.x5t#S256 claim in the token
|
|
1513
|
+
|
|
1514
|
+
Returns:
|
|
1515
|
+
Base64url-encoded certificate thumbprint
|
|
1516
|
+
"""
|
|
1517
|
+
from cryptography import x509
|
|
1518
|
+
from cryptography.hazmat.backends import default_backend
|
|
1519
|
+
import hashlib
|
|
1520
|
+
import base64
|
|
1521
|
+
|
|
1522
|
+
# Load certificate
|
|
1523
|
+
with open(self.cert_path, 'rb') as f:
|
|
1524
|
+
cert_data = f.read()
|
|
1525
|
+
|
|
1526
|
+
cert = x509.load_pem_x509_certificate(cert_data, default_backend())
|
|
1527
|
+
|
|
1528
|
+
# Calculate thumbprint
|
|
1529
|
+
thumbprint = hashlib.sha256(cert.public_bytes()).digest()
|
|
1530
|
+
|
|
1531
|
+
# Base64url encode
|
|
1532
|
+
return base64.urlsafe_b64encode(thumbprint).rstrip(b'=').decode('utf-8')
|
|
1533
|
+
|
|
1534
|
+
async def close(self):
|
|
1535
|
+
"""Close HTTP client"""
|
|
1536
|
+
await self.http_client.aclose()
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
---
|
|
1540
|
+
|
|
1541
|
+
## 8. GDPR Compliance
|
|
1542
|
+
|
|
1543
|
+
### 8.1 User Data Management (Python)
|
|
1544
|
+
|
|
1545
|
+
```python
|
|
1546
|
+
# gdpr_handler.py
|
|
1547
|
+
from typing import Dict, Any, Optional
|
|
1548
|
+
from datetime import datetime
|
|
1549
|
+
from auth0_client import Auth0Client
|
|
1550
|
+
|
|
1551
|
+
class GDPRHandler:
|
|
1552
|
+
"""
|
|
1553
|
+
GDPR compliance handler for user data rights
|
|
1554
|
+
|
|
1555
|
+
Implements:
|
|
1556
|
+
- Right to access (data export)
|
|
1557
|
+
- Right to portability (structured data export)
|
|
1558
|
+
- Right to erasure (account deletion)
|
|
1559
|
+
- Right to rectification (data correction)
|
|
1560
|
+
"""
|
|
1561
|
+
|
|
1562
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
1563
|
+
self.client = auth0_client
|
|
1564
|
+
self.base_url = f"https://{auth0_client.domain}/api/v2"
|
|
1565
|
+
|
|
1566
|
+
async def export_user_data(self, user_id: str) -> Dict[str, Any]:
|
|
1567
|
+
"""
|
|
1568
|
+
Export user data (Right to Access and Portability)
|
|
1569
|
+
|
|
1570
|
+
Args:
|
|
1571
|
+
user_id: User ID
|
|
1572
|
+
|
|
1573
|
+
Returns:
|
|
1574
|
+
User data in structured, machine-readable format
|
|
1575
|
+
"""
|
|
1576
|
+
# Get user profile from Auth0
|
|
1577
|
+
user_data = await self._get_auth0_user_data(user_id)
|
|
1578
|
+
|
|
1579
|
+
# Get user activity logs (last 90 days per GDPR)
|
|
1580
|
+
activity_logs = await self._get_user_activity_logs(user_id)
|
|
1581
|
+
|
|
1582
|
+
# Get consent records
|
|
1583
|
+
consents = await self._get_user_consents(user_id)
|
|
1584
|
+
|
|
1585
|
+
# Compile export
|
|
1586
|
+
export_data = {
|
|
1587
|
+
"user_id": user_id,
|
|
1588
|
+
"export_date": datetime.utcnow().isoformat(),
|
|
1589
|
+
"user_profile": user_data,
|
|
1590
|
+
"activity_logs": activity_logs,
|
|
1591
|
+
"consents": consents
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return export_data
|
|
1595
|
+
|
|
1596
|
+
async def delete_user_account(self, user_id: str) -> Dict[str, Any]:
|
|
1597
|
+
"""
|
|
1598
|
+
Delete user account and all associated data (Right to Erasure)
|
|
1599
|
+
|
|
1600
|
+
Args:
|
|
1601
|
+
user_id: User ID
|
|
1602
|
+
|
|
1603
|
+
Returns:
|
|
1604
|
+
Deletion confirmation
|
|
1605
|
+
"""
|
|
1606
|
+
# Delete from Auth0
|
|
1607
|
+
await self._delete_auth0_user(user_id)
|
|
1608
|
+
|
|
1609
|
+
# Delete from application database
|
|
1610
|
+
await self._delete_application_data(user_id)
|
|
1611
|
+
|
|
1612
|
+
# Log deletion event
|
|
1613
|
+
await self._log_deletion_event(user_id)
|
|
1614
|
+
|
|
1615
|
+
return {
|
|
1616
|
+
"user_id": user_id,
|
|
1617
|
+
"deleted": True,
|
|
1618
|
+
"timestamp": datetime.utcnow().isoformat()
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
async def update_user_data(
|
|
1622
|
+
self,
|
|
1623
|
+
user_id: str,
|
|
1624
|
+
updates: Dict[str, Any]
|
|
1625
|
+
) -> Dict[str, Any]:
|
|
1626
|
+
"""
|
|
1627
|
+
Update user data (Right to Rectification)
|
|
1628
|
+
|
|
1629
|
+
Args:
|
|
1630
|
+
user_id: User ID
|
|
1631
|
+
updates: Data updates
|
|
1632
|
+
|
|
1633
|
+
Returns:
|
|
1634
|
+
Updated user data
|
|
1635
|
+
"""
|
|
1636
|
+
# Update in Auth0
|
|
1637
|
+
updated_user = await self._update_auth0_user(user_id, updates)
|
|
1638
|
+
|
|
1639
|
+
# Update in application database
|
|
1640
|
+
await self._update_application_data(user_id, updates)
|
|
1641
|
+
|
|
1642
|
+
return {
|
|
1643
|
+
"user_id": user_id,
|
|
1644
|
+
"updated": True,
|
|
1645
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
1646
|
+
"data": updated_user
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
async def record_consent(
|
|
1650
|
+
self,
|
|
1651
|
+
user_id: str,
|
|
1652
|
+
consent_type: str,
|
|
1653
|
+
granted: bool,
|
|
1654
|
+
purpose: str
|
|
1655
|
+
) -> None:
|
|
1656
|
+
"""
|
|
1657
|
+
Record user consent
|
|
1658
|
+
|
|
1659
|
+
Args:
|
|
1660
|
+
user_id: User ID
|
|
1661
|
+
consent_type: Type of consent (e.g., "marketing", "analytics")
|
|
1662
|
+
granted: Whether consent was granted
|
|
1663
|
+
purpose: Purpose of data processing
|
|
1664
|
+
"""
|
|
1665
|
+
consent_record = {
|
|
1666
|
+
"user_id": user_id,
|
|
1667
|
+
"consent_type": consent_type,
|
|
1668
|
+
"granted": granted,
|
|
1669
|
+
"purpose": purpose,
|
|
1670
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
1671
|
+
"valid_until": None # Or set expiration if applicable
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
# Store in your consent management database
|
|
1675
|
+
await self._store_consent(consent_record)
|
|
1676
|
+
|
|
1677
|
+
async def get_user_consents(self, user_id: str) -> list:
|
|
1678
|
+
"""
|
|
1679
|
+
Get all user consents
|
|
1680
|
+
|
|
1681
|
+
Args:
|
|
1682
|
+
user_id: User ID
|
|
1683
|
+
|
|
1684
|
+
Returns:
|
|
1685
|
+
List of consent records
|
|
1686
|
+
"""
|
|
1687
|
+
return await self._get_user_consents(user_id)
|
|
1688
|
+
|
|
1689
|
+
async def _get_auth0_user_data(self, user_id: str) -> Dict[str, Any]:
|
|
1690
|
+
"""Get user profile from Auth0"""
|
|
1691
|
+
user_endpoint = f"{self.base_url}/users/{user_id}"
|
|
1692
|
+
|
|
1693
|
+
response = await self.client.http_client.get(
|
|
1694
|
+
user_endpoint,
|
|
1695
|
+
headers={"Authorization": f"Bearer {await self._get_management_token()}"}
|
|
1696
|
+
)
|
|
1697
|
+
response.raise_for_status()
|
|
1698
|
+
|
|
1699
|
+
return response.json()
|
|
1700
|
+
|
|
1701
|
+
async def _get_user_activity_logs(self, user_id: str) -> list:
|
|
1702
|
+
"""Get user activity logs"""
|
|
1703
|
+
# Implementation: Query Auth0 logs for user activity
|
|
1704
|
+
# Limit to last 90 days per GDPR
|
|
1705
|
+
return []
|
|
1706
|
+
|
|
1707
|
+
async def _delete_auth0_user(self, user_id: str) -> None:
|
|
1708
|
+
"""Delete user from Auth0"""
|
|
1709
|
+
user_endpoint = f"{self.base_url}/users/{user_id}"
|
|
1710
|
+
|
|
1711
|
+
await self.client.http_client.delete(
|
|
1712
|
+
user_endpoint,
|
|
1713
|
+
headers={"Authorization": f"Bearer {await self._get_management_token()}"}
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
async def _delete_application_data(self, user_id: str) -> None:
|
|
1717
|
+
"""Delete user data from application database"""
|
|
1718
|
+
# Implementation: Delete from your application database
|
|
1719
|
+
pass
|
|
1720
|
+
|
|
1721
|
+
async def _update_auth0_user(
|
|
1722
|
+
self,
|
|
1723
|
+
user_id: str,
|
|
1724
|
+
updates: Dict[str, Any]
|
|
1725
|
+
) -> Dict[str, Any]:
|
|
1726
|
+
"""Update user in Auth0"""
|
|
1727
|
+
user_endpoint = f"{self.base_url}/users/{user_id}"
|
|
1728
|
+
|
|
1729
|
+
response = await self.client.http_client.patch(
|
|
1730
|
+
user_endpoint,
|
|
1731
|
+
json=updates,
|
|
1732
|
+
headers={"Authorization": f"Bearer {await self._get_management_token()}"}
|
|
1733
|
+
)
|
|
1734
|
+
response.raise_for_status()
|
|
1735
|
+
|
|
1736
|
+
return response.json()
|
|
1737
|
+
|
|
1738
|
+
async def _update_application_data(
|
|
1739
|
+
self,
|
|
1740
|
+
user_id: str,
|
|
1741
|
+
updates: Dict[str, Any]
|
|
1742
|
+
) -> None:
|
|
1743
|
+
"""Update user data in application database"""
|
|
1744
|
+
# Implementation: Update your application database
|
|
1745
|
+
pass
|
|
1746
|
+
|
|
1747
|
+
async def _store_consent(self, consent_record: Dict[str, Any]) -> None:
|
|
1748
|
+
"""Store consent record in database"""
|
|
1749
|
+
# Implementation: Store in your consent management database
|
|
1750
|
+
pass
|
|
1751
|
+
|
|
1752
|
+
async def _get_user_consents(self, user_id: str) -> list:
|
|
1753
|
+
"""Get user consents from database"""
|
|
1754
|
+
# Implementation: Query your consent management database
|
|
1755
|
+
return []
|
|
1756
|
+
|
|
1757
|
+
async def _log_deletion_event(self, user_id: str) -> None:
|
|
1758
|
+
"""Log account deletion event"""
|
|
1759
|
+
# Implementation: Log to audit system
|
|
1760
|
+
pass
|
|
1761
|
+
|
|
1762
|
+
async def _get_management_token(self) -> str:
|
|
1763
|
+
"""Get Management API token"""
|
|
1764
|
+
# Implementation: Use client credentials flow
|
|
1765
|
+
return ""
|
|
1766
|
+
```
|
|
1767
|
+
|
|
1768
|
+
---
|
|
1769
|
+
|
|
1770
|
+
## 9. Error Handling and Retry Logic
|
|
1771
|
+
|
|
1772
|
+
### 9.1 Retry Handler (Python)
|
|
1773
|
+
|
|
1774
|
+
```python
|
|
1775
|
+
# retry_handler.py
|
|
1776
|
+
import asyncio
|
|
1777
|
+
import logging
|
|
1778
|
+
from typing import Callable, Any, Optional
|
|
1779
|
+
from datetime import datetime
|
|
1780
|
+
import httpx
|
|
1781
|
+
|
|
1782
|
+
logger = logging.getLogger(__name__)
|
|
1783
|
+
|
|
1784
|
+
class Auth0RetryHandler:
|
|
1785
|
+
"""
|
|
1786
|
+
Retry handler for Auth0 API calls with exponential backoff
|
|
1787
|
+
"""
|
|
1788
|
+
|
|
1789
|
+
def __init__(
|
|
1790
|
+
self,
|
|
1791
|
+
max_retries: int = 3,
|
|
1792
|
+
base_delay: float = 1.0,
|
|
1793
|
+
max_delay: float = 32.0,
|
|
1794
|
+
exponential_base: float = 2.0
|
|
1795
|
+
):
|
|
1796
|
+
self.max_retries = max_retries
|
|
1797
|
+
self.base_delay = base_delay
|
|
1798
|
+
self.max_delay = max_delay
|
|
1799
|
+
self.exponential_base = exponential_base
|
|
1800
|
+
|
|
1801
|
+
async def execute_with_retry(
|
|
1802
|
+
self,
|
|
1803
|
+
func: Callable,
|
|
1804
|
+
*args: Any,
|
|
1805
|
+
**kwargs: Any
|
|
1806
|
+
) -> Any:
|
|
1807
|
+
"""
|
|
1808
|
+
Execute function with retry logic
|
|
1809
|
+
|
|
1810
|
+
Args:
|
|
1811
|
+
func: Function to execute
|
|
1812
|
+
*args: Function arguments
|
|
1813
|
+
**kwargs: Function keyword arguments
|
|
1814
|
+
|
|
1815
|
+
Returns:
|
|
1816
|
+
Function result
|
|
1817
|
+
|
|
1818
|
+
Raises:
|
|
1819
|
+
Exception: If all retries exhausted
|
|
1820
|
+
"""
|
|
1821
|
+
last_exception = None
|
|
1822
|
+
|
|
1823
|
+
for attempt in range(self.max_retries):
|
|
1824
|
+
try:
|
|
1825
|
+
return await func(*args, **kwargs)
|
|
1826
|
+
|
|
1827
|
+
except httpx.HTTPStatusError as e:
|
|
1828
|
+
last_exception = e
|
|
1829
|
+
|
|
1830
|
+
# Don't retry client errors (4xx)
|
|
1831
|
+
if 400 <= e.response.status_code < 500:
|
|
1832
|
+
logger.error(f"Client error {e.response.status_code}: {e}")
|
|
1833
|
+
raise
|
|
1834
|
+
|
|
1835
|
+
# Retry on server errors (5xx) and rate limiting (429)
|
|
1836
|
+
if e.response.status_code >= 500 or e.response.status_code == 429:
|
|
1837
|
+
delay = self._calculate_delay(attempt)
|
|
1838
|
+
|
|
1839
|
+
logger.warning(
|
|
1840
|
+
f"Attempt {attempt + 1} failed with status {e.response.status_code}. "
|
|
1841
|
+
f"Retrying in {delay:.2f} seconds..."
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
await asyncio.sleep(delay)
|
|
1845
|
+
continue
|
|
1846
|
+
|
|
1847
|
+
raise
|
|
1848
|
+
|
|
1849
|
+
except (httpx.RequestError, httpx.TimeoutException) as e:
|
|
1850
|
+
last_exception = e
|
|
1851
|
+
|
|
1852
|
+
delay = self._calculate_delay(attempt)
|
|
1853
|
+
|
|
1854
|
+
logger.warning(
|
|
1855
|
+
f"Attempt {attempt + 1} failed with network error. "
|
|
1856
|
+
f"Retrying in {delay:.2f} seconds..."
|
|
1857
|
+
)
|
|
1858
|
+
|
|
1859
|
+
await asyncio.sleep(delay)
|
|
1860
|
+
continue
|
|
1861
|
+
|
|
1862
|
+
except Exception as e:
|
|
1863
|
+
# Don't retry on other exceptions
|
|
1864
|
+
logger.error(f"Unexpected error: {e}")
|
|
1865
|
+
raise
|
|
1866
|
+
|
|
1867
|
+
# All retries exhausted
|
|
1868
|
+
logger.error(f"All {self.max_retries} retries exhausted")
|
|
1869
|
+
raise last_exception
|
|
1870
|
+
|
|
1871
|
+
def _calculate_delay(self, attempt: int) -> float:
|
|
1872
|
+
"""
|
|
1873
|
+
Calculate delay with exponential backoff
|
|
1874
|
+
|
|
1875
|
+
Args:
|
|
1876
|
+
attempt: Attempt number (0-indexed)
|
|
1877
|
+
|
|
1878
|
+
Returns:
|
|
1879
|
+
Delay in seconds
|
|
1880
|
+
"""
|
|
1881
|
+
delay = self.base_delay * (self.exponential_base ** attempt)
|
|
1882
|
+
return min(delay, self.max_delay)
|
|
1883
|
+
|
|
1884
|
+
class Auth0ErrorHandler:
|
|
1885
|
+
"""
|
|
1886
|
+
Centralized error handling for Auth0 operations
|
|
1887
|
+
"""
|
|
1888
|
+
|
|
1889
|
+
@staticmethod
|
|
1890
|
+
def handle_auth_error(error: Exception) -> Dict[str, Any]:
|
|
1891
|
+
"""
|
|
1892
|
+
Handle authentication errors
|
|
1893
|
+
|
|
1894
|
+
Args:
|
|
1895
|
+
error: Exception
|
|
1896
|
+
|
|
1897
|
+
Returns:
|
|
1898
|
+
Error response dictionary
|
|
1899
|
+
"""
|
|
1900
|
+
if isinstance(error, httpx.HTTPStatusError):
|
|
1901
|
+
status_code = error.response.status_code
|
|
1902
|
+
|
|
1903
|
+
if status_code == 401:
|
|
1904
|
+
return {
|
|
1905
|
+
"error": "invalid_token",
|
|
1906
|
+
"error_description": "Token is invalid or expired",
|
|
1907
|
+
"status_code": status_code
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
elif status_code == 403:
|
|
1911
|
+
return {
|
|
1912
|
+
"error": "insufficient_scope",
|
|
1913
|
+
"error_description": "Token lacks required scope",
|
|
1914
|
+
"status_code": status_code
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
elif status_code == 429:
|
|
1918
|
+
return {
|
|
1919
|
+
"error": "too_many_requests",
|
|
1920
|
+
"error_description": "Rate limit exceeded",
|
|
1921
|
+
"status_code": status_code
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
elif status_code >= 500:
|
|
1925
|
+
return {
|
|
1926
|
+
"error": "server_error",
|
|
1927
|
+
"error_description": "Auth0 server error",
|
|
1928
|
+
"status_code": status_code
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
return {
|
|
1932
|
+
"error": "unknown_error",
|
|
1933
|
+
"error_description": str(error),
|
|
1934
|
+
"status_code": 500
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
@staticmethod
|
|
1938
|
+
def should_retry(error: Exception) -> bool:
|
|
1939
|
+
"""
|
|
1940
|
+
Determine if error is retryable
|
|
1941
|
+
|
|
1942
|
+
Args:
|
|
1943
|
+
error: Exception
|
|
1944
|
+
|
|
1945
|
+
Returns:
|
|
1946
|
+
True if error is retryable
|
|
1947
|
+
"""
|
|
1948
|
+
if isinstance(error, httpx.HTTPStatusError):
|
|
1949
|
+
status_code = error.response.status_code
|
|
1950
|
+
|
|
1951
|
+
# Retry on server errors and rate limiting
|
|
1952
|
+
return status_code >= 500 or status_code == 429
|
|
1953
|
+
|
|
1954
|
+
if isinstance(error, (httpx.RequestError, httpx.TimeoutException)):
|
|
1955
|
+
return True
|
|
1956
|
+
|
|
1957
|
+
return False
|
|
1958
|
+
```
|
|
1959
|
+
|
|
1960
|
+
### 9.2 Logging and Monitoring (Python)
|
|
1961
|
+
|
|
1962
|
+
```python
|
|
1963
|
+
# security_logger.py
|
|
1964
|
+
import logging
|
|
1965
|
+
import json
|
|
1966
|
+
from typing import Dict, Any, Optional
|
|
1967
|
+
from datetime import datetime
|
|
1968
|
+
from enum import Enum
|
|
1969
|
+
|
|
1970
|
+
class SecurityEventType(Enum):
|
|
1971
|
+
"""Security event types"""
|
|
1972
|
+
LOGIN_SUCCESS = "login_success"
|
|
1973
|
+
LOGIN_FAILURE = "login_failure"
|
|
1974
|
+
MFA_CHALLENGE = "mfa_challenge"
|
|
1975
|
+
MFA_SUCCESS = "mfa_success"
|
|
1976
|
+
MFA_FAILURE = "mfa_failure"
|
|
1977
|
+
TOKEN_ISSUED = "token_issued"
|
|
1978
|
+
TOKEN_REFRESH = "token_refresh"
|
|
1979
|
+
TOKEN_REVOKED = "token_revoked"
|
|
1980
|
+
ATTACK_DETECTED = "attack_detected"
|
|
1981
|
+
BREACHED_PASSWORD = "breached_password"
|
|
1982
|
+
ACCOUNT_LOCKED = "account_locked"
|
|
1983
|
+
DATA_EXPORT = "data_export"
|
|
1984
|
+
DATA_DELETION = "data_deletion"
|
|
1985
|
+
|
|
1986
|
+
class SecurityLogger:
|
|
1987
|
+
"""
|
|
1988
|
+
Centralized security event logging
|
|
1989
|
+
|
|
1990
|
+
Logs security-relevant events for audit and compliance
|
|
1991
|
+
"""
|
|
1992
|
+
|
|
1993
|
+
def __init__(self, log_level: str = "INFO"):
|
|
1994
|
+
self.logger = logging.getLogger("auth0_security")
|
|
1995
|
+
self.logger.setLevel(getattr(logging, log_level.upper()))
|
|
1996
|
+
|
|
1997
|
+
# Configure handler (in production, send to SIEM)
|
|
1998
|
+
handler = logging.StreamHandler()
|
|
1999
|
+
handler.setFormatter(
|
|
2000
|
+
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
2001
|
+
)
|
|
2002
|
+
self.logger.addHandler(handler)
|
|
2003
|
+
|
|
2004
|
+
def log_security_event(
|
|
2005
|
+
self,
|
|
2006
|
+
event_type: SecurityEventType,
|
|
2007
|
+
user_id: Optional[str] = None,
|
|
2008
|
+
ip_address: Optional[str] = None,
|
|
2009
|
+
details: Optional[Dict[str, Any]] = None
|
|
2010
|
+
) -> None:
|
|
2011
|
+
"""
|
|
2012
|
+
Log security event
|
|
2013
|
+
|
|
2014
|
+
Args:
|
|
2015
|
+
event_type: Type of security event
|
|
2016
|
+
user_id: User ID (if applicable)
|
|
2017
|
+
ip_address: IP address (if applicable)
|
|
2018
|
+
details: Additional event details
|
|
2019
|
+
"""
|
|
2020
|
+
event = {
|
|
2021
|
+
"event_type": event_type.value,
|
|
2022
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
2023
|
+
"user_id": user_id,
|
|
2024
|
+
"ip_address": ip_address,
|
|
2025
|
+
"details": details or {}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
# Log as structured JSON
|
|
2029
|
+
self.logger.info(json.dumps(event))
|
|
2030
|
+
|
|
2031
|
+
def log_login_success(
|
|
2032
|
+
self,
|
|
2033
|
+
user_id: str,
|
|
2034
|
+
ip_address: str,
|
|
2035
|
+
mfa_used: bool = False
|
|
2036
|
+
) -> None:
|
|
2037
|
+
"""Log successful login"""
|
|
2038
|
+
self.log_security_event(
|
|
2039
|
+
SecurityEventType.LOGIN_SUCCESS,
|
|
2040
|
+
user_id=user_id,
|
|
2041
|
+
ip_address=ip_address,
|
|
2042
|
+
details={"mfa_used": mfa_used}
|
|
2043
|
+
)
|
|
2044
|
+
|
|
2045
|
+
def log_login_failure(
|
|
2046
|
+
self,
|
|
2047
|
+
email: str,
|
|
2048
|
+
ip_address: str,
|
|
2049
|
+
reason: str
|
|
2050
|
+
) -> None:
|
|
2051
|
+
"""Log failed login attempt"""
|
|
2052
|
+
self.log_security_event(
|
|
2053
|
+
SecurityEventType.LOGIN_FAILURE,
|
|
2054
|
+
ip_address=ip_address,
|
|
2055
|
+
details={"email": email, "reason": reason}
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
def log_mfa_challenge(
|
|
2059
|
+
self,
|
|
2060
|
+
user_id: str,
|
|
2061
|
+
mfa_method: str
|
|
2062
|
+
) -> None:
|
|
2063
|
+
"""Log MFA challenge"""
|
|
2064
|
+
self.log_security_event(
|
|
2065
|
+
SecurityEventType.MFA_CHALLENGE,
|
|
2066
|
+
user_id=user_id,
|
|
2067
|
+
details={"mfa_method": mfa_method}
|
|
2068
|
+
)
|
|
2069
|
+
|
|
2070
|
+
def log_attack_detected(
|
|
2071
|
+
self,
|
|
2072
|
+
attack_type: str,
|
|
2073
|
+
ip_address: str,
|
|
2074
|
+
target_user_id: Optional[str] = None
|
|
2075
|
+
) -> None:
|
|
2076
|
+
"""Log detected attack"""
|
|
2077
|
+
self.log_security_event(
|
|
2078
|
+
SecurityEventType.ATTACK_DETECTED,
|
|
2079
|
+
ip_address=ip_address,
|
|
2080
|
+
user_id=target_user_id,
|
|
2081
|
+
details={"attack_type": attack_type}
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
def log_breached_password(
|
|
2085
|
+
self,
|
|
2086
|
+
user_id: str,
|
|
2087
|
+
ip_address: str
|
|
2088
|
+
) -> None:
|
|
2089
|
+
"""Log breached password detection"""
|
|
2090
|
+
self.log_security_event(
|
|
2091
|
+
SecurityEventType.BREACHED_PASSWORD,
|
|
2092
|
+
user_id=user_id,
|
|
2093
|
+
ip_address=ip_address
|
|
2094
|
+
)
|
|
2095
|
+
|
|
2096
|
+
def log_token_issued(
|
|
2097
|
+
self,
|
|
2098
|
+
user_id: str,
|
|
2099
|
+
token_type: str,
|
|
2100
|
+
scopes: list
|
|
2101
|
+
) -> None:
|
|
2102
|
+
"""Log token issuance"""
|
|
2103
|
+
self.log_security_event(
|
|
2104
|
+
SecurityEventType.TOKEN_ISSUED,
|
|
2105
|
+
user_id=user_id,
|
|
2106
|
+
details={"token_type": token_type, "scopes": scopes}
|
|
2107
|
+
)
|
|
2108
|
+
|
|
2109
|
+
def log_data_export(
|
|
2110
|
+
self,
|
|
2111
|
+
user_id: str,
|
|
2112
|
+
requested_by: str
|
|
2113
|
+
) -> None:
|
|
2114
|
+
"""Log GDPR data export"""
|
|
2115
|
+
self.log_security_event(
|
|
2116
|
+
SecurityEventType.DATA_EXPORT,
|
|
2117
|
+
user_id=user_id,
|
|
2118
|
+
details={"requested_by": requested_by}
|
|
2119
|
+
)
|
|
2120
|
+
|
|
2121
|
+
def log_data_deletion(
|
|
2122
|
+
self,
|
|
2123
|
+
user_id: str,
|
|
2124
|
+
deleted_by: str
|
|
2125
|
+
) -> None:
|
|
2126
|
+
"""Log GDPR data deletion"""
|
|
2127
|
+
self.log_security_event(
|
|
2128
|
+
SecurityEventType.DATA_DELETION,
|
|
2129
|
+
user_id=user_id,
|
|
2130
|
+
details={"deleted_by": deleted_by}
|
|
2131
|
+
)
|
|
2132
|
+
```
|
|
2133
|
+
|
|
2134
|
+
---
|
|
2135
|
+
|
|
2136
|
+
## 10. Security Monitoring
|
|
2137
|
+
|
|
2138
|
+
### 10.1 Metrics Collector (Python)
|
|
2139
|
+
|
|
2140
|
+
```python
|
|
2141
|
+
# metrics_collector.py
|
|
2142
|
+
from typing import Dict, Any, List
|
|
2143
|
+
from datetime import datetime, timedelta
|
|
2144
|
+
from collections import defaultdict
|
|
2145
|
+
from auth0_client import Auth0Client
|
|
2146
|
+
|
|
2147
|
+
class SecurityMetricsCollector:
|
|
2148
|
+
"""
|
|
2149
|
+
Collect and analyze security metrics
|
|
2150
|
+
|
|
2151
|
+
Tracks:
|
|
2152
|
+
- Authentication success/failure rates
|
|
2153
|
+
- MFA adoption and success rates
|
|
2154
|
+
- Attack detection rates
|
|
2155
|
+
- Token issuance patterns
|
|
2156
|
+
- Breached password detections
|
|
2157
|
+
"""
|
|
2158
|
+
|
|
2159
|
+
def __init__(self, auth0_client: Auth0Client):
|
|
2160
|
+
self.client = auth0_client
|
|
2161
|
+
self.metrics_cache: Dict[str, Any] = defaultdict(list)
|
|
2162
|
+
|
|
2163
|
+
async def collect_metrics(
|
|
2164
|
+
self,
|
|
2165
|
+
time_range: timedelta = timedelta(hours=24)
|
|
2166
|
+
) -> Dict[str, Any]:
|
|
2167
|
+
"""
|
|
2168
|
+
Collect all security metrics
|
|
2169
|
+
|
|
2170
|
+
Args:
|
|
2171
|
+
time_range: Time range for metrics
|
|
2172
|
+
|
|
2173
|
+
Returns:
|
|
2174
|
+
Dictionary with all metrics
|
|
2175
|
+
"""
|
|
2176
|
+
end_time = datetime.utcnow()
|
|
2177
|
+
start_time = end_time - time_range
|
|
2178
|
+
|
|
2179
|
+
metrics = {
|
|
2180
|
+
"authentication": await self._collect_authentication_metrics(
|
|
2181
|
+
start_time, end_time
|
|
2182
|
+
),
|
|
2183
|
+
"mfa": await self._collect_mfa_metrics(start_time, end_time),
|
|
2184
|
+
"attacks": await self._collect_attack_metrics(start_time, end_time),
|
|
2185
|
+
"tokens": await self._collect_token_metrics(start_time, end_time),
|
|
2186
|
+
"compliance": await self._collect_compliance_metrics(start_time, end_time)
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
return metrics
|
|
2190
|
+
|
|
2191
|
+
async def _collect_authentication_metrics(
|
|
2192
|
+
self,
|
|
2193
|
+
start_time: datetime,
|
|
2194
|
+
end_time: datetime
|
|
2195
|
+
) -> Dict[str, Any]:
|
|
2196
|
+
"""Collect authentication metrics"""
|
|
2197
|
+
# Implementation: Query Auth0 logs for authentication events
|
|
2198
|
+
|
|
2199
|
+
return {
|
|
2200
|
+
"total_logins": 0,
|
|
2201
|
+
"successful_logins": 0,
|
|
2202
|
+
"failed_logins": 0,
|
|
2203
|
+
"success_rate": 0.0,
|
|
2204
|
+
"unique_users": 0,
|
|
2205
|
+
"unique_ips": 0
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
async def _collect_mfa_metrics(
|
|
2209
|
+
self,
|
|
2210
|
+
start_time: datetime,
|
|
2211
|
+
end_time: datetime
|
|
2212
|
+
) -> Dict[str, Any]:
|
|
2213
|
+
"""Collect MFA metrics"""
|
|
2214
|
+
# Implementation: Query Auth0 logs for MFA events
|
|
2215
|
+
|
|
2216
|
+
return {
|
|
2217
|
+
"mfa_challenges": 0,
|
|
2218
|
+
"mfa_successes": 0,
|
|
2219
|
+
"mfa_failures": 0,
|
|
2220
|
+
"success_rate": 0.0,
|
|
2221
|
+
"adoption_rate": 0.0,
|
|
2222
|
+
"by_factor": {
|
|
2223
|
+
"otp": 0,
|
|
2224
|
+
"push": 0,
|
|
2225
|
+
"webauthn": 0,
|
|
2226
|
+
"sms": 0
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
async def _collect_attack_metrics(
|
|
2231
|
+
self,
|
|
2232
|
+
start_time: datetime,
|
|
2233
|
+
end_time: datetime
|
|
2234
|
+
) -> Dict[str, Any]:
|
|
2235
|
+
"""Collect attack metrics"""
|
|
2236
|
+
# Implementation: Query Auth0 Security Center
|
|
2237
|
+
|
|
2238
|
+
return {
|
|
2239
|
+
"bot_detection": {
|
|
2240
|
+
"blocked": 0,
|
|
2241
|
+
"challenged": 0
|
|
2242
|
+
},
|
|
2243
|
+
"brute_force": {
|
|
2244
|
+
"blocked_attempts": 0,
|
|
2245
|
+
"locked_accounts": 0
|
|
2246
|
+
},
|
|
2247
|
+
"breached_passwords": {
|
|
2248
|
+
"detected": 0,
|
|
2249
|
+
"blocked": 0
|
|
2250
|
+
},
|
|
2251
|
+
"suspicious_ip": {
|
|
2252
|
+
"throttled": 0,
|
|
2253
|
+
"blocked": 0
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
async def _collect_token_metrics(
|
|
2258
|
+
self,
|
|
2259
|
+
start_time: datetime,
|
|
2260
|
+
end_time: datetime
|
|
2261
|
+
) -> Dict[str, Any]:
|
|
2262
|
+
"""Collect token metrics"""
|
|
2263
|
+
# Implementation: Query token issuance and refresh patterns
|
|
2264
|
+
|
|
2265
|
+
return {
|
|
2266
|
+
"tokens_issued": 0,
|
|
2267
|
+
"tokens_refreshed": 0,
|
|
2268
|
+
"tokens_revoked": 0,
|
|
2269
|
+
"refresh_rate": 0.0,
|
|
2270
|
+
"avg_lifetime": 0.0
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
async def _collect_compliance_metrics(
|
|
2274
|
+
self,
|
|
2275
|
+
start_time: datetime,
|
|
2276
|
+
end_time: datetime
|
|
2277
|
+
) -> Dict[str, Any]:
|
|
2278
|
+
"""Collect compliance metrics"""
|
|
2279
|
+
# Implementation: Query GDPR and compliance events
|
|
2280
|
+
|
|
2281
|
+
return {
|
|
2282
|
+
"gdpr": {
|
|
2283
|
+
"data_exports": 0,
|
|
2284
|
+
"data_deletions": 0,
|
|
2285
|
+
"consent_records": 0
|
|
2286
|
+
},
|
|
2287
|
+
"audit_logs": {
|
|
2288
|
+
"total_events": 0,
|
|
2289
|
+
"retention_compliance": 100.0
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
async def generate_report(self) -> str:
|
|
2294
|
+
"""
|
|
2295
|
+
Generate security metrics report
|
|
2296
|
+
|
|
2297
|
+
Returns:
|
|
2298
|
+
Formatted report string
|
|
2299
|
+
"""
|
|
2300
|
+
metrics = await self.collect_metrics()
|
|
2301
|
+
|
|
2302
|
+
report = f"""
|
|
2303
|
+
# Auth0 Security Metrics Report
|
|
2304
|
+
Generated: {datetime.utcnow().isoformat()}
|
|
2305
|
+
|
|
2306
|
+
## Authentication
|
|
2307
|
+
- Total Logins: {metrics['authentication']['total_logins']}
|
|
2308
|
+
- Success Rate: {metrics['authentication']['success_rate']:.2%}
|
|
2309
|
+
- Unique Users: {metrics['authentication']['unique_users']}
|
|
2310
|
+
|
|
2311
|
+
## MFA
|
|
2312
|
+
- MFA Adoption: {metrics['mfa']['adoption_rate']:.2%}
|
|
2313
|
+
- Success Rate: {metrics['mfa']['success_rate']:.2%}
|
|
2314
|
+
- Challenges: {metrics['mfa']['mfa_challenges']}
|
|
2315
|
+
|
|
2316
|
+
## Attack Protection
|
|
2317
|
+
- Bot Detection: {metrics['attacks']['bot_detection']['blocked']} blocked
|
|
2318
|
+
- Brute Force: {metrics['attacks']['brute_force']['blocked_attempts']} blocked attempts
|
|
2319
|
+
- Breached Passwords: {metrics['attacks']['breached_passwords']['detected']} detected
|
|
2320
|
+
- Suspicious IP: {metrics['attacks']['suspicious_ip']['throttled']} throttled
|
|
2321
|
+
|
|
2322
|
+
## Tokens
|
|
2323
|
+
- Issued: {metrics['tokens']['tokens_issued']}
|
|
2324
|
+
- Refreshed: {metrics['tokens']['tokens_refreshed']}
|
|
2325
|
+
- Revoked: {metrics['tokens']['tokens_revoked']}
|
|
2326
|
+
|
|
2327
|
+
## Compliance
|
|
2328
|
+
- Data Exports: {metrics['compliance']['gdpr']['data_exports']}
|
|
2329
|
+
- Data Deletions: {metrics['compliance']['gdpr']['data_deletions']}
|
|
2330
|
+
"""
|
|
2331
|
+
|
|
2332
|
+
return report
|
|
2333
|
+
```
|
|
2334
|
+
|
|
2335
|
+
---
|
|
2336
|
+
|
|
2337
|
+
## Usage Examples
|
|
2338
|
+
|
|
2339
|
+
### Complete Authentication Flow
|
|
2340
|
+
|
|
2341
|
+
```python
|
|
2342
|
+
# example_auth_flow.py
|
|
2343
|
+
import asyncio
|
|
2344
|
+
from config import Auth0Config, SecurityConfig
|
|
2345
|
+
from auth0_client import Auth0Client
|
|
2346
|
+
from token_validator import TokenValidator
|
|
2347
|
+
from token_rotation import TokenRotationManager
|
|
2348
|
+
from security_logger import SecurityLogger
|
|
2349
|
+
|
|
2350
|
+
async def authenticate_user(code: str, redirect_uri: str):
|
|
2351
|
+
"""Complete authentication flow with security features"""
|
|
2352
|
+
|
|
2353
|
+
# Initialize components
|
|
2354
|
+
auth0_config = Auth0Config.from_env()
|
|
2355
|
+
security_config = SecurityConfig.from_env()
|
|
2356
|
+
|
|
2357
|
+
auth0_client = Auth0Client(auth0_config, security_config)
|
|
2358
|
+
token_validator = TokenValidator(auth0_config)
|
|
2359
|
+
token_manager = TokenRotationManager(auth0_client, token_validator)
|
|
2360
|
+
security_logger = SecurityLogger()
|
|
2361
|
+
|
|
2362
|
+
try:
|
|
2363
|
+
# Exchange authorization code for tokens
|
|
2364
|
+
tokens = await auth0_client.get_token(code, redirect_uri)
|
|
2365
|
+
|
|
2366
|
+
# Validate access token
|
|
2367
|
+
payload = await token_validator.validate_access_token(tokens["access_token"])
|
|
2368
|
+
|
|
2369
|
+
# Get user info
|
|
2370
|
+
user_info = await auth0_client.get_user_info(tokens["access_token"])
|
|
2371
|
+
|
|
2372
|
+
# Log successful login
|
|
2373
|
+
security_logger.log_login_success(
|
|
2374
|
+
user_id=payload["sub"],
|
|
2375
|
+
ip_address="user_ip_address", # Get from request
|
|
2376
|
+
mfa_used="amr" in payload # amr = Authentication Methods References
|
|
2377
|
+
)
|
|
2378
|
+
|
|
2379
|
+
return {
|
|
2380
|
+
"user": user_info,
|
|
2381
|
+
"tokens": tokens
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
finally:
|
|
2385
|
+
await auth0_client.close()
|
|
2386
|
+
|
|
2387
|
+
# Run example
|
|
2388
|
+
asyncio.run(authenticate_user("code_from_callback", "https://yourapp.com/callback"))
|
|
2389
|
+
```
|
|
2390
|
+
|
|
2391
|
+
---
|
|
2392
|
+
|
|
2393
|
+
## Testing Examples
|
|
2394
|
+
|
|
2395
|
+
### Test Breached Password Detection
|
|
2396
|
+
|
|
2397
|
+
```python
|
|
2398
|
+
# test_breached_password.py
|
|
2399
|
+
import asyncio
|
|
2400
|
+
from config import Auth0Config, SecurityConfig
|
|
2401
|
+
from auth0_client import Auth0Client
|
|
2402
|
+
from breached_password import BreachedPasswordDetector
|
|
2403
|
+
|
|
2404
|
+
async def test_breached_password():
|
|
2405
|
+
"""Test breached password detection"""
|
|
2406
|
+
|
|
2407
|
+
auth0_config = Auth0Config.from_env()
|
|
2408
|
+
security_config = SecurityConfig.from_env()
|
|
2409
|
+
|
|
2410
|
+
auth0_client = Auth0Client(auth0_config, security_config)
|
|
2411
|
+
detector = BreachedPasswordDetector(auth0_client)
|
|
2412
|
+
|
|
2413
|
+
# Use test password that always triggers detection
|
|
2414
|
+
test_password = detector.test_breached_password()
|
|
2415
|
+
|
|
2416
|
+
print(f"Test password: {test_password}")
|
|
2417
|
+
print("This password will trigger Auth0's breached password detection")
|
|
2418
|
+
|
|
2419
|
+
await auth0_client.close()
|
|
2420
|
+
|
|
2421
|
+
asyncio.run(test_breached_password())
|
|
2422
|
+
```
|
|
2423
|
+
|
|
2424
|
+
---
|
|
2425
|
+
|
|
2426
|
+
## Best Practices Summary
|
|
2427
|
+
|
|
2428
|
+
1. **Always use HTTPS** for all token transmission
|
|
2429
|
+
2. **Validate tokens** on every request (signature, expiration, issuer, audience)
|
|
2430
|
+
3. **Enable refresh token rotation** to detect token theft
|
|
2431
|
+
4. **Use RS256** algorithm for tokens (asymmetric, more secure)
|
|
2432
|
+
5. **Implement MFA** for sensitive operations
|
|
2433
|
+
6. **Monitor security events** with centralized logging
|
|
2434
|
+
7. **Follow GDPR requirements** for user data rights
|
|
2435
|
+
8. **Use DPoP or mTLS** for enhanced token security
|
|
2436
|
+
9. **Configure attack protection** with appropriate thresholds
|
|
2437
|
+
10. **Regularly audit** security configurations and access patterns
|
|
2438
|
+
|
|
2439
|
+
---
|
|
2440
|
+
|
|
2441
|
+
For more details, see:
|
|
2442
|
+
- [Attack Protection Overview](modules/attack-protection-overview.md)
|
|
2443
|
+
- [MFA Implementation](modules/mfa-overview.md)
|
|
2444
|
+
- [Token Best Practices](modules/token-best-practices.md)
|
|
2445
|
+
- [DPoP Implementation](modules/dpop-implementation.md)
|
|
2446
|
+
- [GDPR Compliance](modules/gdpr-compliance.md)
|