@juspay/neurolink 9.31.1 → 9.32.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/CHANGELOG.md +12 -0
- package/dist/auth/AuthProviderFactory.d.ts +71 -0
- package/dist/auth/AuthProviderFactory.js +111 -0
- package/dist/auth/AuthProviderRegistry.d.ts +33 -0
- package/dist/auth/AuthProviderRegistry.js +190 -0
- package/dist/auth/RequestContext.d.ts +23 -0
- package/dist/auth/RequestContext.js +78 -0
- package/dist/auth/authContext.d.ts +198 -0
- package/dist/auth/authContext.js +314 -0
- package/dist/auth/errors.d.ts +63 -0
- package/dist/auth/errors.js +39 -0
- package/dist/auth/index.d.ts +20 -8
- package/dist/auth/index.js +35 -7
- package/dist/auth/middleware/AuthMiddleware.d.ts +181 -0
- package/dist/auth/middleware/AuthMiddleware.js +519 -0
- package/dist/auth/middleware/rateLimitByUser.d.ts +282 -0
- package/dist/auth/middleware/rateLimitByUser.js +554 -0
- package/dist/auth/providers/BaseAuthProvider.d.ts +259 -0
- package/dist/auth/providers/BaseAuthProvider.js +723 -0
- package/dist/auth/providers/CognitoProvider.d.ts +61 -0
- package/dist/auth/providers/CognitoProvider.js +304 -0
- package/dist/auth/providers/KeycloakProvider.d.ts +61 -0
- package/dist/auth/providers/KeycloakProvider.js +393 -0
- package/dist/auth/providers/auth0.d.ts +59 -0
- package/dist/auth/providers/auth0.js +274 -0
- package/dist/auth/providers/betterAuth.d.ts +51 -0
- package/dist/auth/providers/betterAuth.js +182 -0
- package/dist/auth/providers/clerk.d.ts +65 -0
- package/dist/auth/providers/clerk.js +317 -0
- package/dist/auth/providers/custom.d.ts +64 -0
- package/dist/auth/providers/custom.js +112 -0
- package/dist/auth/providers/firebase.d.ts +63 -0
- package/dist/auth/providers/firebase.js +226 -0
- package/dist/auth/providers/jwt.d.ts +68 -0
- package/dist/auth/providers/jwt.js +212 -0
- package/dist/auth/providers/oauth2.d.ts +73 -0
- package/dist/auth/providers/oauth2.js +303 -0
- package/dist/auth/providers/supabase.d.ts +63 -0
- package/dist/auth/providers/supabase.js +259 -0
- package/dist/auth/providers/workos.d.ts +61 -0
- package/dist/auth/providers/workos.js +284 -0
- package/dist/auth/serverBridge.d.ts +14 -0
- package/dist/auth/serverBridge.js +25 -0
- package/dist/auth/sessionManager.d.ts +142 -0
- package/dist/auth/sessionManager.js +437 -0
- package/dist/cli/commands/authProviders.d.ts +43 -0
- package/dist/cli/commands/authProviders.js +399 -0
- package/dist/cli/commands/proxy.js +4 -2
- package/dist/cli/factories/authCommandFactory.d.ts +23 -5
- package/dist/cli/factories/authCommandFactory.js +108 -5
- package/dist/cli/parser.js +1 -1
- package/dist/client/auth/AuthProviderFactory.js +111 -0
- package/dist/client/auth/AuthProviderRegistry.js +190 -0
- package/dist/client/auth/RequestContext.js +78 -0
- package/dist/client/auth/accountPool.js +178 -0
- package/dist/client/auth/authContext.js +314 -0
- package/dist/client/auth/errors.js +39 -0
- package/dist/client/auth/index.js +61 -0
- package/dist/client/auth/middleware/AuthMiddleware.js +519 -0
- package/dist/client/auth/middleware/rateLimitByUser.js +554 -0
- package/dist/client/auth/providers/BaseAuthProvider.js +723 -0
- package/dist/client/auth/providers/CognitoProvider.js +304 -0
- package/dist/client/auth/providers/KeycloakProvider.js +393 -0
- package/dist/client/auth/providers/auth0.js +274 -0
- package/dist/client/auth/providers/betterAuth.js +182 -0
- package/dist/client/auth/providers/clerk.js +317 -0
- package/dist/client/auth/providers/custom.js +112 -0
- package/dist/client/auth/providers/firebase.js +226 -0
- package/dist/client/auth/providers/jwt.js +212 -0
- package/dist/client/auth/providers/oauth2.js +303 -0
- package/dist/client/auth/providers/supabase.js +259 -0
- package/dist/client/auth/providers/workos.js +284 -0
- package/dist/client/auth/serverBridge.js +25 -0
- package/dist/client/auth/sessionManager.js +437 -0
- package/dist/client/core/infrastructure/baseRegistry.js +5 -1
- package/dist/client/index.js +25 -0
- package/dist/client/mcp/toolRegistry.js +11 -1
- package/dist/client/neurolink.js +218 -0
- package/dist/client/rag/ChunkerRegistry.js +2 -2
- package/dist/client/rag/metadata/MetadataExtractorRegistry.js +2 -2
- package/dist/client/rag/reranker/RerankerRegistry.js +2 -2
- package/dist/client/server/routes/agentRoutes.js +20 -2
- package/dist/client/types/authTypes.js +2 -1
- package/dist/core/infrastructure/baseRegistry.d.ts +3 -1
- package/dist/core/infrastructure/baseRegistry.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25 -0
- package/dist/lib/auth/AuthProviderFactory.d.ts +71 -0
- package/dist/lib/auth/AuthProviderFactory.js +112 -0
- package/dist/lib/auth/AuthProviderRegistry.d.ts +33 -0
- package/dist/lib/auth/AuthProviderRegistry.js +191 -0
- package/dist/lib/auth/RequestContext.d.ts +23 -0
- package/dist/lib/auth/RequestContext.js +79 -0
- package/dist/lib/auth/authContext.d.ts +198 -0
- package/dist/lib/auth/authContext.js +315 -0
- package/dist/lib/auth/errors.d.ts +63 -0
- package/dist/lib/auth/errors.js +40 -0
- package/dist/lib/auth/index.d.ts +20 -8
- package/dist/lib/auth/index.js +35 -7
- package/dist/lib/auth/middleware/AuthMiddleware.d.ts +181 -0
- package/dist/lib/auth/middleware/AuthMiddleware.js +520 -0
- package/dist/lib/auth/middleware/rateLimitByUser.d.ts +282 -0
- package/dist/lib/auth/middleware/rateLimitByUser.js +555 -0
- package/dist/lib/auth/providers/BaseAuthProvider.d.ts +259 -0
- package/dist/lib/auth/providers/BaseAuthProvider.js +724 -0
- package/dist/lib/auth/providers/CognitoProvider.d.ts +61 -0
- package/dist/lib/auth/providers/CognitoProvider.js +305 -0
- package/dist/lib/auth/providers/KeycloakProvider.d.ts +61 -0
- package/dist/lib/auth/providers/KeycloakProvider.js +394 -0
- package/dist/lib/auth/providers/auth0.d.ts +59 -0
- package/dist/lib/auth/providers/auth0.js +275 -0
- package/dist/lib/auth/providers/betterAuth.d.ts +51 -0
- package/dist/lib/auth/providers/betterAuth.js +183 -0
- package/dist/lib/auth/providers/clerk.d.ts +65 -0
- package/dist/lib/auth/providers/clerk.js +318 -0
- package/dist/lib/auth/providers/custom.d.ts +64 -0
- package/dist/lib/auth/providers/custom.js +113 -0
- package/dist/lib/auth/providers/firebase.d.ts +63 -0
- package/dist/lib/auth/providers/firebase.js +227 -0
- package/dist/lib/auth/providers/jwt.d.ts +68 -0
- package/dist/lib/auth/providers/jwt.js +213 -0
- package/dist/lib/auth/providers/oauth2.d.ts +73 -0
- package/dist/lib/auth/providers/oauth2.js +304 -0
- package/dist/lib/auth/providers/supabase.d.ts +63 -0
- package/dist/lib/auth/providers/supabase.js +260 -0
- package/dist/lib/auth/providers/workos.d.ts +61 -0
- package/dist/lib/auth/providers/workos.js +285 -0
- package/dist/lib/auth/serverBridge.d.ts +14 -0
- package/dist/lib/auth/serverBridge.js +26 -0
- package/dist/lib/auth/sessionManager.d.ts +142 -0
- package/dist/lib/auth/sessionManager.js +438 -0
- package/dist/lib/core/infrastructure/baseRegistry.d.ts +3 -1
- package/dist/lib/core/infrastructure/baseRegistry.js +5 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +25 -0
- package/dist/lib/mcp/toolRegistry.js +11 -1
- package/dist/lib/neurolink.d.ts +42 -1
- package/dist/lib/neurolink.js +218 -0
- package/dist/lib/rag/ChunkerRegistry.js +2 -2
- package/dist/lib/rag/metadata/MetadataExtractorRegistry.js +2 -2
- package/dist/lib/rag/reranker/RerankerRegistry.js +2 -2
- package/dist/lib/server/routes/agentRoutes.js +20 -2
- package/dist/lib/types/authTypes.d.ts +937 -1
- package/dist/lib/types/authTypes.js +2 -1
- package/dist/lib/types/configTypes.d.ts +46 -0
- package/dist/lib/types/generateTypes.d.ts +6 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/streamTypes.d.ts +6 -0
- package/dist/mcp/toolRegistry.js +11 -1
- package/dist/neurolink.d.ts +42 -1
- package/dist/neurolink.js +218 -0
- package/dist/rag/ChunkerRegistry.js +2 -2
- package/dist/rag/metadata/MetadataExtractorRegistry.js +2 -2
- package/dist/rag/reranker/RerankerRegistry.js +2 -2
- package/dist/server/routes/agentRoutes.js +20 -2
- package/dist/types/authTypes.d.ts +937 -1
- package/dist/types/authTypes.js +2 -1
- package/dist/types/configTypes.d.ts +46 -0
- package/dist/types/generateTypes.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/streamTypes.d.ts +6 -0
- package/package.json +2 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// src/lib/auth/providers/supabase.ts
|
|
2
|
+
import { BaseAuthProvider } from "./BaseAuthProvider.js";
|
|
3
|
+
import { AuthError } from "../errors.js";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
6
|
+
import * as jose from "jose";
|
|
7
|
+
/**
|
|
8
|
+
* Supabase Authentication Provider
|
|
9
|
+
*
|
|
10
|
+
* Supports Supabase JWT validation and user management.
|
|
11
|
+
* Can validate tokens locally with JWT secret or via Supabase API.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Local JWT validation with JWT secret
|
|
15
|
+
* - API-based token validation
|
|
16
|
+
* - User profile fetching (requires service role key)
|
|
17
|
+
* - Role extraction from app_metadata
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const supabase = new SupabaseAuthProvider({
|
|
22
|
+
* type: "supabase",
|
|
23
|
+
* url: "https://your-project.supabase.co",
|
|
24
|
+
* anonKey: "your-anon-key",
|
|
25
|
+
* jwtSecret: "your-jwt-secret" // Optional for local validation
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const result = await supabase.authenticateToken(accessToken);
|
|
29
|
+
* if (result.valid) {
|
|
30
|
+
* console.log("Authenticated user:", result.user);
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class SupabaseAuthProvider extends BaseAuthProvider {
|
|
35
|
+
type = "supabase";
|
|
36
|
+
supabaseUrl;
|
|
37
|
+
anonKey;
|
|
38
|
+
serviceRoleKey;
|
|
39
|
+
jwtSecret;
|
|
40
|
+
constructor(config) {
|
|
41
|
+
super(config);
|
|
42
|
+
if (!config.url) {
|
|
43
|
+
throw AuthError.create("CONFIGURATION_ERROR", "Supabase URL is required", { details: { missingFields: ["url"] } });
|
|
44
|
+
}
|
|
45
|
+
if (!config.anonKey) {
|
|
46
|
+
throw AuthError.create("CONFIGURATION_ERROR", "Supabase anon key is required", { details: { missingFields: ["anonKey"] } });
|
|
47
|
+
}
|
|
48
|
+
this.supabaseUrl = config.url.replace(/\/$/, ""); // Remove trailing slash
|
|
49
|
+
this.anonKey = config.anonKey;
|
|
50
|
+
this.serviceRoleKey = config.serviceRoleKey;
|
|
51
|
+
this.jwtSecret = config.jwtSecret;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate Supabase JWT
|
|
55
|
+
*/
|
|
56
|
+
async authenticateToken(token, _context) {
|
|
57
|
+
try {
|
|
58
|
+
// If JWT secret is provided, verify locally
|
|
59
|
+
if (this.jwtSecret) {
|
|
60
|
+
const secret = new TextEncoder().encode(this.jwtSecret);
|
|
61
|
+
const { payload } = await jose.jwtVerify(token, secret);
|
|
62
|
+
// Reject tokens without a sub claim (anon/service_role JWTs)
|
|
63
|
+
if (!payload.sub) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
error: "Token missing sub claim: cannot authenticate without a user identity",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Only accept tokens with "authenticated" role
|
|
70
|
+
const role = payload.role;
|
|
71
|
+
if (role && role !== "authenticated") {
|
|
72
|
+
return {
|
|
73
|
+
valid: false,
|
|
74
|
+
error: `Invalid token role: ${role}. Only "authenticated" role is accepted`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const user = this.payloadToUser(payload);
|
|
78
|
+
return {
|
|
79
|
+
valid: true,
|
|
80
|
+
payload: payload,
|
|
81
|
+
user,
|
|
82
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
|
|
83
|
+
tokenType: "jwt",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Otherwise, validate via Supabase API
|
|
87
|
+
const proxyFetch = createProxyFetch();
|
|
88
|
+
const response = await proxyFetch(`${this.supabaseUrl}/auth/v1/user`, {
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${token}`,
|
|
91
|
+
apikey: this.anonKey,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
return {
|
|
96
|
+
valid: false,
|
|
97
|
+
error: `Token validation failed: HTTP ${response.status}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const userData = (await response.json());
|
|
101
|
+
const user = this.supabaseUserToAuthUser(userData);
|
|
102
|
+
return {
|
|
103
|
+
valid: true,
|
|
104
|
+
payload: userData,
|
|
105
|
+
user,
|
|
106
|
+
tokenType: "jwt",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Convert JWT payload to AuthUser
|
|
118
|
+
*/
|
|
119
|
+
payloadToUser(payload) {
|
|
120
|
+
const appMetadata = payload.app_metadata;
|
|
121
|
+
const userMetadata = payload.user_metadata;
|
|
122
|
+
// Use payload.role (Supabase standard claim) for the roles array
|
|
123
|
+
const role = payload.role;
|
|
124
|
+
return {
|
|
125
|
+
id: payload.sub,
|
|
126
|
+
email: payload.email,
|
|
127
|
+
name: userMetadata?.full_name || userMetadata?.name,
|
|
128
|
+
picture: userMetadata?.avatar_url,
|
|
129
|
+
emailVerified: payload.email_confirmed || false,
|
|
130
|
+
roles: role ? [role] : appMetadata?.roles || [],
|
|
131
|
+
permissions: appMetadata?.permissions || [],
|
|
132
|
+
metadata: userMetadata,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Convert Supabase user to AuthUser
|
|
137
|
+
*/
|
|
138
|
+
supabaseUserToAuthUser(userData) {
|
|
139
|
+
const appMetadata = userData.app_metadata;
|
|
140
|
+
const userMetadata = userData.user_metadata;
|
|
141
|
+
return {
|
|
142
|
+
id: userData.id,
|
|
143
|
+
email: userData.email,
|
|
144
|
+
name: userMetadata?.full_name || userMetadata?.name,
|
|
145
|
+
picture: userMetadata?.avatar_url,
|
|
146
|
+
emailVerified: !!userData.email_confirmed_at,
|
|
147
|
+
roles: appMetadata?.roles || [],
|
|
148
|
+
permissions: appMetadata?.permissions || [],
|
|
149
|
+
createdAt: userData.created_at
|
|
150
|
+
? new Date(userData.created_at)
|
|
151
|
+
: undefined,
|
|
152
|
+
lastLoginAt: userData.last_sign_in_at
|
|
153
|
+
? new Date(userData.last_sign_in_at)
|
|
154
|
+
: undefined,
|
|
155
|
+
metadata: userMetadata,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get user by ID via Supabase Admin API
|
|
160
|
+
* Requires service role key
|
|
161
|
+
*/
|
|
162
|
+
async getUser(userId) {
|
|
163
|
+
if (!this.serviceRoleKey) {
|
|
164
|
+
logger.warn("Service role key required for user lookup");
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const proxyFetch = createProxyFetch();
|
|
169
|
+
const response = await proxyFetch(`${this.supabaseUrl}/auth/v1/admin/users/${userId}`, {
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${this.serviceRoleKey}`,
|
|
172
|
+
apikey: this.anonKey,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
if (response.status === 404) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
throw AuthError.create("PROVIDER_ERROR", `Supabase API returned ${response.status}`, { details: { statusCode: response.status } });
|
|
180
|
+
}
|
|
181
|
+
const userData = (await response.json());
|
|
182
|
+
return this.supabaseUserToAuthUser(userData);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.error("Failed to fetch Supabase user:", error);
|
|
186
|
+
if (error &&
|
|
187
|
+
typeof error === "object" &&
|
|
188
|
+
"code" in error &&
|
|
189
|
+
typeof error.code === "string") {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get user by email via Supabase Admin API
|
|
197
|
+
* Requires service role key
|
|
198
|
+
*/
|
|
199
|
+
async getUserByEmail(email) {
|
|
200
|
+
if (!this.serviceRoleKey) {
|
|
201
|
+
logger.warn("Service role key required for user lookup by email");
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const proxyFetch = createProxyFetch();
|
|
206
|
+
const response = await proxyFetch(`${this.supabaseUrl}/auth/v1/admin/users?email=${encodeURIComponent(email)}`, {
|
|
207
|
+
headers: {
|
|
208
|
+
Authorization: `Bearer ${this.serviceRoleKey}`,
|
|
209
|
+
apikey: this.anonKey,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw AuthError.create("PROVIDER_ERROR", `Supabase API returned ${response.status}`, { details: { statusCode: response.status } });
|
|
214
|
+
}
|
|
215
|
+
const result = (await response.json());
|
|
216
|
+
const users = result.users || [];
|
|
217
|
+
if (users.length === 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return this.supabaseUserToAuthUser(users[0]);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
logger.error("Failed to fetch Supabase user by email:", error);
|
|
224
|
+
if (error &&
|
|
225
|
+
typeof error === "object" &&
|
|
226
|
+
"code" in error &&
|
|
227
|
+
typeof error.code === "string") {
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Health check
|
|
235
|
+
*/
|
|
236
|
+
async healthCheck() {
|
|
237
|
+
try {
|
|
238
|
+
const proxyFetch = createProxyFetch();
|
|
239
|
+
const response = await proxyFetch(`${this.supabaseUrl}/auth/v1/health`, {
|
|
240
|
+
headers: {
|
|
241
|
+
apikey: this.anonKey,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
healthy: response.ok,
|
|
246
|
+
providerConnected: response.ok,
|
|
247
|
+
sessionStorageHealthy: true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
return {
|
|
252
|
+
healthy: false,
|
|
253
|
+
providerConnected: false,
|
|
254
|
+
sessionStorageHealthy: true,
|
|
255
|
+
error: error instanceof Error ? error.message : String(error),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// src/lib/auth/providers/workos.ts
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
4
|
+
import { AuthError } from "../errors.js";
|
|
5
|
+
import * as jose from "jose";
|
|
6
|
+
import { BaseAuthProvider } from "./BaseAuthProvider.js";
|
|
7
|
+
/**
|
|
8
|
+
* WorkOS Authentication Provider
|
|
9
|
+
*
|
|
10
|
+
* Supports WorkOS for enterprise SSO and user management.
|
|
11
|
+
* Validates JWTs issued by WorkOS and fetches user information.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - JWT validation using WorkOS JWKS
|
|
15
|
+
* - SSO token validation
|
|
16
|
+
* - Enterprise directory integration
|
|
17
|
+
* - Organization support for multi-tenant apps
|
|
18
|
+
* - Session management (inherited from BaseAuthProvider)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const workos = new WorkOSProvider({
|
|
23
|
+
* type: "workos",
|
|
24
|
+
* apiKey: "sk_...",
|
|
25
|
+
* clientId: "client_..."
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const result = await workos.authenticateToken(accessToken);
|
|
29
|
+
* if (result.valid) {
|
|
30
|
+
* console.log("Authenticated user:", result.user);
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class WorkOSProvider extends BaseAuthProvider {
|
|
35
|
+
type = "workos";
|
|
36
|
+
apiKey;
|
|
37
|
+
clientId;
|
|
38
|
+
organizationId;
|
|
39
|
+
jwks = null;
|
|
40
|
+
constructor(config) {
|
|
41
|
+
super(config);
|
|
42
|
+
if (!config.apiKey) {
|
|
43
|
+
throw AuthError.create("CONFIGURATION_ERROR", "WorkOS API key is required", { details: { provider: "workos", missingFields: ["apiKey"] } });
|
|
44
|
+
}
|
|
45
|
+
if (!config.clientId) {
|
|
46
|
+
throw AuthError.create("CONFIGURATION_ERROR", "WorkOS client ID is required", { details: { provider: "workos", missingFields: ["clientId"] } });
|
|
47
|
+
}
|
|
48
|
+
this.apiKey = config.apiKey;
|
|
49
|
+
this.clientId = config.clientId;
|
|
50
|
+
this.organizationId = config.organizationId;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Initialize JWKS for WorkOS token verification
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
const jwksUrl = new URL("https://api.workos.com/sso/jwks");
|
|
57
|
+
this.jwks = jose.createRemoteJWKSet(jwksUrl);
|
|
58
|
+
logger.debug("WorkOS provider initialized");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate WorkOS access token
|
|
62
|
+
*/
|
|
63
|
+
async authenticateToken(token, _context) {
|
|
64
|
+
if (!this.jwks) {
|
|
65
|
+
await this.initialize();
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
// Verify the JWT
|
|
69
|
+
const { payload } = await jose.jwtVerify(token, this.jwks, {
|
|
70
|
+
audience: this.clientId,
|
|
71
|
+
});
|
|
72
|
+
// Enforce organizationId if configured
|
|
73
|
+
if (this.organizationId &&
|
|
74
|
+
payload.org_id !== this.organizationId) {
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
error: `Organization mismatch: expected ${this.organizationId}, got ${payload.org_id}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const user = {
|
|
81
|
+
id: payload.sub,
|
|
82
|
+
email: payload.email,
|
|
83
|
+
name: payload.first_name && payload.last_name
|
|
84
|
+
? `${payload.first_name} ${payload.last_name}`.trim()
|
|
85
|
+
: undefined,
|
|
86
|
+
emailVerified: true, // WorkOS verifies emails via SSO
|
|
87
|
+
roles: payload.roles || [],
|
|
88
|
+
permissions: payload.permissions || [],
|
|
89
|
+
organizationId: payload.org_id,
|
|
90
|
+
metadata: {
|
|
91
|
+
connection_id: payload.connection_id,
|
|
92
|
+
connection_type: payload.connection_type,
|
|
93
|
+
idp_id: payload.idp_id,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
valid: true,
|
|
98
|
+
payload: payload,
|
|
99
|
+
user,
|
|
100
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
|
|
101
|
+
tokenType: "jwt",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// If JWT validation fails, try session validation via API
|
|
106
|
+
return this.validateSessionViaAPI(token);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate session via WorkOS API
|
|
111
|
+
*/
|
|
112
|
+
async validateSessionViaAPI(token) {
|
|
113
|
+
try {
|
|
114
|
+
const proxyFetch = createProxyFetch();
|
|
115
|
+
const response = await proxyFetch("https://api.workos.com/user_management/authenticate", {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
session_token: token,
|
|
123
|
+
client_id: this.clientId,
|
|
124
|
+
}),
|
|
125
|
+
signal: AbortSignal.timeout(5000),
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: `Session validation failed: HTTP ${response.status}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const data = (await response.json());
|
|
134
|
+
if (!data.user) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: "User not found in session",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Enforce organizationId if configured
|
|
141
|
+
if (this.organizationId && data.organization_id !== this.organizationId) {
|
|
142
|
+
return {
|
|
143
|
+
valid: false,
|
|
144
|
+
error: `Organization mismatch: expected ${this.organizationId}, got ${data.organization_id}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const user = {
|
|
148
|
+
id: data.user.id,
|
|
149
|
+
email: data.user.email,
|
|
150
|
+
name: data.user.first_name && data.user.last_name
|
|
151
|
+
? `${data.user.first_name} ${data.user.last_name}`.trim()
|
|
152
|
+
: undefined,
|
|
153
|
+
picture: data.user.profile_picture_url,
|
|
154
|
+
emailVerified: data.user.email_verified,
|
|
155
|
+
roles: [],
|
|
156
|
+
permissions: [],
|
|
157
|
+
organizationId: data.organization_id,
|
|
158
|
+
createdAt: data.user.created_at
|
|
159
|
+
? new Date(data.user.created_at)
|
|
160
|
+
: undefined,
|
|
161
|
+
metadata: data.user,
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
valid: true,
|
|
165
|
+
payload: data,
|
|
166
|
+
user,
|
|
167
|
+
tokenType: "session",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
valid: false,
|
|
173
|
+
error: error instanceof Error ? error.message : String(error),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get user by ID via WorkOS API
|
|
179
|
+
*/
|
|
180
|
+
async getUser(userId) {
|
|
181
|
+
try {
|
|
182
|
+
const proxyFetch = createProxyFetch();
|
|
183
|
+
const response = await proxyFetch(`https://api.workos.com/user_management/users/${userId}`, {
|
|
184
|
+
headers: {
|
|
185
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
if (response.status === 404) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
throw AuthError.create("PROVIDER_ERROR", `WorkOS API returned ${response.status}`, { details: { provider: "workos", statusCode: response.status } });
|
|
193
|
+
}
|
|
194
|
+
const data = (await response.json());
|
|
195
|
+
return {
|
|
196
|
+
id: data.id,
|
|
197
|
+
email: data.email,
|
|
198
|
+
name: data.first_name && data.last_name
|
|
199
|
+
? `${data.first_name} ${data.last_name}`.trim()
|
|
200
|
+
: undefined,
|
|
201
|
+
picture: data.profile_picture_url,
|
|
202
|
+
emailVerified: data.email_verified,
|
|
203
|
+
roles: [],
|
|
204
|
+
permissions: [],
|
|
205
|
+
createdAt: data.created_at
|
|
206
|
+
? new Date(data.created_at)
|
|
207
|
+
: undefined,
|
|
208
|
+
metadata: data,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger.error("Failed to fetch WorkOS user:", error instanceof Error ? error.message : String(error));
|
|
213
|
+
// Always rethrow -- transport errors should not be silenced as null
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get user by email via WorkOS API
|
|
219
|
+
*/
|
|
220
|
+
async getUserByEmail(email) {
|
|
221
|
+
try {
|
|
222
|
+
const proxyFetch = createProxyFetch();
|
|
223
|
+
const response = await proxyFetch(`https://api.workos.com/user_management/users?email=${encodeURIComponent(email)}`, {
|
|
224
|
+
headers: {
|
|
225
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
throw AuthError.create("PROVIDER_ERROR", `WorkOS API returned ${response.status}`, { details: { provider: "workos", statusCode: response.status } });
|
|
230
|
+
}
|
|
231
|
+
const result = (await response.json());
|
|
232
|
+
const users = result.data || [];
|
|
233
|
+
if (users.length === 0) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const data = users[0];
|
|
237
|
+
return {
|
|
238
|
+
id: data.id,
|
|
239
|
+
email: data.email,
|
|
240
|
+
name: data.first_name && data.last_name
|
|
241
|
+
? `${data.first_name} ${data.last_name}`.trim()
|
|
242
|
+
: undefined,
|
|
243
|
+
picture: data.profile_picture_url,
|
|
244
|
+
emailVerified: data.email_verified,
|
|
245
|
+
roles: [],
|
|
246
|
+
permissions: [],
|
|
247
|
+
createdAt: data.created_at
|
|
248
|
+
? new Date(data.created_at)
|
|
249
|
+
: undefined,
|
|
250
|
+
metadata: data,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
logger.error("Failed to fetch WorkOS user by email:", error instanceof Error ? error.message : String(error));
|
|
255
|
+
// Rethrow AuthErrors, silence unknown transport errors
|
|
256
|
+
if (error instanceof Error && error.name === "AuthError") {
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Health check
|
|
264
|
+
*/
|
|
265
|
+
async healthCheck() {
|
|
266
|
+
try {
|
|
267
|
+
const proxyFetch = createProxyFetch();
|
|
268
|
+
const response = await proxyFetch("https://api.workos.com/sso/jwks");
|
|
269
|
+
return {
|
|
270
|
+
healthy: response.ok,
|
|
271
|
+
providerConnected: response.ok,
|
|
272
|
+
sessionStorageHealthy: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
return {
|
|
277
|
+
healthy: false,
|
|
278
|
+
providerConnected: false,
|
|
279
|
+
sessionStorageHealthy: true,
|
|
280
|
+
error: error instanceof Error ? error.message : String(error),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge between auth providers and NeuroLink's server middleware.
|
|
3
|
+
* Converts an auth provider's authenticateToken() into the validate
|
|
4
|
+
* callback expected by the existing createAuthMiddleware.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create a validate function for server auth middleware from an auth provider.
|
|
8
|
+
*/
|
|
9
|
+
export function createAuthValidatorFromProvider(provider) {
|
|
10
|
+
return async (token, ctx) => {
|
|
11
|
+
const result = await provider.authenticateToken(token, ctx);
|
|
12
|
+
if (!result.valid) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (result.user) {
|
|
16
|
+
return {
|
|
17
|
+
id: result.user.id,
|
|
18
|
+
email: result.user.email,
|
|
19
|
+
roles: result.user.roles,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Fail closed: valid token without a resolved user is treated as failure
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
}
|