aigroup-workflow 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/fix-build.md +10 -5
- package/.claude/commands/init-project.md +13 -8
- package/.claude/commands/plan.md +15 -8
- package/.claude/commands/review.md +12 -6
- package/.claude/commands/tdd.md +11 -5
- package/.claude/commands/workflow-start.md +20 -11
- package/.claude/settings.json +28 -0
- package/.codex/agents/architect.toml +207 -0
- package/.codex/agents/build-error-resolver.toml +110 -0
- package/.codex/agents/code-reviewer.toml +233 -0
- package/.codex/agents/doc-updater.toml +103 -0
- package/.codex/agents/e2e-runner.toml +103 -0
- package/.codex/agents/get-current-datetime.toml +23 -0
- package/.codex/agents/init-architect.toml +181 -0
- package/.codex/agents/planner.toml +208 -0
- package/.codex/agents/refactor-cleaner.toml +81 -0
- package/.codex/agents/rust-reviewer.toml +90 -0
- package/.codex/agents/security-reviewer.toml +104 -0
- package/.codex/agents/tdd-guide.toml +87 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +23 -1
- package/LICENSE +20 -20
- package/README.md +333 -333
- package/agents/a11y-architect.md +141 -141
- package/agents/architect.md +211 -211
- package/agents/build-error-resolver.md +114 -114
- package/agents/chief-of-staff.md +151 -151
- package/agents/code-architect.md +71 -71
- package/agents/code-explorer.md +69 -69
- package/agents/code-reviewer.md +237 -237
- package/agents/code-simplifier.md +47 -47
- package/agents/comment-analyzer.md +45 -45
- package/agents/conversation-analyzer.md +52 -52
- package/agents/cpp-build-resolver.md +90 -90
- package/agents/cpp-reviewer.md +72 -72
- package/agents/csharp-reviewer.md +101 -101
- package/agents/dart-build-resolver.md +201 -201
- package/agents/database-reviewer.md +91 -91
- package/agents/doc-updater.md +107 -107
- package/agents/docs-lookup.md +68 -68
- package/agents/e2e-runner.md +107 -107
- package/agents/flutter-reviewer.md +243 -243
- package/agents/gan-evaluator.md +209 -209
- package/agents/gan-generator.md +131 -131
- package/agents/gan-planner.md +99 -99
- package/agents/get-current-datetime.md +26 -26
- package/agents/go-build-resolver.md +94 -94
- package/agents/go-reviewer.md +76 -76
- package/agents/harness-optimizer.md +35 -35
- package/agents/healthcare-reviewer.md +83 -83
- package/agents/java-build-resolver.md +153 -153
- package/agents/java-reviewer.md +92 -92
- package/agents/kotlin-build-resolver.md +118 -118
- package/agents/kotlin-reviewer.md +159 -159
- package/agents/loop-operator.md +36 -36
- package/agents/opensource-forker.md +198 -198
- package/agents/opensource-packager.md +249 -249
- package/agents/opensource-sanitizer.md +188 -188
- package/agents/performance-optimizer.md +446 -446
- package/agents/planner.md +212 -212
- package/agents/pr-test-analyzer.md +45 -45
- package/agents/python-reviewer.md +98 -98
- package/agents/pytorch-build-resolver.md +120 -120
- package/agents/refactor-cleaner.md +85 -85
- package/agents/rust-build-resolver.md +148 -148
- package/agents/rust-reviewer.md +94 -94
- package/agents/security-reviewer.md +108 -108
- package/agents/seo-specialist.md +59 -59
- package/agents/silent-failure-hunter.md +50 -50
- package/agents/tdd-guide.md +91 -91
- package/agents/type-design-analyzer.md +41 -41
- package/agents/typescript-reviewer.md +112 -112
- package/cli/commands/update.mjs +1 -1
- package/cli/utils/scaffold.mjs +53 -0
- package/docs/rules/agents.md +166 -50
- package/docs/rules/cpp/coding-style.md +44 -44
- package/docs/rules/cpp/hooks.md +39 -39
- package/docs/rules/cpp/patterns.md +51 -51
- package/docs/rules/cpp/security.md +51 -51
- package/docs/rules/cpp/testing.md +44 -44
- package/docs/rules/csharp/coding-style.md +72 -72
- package/docs/rules/csharp/hooks.md +25 -25
- package/docs/rules/csharp/patterns.md +50 -50
- package/docs/rules/csharp/security.md +58 -58
- package/docs/rules/csharp/testing.md +46 -46
- package/docs/rules/dart/coding-style.md +159 -159
- package/docs/rules/dart/hooks.md +66 -66
- package/docs/rules/dart/patterns.md +261 -261
- package/docs/rules/dart/security.md +135 -135
- package/docs/rules/dart/testing.md +215 -215
- package/docs/rules/golang/coding-style.md +32 -32
- package/docs/rules/golang/hooks.md +17 -17
- package/docs/rules/golang/patterns.md +45 -45
- package/docs/rules/golang/security.md +34 -34
- package/docs/rules/golang/testing.md +31 -31
- package/docs/rules/java/coding-style.md +114 -114
- package/docs/rules/java/hooks.md +18 -18
- package/docs/rules/java/patterns.md +146 -146
- package/docs/rules/java/security.md +100 -100
- package/docs/rules/java/testing.md +131 -131
- package/docs/rules/kotlin/coding-style.md +86 -86
- package/docs/rules/kotlin/hooks.md +17 -17
- package/docs/rules/kotlin/patterns.md +146 -146
- package/docs/rules/kotlin/security.md +82 -82
- package/docs/rules/kotlin/testing.md +128 -128
- package/docs/rules/perl/coding-style.md +46 -46
- package/docs/rules/perl/hooks.md +22 -22
- package/docs/rules/perl/patterns.md +76 -76
- package/docs/rules/perl/security.md +69 -69
- package/docs/rules/perl/testing.md +54 -54
- package/docs/rules/php/coding-style.md +40 -40
- package/docs/rules/php/hooks.md +24 -24
- package/docs/rules/php/patterns.md +33 -33
- package/docs/rules/php/security.md +37 -37
- package/docs/rules/php/testing.md +39 -39
- package/docs/rules/python/coding-style.md +42 -42
- package/docs/rules/python/hooks.md +19 -19
- package/docs/rules/python/patterns.md +39 -39
- package/docs/rules/python/security.md +30 -30
- package/docs/rules/python/testing.md +38 -38
- package/docs/rules/rust/coding-style.md +151 -151
- package/docs/rules/rust/hooks.md +16 -16
- package/docs/rules/rust/patterns.md +168 -168
- package/docs/rules/rust/security.md +141 -141
- package/docs/rules/rust/testing.md +154 -154
- package/docs/rules/swift/coding-style.md +47 -47
- package/docs/rules/swift/hooks.md +20 -20
- package/docs/rules/swift/patterns.md +66 -66
- package/docs/rules/swift/security.md +33 -33
- package/docs/rules/swift/testing.md +45 -45
- package/docs/rules/typescript/coding-style.md +199 -199
- package/docs/rules/typescript/hooks.md +22 -22
- package/docs/rules/typescript/patterns.md +52 -52
- package/docs/rules/typescript/security.md +28 -28
- package/docs/rules/typescript/testing.md +18 -18
- package/docs/rules/web/coding-style.md +96 -96
- package/docs/rules/web/design-quality.md +62 -62
- package/docs/rules/web/hooks.md +120 -120
- package/docs/rules/web/patterns.md +79 -79
- package/docs/rules/web/performance.md +64 -64
- package/docs/rules/web/security.md +57 -57
- package/docs/rules/web/testing.md +55 -55
- package/docs/templates/README.md +36 -36
- package/docs/templates/ai-project-final.md +124 -124
- package/docs/templates/ai-project.md +105 -105
- package/docs/templates/api.md +157 -157
- package/docs/templates/bug.md +62 -62
- package/docs/templates/code-review.md +87 -87
- package/docs/templates/generic.md +116 -116
- package/docs/templates/implementation-plan.md +1 -1
- package/docs/templates/meeting.md +68 -68
- package/docs/templates/prd.md +98 -98
- package/docs/templates/ui.md +134 -134
- package/docs/workflow-pipeline.md +5 -5
- package/package.json +40 -39
- package/skills/SUPERPOWERS-LICENSE +21 -21
- package/skills/ai-ml/fine-tuning-expert/SKILL.md +162 -162
- package/skills/ai-ml/fine-tuning-expert/references/dataset-preparation.md +540 -540
- package/skills/ai-ml/fine-tuning-expert/references/deployment-optimization.md +673 -673
- package/skills/ai-ml/fine-tuning-expert/references/evaluation-metrics.md +597 -597
- package/skills/ai-ml/fine-tuning-expert/references/hyperparameter-tuning.md +565 -565
- package/skills/ai-ml/fine-tuning-expert/references/lora-peft.md +347 -347
- package/skills/ai-ml/ml-pipeline/SKILL.md +159 -159
- package/skills/ai-ml/ml-pipeline/references/experiment-tracking.md +833 -833
- package/skills/ai-ml/ml-pipeline/references/feature-engineering.md +631 -631
- package/skills/ai-ml/ml-pipeline/references/model-validation.md +978 -978
- package/skills/ai-ml/ml-pipeline/references/pipeline-orchestration.md +907 -907
- package/skills/ai-ml/ml-pipeline/references/training-pipelines.md +782 -782
- package/skills/ai-ml/rag-architect/SKILL.md +194 -194
- package/skills/ai-ml/rag-architect/references/chunking-strategies.md +878 -878
- package/skills/ai-ml/rag-architect/references/embedding-models.md +561 -561
- package/skills/ai-ml/rag-architect/references/rag-evaluation.md +833 -833
- package/skills/ai-ml/rag-architect/references/retrieval-optimization.md +795 -795
- package/skills/ai-ml/rag-architect/references/vector-databases.md +589 -589
- package/skills/ai-ml/spark-engineer/SKILL.md +148 -148
- package/skills/ai-ml/spark-engineer/references/partitioning-caching.md +543 -543
- package/skills/ai-ml/spark-engineer/references/performance-tuning.md +544 -544
- package/skills/ai-ml/spark-engineer/references/rdd-operations.md +599 -599
- package/skills/ai-ml/spark-engineer/references/spark-sql-dataframes.md +474 -474
- package/skills/ai-ml/spark-engineer/references/streaming-patterns.md +786 -786
- package/skills/backend/api-designer/SKILL.md +217 -217
- package/skills/backend/api-designer/references/error-handling.md +541 -541
- package/skills/backend/api-designer/references/openapi.md +824 -824
- package/skills/backend/api-designer/references/pagination.md +494 -494
- package/skills/backend/api-designer/references/rest-patterns.md +335 -335
- package/skills/backend/api-designer/references/versioning.md +391 -391
- package/skills/backend/architecture-designer/SKILL.md +117 -117
- package/skills/backend/architecture-designer/references/adr-template.md +116 -116
- package/skills/backend/architecture-designer/references/architecture-patterns.md +111 -111
- package/skills/backend/architecture-designer/references/database-selection.md +102 -102
- package/skills/backend/architecture-designer/references/nfr-checklist.md +112 -112
- package/skills/backend/architecture-designer/references/system-design.md +100 -100
- package/skills/backend/code-documenter/SKILL.md +147 -147
- package/skills/backend/code-documenter/references/api-docs-fastapi-django.md +166 -166
- package/skills/backend/code-documenter/references/api-docs-nestjs-express.md +220 -220
- package/skills/backend/code-documenter/references/coverage-reports.md +125 -125
- package/skills/backend/code-documenter/references/documentation-systems.md +333 -333
- package/skills/backend/code-documenter/references/interactive-api-docs.md +531 -531
- package/skills/backend/code-documenter/references/python-docstrings.md +121 -121
- package/skills/backend/code-documenter/references/typescript-jsdoc.md +145 -145
- package/skills/backend/code-documenter/references/user-guides-tutorials.md +530 -530
- package/skills/backend/debugging-wizard/SKILL.md +105 -105
- package/skills/backend/debugging-wizard/references/common-patterns.md +132 -132
- package/skills/backend/debugging-wizard/references/debugging-tools.md +140 -140
- package/skills/backend/debugging-wizard/references/quick-fixes.md +177 -177
- package/skills/backend/debugging-wizard/references/strategies.md +142 -142
- package/skills/backend/debugging-wizard/references/systematic-debugging.md +367 -367
- package/skills/backend/feature-forge/SKILL.md +98 -98
- package/skills/backend/feature-forge/references/acceptance-criteria.md +104 -104
- package/skills/backend/feature-forge/references/ears-syntax.md +99 -99
- package/skills/backend/feature-forge/references/interview-questions.md +150 -150
- package/skills/backend/feature-forge/references/pre-discovery-subagents.md +54 -54
- package/skills/backend/feature-forge/references/specification-template.md +103 -103
- package/skills/backend/fullstack-guardian/SKILL.md +105 -105
- package/skills/backend/fullstack-guardian/references/api-design-standards.md +307 -307
- package/skills/backend/fullstack-guardian/references/architecture-decisions.md +350 -350
- package/skills/backend/fullstack-guardian/references/backend-patterns.md +237 -237
- package/skills/backend/fullstack-guardian/references/common-patterns.md +134 -134
- package/skills/backend/fullstack-guardian/references/deliverables-checklist.md +354 -354
- package/skills/backend/fullstack-guardian/references/design-template.md +91 -91
- package/skills/backend/fullstack-guardian/references/error-handling.md +135 -135
- package/skills/backend/fullstack-guardian/references/frontend-patterns.md +340 -340
- package/skills/backend/fullstack-guardian/references/integration-patterns.md +333 -333
- package/skills/backend/fullstack-guardian/references/security-checklist.md +106 -106
- package/skills/backend/graphql-architect/SKILL.md +146 -146
- package/skills/backend/graphql-architect/references/federation.md +418 -418
- package/skills/backend/graphql-architect/references/migration-from-rest.md +1141 -1141
- package/skills/backend/graphql-architect/references/resolvers.md +425 -425
- package/skills/backend/graphql-architect/references/schema-design.md +393 -393
- package/skills/backend/graphql-architect/references/security.md +569 -569
- package/skills/backend/graphql-architect/references/subscriptions.md +510 -510
- package/skills/backend/legacy-modernizer/SKILL.md +137 -137
- package/skills/backend/legacy-modernizer/references/legacy-testing.md +381 -381
- package/skills/backend/legacy-modernizer/references/migration-strategies.md +423 -423
- package/skills/backend/legacy-modernizer/references/refactoring-patterns.md +395 -395
- package/skills/backend/legacy-modernizer/references/strangler-fig-pattern.md +281 -281
- package/skills/backend/legacy-modernizer/references/system-assessment.md +487 -487
- package/skills/backend/microservices-architect/SKILL.md +164 -164
- package/skills/backend/microservices-architect/references/communication.md +499 -499
- package/skills/backend/microservices-architect/references/data.md +721 -721
- package/skills/backend/microservices-architect/references/decomposition.md +344 -344
- package/skills/backend/microservices-architect/references/observability.md +805 -805
- package/skills/backend/microservices-architect/references/patterns.md +603 -603
- package/skills/database/database-optimizer/SKILL.md +147 -147
- package/skills/database/database-optimizer/references/index-strategies.md +331 -331
- package/skills/database/database-optimizer/references/monitoring-analysis.md +501 -501
- package/skills/database/database-optimizer/references/mysql-tuning.md +452 -452
- package/skills/database/database-optimizer/references/postgresql-tuning.md +413 -413
- package/skills/database/database-optimizer/references/query-optimization.md +251 -251
- package/skills/database/postgres-pro/SKILL.md +152 -152
- package/skills/database/postgres-pro/references/extensions.md +404 -404
- package/skills/database/postgres-pro/references/jsonb.md +321 -321
- package/skills/database/postgres-pro/references/maintenance.md +481 -481
- package/skills/database/postgres-pro/references/performance.md +265 -265
- package/skills/database/postgres-pro/references/replication.md +446 -446
- package/skills/database/sql-pro/SKILL.md +129 -129
- package/skills/database/sql-pro/references/database-design.md +402 -402
- package/skills/database/sql-pro/references/dialect-differences.md +419 -419
- package/skills/database/sql-pro/references/optimization.md +384 -384
- package/skills/database/sql-pro/references/query-patterns.md +285 -285
- package/skills/database/sql-pro/references/window-functions.md +328 -328
- package/skills/dotnet/csharp-developer/SKILL.md +125 -125
- package/skills/dotnet/csharp-developer/references/aspnet-core.md +394 -394
- package/skills/dotnet/csharp-developer/references/blazor.md +553 -553
- package/skills/dotnet/csharp-developer/references/entity-framework.md +409 -409
- package/skills/dotnet/csharp-developer/references/modern-csharp.md +248 -248
- package/skills/dotnet/csharp-developer/references/performance.md +498 -498
- package/skills/dotnet/dotnet-core-expert/SKILL.md +138 -138
- package/skills/dotnet/dotnet-core-expert/references/authentication.md +546 -546
- package/skills/dotnet/dotnet-core-expert/references/clean-architecture.md +455 -455
- package/skills/dotnet/dotnet-core-expert/references/cloud-native.md +548 -548
- package/skills/dotnet/dotnet-core-expert/references/entity-framework.md +440 -440
- package/skills/dotnet/dotnet-core-expert/references/minimal-apis.md +319 -319
- package/skills/frontend/angular-architect/SKILL.md +152 -152
- package/skills/frontend/angular-architect/references/components.md +297 -297
- package/skills/frontend/angular-architect/references/ngrx.md +401 -401
- package/skills/frontend/angular-architect/references/routing.md +361 -361
- package/skills/frontend/angular-architect/references/rxjs.md +319 -319
- package/skills/frontend/angular-architect/references/testing.md +405 -405
- package/skills/frontend/design-commands/design.md +91 -91
- package/skills/frontend/design-commands/handoff.md +97 -97
- package/skills/frontend/design-commands/prototype.md +120 -120
- package/skills/frontend/design-commands/spec.md +160 -160
- package/skills/frontend/design-commands/style.md +78 -78
- package/skills/frontend/flutter-expert/SKILL.md +138 -138
- package/skills/frontend/flutter-expert/references/bloc-state.md +259 -259
- package/skills/frontend/flutter-expert/references/gorouter-navigation.md +119 -119
- package/skills/frontend/flutter-expert/references/performance.md +99 -99
- package/skills/frontend/flutter-expert/references/project-structure.md +118 -118
- package/skills/frontend/flutter-expert/references/riverpod-state.md +130 -130
- package/skills/frontend/flutter-expert/references/widget-patterns.md +123 -123
- package/skills/frontend/nextjs-developer/SKILL.md +143 -143
- package/skills/frontend/nextjs-developer/references/app-router.md +311 -311
- package/skills/frontend/nextjs-developer/references/data-fetching.md +482 -482
- package/skills/frontend/nextjs-developer/references/deployment.md +545 -545
- package/skills/frontend/nextjs-developer/references/server-actions.md +462 -462
- package/skills/frontend/nextjs-developer/references/server-components.md +384 -384
- package/skills/frontend/react-expert/SKILL.md +149 -149
- package/skills/frontend/react-expert/references/hooks-patterns.md +162 -162
- package/skills/frontend/react-expert/references/migration-class-to-modern.md +1119 -1119
- package/skills/frontend/react-expert/references/performance.md +168 -168
- package/skills/frontend/react-expert/references/react-19-features.md +174 -174
- package/skills/frontend/react-expert/references/server-components.md +143 -143
- package/skills/frontend/react-expert/references/state-management.md +171 -171
- package/skills/frontend/react-expert/references/testing-react.md +174 -174
- package/skills/frontend/react-native-expert/SKILL.md +185 -185
- package/skills/frontend/react-native-expert/references/expo-router.md +187 -187
- package/skills/frontend/react-native-expert/references/list-optimization.md +204 -204
- package/skills/frontend/react-native-expert/references/platform-handling.md +188 -188
- package/skills/frontend/react-native-expert/references/project-structure.md +171 -171
- package/skills/frontend/react-native-expert/references/storage-hooks.md +173 -173
- package/skills/frontend/senior-frontend/SKILL.md +477 -477
- package/skills/frontend/senior-frontend/references/frontend_best_practices.md +806 -806
- package/skills/frontend/senior-frontend/references/nextjs_optimization_guide.md +724 -724
- package/skills/frontend/senior-frontend/references/react_patterns.md +746 -746
- package/skills/frontend/senior-frontend/scripts/bundle_analyzer.py +407 -407
- package/skills/frontend/senior-frontend/scripts/component_generator.py +329 -329
- package/skills/frontend/senior-frontend/scripts/frontend_scaffolder.py +1005 -1005
- package/skills/frontend/ui-ux-pro-max/SKILL.md +386 -386
- package/skills/frontend/ui-ux-pro-max/data/charts.csv +26 -26
- package/skills/frontend/ui-ux-pro-max/data/colors.csv +97 -97
- package/skills/frontend/ui-ux-pro-max/data/icons.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/landing.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/data/products.csv +96 -96
- package/skills/frontend/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/skills/frontend/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/skills/frontend/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/skills/frontend/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/skills/frontend/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/skills/frontend/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/skills/frontend/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/skills/frontend/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/skills/frontend/ui-ux-pro-max/data/styles.csv +68 -68
- package/skills/frontend/ui-ux-pro-max/data/typography.csv +57 -57
- package/skills/frontend/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/skills/frontend/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/skills/frontend/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/skills/frontend/ui-ux-pro-max/scripts/core.py +253 -253
- package/skills/frontend/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/skills/frontend/ui-ux-pro-max/scripts/search.py +114 -114
- package/skills/frontend/vue-expert/SKILL.md +98 -98
- package/skills/frontend/vue-expert/references/build-tooling.md +480 -480
- package/skills/frontend/vue-expert/references/components.md +448 -448
- package/skills/frontend/vue-expert/references/composition-api.md +299 -299
- package/skills/frontend/vue-expert/references/mobile-hybrid.md +636 -636
- package/skills/frontend/vue-expert/references/nuxt.md +669 -669
- package/skills/frontend/vue-expert/references/state-management.md +449 -449
- package/skills/frontend/vue-expert/references/typescript.md +584 -584
- package/skills/frontend/vue-expert-js/SKILL.md +167 -167
- package/skills/frontend/vue-expert-js/references/component-architecture.md +219 -219
- package/skills/frontend/vue-expert-js/references/composables-patterns.md +183 -183
- package/skills/frontend/vue-expert-js/references/jsdoc-typing.md +535 -535
- package/skills/frontend/vue-expert-js/references/state-management.md +249 -249
- package/skills/frontend/vue-expert-js/references/testing-patterns.md +237 -237
- package/skills/go-rust-cpp/cpp-pro/SKILL.md +115 -115
- package/skills/go-rust-cpp/cpp-pro/references/build-tooling.md +440 -440
- package/skills/go-rust-cpp/cpp-pro/references/concurrency.md +437 -437
- package/skills/go-rust-cpp/cpp-pro/references/memory-performance.md +397 -397
- package/skills/go-rust-cpp/cpp-pro/references/modern-cpp.md +304 -304
- package/skills/go-rust-cpp/cpp-pro/references/templates.md +357 -357
- package/skills/go-rust-cpp/golang-pro/SKILL.md +122 -122
- package/skills/go-rust-cpp/golang-pro/references/concurrency.md +329 -329
- package/skills/go-rust-cpp/golang-pro/references/generics.md +442 -442
- package/skills/go-rust-cpp/golang-pro/references/interfaces.md +432 -432
- package/skills/go-rust-cpp/golang-pro/references/project-structure.md +477 -477
- package/skills/go-rust-cpp/golang-pro/references/testing.md +451 -451
- package/skills/go-rust-cpp/rust-engineer/SKILL.md +167 -167
- package/skills/go-rust-cpp/rust-engineer/references/async.md +458 -458
- package/skills/go-rust-cpp/rust-engineer/references/error-handling.md +334 -334
- package/skills/go-rust-cpp/rust-engineer/references/ownership.md +278 -278
- package/skills/go-rust-cpp/rust-engineer/references/testing.md +470 -470
- package/skills/go-rust-cpp/rust-engineer/references/traits.md +413 -413
- package/skills/infra/cli-developer/SKILL.md +113 -113
- package/skills/infra/cli-developer/references/design-patterns.md +221 -221
- package/skills/infra/cli-developer/references/go-cli.md +540 -540
- package/skills/infra/cli-developer/references/node-cli.md +383 -383
- package/skills/infra/cli-developer/references/python-cli.md +422 -422
- package/skills/infra/cli-developer/references/ux-patterns.md +448 -448
- package/skills/infra/cloud-architect/SKILL.md +216 -216
- package/skills/infra/cloud-architect/references/aws.md +394 -394
- package/skills/infra/cloud-architect/references/azure.md +562 -562
- package/skills/infra/cloud-architect/references/cost.md +582 -582
- package/skills/infra/cloud-architect/references/gcp.md +633 -633
- package/skills/infra/cloud-architect/references/multi-cloud.md +483 -483
- package/skills/infra/devops-engineer/SKILL.md +144 -144
- package/skills/infra/devops-engineer/references/deployment-strategies.md +241 -241
- package/skills/infra/devops-engineer/references/docker-patterns.md +113 -113
- package/skills/infra/devops-engineer/references/github-actions.md +139 -139
- package/skills/infra/devops-engineer/references/incident-response.md +331 -331
- package/skills/infra/devops-engineer/references/kubernetes.md +154 -154
- package/skills/infra/devops-engineer/references/platform-engineering.md +417 -417
- package/skills/infra/devops-engineer/references/release-automation.md +527 -527
- package/skills/infra/devops-engineer/references/terraform-iac.md +141 -141
- package/skills/infra/kubernetes-specialist/SKILL.md +241 -241
- package/skills/infra/kubernetes-specialist/references/configuration.md +452 -452
- package/skills/infra/kubernetes-specialist/references/cost-optimization.md +458 -458
- package/skills/infra/kubernetes-specialist/references/custom-operators.md +563 -563
- package/skills/infra/kubernetes-specialist/references/gitops.md +530 -530
- package/skills/infra/kubernetes-specialist/references/helm-charts.md +912 -912
- package/skills/infra/kubernetes-specialist/references/multi-cluster.md +507 -507
- package/skills/infra/kubernetes-specialist/references/networking.md +447 -447
- package/skills/infra/kubernetes-specialist/references/service-mesh.md +459 -459
- package/skills/infra/kubernetes-specialist/references/storage.md +535 -535
- package/skills/infra/kubernetes-specialist/references/troubleshooting.md +414 -414
- package/skills/infra/kubernetes-specialist/references/workloads.md +377 -377
- package/skills/infra/mcp-developer/SKILL.md +143 -143
- package/skills/infra/mcp-developer/references/protocol.md +244 -244
- package/skills/infra/mcp-developer/references/python-sdk.md +367 -367
- package/skills/infra/mcp-developer/references/resources.md +554 -554
- package/skills/infra/mcp-developer/references/tools.md +480 -480
- package/skills/infra/mcp-developer/references/typescript-sdk.md +350 -350
- package/skills/infra/monitoring-expert/SKILL.md +176 -176
- package/skills/infra/monitoring-expert/references/alerting-rules.md +141 -141
- package/skills/infra/monitoring-expert/references/application-profiling.md +331 -331
- package/skills/infra/monitoring-expert/references/capacity-planning.md +344 -344
- package/skills/infra/monitoring-expert/references/dashboards.md +126 -126
- package/skills/infra/monitoring-expert/references/opentelemetry.md +123 -123
- package/skills/infra/monitoring-expert/references/performance-testing.md +269 -269
- package/skills/infra/monitoring-expert/references/prometheus-metrics.md +136 -136
- package/skills/infra/monitoring-expert/references/structured-logging.md +142 -142
- package/skills/infra/sre-engineer/SKILL.md +181 -181
- package/skills/infra/sre-engineer/references/automation-toil.md +492 -492
- package/skills/infra/sre-engineer/references/error-budget-policy.md +334 -334
- package/skills/infra/sre-engineer/references/incident-chaos.md +576 -576
- package/skills/infra/sre-engineer/references/monitoring-alerting.md +424 -424
- package/skills/infra/sre-engineer/references/slo-sli-management.md +238 -238
- package/skills/infra/terraform-engineer/SKILL.md +143 -143
- package/skills/infra/terraform-engineer/references/best-practices.md +583 -583
- package/skills/infra/terraform-engineer/references/module-patterns.md +297 -297
- package/skills/infra/terraform-engineer/references/providers.md +452 -452
- package/skills/infra/terraform-engineer/references/state-management.md +371 -371
- package/skills/infra/terraform-engineer/references/testing.md +486 -486
- package/skills/infra/websocket-engineer/SKILL.md +168 -168
- package/skills/infra/websocket-engineer/references/alternatives.md +391 -391
- package/skills/infra/websocket-engineer/references/patterns.md +400 -400
- package/skills/infra/websocket-engineer/references/protocol.md +195 -195
- package/skills/infra/websocket-engineer/references/scaling.md +333 -333
- package/skills/infra/websocket-engineer/references/security.md +474 -474
- package/skills/java/java-architect/SKILL.md +132 -132
- package/skills/java/java-architect/references/jpa-optimization.md +393 -393
- package/skills/java/java-architect/references/reactive-webflux.md +356 -356
- package/skills/java/java-architect/references/spring-boot-setup.md +269 -269
- package/skills/java/java-architect/references/spring-security.md +445 -445
- package/skills/java/java-architect/references/testing-patterns.md +500 -500
- package/skills/java/kotlin-specialist/SKILL.md +147 -147
- package/skills/java/kotlin-specialist/references/android-compose.md +419 -419
- package/skills/java/kotlin-specialist/references/coroutines-flow.md +276 -276
- package/skills/java/kotlin-specialist/references/dsl-idioms.md +421 -421
- package/skills/java/kotlin-specialist/references/ktor-server.md +426 -426
- package/skills/java/kotlin-specialist/references/multiplatform-kmp.md +380 -380
- package/skills/java/spring-boot-engineer/SKILL.md +195 -195
- package/skills/java/spring-boot-engineer/references/cloud.md +498 -498
- package/skills/java/spring-boot-engineer/references/data.md +381 -381
- package/skills/java/spring-boot-engineer/references/security.md +459 -459
- package/skills/java/spring-boot-engineer/references/testing.md +545 -545
- package/skills/java/spring-boot-engineer/references/web.md +295 -295
- package/skills/javascript/javascript-pro/SKILL.md +132 -132
- package/skills/javascript/javascript-pro/references/async-patterns.md +334 -334
- package/skills/javascript/javascript-pro/references/browser-apis.md +398 -398
- package/skills/javascript/javascript-pro/references/modern-syntax.md +272 -272
- package/skills/javascript/javascript-pro/references/modules.md +357 -357
- package/skills/javascript/javascript-pro/references/node-essentials.md +471 -471
- package/skills/javascript/nestjs-expert/SKILL.md +206 -206
- package/skills/javascript/nestjs-expert/references/authentication.md +166 -166
- package/skills/javascript/nestjs-expert/references/controllers-routing.md +111 -111
- package/skills/javascript/nestjs-expert/references/dtos-validation.md +153 -153
- package/skills/javascript/nestjs-expert/references/migration-from-express.md +1237 -1237
- package/skills/javascript/nestjs-expert/references/services-di.md +140 -140
- package/skills/javascript/nestjs-expert/references/testing-patterns.md +186 -186
- package/skills/javascript/typescript-pro/SKILL.md +145 -145
- package/skills/javascript/typescript-pro/references/advanced-types.md +259 -259
- package/skills/javascript/typescript-pro/references/configuration.md +445 -445
- package/skills/javascript/typescript-pro/references/patterns.md +484 -484
- package/skills/javascript/typescript-pro/references/type-guards.md +352 -352
- package/skills/javascript/typescript-pro/references/utility-types.md +329 -329
- package/skills/php/laravel-specialist/SKILL.md +262 -262
- package/skills/php/laravel-specialist/references/eloquent.md +351 -351
- package/skills/php/laravel-specialist/references/livewire.md +512 -512
- package/skills/php/laravel-specialist/references/queues.md +423 -423
- package/skills/php/laravel-specialist/references/routing.md +362 -362
- package/skills/php/laravel-specialist/references/testing.md +522 -522
- package/skills/php/php-pro/SKILL.md +206 -206
- package/skills/php/php-pro/references/async-patterns.md +412 -412
- package/skills/php/php-pro/references/laravel-patterns.md +377 -377
- package/skills/php/php-pro/references/modern-php-features.md +323 -323
- package/skills/php/php-pro/references/symfony-patterns.md +466 -466
- package/skills/php/php-pro/references/testing-quality.md +466 -466
- package/skills/product/competitive-analysis/SKILL.md +257 -257
- package/skills/product/meeting-notes/SKILL.md +266 -266
- package/skills/product/prd-template/SKILL.md +150 -150
- package/skills/product/stakeholder-update/SKILL.md +225 -225
- package/skills/product/user-research-synthesis/SKILL.md +235 -235
- package/skills/python/django-expert/SKILL.md +162 -162
- package/skills/python/django-expert/references/authentication.md +145 -145
- package/skills/python/django-expert/references/drf-serializers.md +148 -148
- package/skills/python/django-expert/references/models-orm.md +151 -151
- package/skills/python/django-expert/references/testing-django.md +204 -204
- package/skills/python/django-expert/references/viewsets-views.md +153 -153
- package/skills/python/fastapi-expert/SKILL.md +185 -185
- package/skills/python/fastapi-expert/references/async-sqlalchemy.md +146 -146
- package/skills/python/fastapi-expert/references/authentication.md +159 -159
- package/skills/python/fastapi-expert/references/endpoints-routing.md +142 -142
- package/skills/python/fastapi-expert/references/migration-from-django.md +996 -996
- package/skills/python/fastapi-expert/references/pydantic-v2.md +135 -135
- package/skills/python/fastapi-expert/references/testing-async.md +159 -159
- package/skills/python/pandas-pro/SKILL.md +178 -178
- package/skills/python/pandas-pro/references/aggregation-groupby.md +545 -545
- package/skills/python/pandas-pro/references/data-cleaning.md +500 -500
- package/skills/python/pandas-pro/references/dataframe-operations.md +420 -420
- package/skills/python/pandas-pro/references/merging-joining.md +596 -596
- package/skills/python/pandas-pro/references/performance-optimization.md +597 -597
- package/skills/python/python-pro/SKILL.md +177 -177
- package/skills/python/python-pro/references/async-patterns.md +356 -356
- package/skills/python/python-pro/references/packaging.md +460 -460
- package/skills/python/python-pro/references/standard-library.md +378 -378
- package/skills/python/python-pro/references/testing.md +404 -404
- package/skills/python/python-pro/references/type-system.md +290 -290
- package/skills/quality/chaos-engineer/SKILL.md +182 -182
- package/skills/quality/chaos-engineer/references/chaos-tools.md +511 -511
- package/skills/quality/chaos-engineer/references/experiment-design.md +229 -229
- package/skills/quality/chaos-engineer/references/game-days.md +434 -434
- package/skills/quality/chaos-engineer/references/infrastructure-chaos.md +348 -348
- package/skills/quality/chaos-engineer/references/kubernetes-chaos.md +432 -432
- package/skills/quality/code-reviewer/SKILL.md +119 -119
- package/skills/quality/code-reviewer/references/common-issues.md +142 -142
- package/skills/quality/code-reviewer/references/feedback-examples.md +144 -144
- package/skills/quality/code-reviewer/references/receiving-feedback.md +238 -238
- package/skills/quality/code-reviewer/references/report-template.md +109 -109
- package/skills/quality/code-reviewer/references/review-checklist.md +88 -88
- package/skills/quality/code-reviewer/references/spec-compliance-review.md +258 -258
- package/skills/quality/playwright-expert/SKILL.md +169 -169
- package/skills/quality/playwright-expert/references/api-mocking.md +140 -140
- package/skills/quality/playwright-expert/references/configuration.md +155 -155
- package/skills/quality/playwright-expert/references/debugging-flaky.md +150 -150
- package/skills/quality/playwright-expert/references/page-object-model.md +152 -152
- package/skills/quality/playwright-expert/references/selectors-locators.md +119 -119
- package/skills/quality/secure-code-guardian/SKILL.md +191 -191
- package/skills/quality/secure-code-guardian/references/authentication.md +136 -136
- package/skills/quality/secure-code-guardian/references/input-validation.md +146 -146
- package/skills/quality/secure-code-guardian/references/owasp-prevention.md +135 -135
- package/skills/quality/secure-code-guardian/references/security-headers.md +133 -133
- package/skills/quality/secure-code-guardian/references/xss-csrf.md +157 -157
- package/skills/quality/security-reviewer/SKILL.md +103 -103
- package/skills/quality/security-reviewer/references/infrastructure-security.md +268 -268
- package/skills/quality/security-reviewer/references/penetration-testing.md +268 -268
- package/skills/quality/security-reviewer/references/report-template.md +170 -170
- package/skills/quality/security-reviewer/references/sast-tools.md +117 -117
- package/skills/quality/security-reviewer/references/secret-scanning.md +125 -125
- package/skills/quality/security-reviewer/references/vulnerability-patterns.md +152 -152
- package/skills/quality/senior-qa/README.md +196 -196
- package/skills/quality/senior-qa/SKILL.md +399 -399
- package/skills/quality/senior-qa/references/qa_best_practices.md +964 -964
- package/skills/quality/senior-qa/references/test_automation_patterns.md +1009 -1009
- package/skills/quality/senior-qa/references/testing_strategies.md +649 -649
- package/skills/quality/senior-qa/scripts/coverage_analyzer.py +836 -836
- package/skills/quality/senior-qa/scripts/e2e_test_scaffolder.py +820 -820
- package/skills/quality/senior-qa/scripts/test_suite_generator.py +605 -605
- package/skills/quality/tdd-guide/HOW_TO_USE.md +313 -313
- package/skills/quality/tdd-guide/README.md +680 -680
- package/skills/quality/tdd-guide/SKILL.md +122 -122
- package/skills/quality/tdd-guide/assets/expected_output.json +77 -77
- package/skills/quality/tdd-guide/assets/sample_input_python.json +39 -39
- package/skills/quality/tdd-guide/assets/sample_input_typescript.json +36 -36
- package/skills/quality/tdd-guide/references/ci-integration.md +195 -195
- package/skills/quality/tdd-guide/references/framework-guide.md +206 -206
- package/skills/quality/tdd-guide/references/tdd-best-practices.md +128 -128
- package/skills/quality/tdd-guide/scripts/coverage_analyzer.py +434 -434
- package/skills/quality/tdd-guide/scripts/fixture_generator.py +440 -440
- package/skills/quality/tdd-guide/scripts/format_detector.py +384 -384
- package/skills/quality/tdd-guide/scripts/framework_adapter.py +428 -428
- package/skills/quality/tdd-guide/scripts/metrics_calculator.py +456 -456
- package/skills/quality/tdd-guide/scripts/output_formatter.py +354 -354
- package/skills/quality/tdd-guide/scripts/tdd_workflow.py +474 -474
- package/skills/quality/tdd-guide/scripts/test_generator.py +438 -438
- package/skills/quality/test-master/SKILL.md +94 -94
- package/skills/quality/test-master/references/automation-frameworks.md +294 -294
- package/skills/quality/test-master/references/e2e-testing.md +128 -128
- package/skills/quality/test-master/references/integration-testing.md +120 -120
- package/skills/quality/test-master/references/performance-testing.md +118 -118
- package/skills/quality/test-master/references/qa-methodology.md +247 -247
- package/skills/quality/test-master/references/security-testing.md +127 -127
- package/skills/quality/test-master/references/tdd-iron-laws.md +174 -174
- package/skills/quality/test-master/references/test-reports.md +104 -104
- package/skills/quality/test-master/references/testing-anti-patterns.md +231 -231
- package/skills/quality/test-master/references/unit-testing.md +113 -113
- package/skills/ruby/rails-expert/SKILL.md +154 -154
- package/skills/ruby/rails-expert/references/active-record.md +244 -244
- package/skills/ruby/rails-expert/references/api-development.md +401 -401
- package/skills/ruby/rails-expert/references/background-jobs.md +272 -272
- package/skills/ruby/rails-expert/references/hotwire-turbo.md +228 -228
- package/skills/ruby/rails-expert/references/rspec-testing.md +367 -367
- package/skills/swift/swift-expert/SKILL.md +163 -163
- package/skills/swift/swift-expert/references/async-concurrency.md +360 -360
- package/skills/swift/swift-expert/references/memory-performance.md +377 -377
- package/skills/swift/swift-expert/references/protocol-oriented.md +354 -354
- package/skills/swift/swift-expert/references/swiftui-patterns.md +291 -291
- package/skills/swift/swift-expert/references/testing-patterns.md +399 -399
- package/skills/workflow/brainstorming/SKILL.md +164 -164
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -214
- package/skills/workflow/brainstorming/scripts/helper.js +88 -88
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -354
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -148
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -56
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -49
- package/skills/workflow/brainstorming/visual-companion.md +287 -287
- package/skills/workflow/documentation/SKILL.md +45 -45
- package/skills/workflow/entropy-management/SKILL.md +115 -115
- package/skills/workflow/executing-plans/SKILL.md +70 -70
- package/skills/workflow/finishing-a-development-branch/SKILL.md +200 -200
- package/skills/workflow/receiving-code-review/SKILL.md +213 -213
- package/skills/workflow/requesting-code-review/SKILL.md +105 -105
- package/skills/workflow/requesting-code-review/code-reviewer.md +146 -146
- package/skills/workflow/requirement-engineering/SKILL.md +111 -111
- package/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/workflow/systematic-debugging/SKILL.md +296 -296
- package/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/workflow/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/workflow/systematic-debugging/find-polluter.sh +63 -63
- package/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/workflow/systematic-debugging/test-academic.md +14 -14
- package/skills/workflow/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/workflow/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/workflow/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/workflow/using-git-worktrees/SKILL.md +218 -218
- package/skills/workflow/verification-before-completion/SKILL.md +139 -139
- package/skills/workflow/writing-plans/SKILL.md +151 -151
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -49
- package/skills/workflow/writing-skills/SKILL.md +655 -655
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -1150
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -187
- package/skills/workflow/writing-skills/render-graphs.js +168 -168
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -384
|
@@ -1,546 +1,546 @@
|
|
|
1
|
-
# Authentication & Authorization
|
|
2
|
-
|
|
3
|
-
## JWT Authentication Setup
|
|
4
|
-
|
|
5
|
-
```csharp
|
|
6
|
-
// Domain/Entities/User.cs
|
|
7
|
-
namespace Domain.Entities;
|
|
8
|
-
|
|
9
|
-
public class User
|
|
10
|
-
{
|
|
11
|
-
public int Id { get; private set; }
|
|
12
|
-
public string Email { get; private set; } = string.Empty;
|
|
13
|
-
public string PasswordHash { get; private set; } = string.Empty;
|
|
14
|
-
public string FirstName { get; private set; } = string.Empty;
|
|
15
|
-
public string LastName { get; private set; } = string.Empty;
|
|
16
|
-
public List<string> Roles { get; private set; } = new();
|
|
17
|
-
public DateTime CreatedAt { get; private set; }
|
|
18
|
-
public bool IsActive { get; private set; } = true;
|
|
19
|
-
|
|
20
|
-
private User() { } // EF Core
|
|
21
|
-
|
|
22
|
-
public static User Create(string email, string passwordHash, string firstName, string lastName)
|
|
23
|
-
{
|
|
24
|
-
return new User
|
|
25
|
-
{
|
|
26
|
-
Email = email,
|
|
27
|
-
PasswordHash = passwordHash,
|
|
28
|
-
FirstName = firstName,
|
|
29
|
-
LastName = lastName,
|
|
30
|
-
Roles = new List<string> { "User" },
|
|
31
|
-
CreatedAt = DateTime.UtcNow
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public void AddRole(string role)
|
|
36
|
-
{
|
|
37
|
-
if (!Roles.Contains(role))
|
|
38
|
-
{
|
|
39
|
-
Roles.Add(role);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## JWT Service
|
|
46
|
-
|
|
47
|
-
```csharp
|
|
48
|
-
// Application/Common/Interfaces/IJwtService.cs
|
|
49
|
-
namespace Application.Common.Interfaces;
|
|
50
|
-
|
|
51
|
-
public interface IJwtService
|
|
52
|
-
{
|
|
53
|
-
string GenerateToken(int userId, string email, List<string> roles);
|
|
54
|
-
int? ValidateToken(string token);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Infrastructure/Services/JwtService.cs
|
|
58
|
-
using System.IdentityModel.Tokens.Jwt;
|
|
59
|
-
using System.Security.Claims;
|
|
60
|
-
using System.Text;
|
|
61
|
-
using Microsoft.Extensions.Options;
|
|
62
|
-
using Microsoft.IdentityModel.Tokens;
|
|
63
|
-
|
|
64
|
-
namespace Infrastructure.Services;
|
|
65
|
-
|
|
66
|
-
public class JwtSettings
|
|
67
|
-
{
|
|
68
|
-
public string Secret { get; init; } = string.Empty;
|
|
69
|
-
public string Issuer { get; init; } = string.Empty;
|
|
70
|
-
public string Audience { get; init; } = string.Empty;
|
|
71
|
-
public int ExpirationMinutes { get; init; } = 60;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public class JwtService : IJwtService
|
|
75
|
-
{
|
|
76
|
-
private readonly JwtSettings _settings;
|
|
77
|
-
|
|
78
|
-
public JwtService(IOptions<JwtSettings> settings)
|
|
79
|
-
{
|
|
80
|
-
_settings = settings.Value;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
public string GenerateToken(int userId, string email, List<string> roles)
|
|
84
|
-
{
|
|
85
|
-
var claims = new List<Claim>
|
|
86
|
-
{
|
|
87
|
-
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
|
88
|
-
new(JwtRegisteredClaimNames.Email, email),
|
|
89
|
-
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
|
93
|
-
|
|
94
|
-
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Secret));
|
|
95
|
-
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
96
|
-
|
|
97
|
-
var token = new JwtSecurityToken(
|
|
98
|
-
issuer: _settings.Issuer,
|
|
99
|
-
audience: _settings.Audience,
|
|
100
|
-
claims: claims,
|
|
101
|
-
expires: DateTime.UtcNow.AddMinutes(_settings.ExpirationMinutes),
|
|
102
|
-
signingCredentials: credentials
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public int? ValidateToken(string token)
|
|
109
|
-
{
|
|
110
|
-
var tokenHandler = new JwtSecurityTokenHandler();
|
|
111
|
-
var key = Encoding.UTF8.GetBytes(_settings.Secret);
|
|
112
|
-
|
|
113
|
-
try
|
|
114
|
-
{
|
|
115
|
-
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
|
116
|
-
{
|
|
117
|
-
ValidateIssuerSigningKey = true,
|
|
118
|
-
IssuerSigningKey = new SymmetricSecurityKey(key),
|
|
119
|
-
ValidateIssuer = true,
|
|
120
|
-
ValidIssuer = _settings.Issuer,
|
|
121
|
-
ValidateAudience = true,
|
|
122
|
-
ValidAudience = _settings.Audience,
|
|
123
|
-
ClockSkew = TimeSpan.Zero
|
|
124
|
-
}, out SecurityToken validatedToken);
|
|
125
|
-
|
|
126
|
-
var jwtToken = (JwtSecurityToken)validatedToken;
|
|
127
|
-
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value);
|
|
128
|
-
|
|
129
|
-
return userId;
|
|
130
|
-
}
|
|
131
|
-
catch
|
|
132
|
-
{
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Password Hashing
|
|
140
|
-
|
|
141
|
-
```csharp
|
|
142
|
-
// Application/Common/Interfaces/IPasswordHasher.cs
|
|
143
|
-
namespace Application.Common.Interfaces;
|
|
144
|
-
|
|
145
|
-
public interface IPasswordHasher
|
|
146
|
-
{
|
|
147
|
-
string HashPassword(string password);
|
|
148
|
-
bool VerifyPassword(string password, string hash);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Infrastructure/Services/PasswordHasher.cs
|
|
152
|
-
using System.Security.Cryptography;
|
|
153
|
-
|
|
154
|
-
namespace Infrastructure.Services;
|
|
155
|
-
|
|
156
|
-
public class PasswordHasher : IPasswordHasher
|
|
157
|
-
{
|
|
158
|
-
private const int SaltSize = 16;
|
|
159
|
-
private const int HashSize = 32;
|
|
160
|
-
private const int Iterations = 100000;
|
|
161
|
-
|
|
162
|
-
public string HashPassword(string password)
|
|
163
|
-
{
|
|
164
|
-
using var rng = RandomNumberGenerator.Create();
|
|
165
|
-
var salt = new byte[SaltSize];
|
|
166
|
-
rng.GetBytes(salt);
|
|
167
|
-
|
|
168
|
-
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
169
|
-
password,
|
|
170
|
-
salt,
|
|
171
|
-
Iterations,
|
|
172
|
-
HashAlgorithmName.SHA256);
|
|
173
|
-
|
|
174
|
-
var hash = pbkdf2.GetBytes(HashSize);
|
|
175
|
-
|
|
176
|
-
var hashBytes = new byte[SaltSize + HashSize];
|
|
177
|
-
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
|
|
178
|
-
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
|
|
179
|
-
|
|
180
|
-
return Convert.ToBase64String(hashBytes);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
public bool VerifyPassword(string password, string hash)
|
|
184
|
-
{
|
|
185
|
-
var hashBytes = Convert.FromBase64String(hash);
|
|
186
|
-
|
|
187
|
-
var salt = new byte[SaltSize];
|
|
188
|
-
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
|
|
189
|
-
|
|
190
|
-
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
191
|
-
password,
|
|
192
|
-
salt,
|
|
193
|
-
Iterations,
|
|
194
|
-
HashAlgorithmName.SHA256);
|
|
195
|
-
|
|
196
|
-
var testHash = pbkdf2.GetBytes(HashSize);
|
|
197
|
-
|
|
198
|
-
for (int i = 0; i < HashSize; i++)
|
|
199
|
-
{
|
|
200
|
-
if (hashBytes[i + SaltSize] != testHash[i])
|
|
201
|
-
{
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Authentication Commands
|
|
212
|
-
|
|
213
|
-
```csharp
|
|
214
|
-
// Application/Auth/Commands/Register/RegisterCommand.cs
|
|
215
|
-
using MediatR;
|
|
216
|
-
|
|
217
|
-
namespace Application.Auth.Commands.Register;
|
|
218
|
-
|
|
219
|
-
public record RegisterCommand(
|
|
220
|
-
string Email,
|
|
221
|
-
string Password,
|
|
222
|
-
string FirstName,
|
|
223
|
-
string LastName
|
|
224
|
-
) : IRequest<AuthResponse>;
|
|
225
|
-
|
|
226
|
-
// Application/Auth/Commands/Register/RegisterCommandHandler.cs
|
|
227
|
-
using Application.Common.Interfaces;
|
|
228
|
-
using Domain.Entities;
|
|
229
|
-
using MediatR;
|
|
230
|
-
using Microsoft.EntityFrameworkCore;
|
|
231
|
-
|
|
232
|
-
namespace Application.Auth.Commands.Register;
|
|
233
|
-
|
|
234
|
-
public class RegisterCommandHandler : IRequestHandler<RegisterCommand, AuthResponse>
|
|
235
|
-
{
|
|
236
|
-
private readonly IApplicationDbContext _context;
|
|
237
|
-
private readonly IPasswordHasher _passwordHasher;
|
|
238
|
-
private readonly IJwtService _jwtService;
|
|
239
|
-
|
|
240
|
-
public RegisterCommandHandler(
|
|
241
|
-
IApplicationDbContext context,
|
|
242
|
-
IPasswordHasher passwordHasher,
|
|
243
|
-
IJwtService jwtService)
|
|
244
|
-
{
|
|
245
|
-
_context = context;
|
|
246
|
-
_passwordHasher = passwordHasher;
|
|
247
|
-
_jwtService = jwtService;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
public async Task<AuthResponse> Handle(
|
|
251
|
-
RegisterCommand request,
|
|
252
|
-
CancellationToken cancellationToken)
|
|
253
|
-
{
|
|
254
|
-
var existingUser = await _context.Users
|
|
255
|
-
.FirstOrDefaultAsync(u => u.Email == request.Email, cancellationToken);
|
|
256
|
-
|
|
257
|
-
if (existingUser is not null)
|
|
258
|
-
{
|
|
259
|
-
throw new ValidationException("Email already registered");
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
var passwordHash = _passwordHasher.HashPassword(request.Password);
|
|
263
|
-
|
|
264
|
-
var user = User.Create(
|
|
265
|
-
request.Email,
|
|
266
|
-
passwordHash,
|
|
267
|
-
request.FirstName,
|
|
268
|
-
request.LastName);
|
|
269
|
-
|
|
270
|
-
_context.Users.Add(user);
|
|
271
|
-
await _context.SaveChangesAsync(cancellationToken);
|
|
272
|
-
|
|
273
|
-
var token = _jwtService.GenerateToken(user.Id, user.Email, user.Roles);
|
|
274
|
-
|
|
275
|
-
return new AuthResponse(token, user.Email, user.FirstName, user.LastName);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Application/Auth/Commands/Login/LoginCommand.cs
|
|
280
|
-
public record LoginCommand(
|
|
281
|
-
string Email,
|
|
282
|
-
string Password
|
|
283
|
-
) : IRequest<AuthResponse>;
|
|
284
|
-
|
|
285
|
-
// Application/Auth/Commands/Login/LoginCommandHandler.cs
|
|
286
|
-
public class LoginCommandHandler : IRequestHandler<LoginCommand, AuthResponse>
|
|
287
|
-
{
|
|
288
|
-
private readonly IApplicationDbContext _context;
|
|
289
|
-
private readonly IPasswordHasher _passwordHasher;
|
|
290
|
-
private readonly IJwtService _jwtService;
|
|
291
|
-
|
|
292
|
-
public LoginCommandHandler(
|
|
293
|
-
IApplicationDbContext context,
|
|
294
|
-
IPasswordHasher passwordHasher,
|
|
295
|
-
IJwtService jwtService)
|
|
296
|
-
{
|
|
297
|
-
_context = context;
|
|
298
|
-
_passwordHasher = passwordHasher;
|
|
299
|
-
_jwtService = jwtService;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
public async Task<AuthResponse> Handle(
|
|
303
|
-
LoginCommand request,
|
|
304
|
-
CancellationToken cancellationToken)
|
|
305
|
-
{
|
|
306
|
-
var user = await _context.Users
|
|
307
|
-
.FirstOrDefaultAsync(u => u.Email == request.Email, cancellationToken);
|
|
308
|
-
|
|
309
|
-
if (user is null || !_passwordHasher.VerifyPassword(request.Password, user.PasswordHash))
|
|
310
|
-
{
|
|
311
|
-
throw new UnauthorizedException("Invalid credentials");
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (!user.IsActive)
|
|
315
|
-
{
|
|
316
|
-
throw new UnauthorizedException("Account is inactive");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
var token = _jwtService.GenerateToken(user.Id, user.Email, user.Roles);
|
|
320
|
-
|
|
321
|
-
return new AuthResponse(token, user.Email, user.FirstName, user.LastName);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
public record AuthResponse(string Token, string Email, string FirstName, string LastName);
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## Configure Authentication in Program.cs
|
|
329
|
-
|
|
330
|
-
```csharp
|
|
331
|
-
using System.Text;
|
|
332
|
-
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
333
|
-
using Microsoft.IdentityModel.Tokens;
|
|
334
|
-
|
|
335
|
-
var builder = WebApplication.CreateBuilder(args);
|
|
336
|
-
|
|
337
|
-
// Configure JWT settings
|
|
338
|
-
builder.Services.Configure<JwtSettings>(
|
|
339
|
-
builder.Configuration.GetSection("JwtSettings"));
|
|
340
|
-
|
|
341
|
-
var jwtSettings = builder.Configuration
|
|
342
|
-
.GetSection("JwtSettings")
|
|
343
|
-
.Get<JwtSettings>()!;
|
|
344
|
-
|
|
345
|
-
// Add authentication
|
|
346
|
-
builder.Services.AddAuthentication(options =>
|
|
347
|
-
{
|
|
348
|
-
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
349
|
-
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
350
|
-
})
|
|
351
|
-
.AddJwtBearer(options =>
|
|
352
|
-
{
|
|
353
|
-
options.TokenValidationParameters = new TokenValidationParameters
|
|
354
|
-
{
|
|
355
|
-
ValidateIssuerSigningKey = true,
|
|
356
|
-
IssuerSigningKey = new SymmetricSecurityKey(
|
|
357
|
-
Encoding.UTF8.GetBytes(jwtSettings.Secret)),
|
|
358
|
-
ValidateIssuer = true,
|
|
359
|
-
ValidIssuer = jwtSettings.Issuer,
|
|
360
|
-
ValidateAudience = true,
|
|
361
|
-
ValidAudience = jwtSettings.Audience,
|
|
362
|
-
ValidateLifetime = true,
|
|
363
|
-
ClockSkew = TimeSpan.Zero
|
|
364
|
-
};
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
builder.Services.AddAuthorization();
|
|
368
|
-
|
|
369
|
-
var app = builder.Build();
|
|
370
|
-
|
|
371
|
-
app.UseAuthentication();
|
|
372
|
-
app.UseAuthorization();
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
## Authorization Policies
|
|
376
|
-
|
|
377
|
-
```csharp
|
|
378
|
-
// Configure policies
|
|
379
|
-
builder.Services.AddAuthorization(options =>
|
|
380
|
-
{
|
|
381
|
-
options.AddPolicy("AdminOnly", policy =>
|
|
382
|
-
policy.RequireRole("Admin"));
|
|
383
|
-
|
|
384
|
-
options.AddPolicy("UserOrAdmin", policy =>
|
|
385
|
-
policy.RequireRole("User", "Admin"));
|
|
386
|
-
|
|
387
|
-
options.AddPolicy("RequireEmailVerified", policy =>
|
|
388
|
-
policy.RequireClaim("email_verified", "true"));
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Apply to endpoints
|
|
392
|
-
app.MapGet("/api/admin/users", GetAllUsers)
|
|
393
|
-
.RequireAuthorization("AdminOnly");
|
|
394
|
-
|
|
395
|
-
app.MapGet("/api/profile", GetProfile)
|
|
396
|
-
.RequireAuthorization();
|
|
397
|
-
|
|
398
|
-
app.MapPost("/api/products", CreateProduct)
|
|
399
|
-
.RequireAuthorization("AdminOnly");
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
## Current User Service
|
|
403
|
-
|
|
404
|
-
```csharp
|
|
405
|
-
// Application/Common/Interfaces/ICurrentUserService.cs
|
|
406
|
-
namespace Application.Common.Interfaces;
|
|
407
|
-
|
|
408
|
-
public interface ICurrentUserService
|
|
409
|
-
{
|
|
410
|
-
int? UserId { get; }
|
|
411
|
-
string? Email { get; }
|
|
412
|
-
bool IsAuthenticated { get; }
|
|
413
|
-
bool IsInRole(string role);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Infrastructure/Services/CurrentUserService.cs
|
|
417
|
-
using System.Security.Claims;
|
|
418
|
-
using Microsoft.AspNetCore.Http;
|
|
419
|
-
|
|
420
|
-
namespace Infrastructure.Services;
|
|
421
|
-
|
|
422
|
-
public class CurrentUserService : ICurrentUserService
|
|
423
|
-
{
|
|
424
|
-
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
425
|
-
|
|
426
|
-
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
|
|
427
|
-
{
|
|
428
|
-
_httpContextAccessor = httpContextAccessor;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
public int? UserId
|
|
432
|
-
{
|
|
433
|
-
get
|
|
434
|
-
{
|
|
435
|
-
var userIdClaim = _httpContextAccessor.HttpContext?.User?
|
|
436
|
-
.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
437
|
-
|
|
438
|
-
return int.TryParse(userIdClaim, out var userId) ? userId : null;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
public string? Email =>
|
|
443
|
-
_httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Email);
|
|
444
|
-
|
|
445
|
-
public bool IsAuthenticated =>
|
|
446
|
-
_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
|
447
|
-
|
|
448
|
-
public bool IsInRole(string role) =>
|
|
449
|
-
_httpContextAccessor.HttpContext?.User?.IsInRole(role) ?? false;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Register service
|
|
453
|
-
builder.Services.AddHttpContextAccessor();
|
|
454
|
-
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
## Auth Endpoints
|
|
458
|
-
|
|
459
|
-
```csharp
|
|
460
|
-
// WebApi/Endpoints/AuthEndpoints.cs
|
|
461
|
-
using Application.Auth.Commands.Login;
|
|
462
|
-
using Application.Auth.Commands.Register;
|
|
463
|
-
using MediatR;
|
|
464
|
-
|
|
465
|
-
namespace WebApi.Endpoints;
|
|
466
|
-
|
|
467
|
-
public static class AuthEndpoints
|
|
468
|
-
{
|
|
469
|
-
public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder app)
|
|
470
|
-
{
|
|
471
|
-
var group = app.MapGroup("/api/auth")
|
|
472
|
-
.WithTags("Authentication")
|
|
473
|
-
.WithOpenApi();
|
|
474
|
-
|
|
475
|
-
group.MapPost("/register", async (
|
|
476
|
-
RegisterCommand command,
|
|
477
|
-
ISender sender) =>
|
|
478
|
-
{
|
|
479
|
-
var response = await sender.Send(command);
|
|
480
|
-
return Results.Ok(response);
|
|
481
|
-
})
|
|
482
|
-
.AllowAnonymous();
|
|
483
|
-
|
|
484
|
-
group.MapPost("/login", async (
|
|
485
|
-
LoginCommand command,
|
|
486
|
-
ISender sender) =>
|
|
487
|
-
{
|
|
488
|
-
var response = await sender.Send(command);
|
|
489
|
-
return Results.Ok(response);
|
|
490
|
-
})
|
|
491
|
-
.AllowAnonymous();
|
|
492
|
-
|
|
493
|
-
group.MapGet("/me", async (
|
|
494
|
-
ICurrentUserService currentUser,
|
|
495
|
-
IApplicationDbContext context) =>
|
|
496
|
-
{
|
|
497
|
-
if (currentUser.UserId is null)
|
|
498
|
-
{
|
|
499
|
-
return Results.Unauthorized();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
var user = await context.Users
|
|
503
|
-
.FindAsync(currentUser.UserId.Value);
|
|
504
|
-
|
|
505
|
-
return user is not null
|
|
506
|
-
? Results.Ok(new
|
|
507
|
-
{
|
|
508
|
-
user.Email,
|
|
509
|
-
user.FirstName,
|
|
510
|
-
user.LastName,
|
|
511
|
-
user.Roles
|
|
512
|
-
})
|
|
513
|
-
: Results.NotFound();
|
|
514
|
-
})
|
|
515
|
-
.RequireAuthorization();
|
|
516
|
-
|
|
517
|
-
return app;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
## appsettings.json
|
|
523
|
-
|
|
524
|
-
```json
|
|
525
|
-
{
|
|
526
|
-
"JwtSettings": {
|
|
527
|
-
"Secret": "your-super-secret-key-minimum-32-characters",
|
|
528
|
-
"Issuer": "YourApp",
|
|
529
|
-
"Audience": "YourAppUsers",
|
|
530
|
-
"ExpirationMinutes": 60
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
## Quick Reference
|
|
536
|
-
|
|
537
|
-
| Pattern | Usage |
|
|
538
|
-
|---------|-------|
|
|
539
|
-
| `RequireAuthorization()` | Endpoint requires authentication |
|
|
540
|
-
| `RequireAuthorization("Policy")` | Endpoint requires specific policy |
|
|
541
|
-
| `AllowAnonymous()` | Allow unauthenticated access |
|
|
542
|
-
| `RequireRole("Admin")` | Require specific role |
|
|
543
|
-
| JWT Bearer | Token-based authentication |
|
|
544
|
-
| `ICurrentUserService` | Access current user info |
|
|
545
|
-
| `IPasswordHasher` | Hash and verify passwords |
|
|
546
|
-
| `IJwtService` | Generate and validate tokens |
|
|
1
|
+
# Authentication & Authorization
|
|
2
|
+
|
|
3
|
+
## JWT Authentication Setup
|
|
4
|
+
|
|
5
|
+
```csharp
|
|
6
|
+
// Domain/Entities/User.cs
|
|
7
|
+
namespace Domain.Entities;
|
|
8
|
+
|
|
9
|
+
public class User
|
|
10
|
+
{
|
|
11
|
+
public int Id { get; private set; }
|
|
12
|
+
public string Email { get; private set; } = string.Empty;
|
|
13
|
+
public string PasswordHash { get; private set; } = string.Empty;
|
|
14
|
+
public string FirstName { get; private set; } = string.Empty;
|
|
15
|
+
public string LastName { get; private set; } = string.Empty;
|
|
16
|
+
public List<string> Roles { get; private set; } = new();
|
|
17
|
+
public DateTime CreatedAt { get; private set; }
|
|
18
|
+
public bool IsActive { get; private set; } = true;
|
|
19
|
+
|
|
20
|
+
private User() { } // EF Core
|
|
21
|
+
|
|
22
|
+
public static User Create(string email, string passwordHash, string firstName, string lastName)
|
|
23
|
+
{
|
|
24
|
+
return new User
|
|
25
|
+
{
|
|
26
|
+
Email = email,
|
|
27
|
+
PasswordHash = passwordHash,
|
|
28
|
+
FirstName = firstName,
|
|
29
|
+
LastName = lastName,
|
|
30
|
+
Roles = new List<string> { "User" },
|
|
31
|
+
CreatedAt = DateTime.UtcNow
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public void AddRole(string role)
|
|
36
|
+
{
|
|
37
|
+
if (!Roles.Contains(role))
|
|
38
|
+
{
|
|
39
|
+
Roles.Add(role);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## JWT Service
|
|
46
|
+
|
|
47
|
+
```csharp
|
|
48
|
+
// Application/Common/Interfaces/IJwtService.cs
|
|
49
|
+
namespace Application.Common.Interfaces;
|
|
50
|
+
|
|
51
|
+
public interface IJwtService
|
|
52
|
+
{
|
|
53
|
+
string GenerateToken(int userId, string email, List<string> roles);
|
|
54
|
+
int? ValidateToken(string token);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Infrastructure/Services/JwtService.cs
|
|
58
|
+
using System.IdentityModel.Tokens.Jwt;
|
|
59
|
+
using System.Security.Claims;
|
|
60
|
+
using System.Text;
|
|
61
|
+
using Microsoft.Extensions.Options;
|
|
62
|
+
using Microsoft.IdentityModel.Tokens;
|
|
63
|
+
|
|
64
|
+
namespace Infrastructure.Services;
|
|
65
|
+
|
|
66
|
+
public class JwtSettings
|
|
67
|
+
{
|
|
68
|
+
public string Secret { get; init; } = string.Empty;
|
|
69
|
+
public string Issuer { get; init; } = string.Empty;
|
|
70
|
+
public string Audience { get; init; } = string.Empty;
|
|
71
|
+
public int ExpirationMinutes { get; init; } = 60;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public class JwtService : IJwtService
|
|
75
|
+
{
|
|
76
|
+
private readonly JwtSettings _settings;
|
|
77
|
+
|
|
78
|
+
public JwtService(IOptions<JwtSettings> settings)
|
|
79
|
+
{
|
|
80
|
+
_settings = settings.Value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public string GenerateToken(int userId, string email, List<string> roles)
|
|
84
|
+
{
|
|
85
|
+
var claims = new List<Claim>
|
|
86
|
+
{
|
|
87
|
+
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
|
88
|
+
new(JwtRegisteredClaimNames.Email, email),
|
|
89
|
+
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
|
93
|
+
|
|
94
|
+
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Secret));
|
|
95
|
+
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
96
|
+
|
|
97
|
+
var token = new JwtSecurityToken(
|
|
98
|
+
issuer: _settings.Issuer,
|
|
99
|
+
audience: _settings.Audience,
|
|
100
|
+
claims: claims,
|
|
101
|
+
expires: DateTime.UtcNow.AddMinutes(_settings.ExpirationMinutes),
|
|
102
|
+
signingCredentials: credentials
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public int? ValidateToken(string token)
|
|
109
|
+
{
|
|
110
|
+
var tokenHandler = new JwtSecurityTokenHandler();
|
|
111
|
+
var key = Encoding.UTF8.GetBytes(_settings.Secret);
|
|
112
|
+
|
|
113
|
+
try
|
|
114
|
+
{
|
|
115
|
+
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
|
116
|
+
{
|
|
117
|
+
ValidateIssuerSigningKey = true,
|
|
118
|
+
IssuerSigningKey = new SymmetricSecurityKey(key),
|
|
119
|
+
ValidateIssuer = true,
|
|
120
|
+
ValidIssuer = _settings.Issuer,
|
|
121
|
+
ValidateAudience = true,
|
|
122
|
+
ValidAudience = _settings.Audience,
|
|
123
|
+
ClockSkew = TimeSpan.Zero
|
|
124
|
+
}, out SecurityToken validatedToken);
|
|
125
|
+
|
|
126
|
+
var jwtToken = (JwtSecurityToken)validatedToken;
|
|
127
|
+
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value);
|
|
128
|
+
|
|
129
|
+
return userId;
|
|
130
|
+
}
|
|
131
|
+
catch
|
|
132
|
+
{
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Password Hashing
|
|
140
|
+
|
|
141
|
+
```csharp
|
|
142
|
+
// Application/Common/Interfaces/IPasswordHasher.cs
|
|
143
|
+
namespace Application.Common.Interfaces;
|
|
144
|
+
|
|
145
|
+
public interface IPasswordHasher
|
|
146
|
+
{
|
|
147
|
+
string HashPassword(string password);
|
|
148
|
+
bool VerifyPassword(string password, string hash);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Infrastructure/Services/PasswordHasher.cs
|
|
152
|
+
using System.Security.Cryptography;
|
|
153
|
+
|
|
154
|
+
namespace Infrastructure.Services;
|
|
155
|
+
|
|
156
|
+
public class PasswordHasher : IPasswordHasher
|
|
157
|
+
{
|
|
158
|
+
private const int SaltSize = 16;
|
|
159
|
+
private const int HashSize = 32;
|
|
160
|
+
private const int Iterations = 100000;
|
|
161
|
+
|
|
162
|
+
public string HashPassword(string password)
|
|
163
|
+
{
|
|
164
|
+
using var rng = RandomNumberGenerator.Create();
|
|
165
|
+
var salt = new byte[SaltSize];
|
|
166
|
+
rng.GetBytes(salt);
|
|
167
|
+
|
|
168
|
+
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
169
|
+
password,
|
|
170
|
+
salt,
|
|
171
|
+
Iterations,
|
|
172
|
+
HashAlgorithmName.SHA256);
|
|
173
|
+
|
|
174
|
+
var hash = pbkdf2.GetBytes(HashSize);
|
|
175
|
+
|
|
176
|
+
var hashBytes = new byte[SaltSize + HashSize];
|
|
177
|
+
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
|
|
178
|
+
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
|
|
179
|
+
|
|
180
|
+
return Convert.ToBase64String(hashBytes);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public bool VerifyPassword(string password, string hash)
|
|
184
|
+
{
|
|
185
|
+
var hashBytes = Convert.FromBase64String(hash);
|
|
186
|
+
|
|
187
|
+
var salt = new byte[SaltSize];
|
|
188
|
+
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
|
|
189
|
+
|
|
190
|
+
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
191
|
+
password,
|
|
192
|
+
salt,
|
|
193
|
+
Iterations,
|
|
194
|
+
HashAlgorithmName.SHA256);
|
|
195
|
+
|
|
196
|
+
var testHash = pbkdf2.GetBytes(HashSize);
|
|
197
|
+
|
|
198
|
+
for (int i = 0; i < HashSize; i++)
|
|
199
|
+
{
|
|
200
|
+
if (hashBytes[i + SaltSize] != testHash[i])
|
|
201
|
+
{
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Authentication Commands
|
|
212
|
+
|
|
213
|
+
```csharp
|
|
214
|
+
// Application/Auth/Commands/Register/RegisterCommand.cs
|
|
215
|
+
using MediatR;
|
|
216
|
+
|
|
217
|
+
namespace Application.Auth.Commands.Register;
|
|
218
|
+
|
|
219
|
+
public record RegisterCommand(
|
|
220
|
+
string Email,
|
|
221
|
+
string Password,
|
|
222
|
+
string FirstName,
|
|
223
|
+
string LastName
|
|
224
|
+
) : IRequest<AuthResponse>;
|
|
225
|
+
|
|
226
|
+
// Application/Auth/Commands/Register/RegisterCommandHandler.cs
|
|
227
|
+
using Application.Common.Interfaces;
|
|
228
|
+
using Domain.Entities;
|
|
229
|
+
using MediatR;
|
|
230
|
+
using Microsoft.EntityFrameworkCore;
|
|
231
|
+
|
|
232
|
+
namespace Application.Auth.Commands.Register;
|
|
233
|
+
|
|
234
|
+
public class RegisterCommandHandler : IRequestHandler<RegisterCommand, AuthResponse>
|
|
235
|
+
{
|
|
236
|
+
private readonly IApplicationDbContext _context;
|
|
237
|
+
private readonly IPasswordHasher _passwordHasher;
|
|
238
|
+
private readonly IJwtService _jwtService;
|
|
239
|
+
|
|
240
|
+
public RegisterCommandHandler(
|
|
241
|
+
IApplicationDbContext context,
|
|
242
|
+
IPasswordHasher passwordHasher,
|
|
243
|
+
IJwtService jwtService)
|
|
244
|
+
{
|
|
245
|
+
_context = context;
|
|
246
|
+
_passwordHasher = passwordHasher;
|
|
247
|
+
_jwtService = jwtService;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public async Task<AuthResponse> Handle(
|
|
251
|
+
RegisterCommand request,
|
|
252
|
+
CancellationToken cancellationToken)
|
|
253
|
+
{
|
|
254
|
+
var existingUser = await _context.Users
|
|
255
|
+
.FirstOrDefaultAsync(u => u.Email == request.Email, cancellationToken);
|
|
256
|
+
|
|
257
|
+
if (existingUser is not null)
|
|
258
|
+
{
|
|
259
|
+
throw new ValidationException("Email already registered");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
var passwordHash = _passwordHasher.HashPassword(request.Password);
|
|
263
|
+
|
|
264
|
+
var user = User.Create(
|
|
265
|
+
request.Email,
|
|
266
|
+
passwordHash,
|
|
267
|
+
request.FirstName,
|
|
268
|
+
request.LastName);
|
|
269
|
+
|
|
270
|
+
_context.Users.Add(user);
|
|
271
|
+
await _context.SaveChangesAsync(cancellationToken);
|
|
272
|
+
|
|
273
|
+
var token = _jwtService.GenerateToken(user.Id, user.Email, user.Roles);
|
|
274
|
+
|
|
275
|
+
return new AuthResponse(token, user.Email, user.FirstName, user.LastName);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Application/Auth/Commands/Login/LoginCommand.cs
|
|
280
|
+
public record LoginCommand(
|
|
281
|
+
string Email,
|
|
282
|
+
string Password
|
|
283
|
+
) : IRequest<AuthResponse>;
|
|
284
|
+
|
|
285
|
+
// Application/Auth/Commands/Login/LoginCommandHandler.cs
|
|
286
|
+
public class LoginCommandHandler : IRequestHandler<LoginCommand, AuthResponse>
|
|
287
|
+
{
|
|
288
|
+
private readonly IApplicationDbContext _context;
|
|
289
|
+
private readonly IPasswordHasher _passwordHasher;
|
|
290
|
+
private readonly IJwtService _jwtService;
|
|
291
|
+
|
|
292
|
+
public LoginCommandHandler(
|
|
293
|
+
IApplicationDbContext context,
|
|
294
|
+
IPasswordHasher passwordHasher,
|
|
295
|
+
IJwtService jwtService)
|
|
296
|
+
{
|
|
297
|
+
_context = context;
|
|
298
|
+
_passwordHasher = passwordHasher;
|
|
299
|
+
_jwtService = jwtService;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public async Task<AuthResponse> Handle(
|
|
303
|
+
LoginCommand request,
|
|
304
|
+
CancellationToken cancellationToken)
|
|
305
|
+
{
|
|
306
|
+
var user = await _context.Users
|
|
307
|
+
.FirstOrDefaultAsync(u => u.Email == request.Email, cancellationToken);
|
|
308
|
+
|
|
309
|
+
if (user is null || !_passwordHasher.VerifyPassword(request.Password, user.PasswordHash))
|
|
310
|
+
{
|
|
311
|
+
throw new UnauthorizedException("Invalid credentials");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!user.IsActive)
|
|
315
|
+
{
|
|
316
|
+
throw new UnauthorizedException("Account is inactive");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
var token = _jwtService.GenerateToken(user.Id, user.Email, user.Roles);
|
|
320
|
+
|
|
321
|
+
return new AuthResponse(token, user.Email, user.FirstName, user.LastName);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public record AuthResponse(string Token, string Email, string FirstName, string LastName);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Configure Authentication in Program.cs
|
|
329
|
+
|
|
330
|
+
```csharp
|
|
331
|
+
using System.Text;
|
|
332
|
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
333
|
+
using Microsoft.IdentityModel.Tokens;
|
|
334
|
+
|
|
335
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
336
|
+
|
|
337
|
+
// Configure JWT settings
|
|
338
|
+
builder.Services.Configure<JwtSettings>(
|
|
339
|
+
builder.Configuration.GetSection("JwtSettings"));
|
|
340
|
+
|
|
341
|
+
var jwtSettings = builder.Configuration
|
|
342
|
+
.GetSection("JwtSettings")
|
|
343
|
+
.Get<JwtSettings>()!;
|
|
344
|
+
|
|
345
|
+
// Add authentication
|
|
346
|
+
builder.Services.AddAuthentication(options =>
|
|
347
|
+
{
|
|
348
|
+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
349
|
+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
350
|
+
})
|
|
351
|
+
.AddJwtBearer(options =>
|
|
352
|
+
{
|
|
353
|
+
options.TokenValidationParameters = new TokenValidationParameters
|
|
354
|
+
{
|
|
355
|
+
ValidateIssuerSigningKey = true,
|
|
356
|
+
IssuerSigningKey = new SymmetricSecurityKey(
|
|
357
|
+
Encoding.UTF8.GetBytes(jwtSettings.Secret)),
|
|
358
|
+
ValidateIssuer = true,
|
|
359
|
+
ValidIssuer = jwtSettings.Issuer,
|
|
360
|
+
ValidateAudience = true,
|
|
361
|
+
ValidAudience = jwtSettings.Audience,
|
|
362
|
+
ValidateLifetime = true,
|
|
363
|
+
ClockSkew = TimeSpan.Zero
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
builder.Services.AddAuthorization();
|
|
368
|
+
|
|
369
|
+
var app = builder.Build();
|
|
370
|
+
|
|
371
|
+
app.UseAuthentication();
|
|
372
|
+
app.UseAuthorization();
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Authorization Policies
|
|
376
|
+
|
|
377
|
+
```csharp
|
|
378
|
+
// Configure policies
|
|
379
|
+
builder.Services.AddAuthorization(options =>
|
|
380
|
+
{
|
|
381
|
+
options.AddPolicy("AdminOnly", policy =>
|
|
382
|
+
policy.RequireRole("Admin"));
|
|
383
|
+
|
|
384
|
+
options.AddPolicy("UserOrAdmin", policy =>
|
|
385
|
+
policy.RequireRole("User", "Admin"));
|
|
386
|
+
|
|
387
|
+
options.AddPolicy("RequireEmailVerified", policy =>
|
|
388
|
+
policy.RequireClaim("email_verified", "true"));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Apply to endpoints
|
|
392
|
+
app.MapGet("/api/admin/users", GetAllUsers)
|
|
393
|
+
.RequireAuthorization("AdminOnly");
|
|
394
|
+
|
|
395
|
+
app.MapGet("/api/profile", GetProfile)
|
|
396
|
+
.RequireAuthorization();
|
|
397
|
+
|
|
398
|
+
app.MapPost("/api/products", CreateProduct)
|
|
399
|
+
.RequireAuthorization("AdminOnly");
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Current User Service
|
|
403
|
+
|
|
404
|
+
```csharp
|
|
405
|
+
// Application/Common/Interfaces/ICurrentUserService.cs
|
|
406
|
+
namespace Application.Common.Interfaces;
|
|
407
|
+
|
|
408
|
+
public interface ICurrentUserService
|
|
409
|
+
{
|
|
410
|
+
int? UserId { get; }
|
|
411
|
+
string? Email { get; }
|
|
412
|
+
bool IsAuthenticated { get; }
|
|
413
|
+
bool IsInRole(string role);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Infrastructure/Services/CurrentUserService.cs
|
|
417
|
+
using System.Security.Claims;
|
|
418
|
+
using Microsoft.AspNetCore.Http;
|
|
419
|
+
|
|
420
|
+
namespace Infrastructure.Services;
|
|
421
|
+
|
|
422
|
+
public class CurrentUserService : ICurrentUserService
|
|
423
|
+
{
|
|
424
|
+
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
425
|
+
|
|
426
|
+
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
|
|
427
|
+
{
|
|
428
|
+
_httpContextAccessor = httpContextAccessor;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
public int? UserId
|
|
432
|
+
{
|
|
433
|
+
get
|
|
434
|
+
{
|
|
435
|
+
var userIdClaim = _httpContextAccessor.HttpContext?.User?
|
|
436
|
+
.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
437
|
+
|
|
438
|
+
return int.TryParse(userIdClaim, out var userId) ? userId : null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
public string? Email =>
|
|
443
|
+
_httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Email);
|
|
444
|
+
|
|
445
|
+
public bool IsAuthenticated =>
|
|
446
|
+
_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
|
|
447
|
+
|
|
448
|
+
public bool IsInRole(string role) =>
|
|
449
|
+
_httpContextAccessor.HttpContext?.User?.IsInRole(role) ?? false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Register service
|
|
453
|
+
builder.Services.AddHttpContextAccessor();
|
|
454
|
+
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Auth Endpoints
|
|
458
|
+
|
|
459
|
+
```csharp
|
|
460
|
+
// WebApi/Endpoints/AuthEndpoints.cs
|
|
461
|
+
using Application.Auth.Commands.Login;
|
|
462
|
+
using Application.Auth.Commands.Register;
|
|
463
|
+
using MediatR;
|
|
464
|
+
|
|
465
|
+
namespace WebApi.Endpoints;
|
|
466
|
+
|
|
467
|
+
public static class AuthEndpoints
|
|
468
|
+
{
|
|
469
|
+
public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder app)
|
|
470
|
+
{
|
|
471
|
+
var group = app.MapGroup("/api/auth")
|
|
472
|
+
.WithTags("Authentication")
|
|
473
|
+
.WithOpenApi();
|
|
474
|
+
|
|
475
|
+
group.MapPost("/register", async (
|
|
476
|
+
RegisterCommand command,
|
|
477
|
+
ISender sender) =>
|
|
478
|
+
{
|
|
479
|
+
var response = await sender.Send(command);
|
|
480
|
+
return Results.Ok(response);
|
|
481
|
+
})
|
|
482
|
+
.AllowAnonymous();
|
|
483
|
+
|
|
484
|
+
group.MapPost("/login", async (
|
|
485
|
+
LoginCommand command,
|
|
486
|
+
ISender sender) =>
|
|
487
|
+
{
|
|
488
|
+
var response = await sender.Send(command);
|
|
489
|
+
return Results.Ok(response);
|
|
490
|
+
})
|
|
491
|
+
.AllowAnonymous();
|
|
492
|
+
|
|
493
|
+
group.MapGet("/me", async (
|
|
494
|
+
ICurrentUserService currentUser,
|
|
495
|
+
IApplicationDbContext context) =>
|
|
496
|
+
{
|
|
497
|
+
if (currentUser.UserId is null)
|
|
498
|
+
{
|
|
499
|
+
return Results.Unauthorized();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
var user = await context.Users
|
|
503
|
+
.FindAsync(currentUser.UserId.Value);
|
|
504
|
+
|
|
505
|
+
return user is not null
|
|
506
|
+
? Results.Ok(new
|
|
507
|
+
{
|
|
508
|
+
user.Email,
|
|
509
|
+
user.FirstName,
|
|
510
|
+
user.LastName,
|
|
511
|
+
user.Roles
|
|
512
|
+
})
|
|
513
|
+
: Results.NotFound();
|
|
514
|
+
})
|
|
515
|
+
.RequireAuthorization();
|
|
516
|
+
|
|
517
|
+
return app;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## appsettings.json
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"JwtSettings": {
|
|
527
|
+
"Secret": "your-super-secret-key-minimum-32-characters",
|
|
528
|
+
"Issuer": "YourApp",
|
|
529
|
+
"Audience": "YourAppUsers",
|
|
530
|
+
"ExpirationMinutes": 60
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Quick Reference
|
|
536
|
+
|
|
537
|
+
| Pattern | Usage |
|
|
538
|
+
|---------|-------|
|
|
539
|
+
| `RequireAuthorization()` | Endpoint requires authentication |
|
|
540
|
+
| `RequireAuthorization("Policy")` | Endpoint requires specific policy |
|
|
541
|
+
| `AllowAnonymous()` | Allow unauthenticated access |
|
|
542
|
+
| `RequireRole("Admin")` | Require specific role |
|
|
543
|
+
| JWT Bearer | Token-based authentication |
|
|
544
|
+
| `ICurrentUserService` | Access current user info |
|
|
545
|
+
| `IPasswordHasher` | Hash and verify passwords |
|
|
546
|
+
| `IJwtService` | Generate and validate tokens |
|