@veloxts/auth 0.6.83 → 0.6.85
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/CHANGELOG.md +18 -0
- package/dist/adapter.d.ts +35 -17
- package/dist/adapter.js +33 -17
- package/dist/adapters/auth0.d.ts +316 -0
- package/dist/adapters/auth0.js +539 -0
- package/dist/adapters/clerk.d.ts +281 -0
- package/dist/adapters/clerk.js +314 -0
- package/dist/adapters/index.d.ts +46 -0
- package/dist/adapters/index.js +44 -0
- package/dist/adapters/utils.d.ts +31 -0
- package/dist/adapters/utils.js +49 -0
- package/dist/guards.d.ts +71 -1
- package/dist/guards.js +120 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -3
- package/dist/rate-limit.js +85 -57
- package/dist/testing.d.ts +22 -0
- package/dist/testing.js +25 -0
- package/package.json +9 -5
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clerk Adapter for @veloxts/auth
|
|
3
|
+
*
|
|
4
|
+
* Integrates Clerk (https://clerk.com) with VeloxTS's pluggable
|
|
5
|
+
* authentication system. Clerk is a complete user management platform
|
|
6
|
+
* with authentication, user profiles, and organization management.
|
|
7
|
+
*
|
|
8
|
+
* @module auth/adapters/clerk
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
13
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters/clerk';
|
|
14
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
15
|
+
*
|
|
16
|
+
* // Create Clerk client
|
|
17
|
+
* const clerk = createClerkClient({
|
|
18
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
19
|
+
* publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Create adapter
|
|
23
|
+
* const adapter = createClerkAdapter({ clerk });
|
|
24
|
+
*
|
|
25
|
+
* // Create plugin and register (simplified API)
|
|
26
|
+
* const authPlugin = createAuthAdapterPlugin(adapter);
|
|
27
|
+
*
|
|
28
|
+
* app.use(authPlugin);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
32
|
+
import type { AdapterRoute, AdapterSessionResult, AuthAdapterConfig } from '../adapter.js';
|
|
33
|
+
import { BaseAuthAdapter } from '../adapter.js';
|
|
34
|
+
/**
|
|
35
|
+
* Clerk user data
|
|
36
|
+
*
|
|
37
|
+
* Represents the user data returned by Clerk's backend API.
|
|
38
|
+
*/
|
|
39
|
+
export interface ClerkUser {
|
|
40
|
+
/** Unique user ID */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Primary email address */
|
|
43
|
+
primaryEmailAddressId: string | null;
|
|
44
|
+
/** Email addresses associated with the user */
|
|
45
|
+
emailAddresses: Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
emailAddress: string;
|
|
48
|
+
verification: {
|
|
49
|
+
status: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}>;
|
|
52
|
+
/** User's first name */
|
|
53
|
+
firstName: string | null;
|
|
54
|
+
/** User's last name */
|
|
55
|
+
lastName: string | null;
|
|
56
|
+
/** Full name (computed) */
|
|
57
|
+
fullName: string | null;
|
|
58
|
+
/** Profile image URL */
|
|
59
|
+
imageUrl: string;
|
|
60
|
+
/** Whether the user has a profile image */
|
|
61
|
+
hasImage: boolean;
|
|
62
|
+
/** Account creation timestamp (Unix ms) */
|
|
63
|
+
createdAt: number;
|
|
64
|
+
/** Account update timestamp (Unix ms) */
|
|
65
|
+
updatedAt: number;
|
|
66
|
+
/** Whether user has completed onboarding */
|
|
67
|
+
publicMetadata: Record<string, unknown>;
|
|
68
|
+
/** Private metadata (server-side only) */
|
|
69
|
+
privateMetadata: Record<string, unknown>;
|
|
70
|
+
/** Unsafe metadata (can be set by client) */
|
|
71
|
+
unsafeMetadata: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clerk session claims from JWT
|
|
75
|
+
*
|
|
76
|
+
* Represents the claims in a verified Clerk JWT.
|
|
77
|
+
*/
|
|
78
|
+
export interface ClerkSessionClaims {
|
|
79
|
+
/** Subject (user ID) */
|
|
80
|
+
sub: string;
|
|
81
|
+
/** Session ID */
|
|
82
|
+
sid: string;
|
|
83
|
+
/** Issued at timestamp */
|
|
84
|
+
iat: number;
|
|
85
|
+
/** Expiration timestamp */
|
|
86
|
+
exp: number;
|
|
87
|
+
/** Not before timestamp */
|
|
88
|
+
nbf: number;
|
|
89
|
+
/** Issuer */
|
|
90
|
+
iss: string;
|
|
91
|
+
/** Audience */
|
|
92
|
+
aud?: string;
|
|
93
|
+
/** Authorized parties */
|
|
94
|
+
azp?: string;
|
|
95
|
+
/** Organization ID (if using organizations) */
|
|
96
|
+
org_id?: string;
|
|
97
|
+
/** Organization role */
|
|
98
|
+
org_role?: string;
|
|
99
|
+
/** Organization slug */
|
|
100
|
+
org_slug?: string;
|
|
101
|
+
/** Organization permissions */
|
|
102
|
+
org_permissions?: string[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Clerk verification result
|
|
106
|
+
*
|
|
107
|
+
* Result from verifying a Clerk JWT token.
|
|
108
|
+
*/
|
|
109
|
+
export interface ClerkVerificationResult {
|
|
110
|
+
/** Verified session claims */
|
|
111
|
+
sessionClaims: ClerkSessionClaims;
|
|
112
|
+
/** Session ID */
|
|
113
|
+
sessionId: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clerk client interface
|
|
117
|
+
*
|
|
118
|
+
* Minimal interface for the Clerk backend client.
|
|
119
|
+
* We don't import the actual types to avoid requiring @clerk/backend
|
|
120
|
+
* as a dependency - it's a peer dependency.
|
|
121
|
+
*/
|
|
122
|
+
export interface ClerkClient {
|
|
123
|
+
/** Verify a session token */
|
|
124
|
+
verifyToken: (token: string, options?: {
|
|
125
|
+
authorizedParties?: string[];
|
|
126
|
+
audiences?: string | string[];
|
|
127
|
+
clockSkewInMs?: number;
|
|
128
|
+
}) => Promise<ClerkSessionClaims>;
|
|
129
|
+
/** Get user by ID */
|
|
130
|
+
users: {
|
|
131
|
+
getUser: (userId: string) => Promise<ClerkUser>;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Clerk adapter configuration
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const config: ClerkAdapterConfig = {
|
|
140
|
+
* name: 'clerk',
|
|
141
|
+
* clerk: createClerkClient({ secretKey: '...' }),
|
|
142
|
+
* authorizedParties: ['http://localhost:3000'],
|
|
143
|
+
* debug: true,
|
|
144
|
+
* };
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export interface ClerkAdapterConfig extends AuthAdapterConfig {
|
|
148
|
+
/**
|
|
149
|
+
* Clerk client instance
|
|
150
|
+
*
|
|
151
|
+
* Created using `createClerkClient()` from '@clerk/backend'.
|
|
152
|
+
*/
|
|
153
|
+
clerk: ClerkClient;
|
|
154
|
+
/**
|
|
155
|
+
* Authorized parties for token verification
|
|
156
|
+
*
|
|
157
|
+
* List of origins that are allowed to use the token.
|
|
158
|
+
* Should match your application's origins.
|
|
159
|
+
*
|
|
160
|
+
* @example ['http://localhost:3000', 'https://myapp.com']
|
|
161
|
+
*/
|
|
162
|
+
authorizedParties?: string[];
|
|
163
|
+
/**
|
|
164
|
+
* Expected audience for token verification
|
|
165
|
+
*
|
|
166
|
+
* If set, tokens must contain this audience claim.
|
|
167
|
+
*/
|
|
168
|
+
audiences?: string | string[];
|
|
169
|
+
/**
|
|
170
|
+
* Clock tolerance in seconds for JWT validation
|
|
171
|
+
*
|
|
172
|
+
* Allows for slight differences in server clocks.
|
|
173
|
+
* Passed to Clerk's verifyToken as clockSkewInMs (converted internally).
|
|
174
|
+
*
|
|
175
|
+
* @default 5
|
|
176
|
+
*/
|
|
177
|
+
clockTolerance?: number;
|
|
178
|
+
/**
|
|
179
|
+
* Custom header name for the authorization token
|
|
180
|
+
*
|
|
181
|
+
* @default 'authorization'
|
|
182
|
+
*/
|
|
183
|
+
authHeader?: string;
|
|
184
|
+
/**
|
|
185
|
+
* Whether to fetch full user data from Clerk API
|
|
186
|
+
*
|
|
187
|
+
* When true, makes an additional API call to get full user profile.
|
|
188
|
+
* When false, only uses data from the JWT claims.
|
|
189
|
+
*
|
|
190
|
+
* @default true
|
|
191
|
+
*/
|
|
192
|
+
fetchUserData?: boolean;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Clerk Adapter
|
|
196
|
+
*
|
|
197
|
+
* Integrates Clerk with VeloxTS by:
|
|
198
|
+
* - Verifying Clerk JWTs from the Authorization header
|
|
199
|
+
* - Loading user data from Clerk's API (optional)
|
|
200
|
+
* - Transforming Clerk's user/session to VeloxTS format
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const adapter = new ClerkAdapter();
|
|
205
|
+
* const plugin = createAuthAdapterPlugin({
|
|
206
|
+
* adapter,
|
|
207
|
+
* config: {
|
|
208
|
+
* name: 'clerk',
|
|
209
|
+
* clerk: createClerkClient({ secretKey: '...' }),
|
|
210
|
+
* },
|
|
211
|
+
* });
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export declare class ClerkAdapter extends BaseAuthAdapter<ClerkAdapterConfig> {
|
|
215
|
+
private clerk;
|
|
216
|
+
private authorizedParties?;
|
|
217
|
+
private audiences?;
|
|
218
|
+
private clockToleranceMs;
|
|
219
|
+
private authHeader;
|
|
220
|
+
private fetchUserData;
|
|
221
|
+
constructor();
|
|
222
|
+
/**
|
|
223
|
+
* Initialize the adapter with Clerk client
|
|
224
|
+
*/
|
|
225
|
+
initialize(fastify: FastifyInstance, config: ClerkAdapterConfig): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Get session from Clerk JWT
|
|
228
|
+
*
|
|
229
|
+
* Extracts the Bearer token from the Authorization header,
|
|
230
|
+
* verifies it with Clerk, and optionally fetches user data.
|
|
231
|
+
*/
|
|
232
|
+
getSession(request: FastifyRequest): Promise<AdapterSessionResult | null>;
|
|
233
|
+
/**
|
|
234
|
+
* Get routes for Clerk handler
|
|
235
|
+
*
|
|
236
|
+
* Clerk doesn't typically need server-side routes - authentication
|
|
237
|
+
* is handled client-side via Clerk's SDK. Return empty array.
|
|
238
|
+
*
|
|
239
|
+
* If you need to handle Clerk webhooks, override this method.
|
|
240
|
+
*/
|
|
241
|
+
getRoutes(): AdapterRoute[];
|
|
242
|
+
/**
|
|
243
|
+
* Clean up adapter resources
|
|
244
|
+
*/
|
|
245
|
+
cleanup(): Promise<void>;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Create a Clerk adapter
|
|
249
|
+
*
|
|
250
|
+
* This is the recommended way to create a Clerk adapter.
|
|
251
|
+
* It returns an adapter instance with the configuration attached.
|
|
252
|
+
*
|
|
253
|
+
* @param config - Adapter configuration
|
|
254
|
+
* @returns Clerk adapter with configuration
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters/clerk';
|
|
259
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
260
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
261
|
+
*
|
|
262
|
+
* const clerk = createClerkClient({
|
|
263
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
264
|
+
* });
|
|
265
|
+
*
|
|
266
|
+
* const adapter = createClerkAdapter({
|
|
267
|
+
* clerk,
|
|
268
|
+
* authorizedParties: ['http://localhost:3000'],
|
|
269
|
+
* debug: process.env.NODE_ENV === 'development',
|
|
270
|
+
* });
|
|
271
|
+
*
|
|
272
|
+
* // Simplified API - just pass the adapter
|
|
273
|
+
* const authPlugin = createAuthAdapterPlugin(adapter);
|
|
274
|
+
*
|
|
275
|
+
* app.use(authPlugin);
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export declare function createClerkAdapter(config: ClerkAdapterConfig): ClerkAdapter & {
|
|
279
|
+
config: ClerkAdapterConfig;
|
|
280
|
+
};
|
|
281
|
+
export { AuthAdapterError } from '../adapter.js';
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clerk Adapter for @veloxts/auth
|
|
3
|
+
*
|
|
4
|
+
* Integrates Clerk (https://clerk.com) with VeloxTS's pluggable
|
|
5
|
+
* authentication system. Clerk is a complete user management platform
|
|
6
|
+
* with authentication, user profiles, and organization management.
|
|
7
|
+
*
|
|
8
|
+
* @module auth/adapters/clerk
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
13
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters/clerk';
|
|
14
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
15
|
+
*
|
|
16
|
+
* // Create Clerk client
|
|
17
|
+
* const clerk = createClerkClient({
|
|
18
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
19
|
+
* publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Create adapter
|
|
23
|
+
* const adapter = createClerkAdapter({ clerk });
|
|
24
|
+
*
|
|
25
|
+
* // Create plugin and register (simplified API)
|
|
26
|
+
* const authPlugin = createAuthAdapterPlugin(adapter);
|
|
27
|
+
*
|
|
28
|
+
* app.use(authPlugin);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { AuthAdapterError, BaseAuthAdapter } from '../adapter.js';
|
|
32
|
+
import { extractBearerToken } from './utils.js';
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Clerk Adapter Implementation
|
|
35
|
+
// ============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Clerk Adapter
|
|
38
|
+
*
|
|
39
|
+
* Integrates Clerk with VeloxTS by:
|
|
40
|
+
* - Verifying Clerk JWTs from the Authorization header
|
|
41
|
+
* - Loading user data from Clerk's API (optional)
|
|
42
|
+
* - Transforming Clerk's user/session to VeloxTS format
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const adapter = new ClerkAdapter();
|
|
47
|
+
* const plugin = createAuthAdapterPlugin({
|
|
48
|
+
* adapter,
|
|
49
|
+
* config: {
|
|
50
|
+
* name: 'clerk',
|
|
51
|
+
* clerk: createClerkClient({ secretKey: '...' }),
|
|
52
|
+
* },
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export class ClerkAdapter extends BaseAuthAdapter {
|
|
57
|
+
clerk = null;
|
|
58
|
+
authorizedParties;
|
|
59
|
+
audiences;
|
|
60
|
+
clockToleranceMs = 5000;
|
|
61
|
+
authHeader = 'authorization';
|
|
62
|
+
fetchUserData = true;
|
|
63
|
+
constructor() {
|
|
64
|
+
super('clerk', '1.0.0');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Initialize the adapter with Clerk client
|
|
68
|
+
*/
|
|
69
|
+
async initialize(fastify, config) {
|
|
70
|
+
await super.initialize(fastify, config);
|
|
71
|
+
if (!config.clerk) {
|
|
72
|
+
throw new AuthAdapterError('Clerk client is required in adapter config', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
73
|
+
}
|
|
74
|
+
this.clerk = config.clerk;
|
|
75
|
+
this.authorizedParties = config.authorizedParties;
|
|
76
|
+
this.audiences = config.audiences;
|
|
77
|
+
// Convert clockTolerance from seconds to milliseconds for Clerk SDK
|
|
78
|
+
this.clockToleranceMs = (config.clockTolerance ?? 5) * 1000;
|
|
79
|
+
this.authHeader = config.authHeader ?? 'authorization';
|
|
80
|
+
this.fetchUserData = config.fetchUserData ?? true;
|
|
81
|
+
this.debug(`Initialized with fetchUserData: ${this.fetchUserData}`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get session from Clerk JWT
|
|
85
|
+
*
|
|
86
|
+
* Extracts the Bearer token from the Authorization header,
|
|
87
|
+
* verifies it with Clerk, and optionally fetches user data.
|
|
88
|
+
*/
|
|
89
|
+
async getSession(request) {
|
|
90
|
+
if (!this.clerk) {
|
|
91
|
+
throw new AuthAdapterError('Clerk adapter not initialized', 500, 'ADAPTER_NOT_CONFIGURED');
|
|
92
|
+
}
|
|
93
|
+
// Extract token from Authorization header
|
|
94
|
+
const authHeaderValue = request.headers[this.authHeader];
|
|
95
|
+
if (!authHeaderValue || typeof authHeaderValue !== 'string') {
|
|
96
|
+
this.debug('No authorization header found');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
// Extract Bearer token
|
|
100
|
+
const token = extractBearerToken(authHeaderValue);
|
|
101
|
+
if (!token) {
|
|
102
|
+
this.debug('No Bearer token found in authorization header');
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
// Verify the token with Clerk
|
|
107
|
+
const claims = await this.clerk.verifyToken(token, {
|
|
108
|
+
authorizedParties: this.authorizedParties,
|
|
109
|
+
audiences: this.audiences,
|
|
110
|
+
clockSkewInMs: this.clockToleranceMs,
|
|
111
|
+
});
|
|
112
|
+
this.debug(`Token verified for user: ${claims.sub}`);
|
|
113
|
+
// Optionally fetch full user data
|
|
114
|
+
let user = null;
|
|
115
|
+
if (this.fetchUserData) {
|
|
116
|
+
try {
|
|
117
|
+
user = await this.clerk.users.getUser(claims.sub);
|
|
118
|
+
this.debug(`User data fetched for: ${user.id}`);
|
|
119
|
+
}
|
|
120
|
+
catch (fetchError) {
|
|
121
|
+
this.error('Failed to fetch user data', fetchError instanceof Error ? fetchError : undefined);
|
|
122
|
+
// Continue without full user data - we still have claims
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Transform to VeloxTS format
|
|
126
|
+
return transformClerkSession(claims, user);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Token verification failed - this is expected for invalid/expired tokens
|
|
130
|
+
if (error instanceof Error) {
|
|
131
|
+
this.debug(`Token verification failed: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get routes for Clerk handler
|
|
138
|
+
*
|
|
139
|
+
* Clerk doesn't typically need server-side routes - authentication
|
|
140
|
+
* is handled client-side via Clerk's SDK. Return empty array.
|
|
141
|
+
*
|
|
142
|
+
* If you need to handle Clerk webhooks, override this method.
|
|
143
|
+
*/
|
|
144
|
+
getRoutes() {
|
|
145
|
+
// Clerk handles auth on the client side via their SDK
|
|
146
|
+
// Server only needs to verify tokens, not handle auth routes
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Clean up adapter resources
|
|
151
|
+
*/
|
|
152
|
+
async cleanup() {
|
|
153
|
+
await super.cleanup();
|
|
154
|
+
this.clerk = null;
|
|
155
|
+
this.debug('Adapter cleaned up');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Helper Functions
|
|
160
|
+
// ============================================================================
|
|
161
|
+
/**
|
|
162
|
+
* Get primary email from Clerk user
|
|
163
|
+
*
|
|
164
|
+
* @param user - Clerk user object
|
|
165
|
+
* @returns Primary email address or 'unknown' if not found
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
function getPrimaryEmail(user) {
|
|
170
|
+
if (!user) {
|
|
171
|
+
return 'unknown';
|
|
172
|
+
}
|
|
173
|
+
if (user.primaryEmailAddressId && user.emailAddresses.length > 0) {
|
|
174
|
+
const primary = user.emailAddresses.find((email) => email.id === user.primaryEmailAddressId);
|
|
175
|
+
if (primary) {
|
|
176
|
+
return primary.emailAddress;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Fallback to first email
|
|
180
|
+
if (user.emailAddresses.length > 0) {
|
|
181
|
+
return user.emailAddresses[0].emailAddress;
|
|
182
|
+
}
|
|
183
|
+
return 'unknown';
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if user email is verified
|
|
187
|
+
*
|
|
188
|
+
* @param user - Clerk user object
|
|
189
|
+
* @returns Whether the primary email is verified
|
|
190
|
+
*
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function isEmailVerified(user) {
|
|
194
|
+
if (!user) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (user.primaryEmailAddressId && user.emailAddresses.length > 0) {
|
|
198
|
+
const primary = user.emailAddresses.find((email) => email.id === user.primaryEmailAddressId);
|
|
199
|
+
if (primary?.verification?.status === 'verified') {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get display name from Clerk user
|
|
207
|
+
*
|
|
208
|
+
* @param user - Clerk user object
|
|
209
|
+
* @returns Display name or undefined
|
|
210
|
+
*
|
|
211
|
+
* @internal
|
|
212
|
+
*/
|
|
213
|
+
function getDisplayName(user) {
|
|
214
|
+
if (!user) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
if (user.fullName) {
|
|
218
|
+
return user.fullName;
|
|
219
|
+
}
|
|
220
|
+
if (user.firstName || user.lastName) {
|
|
221
|
+
return [user.firstName, user.lastName].filter(Boolean).join(' ');
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Transform Clerk session to VeloxTS format
|
|
227
|
+
*
|
|
228
|
+
* @param claims - Verified JWT claims
|
|
229
|
+
* @param user - Optional full user data from Clerk API
|
|
230
|
+
* @returns VeloxTS adapter session result
|
|
231
|
+
*
|
|
232
|
+
* @internal
|
|
233
|
+
*/
|
|
234
|
+
function transformClerkSession(claims, user) {
|
|
235
|
+
return {
|
|
236
|
+
user: {
|
|
237
|
+
id: claims.sub,
|
|
238
|
+
email: getPrimaryEmail(user),
|
|
239
|
+
name: getDisplayName(user),
|
|
240
|
+
emailVerified: isEmailVerified(user),
|
|
241
|
+
image: user?.imageUrl,
|
|
242
|
+
providerData: {
|
|
243
|
+
// Include organization data if present
|
|
244
|
+
...(claims.org_id && { organizationId: claims.org_id }),
|
|
245
|
+
...(claims.org_role && { organizationRole: claims.org_role }),
|
|
246
|
+
...(claims.org_slug && { organizationSlug: claims.org_slug }),
|
|
247
|
+
...(claims.org_permissions && { organizationPermissions: claims.org_permissions }),
|
|
248
|
+
// Include metadata if user data was fetched
|
|
249
|
+
...(user && {
|
|
250
|
+
publicMetadata: user.publicMetadata,
|
|
251
|
+
privateMetadata: user.privateMetadata,
|
|
252
|
+
createdAt: user.createdAt,
|
|
253
|
+
updatedAt: user.updatedAt,
|
|
254
|
+
}),
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
session: {
|
|
258
|
+
sessionId: claims.sid,
|
|
259
|
+
userId: claims.sub,
|
|
260
|
+
expiresAt: claims.exp * 1000, // Convert to Unix ms
|
|
261
|
+
isActive: true,
|
|
262
|
+
providerData: {
|
|
263
|
+
issuedAt: claims.iat * 1000,
|
|
264
|
+
notBefore: claims.nbf * 1000,
|
|
265
|
+
issuer: claims.iss,
|
|
266
|
+
...(claims.azp && { authorizedParty: claims.azp }),
|
|
267
|
+
...(claims.aud && { audience: claims.aud }),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// Factory Function
|
|
274
|
+
// ============================================================================
|
|
275
|
+
/**
|
|
276
|
+
* Create a Clerk adapter
|
|
277
|
+
*
|
|
278
|
+
* This is the recommended way to create a Clerk adapter.
|
|
279
|
+
* It returns an adapter instance with the configuration attached.
|
|
280
|
+
*
|
|
281
|
+
* @param config - Adapter configuration
|
|
282
|
+
* @returns Clerk adapter with configuration
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters/clerk';
|
|
287
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
288
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
289
|
+
*
|
|
290
|
+
* const clerk = createClerkClient({
|
|
291
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
292
|
+
* });
|
|
293
|
+
*
|
|
294
|
+
* const adapter = createClerkAdapter({
|
|
295
|
+
* clerk,
|
|
296
|
+
* authorizedParties: ['http://localhost:3000'],
|
|
297
|
+
* debug: process.env.NODE_ENV === 'development',
|
|
298
|
+
* });
|
|
299
|
+
*
|
|
300
|
+
* // Simplified API - just pass the adapter
|
|
301
|
+
* const authPlugin = createAuthAdapterPlugin(adapter);
|
|
302
|
+
*
|
|
303
|
+
* app.use(authPlugin);
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export function createClerkAdapter(config) {
|
|
307
|
+
const adapter = new ClerkAdapter();
|
|
308
|
+
// Attach config for easy access when creating plugin
|
|
309
|
+
return Object.assign(adapter, { config });
|
|
310
|
+
}
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Re-exports
|
|
313
|
+
// ============================================================================
|
|
314
|
+
export { AuthAdapterError } from '../adapter.js';
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Available adapters:
|
|
9
9
|
* - **JwtAdapter** - Built-in JWT authentication using the adapter pattern
|
|
10
10
|
* - **BetterAuthAdapter** - Integration with BetterAuth library
|
|
11
|
+
* - **ClerkAdapter** - Integration with Clerk authentication platform
|
|
12
|
+
* - **Auth0Adapter** - Integration with Auth0 identity platform
|
|
11
13
|
*
|
|
12
14
|
* @module auth/adapters
|
|
13
15
|
*
|
|
@@ -40,8 +42,52 @@
|
|
|
40
42
|
* config: adapter.config,
|
|
41
43
|
* });
|
|
42
44
|
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Clerk Adapter
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters';
|
|
49
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
50
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
51
|
+
*
|
|
52
|
+
* const clerk = createClerkClient({
|
|
53
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* const adapter = createClerkAdapter({
|
|
57
|
+
* name: 'clerk',
|
|
58
|
+
* clerk,
|
|
59
|
+
* authorizedParties: ['http://localhost:3000'],
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* const plugin = createAuthAdapterPlugin({
|
|
63
|
+
* adapter,
|
|
64
|
+
* config: adapter.config,
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Auth0 Adapter
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { createAuth0Adapter } from '@veloxts/auth/adapters';
|
|
71
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
72
|
+
*
|
|
73
|
+
* const adapter = createAuth0Adapter({
|
|
74
|
+
* name: 'auth0',
|
|
75
|
+
* domain: process.env.AUTH0_DOMAIN!,
|
|
76
|
+
* audience: process.env.AUTH0_AUDIENCE!,
|
|
77
|
+
* clientId: process.env.AUTH0_CLIENT_ID, // Optional
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* const plugin = createAuthAdapterPlugin({
|
|
81
|
+
* adapter,
|
|
82
|
+
* config: adapter.config,
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
43
85
|
*/
|
|
44
86
|
export type { JwtAdapterConfig } from './jwt-adapter.js';
|
|
45
87
|
export { AuthAdapterError, createJwtAdapter, JwtAdapter } from './jwt-adapter.js';
|
|
46
88
|
export type { BetterAuthAdapterConfig, BetterAuthApi, BetterAuthHandler, BetterAuthInstance, BetterAuthSession, BetterAuthSessionResult, BetterAuthUser, } from './better-auth.js';
|
|
47
89
|
export { BetterAuthAdapter, createBetterAuthAdapter } from './better-auth.js';
|
|
90
|
+
export type { ClerkAdapterConfig, ClerkClient, ClerkSessionClaims, ClerkUser, ClerkVerificationResult, } from './clerk.js';
|
|
91
|
+
export { ClerkAdapter, createClerkAdapter } from './clerk.js';
|
|
92
|
+
export type { Auth0AdapterConfig, Auth0Claims, Auth0User, JWKSKey, JWKSResponse, JwtVerifier, } from './auth0.js';
|
|
93
|
+
export { Auth0Adapter, createAuth0Adapter } from './auth0.js';
|
package/dist/adapters/index.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Available adapters:
|
|
9
9
|
* - **JwtAdapter** - Built-in JWT authentication using the adapter pattern
|
|
10
10
|
* - **BetterAuthAdapter** - Integration with BetterAuth library
|
|
11
|
+
* - **ClerkAdapter** - Integration with Clerk authentication platform
|
|
12
|
+
* - **Auth0Adapter** - Integration with Auth0 identity platform
|
|
11
13
|
*
|
|
12
14
|
* @module auth/adapters
|
|
13
15
|
*
|
|
@@ -40,6 +42,48 @@
|
|
|
40
42
|
* config: adapter.config,
|
|
41
43
|
* });
|
|
42
44
|
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Clerk Adapter
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { createClerkAdapter } from '@veloxts/auth/adapters';
|
|
49
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
50
|
+
* import { createClerkClient } from '@clerk/backend';
|
|
51
|
+
*
|
|
52
|
+
* const clerk = createClerkClient({
|
|
53
|
+
* secretKey: process.env.CLERK_SECRET_KEY!,
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* const adapter = createClerkAdapter({
|
|
57
|
+
* name: 'clerk',
|
|
58
|
+
* clerk,
|
|
59
|
+
* authorizedParties: ['http://localhost:3000'],
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* const plugin = createAuthAdapterPlugin({
|
|
63
|
+
* adapter,
|
|
64
|
+
* config: adapter.config,
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Auth0 Adapter
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { createAuth0Adapter } from '@veloxts/auth/adapters';
|
|
71
|
+
* import { createAuthAdapterPlugin } from '@veloxts/auth';
|
|
72
|
+
*
|
|
73
|
+
* const adapter = createAuth0Adapter({
|
|
74
|
+
* name: 'auth0',
|
|
75
|
+
* domain: process.env.AUTH0_DOMAIN!,
|
|
76
|
+
* audience: process.env.AUTH0_AUDIENCE!,
|
|
77
|
+
* clientId: process.env.AUTH0_CLIENT_ID, // Optional
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* const plugin = createAuthAdapterPlugin({
|
|
81
|
+
* adapter,
|
|
82
|
+
* config: adapter.config,
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
43
85
|
*/
|
|
44
86
|
export { AuthAdapterError, createJwtAdapter, JwtAdapter } from './jwt-adapter.js';
|
|
45
87
|
export { BetterAuthAdapter, createBetterAuthAdapter } from './better-auth.js';
|
|
88
|
+
export { ClerkAdapter, createClerkAdapter } from './clerk.js';
|
|
89
|
+
export { Auth0Adapter, createAuth0Adapter } from './auth0.js';
|