fraim 2.0.225 → 2.0.227
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/dist/src/middleware/auth.js +19 -1
- package/package.json +2 -2
|
@@ -30,9 +30,21 @@ class AuthMiddleware {
|
|
|
30
30
|
// Skip auth for public health check, admin routes, website signup, sales inquiries, and self-serve access flow
|
|
31
31
|
const rawPath = req.path ?? req.originalUrl ?? req.url ?? '';
|
|
32
32
|
const p = (typeof rawPath === 'string' ? rawPath : '').split('?')[0].replace(/\/$/, '') || '';
|
|
33
|
+
const publicBaseUrl = (process.env.FRAIM_PUBLIC_BASE_URL || 'https://fraim.wellnessatwork.me').replace(/\/+$/, '');
|
|
34
|
+
const mcpResourceMetadataUrl = `${publicBaseUrl}/.well-known/oauth-protected-resource/mcp`;
|
|
35
|
+
const isMcpPath = p === '/mcp' || p.startsWith('/mcp/');
|
|
36
|
+
const setMcpAuthenticateChallenge = () => {
|
|
37
|
+
if (isMcpPath) {
|
|
38
|
+
res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${mcpResourceMetadataUrl}"`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
33
41
|
const publicPrefixes = ['/admin', '/dashboard', '/health', '/pricing', '/fraim-brain', '/api/signup', '/api/sales', '/api/request-access', '/api/installer-key', '/api/installer-download', '/api/installer-availability', '/api/payment/bypass', '/api/pricing', '/api/personas/catalog', '/auth', '/portfolio'];
|
|
34
42
|
// Analytics dashboard is public, but API routes are protected
|
|
35
43
|
const isAnalyticsPublic = p === '/analytics' || p === '/analytics/' || p.startsWith('/analytics/') && p.endsWith('.html');
|
|
44
|
+
const isOAuthMetadataPublic = p === '/.well-known/oauth-authorization-server'
|
|
45
|
+
|| p === '/.well-known/oauth-protected-resource'
|
|
46
|
+
|| p === '/.well-known/oauth-protected-resource/mcp'
|
|
47
|
+
|| p === '/.well-known/openai-apps-challenge';
|
|
36
48
|
// Homepage is public
|
|
37
49
|
if (p === '' || p === '/')
|
|
38
50
|
return next();
|
|
@@ -40,7 +52,8 @@ class AuthMiddleware {
|
|
|
40
52
|
const isPublic = publicPrefixes.some(prefix => p === prefix || p.startsWith(prefix + '/'))
|
|
41
53
|
|| publicExtensions.some(ext => p.endsWith(ext))
|
|
42
54
|
|| p.startsWith('/css/') || p.startsWith('/js/') || p.startsWith('/images/')
|
|
43
|
-
|| isAnalyticsPublic
|
|
55
|
+
|| isAnalyticsPublic
|
|
56
|
+
|| isOAuthMetadataPublic;
|
|
44
57
|
if (isPublic) {
|
|
45
58
|
return next();
|
|
46
59
|
}
|
|
@@ -102,6 +115,7 @@ class AuthMiddleware {
|
|
|
102
115
|
reason: !session ? 'not_found' : session.revoked ? 'revoked' : 'expired',
|
|
103
116
|
outcome: 'failure',
|
|
104
117
|
}).catch(() => undefined);
|
|
118
|
+
setMcpAuthenticateChallenge();
|
|
105
119
|
return res.status(401).json({
|
|
106
120
|
jsonrpc: '2.0',
|
|
107
121
|
error: { code: -32001, message: 'Unauthorized: Session expired or revoked [AUTH-SESSION-INVALID]' },
|
|
@@ -111,6 +125,7 @@ class AuthMiddleware {
|
|
|
111
125
|
const apiKeyData = await this.dbService.getApiKeyByUserId(session.userId, false);
|
|
112
126
|
if (!apiKeyData) {
|
|
113
127
|
console.error(`[FRAIM AUTH] Session valid but no API key for user ${session.userId}`);
|
|
128
|
+
setMcpAuthenticateChallenge();
|
|
114
129
|
return res.status(401).json({
|
|
115
130
|
jsonrpc: '2.0',
|
|
116
131
|
error: { code: -32001, message: 'Unauthorized: No identity for session [AUTH-NO-IDENTITY]' },
|
|
@@ -146,6 +161,7 @@ class AuthMiddleware {
|
|
|
146
161
|
outcome: 'failure',
|
|
147
162
|
reason: `session_user=${session.userId}`,
|
|
148
163
|
}).catch(() => undefined);
|
|
164
|
+
setMcpAuthenticateChallenge();
|
|
149
165
|
return res.status(401).json({
|
|
150
166
|
jsonrpc: '2.0',
|
|
151
167
|
error: { code: -32001, message: 'Unauthorized: Mixed identity [AUTH-MIXED-IDENTITY]' },
|
|
@@ -169,6 +185,7 @@ class AuthMiddleware {
|
|
|
169
185
|
return res.status(404).send('<h1>404</h1><p>Page not found.</p>');
|
|
170
186
|
}
|
|
171
187
|
console.error(`[FRAIM AUTH] Missing API key for ${req.method} ${req.path}`);
|
|
188
|
+
setMcpAuthenticateChallenge();
|
|
172
189
|
return res.status(401).json({
|
|
173
190
|
jsonrpc: '2.0',
|
|
174
191
|
error: { code: -32001, message: 'Unauthorized: Missing API key [AUTH-MISSING]' },
|
|
@@ -194,6 +211,7 @@ class AuthMiddleware {
|
|
|
194
211
|
const apiKeyData = await this.dbService.verifyApiKey(apiKey);
|
|
195
212
|
if (!apiKeyData) {
|
|
196
213
|
console.error(`❌ FRAIM AUTH: Invalid API key: ${apiKey}`);
|
|
214
|
+
setMcpAuthenticateChallenge();
|
|
197
215
|
return res.status(401).json({
|
|
198
216
|
jsonrpc: '2.0',
|
|
199
217
|
error: { code: -32001, message: 'Unauthorized: Invalid x-api-key [AUTH-INVALID]' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.227",
|
|
4
4
|
"description": "FRAIM core CLI and MCP package.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@octokit/rest": "^22.0.1",
|
|
41
|
-
"adm-zip": "^0.
|
|
41
|
+
"adm-zip": "^0.6.0",
|
|
42
42
|
"axios": "^1.7.0",
|
|
43
43
|
"chalk": "4.1.2",
|
|
44
44
|
"commander": "^14.0.2",
|