@polymorphism-tech/morph-spec 4.17.0 → 4.19.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 +36 -0
- package/README.md +2 -2
- package/bin/task-manager.js +12 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/architecture/architecture-expert.md +86 -0
- package/framework/agents/architecture/morph-spec-architect.md +66 -0
- package/framework/agents/architecture/packages-validator.md +77 -0
- package/framework/agents/architecture/security-expert.md +67 -0
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/backend/neon-expert.md +9 -27
- package/framework/agents/frontend/design-system-validator.md +88 -0
- package/framework/agents/frontend/nextjs-component-validator.md +85 -0
- package/framework/agents/frontend/{ui-ux-designer.md → ui-designer.md} +31 -0
- package/framework/agents/integrations/neon-auth.md +151 -0
- package/framework/agents.json +22 -14
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +39 -0
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +78 -44
- package/framework/hooks/claude-code/post-tool-use/loop-detect.js +103 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +21 -2
- package/framework/hooks/claude-code/stop/validate-completion.js +48 -1
- package/framework/hooks/shared/activity-logger.js +56 -1
- package/framework/hooks/shared/build-check.js +79 -0
- package/framework/hooks/shared/payload-utils.js +39 -0
- package/framework/hooks/shared/session-snapshot.js +105 -0
- package/framework/phases.json +1 -1
- package/framework/rules/neon-auth-standards.md +13 -0
- package/framework/rules/neon-standards.md +1 -0
- package/framework/skills/level-0-meta/mcp-registry.json +34 -1
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +3 -3
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +4 -4
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +11 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +12 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +119 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +13 -9
- package/framework/standards/ai-agents/production.md +5 -2
- package/framework/standards/ai-agents/setup.md +13 -14
- package/framework/standards/ai-agents/workflows.md +8 -6
- package/framework/standards/backend/database/postgresql/database.md +5 -5
- package/framework/standards/backend/database/vector-search-rag.md +1 -1
- package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +364 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +9 -9
- package/framework/templates/README.md +2 -2
- package/framework/templates/REGISTRY.json +5 -5
- package/framework/templates/code/dotnet/jobs/agent.cs +0 -1
- package/framework/templates/code/sql/postgresql-migration.sql +10 -10
- package/framework/templates/code/sql/postgresql-migration.template.sql +12 -14
- package/framework/templates/code/sql/postgresql-rls-policy.sql +6 -9
- package/framework/templates/feature/decisions.md +8 -7
- package/framework/templates/integrations/neon-auth-config.cs +133 -0
- package/framework/workflows/docs/full-morph.md +1 -1
- package/package.json +1 -1
- package/src/commands/mcp/mcp-setup.js +21 -0
- package/src/commands/project/init.js +11 -0
- package/src/lib/contracts/sprint-contract-generator.js +137 -0
- package/src/lib/detectors/index.js +2 -2
- package/src/lib/generators/architecture-scanner.js +2 -3
- package/src/lib/installers/mcp-installer.js +25 -0
- package/src/utils/hooks-installer.js +12 -1
- package/framework/agents/integrations/clerk-auth.md +0 -109
- package/framework/rules/clerk-standards.md +0 -13
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -292
- package/framework/templates/integrations/clerk-config.cs +0 -217
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# Clerk Authentication Integration Standard
|
|
2
|
-
|
|
3
|
-
> **Scope:** universal
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** clerk, auth, authentication, nextjs, user management
|
|
6
|
-
> **Load When:** clerk authentication keywords detected
|
|
7
|
-
|
|
8
|
-
Modern authentication and user management for Next.js applications.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
Clerk provides:
|
|
15
|
-
- Pre-built UI components (SignIn, SignUp, UserProfile)
|
|
16
|
-
- Social auth (Google, GitHub, etc.)
|
|
17
|
-
- Multi-factor authentication
|
|
18
|
-
- Session management
|
|
19
|
-
- User metadata and organizations
|
|
20
|
-
|
|
21
|
-
**Primary Use Case:** Next.js 15 + App Router
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Core Principles
|
|
26
|
-
|
|
27
|
-
1. **Server-First**: Use server-side auth checks with `auth()` helper
|
|
28
|
-
2. **Route Protection**: Middleware-based protection for private routes
|
|
29
|
-
3. **Type Safety**: Use Clerk's TypeScript types
|
|
30
|
-
4. **Edge Compatible**: Works with Edge Runtime
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Installation & Setup
|
|
35
|
-
|
|
36
|
-
### Install Packages
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install @clerk/nextjs
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Environment Variables
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
# .env.local
|
|
46
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
|
|
47
|
-
CLERK_SECRET_KEY=sk_test_...
|
|
48
|
-
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
|
|
49
|
-
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
|
|
50
|
-
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
|
|
51
|
-
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Root Layout Wrapper
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// app/layout.tsx
|
|
58
|
-
import { ClerkProvider } from '@clerk/nextjs';
|
|
59
|
-
|
|
60
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
61
|
-
return (
|
|
62
|
-
<ClerkProvider>
|
|
63
|
-
<html lang="en">
|
|
64
|
-
<body>{children}</body>
|
|
65
|
-
</html>
|
|
66
|
-
</ClerkProvider>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Middleware Protection
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
// middleware.ts
|
|
75
|
-
import { authMiddleware } from '@clerk/nextjs';
|
|
76
|
-
|
|
77
|
-
export default authMiddleware({
|
|
78
|
-
publicRoutes: ['/', '/api/webhooks/clerk'],
|
|
79
|
-
ignoredRoutes: ['/api/public']
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export const config = {
|
|
83
|
-
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
|
|
84
|
-
};
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Common Patterns
|
|
90
|
-
|
|
91
|
-
### Sign In Page
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// app/sign-in/[[...sign-in]]/page.tsx
|
|
95
|
-
import { SignIn } from '@clerk/nextjs';
|
|
96
|
-
|
|
97
|
-
export default function SignInPage() {
|
|
98
|
-
return (
|
|
99
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
100
|
-
<SignIn />
|
|
101
|
-
</div>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Server-Side Auth Check
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// app/dashboard/page.tsx
|
|
110
|
-
import { auth } from '@clerk/nextjs';
|
|
111
|
-
import { redirect } from 'next/navigation';
|
|
112
|
-
|
|
113
|
-
export default async function DashboardPage() {
|
|
114
|
-
const { userId } = auth();
|
|
115
|
-
|
|
116
|
-
if (!userId) {
|
|
117
|
-
redirect('/sign-in');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return <div>Dashboard for user {userId}</div>;
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Client Component with User Data
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// components/UserProfile.tsx
|
|
128
|
-
'use client';
|
|
129
|
-
import { useUser } from '@clerk/nextjs';
|
|
130
|
-
|
|
131
|
-
export function UserProfile() {
|
|
132
|
-
const { user, isLoaded, isSignedIn } = useUser();
|
|
133
|
-
|
|
134
|
-
if (!isLoaded) return <div>Loading...</div>;
|
|
135
|
-
if (!isSignedIn) return <div>Not signed in</div>;
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div>
|
|
139
|
-
<p>Welcome, {user.firstName}!</p>
|
|
140
|
-
<p>Email: {user.primaryEmailAddress?.emailAddress}</p>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### API Route Protection
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// app/api/protected/route.ts
|
|
150
|
-
import { auth } from '@clerk/nextjs';
|
|
151
|
-
|
|
152
|
-
export async function GET() {
|
|
153
|
-
const { userId } = auth();
|
|
154
|
-
|
|
155
|
-
if (!userId) {
|
|
156
|
-
return new Response('Unauthorized', { status: 401 });
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Fetch user-specific data
|
|
160
|
-
const data = await fetchUserData(userId);
|
|
161
|
-
|
|
162
|
-
return Response.json(data);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Webhook Integration
|
|
169
|
-
|
|
170
|
-
### Setup Webhook Endpoint
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
// app/api/webhooks/clerk/route.ts
|
|
174
|
-
import { Webhook } from 'svix';
|
|
175
|
-
import { headers } from 'next/headers';
|
|
176
|
-
|
|
177
|
-
export async function POST(req: Request) {
|
|
178
|
-
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!;
|
|
179
|
-
|
|
180
|
-
const headerPayload = headers();
|
|
181
|
-
const svix_id = headerPayload.get('svix-id');
|
|
182
|
-
const svix_timestamp = headerPayload.get('svix-timestamp');
|
|
183
|
-
const svix_signature = headerPayload.get('svix-signature');
|
|
184
|
-
|
|
185
|
-
if (!svix_id || !svix_timestamp || !svix_signature) {
|
|
186
|
-
return new Response('Missing svix headers', { status: 400 });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const payload = await req.json();
|
|
190
|
-
const body = JSON.stringify(payload);
|
|
191
|
-
|
|
192
|
-
const wh = new Webhook(WEBHOOK_SECRET);
|
|
193
|
-
|
|
194
|
-
let evt;
|
|
195
|
-
try {
|
|
196
|
-
evt = wh.verify(body, {
|
|
197
|
-
'svix-id': svix_id,
|
|
198
|
-
'svix-timestamp': svix_timestamp,
|
|
199
|
-
'svix-signature': svix_signature,
|
|
200
|
-
});
|
|
201
|
-
} catch (err) {
|
|
202
|
-
return new Response('Invalid signature', { status: 400 });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Handle events
|
|
206
|
-
switch (evt.type) {
|
|
207
|
-
case 'user.created':
|
|
208
|
-
await handleUserCreated(evt.data);
|
|
209
|
-
break;
|
|
210
|
-
case 'user.updated':
|
|
211
|
-
await handleUserUpdated(evt.data);
|
|
212
|
-
break;
|
|
213
|
-
case 'user.deleted':
|
|
214
|
-
await handleUserDeleted(evt.data);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return new Response('OK');
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## Neon Integration
|
|
225
|
-
|
|
226
|
-
### Sync Users to Neon PostgreSQL (via EF Core / Npgsql)
|
|
227
|
-
|
|
228
|
-
```csharp
|
|
229
|
-
// Services/ClerkUserSyncService.cs
|
|
230
|
-
public sealed class ClerkUserSyncService(IDbContextFactory<AppDbContext> dbFactory)
|
|
231
|
-
{
|
|
232
|
-
public async Task HandleUserCreatedAsync(ClerkUserData userData, CancellationToken ct)
|
|
233
|
-
{
|
|
234
|
-
await using var db = await dbFactory.CreateDbContextAsync(ct);
|
|
235
|
-
|
|
236
|
-
db.Profiles.Add(new Profile
|
|
237
|
-
{
|
|
238
|
-
ClerkUserId = userData.Id,
|
|
239
|
-
Email = userData.EmailAddresses[0].EmailAddress,
|
|
240
|
-
FirstName = userData.FirstName,
|
|
241
|
-
LastName = userData.LastName,
|
|
242
|
-
AvatarUrl = userData.ImageUrl
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
await db.SaveChangesAsync(ct);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## Best Practices
|
|
253
|
-
|
|
254
|
-
### Custom Claims
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
// Add custom claims in Clerk Dashboard or via API
|
|
258
|
-
const { sessionClaims } = auth();
|
|
259
|
-
const role = sessionClaims?.metadata?.role as string;
|
|
260
|
-
|
|
261
|
-
if (role !== 'admin') {
|
|
262
|
-
return new Response('Forbidden', { status: 403 });
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Organizations
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
import { auth } from '@clerk/nextjs';
|
|
270
|
-
|
|
271
|
-
const { orgId, orgRole } = auth();
|
|
272
|
-
|
|
273
|
-
if (!orgId) {
|
|
274
|
-
redirect('/create-organization');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (orgRole !== 'admin') {
|
|
278
|
-
return <div>Admin access required</div>;
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## References
|
|
285
|
-
|
|
286
|
-
- [Clerk Next.js Documentation](https://clerk.com/docs/nextjs)
|
|
287
|
-
- [Clerk API Reference](https://clerk.com/docs/reference/backend-api)
|
|
288
|
-
- [Webhooks Guide](https://clerk.com/docs/integrations/webhooks)
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
// ==============================================================================
|
|
2
|
-
// MORPH-SPEC - Clerk Authentication Configuration Template
|
|
3
|
-
// Configuração de autenticação com Clerk para .NET
|
|
4
|
-
// ==============================================================================
|
|
5
|
-
|
|
6
|
-
using Microsoft.AspNetCore.Authentication;
|
|
7
|
-
using System.Security.Claims;
|
|
8
|
-
|
|
9
|
-
namespace {{Namespace}}.Infrastructure.Auth;
|
|
10
|
-
|
|
11
|
-
// ==============================================================================
|
|
12
|
-
// OPTIONS
|
|
13
|
-
// ==============================================================================
|
|
14
|
-
|
|
15
|
-
public class ClerkOptions
|
|
16
|
-
{
|
|
17
|
-
public const string SectionName = "Clerk";
|
|
18
|
-
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Clerk Secret Key (starts with sk_)
|
|
21
|
-
/// </summary>
|
|
22
|
-
public string SecretKey { get; set; } = string.Empty;
|
|
23
|
-
|
|
24
|
-
/// <summary>
|
|
25
|
-
/// Clerk Publishable Key (starts with pk_)
|
|
26
|
-
/// </summary>
|
|
27
|
-
public string PublishableKey { get; set; } = string.Empty;
|
|
28
|
-
|
|
29
|
-
/// <summary>
|
|
30
|
-
/// Clerk Frontend API URL
|
|
31
|
-
/// </summary>
|
|
32
|
-
public string FrontendApi { get; set; } = string.Empty;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ==============================================================================
|
|
36
|
-
// SERVICE EXTENSIONS
|
|
37
|
-
// ==============================================================================
|
|
38
|
-
|
|
39
|
-
public static class ClerkServiceExtensions
|
|
40
|
-
{
|
|
41
|
-
/// <summary>
|
|
42
|
-
/// Adiciona autenticação Clerk ao projeto
|
|
43
|
-
/// Requer: Clerk.Net.AspNetCore.Security
|
|
44
|
-
/// </summary>
|
|
45
|
-
public static IServiceCollection AddClerkAuthentication(
|
|
46
|
-
this IServiceCollection services,
|
|
47
|
-
IConfiguration configuration)
|
|
48
|
-
{
|
|
49
|
-
services.Configure<ClerkOptions>(configuration.GetSection(ClerkOptions.SectionName));
|
|
50
|
-
|
|
51
|
-
// Opção 1: Usando Clerk.Net SDK oficial
|
|
52
|
-
// services.AddClerk(configuration);
|
|
53
|
-
|
|
54
|
-
// Opção 2: Configuração manual com JWT
|
|
55
|
-
services.AddAuthentication("Clerk")
|
|
56
|
-
.AddJwtBearer("Clerk", options =>
|
|
57
|
-
{
|
|
58
|
-
var clerkOptions = configuration.GetSection(ClerkOptions.SectionName).Get<ClerkOptions>()!;
|
|
59
|
-
|
|
60
|
-
options.Authority = clerkOptions.FrontendApi;
|
|
61
|
-
options.TokenValidationParameters = new()
|
|
62
|
-
{
|
|
63
|
-
ValidateIssuer = true,
|
|
64
|
-
ValidateAudience = false,
|
|
65
|
-
ValidateLifetime = true,
|
|
66
|
-
ValidateIssuerSigningKey = true,
|
|
67
|
-
NameClaimType = ClaimTypes.NameIdentifier
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
services.AddAuthorization();
|
|
72
|
-
|
|
73
|
-
return services;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ==============================================================================
|
|
79
|
-
// CLERK USER SERVICE
|
|
80
|
-
// ==============================================================================
|
|
81
|
-
|
|
82
|
-
public interface IClerkUserService
|
|
83
|
-
{
|
|
84
|
-
Task<ClerkUser?> GetCurrentUserAsync(CancellationToken ct = default);
|
|
85
|
-
Task<ClerkUser?> GetUserByIdAsync(string userId, CancellationToken ct = default);
|
|
86
|
-
Task UpdateUserMetadataAsync(string userId, Dictionary<string, object> metadata, CancellationToken ct = default);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public class ClerkUserService : IClerkUserService
|
|
90
|
-
{
|
|
91
|
-
private readonly HttpClient _httpClient;
|
|
92
|
-
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
93
|
-
private readonly ILogger<ClerkUserService> _logger;
|
|
94
|
-
|
|
95
|
-
public ClerkUserService(
|
|
96
|
-
HttpClient httpClient,
|
|
97
|
-
IHttpContextAccessor httpContextAccessor,
|
|
98
|
-
ILogger<ClerkUserService> logger)
|
|
99
|
-
{
|
|
100
|
-
_httpClient = httpClient;
|
|
101
|
-
_httpContextAccessor = httpContextAccessor;
|
|
102
|
-
_logger = logger;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
public async Task<ClerkUser?> GetCurrentUserAsync(CancellationToken ct = default)
|
|
106
|
-
{
|
|
107
|
-
var userId = _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
108
|
-
if (string.IsNullOrEmpty(userId)) return null;
|
|
109
|
-
|
|
110
|
-
return await GetUserByIdAsync(userId, ct);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public async Task<ClerkUser?> GetUserByIdAsync(string userId, CancellationToken ct = default)
|
|
114
|
-
{
|
|
115
|
-
try
|
|
116
|
-
{
|
|
117
|
-
var response = await _httpClient.GetAsync($"users/{userId}", ct);
|
|
118
|
-
|
|
119
|
-
if (!response.IsSuccessStatusCode)
|
|
120
|
-
{
|
|
121
|
-
_logger.LogWarning("Failed to get Clerk user {UserId}: {Status}", userId, response.StatusCode);
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return await response.Content.ReadFromJsonAsync<ClerkUser>(ct);
|
|
126
|
-
}
|
|
127
|
-
catch (Exception ex)
|
|
128
|
-
{
|
|
129
|
-
_logger.LogError(ex, "Error getting Clerk user {UserId}", userId);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public async Task UpdateUserMetadataAsync(string userId, Dictionary<string, object> metadata, CancellationToken ct = default)
|
|
135
|
-
{
|
|
136
|
-
var request = new { public_metadata = metadata };
|
|
137
|
-
var response = await _httpClient.PatchAsJsonAsync($"users/{userId}", request, ct);
|
|
138
|
-
response.EnsureSuccessStatusCode();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ==============================================================================
|
|
143
|
-
// CLERK USER MODEL
|
|
144
|
-
// ==============================================================================
|
|
145
|
-
|
|
146
|
-
public record ClerkUser
|
|
147
|
-
{
|
|
148
|
-
public string Id { get; init; } = string.Empty;
|
|
149
|
-
public string? FirstName { get; init; }
|
|
150
|
-
public string? LastName { get; init; }
|
|
151
|
-
public string? ImageUrl { get; init; }
|
|
152
|
-
public List<ClerkEmailAddress>? EmailAddresses { get; init; }
|
|
153
|
-
public string? PrimaryEmailAddressId { get; init; }
|
|
154
|
-
public Dictionary<string, object>? PublicMetadata { get; init; }
|
|
155
|
-
public long CreatedAt { get; init; }
|
|
156
|
-
public long UpdatedAt { get; init; }
|
|
157
|
-
|
|
158
|
-
public string? PrimaryEmail => EmailAddresses?
|
|
159
|
-
.FirstOrDefault(e => e.Id == PrimaryEmailAddressId)?.EmailAddress;
|
|
160
|
-
|
|
161
|
-
public string FullName => $"{FirstName} {LastName}".Trim();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public record ClerkEmailAddress
|
|
165
|
-
{
|
|
166
|
-
public string Id { get; init; } = string.Empty;
|
|
167
|
-
public string EmailAddress { get; init; } = string.Empty;
|
|
168
|
-
public bool Verified { get; init; }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ==============================================================================
|
|
172
|
-
// MIDDLEWARE PIPELINE
|
|
173
|
-
// ==============================================================================
|
|
174
|
-
|
|
175
|
-
public static class ClerkMiddlewareExtensions
|
|
176
|
-
{
|
|
177
|
-
public static IApplicationBuilder UseClerkAuthentication(this IApplicationBuilder app)
|
|
178
|
-
{
|
|
179
|
-
app.UseAuthentication();
|
|
180
|
-
app.UseAuthorization();
|
|
181
|
-
return app;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ==============================================================================
|
|
186
|
-
// APPSETTINGS EXAMPLE
|
|
187
|
-
// ==============================================================================
|
|
188
|
-
|
|
189
|
-
/*
|
|
190
|
-
{
|
|
191
|
-
"Clerk": {
|
|
192
|
-
"SecretKey": "sk_test_xxxxx",
|
|
193
|
-
"PublishableKey": "pk_test_xxxxx",
|
|
194
|
-
"FrontendApi": "https://your-app.clerk.accounts.dev"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
*/
|
|
198
|
-
|
|
199
|
-
// ==============================================================================
|
|
200
|
-
// PROGRAM.CS EXAMPLE
|
|
201
|
-
// ==============================================================================
|
|
202
|
-
|
|
203
|
-
/*
|
|
204
|
-
// Program.cs
|
|
205
|
-
var builder = WebApplication.CreateBuilder(args);
|
|
206
|
-
|
|
207
|
-
// Add Clerk authentication
|
|
208
|
-
builder.Services.AddClerkAuthentication(builder.Configuration);
|
|
209
|
-
|
|
210
|
-
var app = builder.Build();
|
|
211
|
-
|
|
212
|
-
app.UseClerkAuthentication();
|
|
213
|
-
|
|
214
|
-
app.MapControllers().RequireAuthorization();
|
|
215
|
-
|
|
216
|
-
app.Run();
|
|
217
|
-
*/
|