@polymorphism-tech/morph-spec 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -1,290 +1,108 @@
|
|
|
1
|
-
# Clerk Auth
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
111
|
-
|
|
112
|
-
if (userId is null)
|
|
113
|
-
return Unauthorized();
|
|
114
|
-
|
|
115
|
-
var user = await _clerk.Users.GetUserAsync(userId);
|
|
116
|
-
|
|
117
|
-
return Ok(new
|
|
118
|
-
{
|
|
119
|
-
user.Id,
|
|
120
|
-
Email = user.EmailAddresses.FirstOrDefault()?.EmailAddress,
|
|
121
|
-
Name = $"{user.FirstName} {user.LastName}"
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
[HttpGet("admin")]
|
|
126
|
-
[Authorize(Roles = "admin")] // Requer role admin
|
|
127
|
-
public IActionResult AdminOnly()
|
|
128
|
-
{
|
|
129
|
-
return Ok("Admin access granted");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Blazor Server
|
|
135
|
-
|
|
136
|
-
```csharp
|
|
137
|
-
// Program.cs
|
|
138
|
-
builder.Services.AddClerk(builder.Configuration);
|
|
139
|
-
builder.Services.AddAuthentication(ClerkAuthenticationDefaults.AuthenticationScheme)
|
|
140
|
-
.AddClerk();
|
|
141
|
-
|
|
142
|
-
// App.razor
|
|
143
|
-
<CascadingAuthenticationState>
|
|
144
|
-
<Router AppAssembly="@typeof(App).Assembly">
|
|
145
|
-
<Found Context="routeData">
|
|
146
|
-
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
|
|
147
|
-
<NotAuthorized>
|
|
148
|
-
<RedirectToLogin />
|
|
149
|
-
</NotAuthorized>
|
|
150
|
-
</AuthorizeRouteView>
|
|
151
|
-
</Found>
|
|
152
|
-
</Router>
|
|
153
|
-
</CascadingAuthenticationState>
|
|
154
|
-
|
|
155
|
-
// Components/RedirectToLogin.razor
|
|
156
|
-
@inject NavigationManager Navigation
|
|
157
|
-
|
|
158
|
-
@code {
|
|
159
|
-
protected override void OnInitialized()
|
|
160
|
-
{
|
|
161
|
-
Navigation.NavigateTo($"/sign-in?redirect_url={Uri.EscapeDataString(Navigation.Uri)}", forceLoad: true);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Páginas Protegidas
|
|
167
|
-
|
|
168
|
-
```razor
|
|
169
|
-
@* Pages/Dashboard.razor *@
|
|
170
|
-
@page "/dashboard"
|
|
171
|
-
@attribute [Authorize]
|
|
172
|
-
|
|
173
|
-
<h1>Dashboard</h1>
|
|
174
|
-
|
|
175
|
-
<AuthorizeView>
|
|
176
|
-
<Authorized>
|
|
177
|
-
<p>Bem-vindo, @context.User.Identity?.Name!</p>
|
|
178
|
-
</Authorized>
|
|
179
|
-
</AuthorizeView>
|
|
180
|
-
|
|
181
|
-
@* Verificar role específica *@
|
|
182
|
-
<AuthorizeView Roles="admin">
|
|
183
|
-
<Authorized>
|
|
184
|
-
<AdminPanel />
|
|
185
|
-
</Authorized>
|
|
186
|
-
<NotAuthorized>
|
|
187
|
-
<p>Acesso restrito a administradores.</p>
|
|
188
|
-
</NotAuthorized>
|
|
189
|
-
</AuthorizeView>
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## Clerk Client para Operações
|
|
193
|
-
|
|
194
|
-
```csharp
|
|
195
|
-
// Buscar usuário
|
|
196
|
-
var user = await _clerk.Users.GetUserAsync(userId);
|
|
197
|
-
|
|
198
|
-
// Listar usuários
|
|
199
|
-
var users = await _clerk.Users.GetUserListAsync(new()
|
|
200
|
-
{
|
|
201
|
-
Limit = 10,
|
|
202
|
-
Offset = 0
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Atualizar metadata
|
|
206
|
-
await _clerk.Users.UpdateUserMetadataAsync(userId, new()
|
|
207
|
-
{
|
|
208
|
-
PublicMetadata = new Dictionary<string, object>
|
|
209
|
-
{
|
|
210
|
-
["plan"] = "pro",
|
|
211
|
-
["company"] = "Acme Inc"
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Deletar usuário
|
|
216
|
-
await _clerk.Users.DeleteUserAsync(userId);
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Webhooks
|
|
220
|
-
|
|
221
|
-
```csharp
|
|
222
|
-
// Controllers/ClerkWebhookController.cs
|
|
223
|
-
[ApiController]
|
|
224
|
-
[Route("api/webhooks/clerk")]
|
|
225
|
-
public class ClerkWebhookController : ControllerBase
|
|
226
|
-
{
|
|
227
|
-
private readonly IUserService _userService;
|
|
228
|
-
|
|
229
|
-
public ClerkWebhookController(IUserService userService)
|
|
230
|
-
{
|
|
231
|
-
_userService = userService;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
[HttpPost]
|
|
235
|
-
public async Task<IActionResult> HandleWebhook([FromBody] ClerkWebhookPayload payload)
|
|
236
|
-
{
|
|
237
|
-
// Verificar signature (importante em produção!)
|
|
238
|
-
|
|
239
|
-
switch (payload.Type)
|
|
240
|
-
{
|
|
241
|
-
case "user.created":
|
|
242
|
-
await _userService.SyncUserAsync(payload.Data);
|
|
243
|
-
break;
|
|
244
|
-
|
|
245
|
-
case "user.updated":
|
|
246
|
-
await _userService.UpdateUserAsync(payload.Data);
|
|
247
|
-
break;
|
|
248
|
-
|
|
249
|
-
case "user.deleted":
|
|
250
|
-
await _userService.DeleteUserAsync(payload.Data.Id);
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return Ok();
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Clerk vs Azure Identity
|
|
260
|
-
|
|
261
|
-
| Aspecto | Clerk | Azure Identity |
|
|
262
|
-
|---------|-------|----------------|
|
|
263
|
-
| **Setup** | Mais rápido | Mais complexo |
|
|
264
|
-
| **Custo** | Freemium (5k MAU grátis) | Grátis (Azure AD) |
|
|
265
|
-
| **Multi-tenant** | Built-in | Requer config |
|
|
266
|
-
| **Social login** | 20+ providers | Limitado |
|
|
267
|
-
| **UI components** | Pré-construídos | Precisa criar |
|
|
268
|
-
| **Ideal para** | SaaS B2C | Enterprise/Azure |
|
|
269
|
-
|
|
270
|
-
## Documentação de Referência
|
|
271
|
-
|
|
272
|
-
- [Clerk C# SDK](https://clerk.com/changelog/2025-01-09-csharp-sdk)
|
|
273
|
-
- [Clerk.Net GitHub](https://github.com/Hawxy/Clerk.Net)
|
|
274
|
-
- [Clerk Documentation](https://clerk.com/docs)
|
|
275
|
-
- [Webhooks](https://clerk.com/docs/integrations/webhooks)
|
|
276
|
-
|
|
277
|
-
## Checklist de Integração
|
|
278
|
-
|
|
279
|
-
- [ ] Secret Key no Key Vault (não hardcoded)
|
|
280
|
-
- [ ] Publishable Key para frontend
|
|
281
|
-
- [ ] Authentication scheme configurado
|
|
282
|
-
- [ ] Authorization policies definidas
|
|
283
|
-
- [ ] Webhook endpoint para sync de usuários
|
|
284
|
-
- [ ] Verificação de webhook signature
|
|
285
|
-
- [ ] Redirect após login configurado
|
|
286
|
-
- [ ] Error handling para sessões expiradas
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
1
|
+
# Clerk Auth
|
|
2
|
+
|
|
3
|
+
> **Layer:** 2 | **Load:** on-keyword | **Keywords:** clerk, auth, login, signup, authentication, session, jwt, user
|
|
4
|
+
|
|
5
|
+
Autenticação SaaS com Clerk para .NET/Blazor. SDK: `Clerk.Net.AspNetCore.Security`.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
// appsettings.json
|
|
11
|
+
{ "Clerk": { "SecretKey": "${CLERK_SECRET_KEY}", "PublishableKey": "pk_test_xxx" } }
|
|
12
|
+
|
|
13
|
+
// Program.cs
|
|
14
|
+
builder.Services.AddClerk(builder.Configuration);
|
|
15
|
+
builder.Services.AddAuthentication(ClerkAuthenticationDefaults.AuthenticationScheme)
|
|
16
|
+
.AddClerk(o => { o.Authority = "https://clerk.{instance}.com"; o.ValidAudiences = ["your-app-id"]; });
|
|
17
|
+
builder.Services.AddAuthorization();
|
|
18
|
+
app.UseAuthentication();
|
|
19
|
+
app.UseAuthorization();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Protected Endpoints
|
|
23
|
+
|
|
24
|
+
```csharp
|
|
25
|
+
// Minimal API
|
|
26
|
+
app.MapGet("/api/profile", async (ClaimsPrincipal user, IClerkClient clerk) =>
|
|
27
|
+
{
|
|
28
|
+
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
29
|
+
if (userId is null) return Results.Unauthorized();
|
|
30
|
+
var u = await clerk.Users.GetUserAsync(userId);
|
|
31
|
+
return Results.Ok(new { u.Id, Email = u.EmailAddresses.FirstOrDefault()?.EmailAddress });
|
|
32
|
+
}).RequireAuthorization();
|
|
33
|
+
|
|
34
|
+
// Controller: same pattern with [Authorize] + User.FindFirstValue()
|
|
35
|
+
// Role-based: [Authorize(Roles = "admin")]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Blazor Server
|
|
39
|
+
|
|
40
|
+
> **Ref:** Same `CascadingAuthenticationState` + `AuthorizeRouteView` pattern as `azure-identity.md`
|
|
41
|
+
|
|
42
|
+
```razor
|
|
43
|
+
@* RedirectToLogin.razor *@
|
|
44
|
+
@inject NavigationManager Nav
|
|
45
|
+
@code {
|
|
46
|
+
protected override void OnInitialized() =>
|
|
47
|
+
Nav.NavigateTo($"/sign-in?redirect_url={Uri.EscapeDataString(Nav.Uri)}", forceLoad: true);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@* Protected page *@
|
|
51
|
+
@page "/dashboard"
|
|
52
|
+
@attribute [Authorize]
|
|
53
|
+
<AuthorizeView><Authorized>Welcome, @context.User.Identity?.Name!</Authorized></AuthorizeView>
|
|
54
|
+
<AuthorizeView Roles="admin"><Authorized><AdminPanel /></Authorized></AuthorizeView>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Client Operations
|
|
58
|
+
|
|
59
|
+
```csharp
|
|
60
|
+
var user = await _clerk.Users.GetUserAsync(userId); // Get
|
|
61
|
+
var users = await _clerk.Users.GetUserListAsync(new() { Limit = 10 }); // List
|
|
62
|
+
await _clerk.Users.UpdateUserMetadataAsync(userId, new() // Update metadata
|
|
63
|
+
{ PublicMetadata = new Dictionary<string, object> { ["plan"] = "pro" } });
|
|
64
|
+
await _clerk.Users.DeleteUserAsync(userId); // Delete
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Webhooks
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
[ApiController, Route("api/webhooks/clerk")]
|
|
71
|
+
public class ClerkWebhookController(IUserService users) : ControllerBase
|
|
72
|
+
{
|
|
73
|
+
[HttpPost]
|
|
74
|
+
public async Task<IActionResult> Handle([FromBody] ClerkWebhookPayload payload)
|
|
75
|
+
{
|
|
76
|
+
switch (payload.Type)
|
|
77
|
+
{
|
|
78
|
+
case "user.created": await users.SyncUserAsync(payload.Data); break;
|
|
79
|
+
case "user.updated": await users.UpdateUserAsync(payload.Data); break;
|
|
80
|
+
case "user.deleted": await users.DeleteUserAsync(payload.Data.Id); break;
|
|
81
|
+
}
|
|
82
|
+
return Ok();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Clerk vs Azure Identity
|
|
88
|
+
|
|
89
|
+
| Aspecto | Clerk | Azure Identity |
|
|
90
|
+
|---------|-------|----------------|
|
|
91
|
+
| Setup | Faster | More complex |
|
|
92
|
+
| Cost | Freemium (5k MAU free) | Free (Azure AD) |
|
|
93
|
+
| Social login | 20+ providers | Limited |
|
|
94
|
+
| UI components | Pre-built | Build your own |
|
|
95
|
+
| Best for | SaaS B2C, MVPs | Enterprise/Azure |
|
|
96
|
+
|
|
97
|
+
## Checklist
|
|
98
|
+
|
|
99
|
+
- [ ] Secret Key in Key Vault (not hardcoded)
|
|
100
|
+
- [ ] Authentication scheme configured
|
|
101
|
+
- [ ] Authorization policies defined
|
|
102
|
+
- [ ] Webhook endpoint + signature validation
|
|
103
|
+
- [ ] Redirect after login configured
|
|
104
|
+
- [ ] Error handling for expired sessions
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Resend Email
|
|
2
|
+
|
|
3
|
+
> **Layer:** 2 | **Load:** on-keyword | **Keywords:** resend, email, envio, transactional, notification, send email, template
|
|
4
|
+
|
|
5
|
+
Transactional email via Resend for .NET. REST API, no official SDK. Simple, developer-friendly.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
// appsettings.json
|
|
11
|
+
{ "Resend": { "BaseUrl": "https://api.resend.com", "ApiKey": "${RESEND_API_KEY}", "FromEmail": "noreply@yourdomain.com" } }
|
|
12
|
+
|
|
13
|
+
// Program.cs
|
|
14
|
+
builder.Services.Configure<ResendOptions>(builder.Configuration.GetSection("Resend"));
|
|
15
|
+
builder.Services.AddHttpClient<IResendClient, ResendClient>((sp, client) =>
|
|
16
|
+
{
|
|
17
|
+
var options = sp.GetRequiredService<IOptions<ResendOptions>>().Value;
|
|
18
|
+
client.BaseAddress = new Uri(options.BaseUrl);
|
|
19
|
+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Client Interface
|
|
24
|
+
|
|
25
|
+
```csharp
|
|
26
|
+
public interface IResendClient
|
|
27
|
+
{
|
|
28
|
+
Task<EmailResponse> SendAsync(SendEmailRequest request, CancellationToken ct = default);
|
|
29
|
+
Task<EmailResponse> SendBatchAsync(IEnumerable<SendEmailRequest> requests, CancellationToken ct = default);
|
|
30
|
+
Task<EmailDetails> GetEmailAsync(string emailId, CancellationToken ct = default);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Implementation: `HttpClient.PostAsJsonAsync("/emails")` + `ReadFromJsonAsync<EmailResponse>`. Log errors, throw `ResendException` on failure.
|
|
35
|
+
|
|
36
|
+
## DTOs
|
|
37
|
+
|
|
38
|
+
```csharp
|
|
39
|
+
public record SendEmailRequest
|
|
40
|
+
{
|
|
41
|
+
[JsonPropertyName("from")] public required string From { get; init; }
|
|
42
|
+
[JsonPropertyName("to")] public required string[] To { get; init; }
|
|
43
|
+
[JsonPropertyName("subject")] public required string Subject { get; init; }
|
|
44
|
+
[JsonPropertyName("html")] public string? Html { get; init; }
|
|
45
|
+
[JsonPropertyName("text")] public string? Text { get; init; }
|
|
46
|
+
[JsonPropertyName("reply_to")] public string? ReplyTo { get; init; }
|
|
47
|
+
[JsonPropertyName("tags")] public Tag[]? Tags { get; init; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public record EmailResponse
|
|
51
|
+
{
|
|
52
|
+
[JsonPropertyName("id")] public string Id { get; init; } = "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public record Tag
|
|
56
|
+
{
|
|
57
|
+
[JsonPropertyName("name")] public required string Name { get; init; }
|
|
58
|
+
[JsonPropertyName("value")] public required string Value { get; init; }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Webhooks
|
|
63
|
+
|
|
64
|
+
```csharp
|
|
65
|
+
[ApiController, Route("api/webhooks/resend")]
|
|
66
|
+
public class ResendWebhookController(IEmailTrackingService tracking, ILogger<ResendWebhookController> logger) : ControllerBase
|
|
67
|
+
{
|
|
68
|
+
[HttpPost]
|
|
69
|
+
public async Task<IActionResult> Handle([FromBody] ResendWebhookPayload payload)
|
|
70
|
+
{
|
|
71
|
+
logger.LogInformation("Resend webhook: {Type} for {EmailId}", payload.Type, payload.Data?.EmailId);
|
|
72
|
+
switch (payload.Type)
|
|
73
|
+
{
|
|
74
|
+
case "email.delivered": await tracking.MarkDeliveredAsync(payload.Data!.EmailId); break;
|
|
75
|
+
case "email.bounced": await tracking.MarkBouncedAsync(payload.Data!.EmailId); break;
|
|
76
|
+
case "email.complained": await tracking.MarkComplainedAsync(payload.Data!.EmailId); break;
|
|
77
|
+
}
|
|
78
|
+
return Ok();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Abstraction Pattern
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
// Use IEmailService abstraction over IResendClient for testability
|
|
87
|
+
public interface IEmailService
|
|
88
|
+
{
|
|
89
|
+
Task<string> SendTransactionalAsync(string to, string subject, string htmlBody, CancellationToken ct = default);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ResendEmailService implements IEmailService using IResendClient
|
|
93
|
+
// FakeEmailClient implements IEmailService for simulation mode
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Gotchas
|
|
97
|
+
|
|
98
|
+
| Issue | Fix |
|
|
99
|
+
|-------|-----|
|
|
100
|
+
| Rate limit: 100 req/sec (free), 1000 (pro) | Queue emails via Hangfire for bulk sends |
|
|
101
|
+
| Domain verification required for production | Verify in Resend dashboard, add DNS records |
|
|
102
|
+
| `from` must match verified domain | Use `noreply@yourdomain.com` |
|
|
103
|
+
| HTML email rendering varies | Test with Litmus/Email on Acid, use MJML |
|
|
104
|
+
| Webhook signature validation | Verify `svix-signature` header in production |
|
|
105
|
+
|
|
106
|
+
## Checklist
|
|
107
|
+
|
|
108
|
+
- [ ] API Key in Key Vault (not hardcoded)
|
|
109
|
+
- [ ] HttpClient with `Authorization: Bearer` header
|
|
110
|
+
- [ ] Domain verified in Resend dashboard
|
|
111
|
+
- [ ] `IEmailService` abstraction for testability
|
|
112
|
+
- [ ] Webhook endpoint + signature validation
|
|
113
|
+
- [ ] Rate limiting handled (queue for bulk)
|
|
114
|
+
- [ ] Tags used for tracking/analytics
|
|
115
|
+
- [ ] Simulation mode (`FakeEmailClient`) for dev
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
*MORPH-SPEC by Polymorphism Tech*
|