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.
@@ -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.225",
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.5.16",
41
+ "adm-zip": "^0.6.0",
42
42
  "axios": "^1.7.0",
43
43
  "chalk": "4.1.2",
44
44
  "commander": "^14.0.2",