Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
local-oauth-provider.ts9.27 kB
/** * Minimal Local OAuth 2.0 Provider for Testing * * Implements OAuth 2.0 Authorization Code Flow: * - Authorization endpoint (user consent) * - Token exchange endpoint * - Userinfo endpoint * * Run: npx tsx testing/local-oauth-provider.ts */ import express from 'express'; import crypto from 'crypto'; import bodyParser from 'body-parser'; const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // In-memory storage const authCodes = new Map<string, { clientId: string; redirectUri: string; userId: string; expiresAt: number }>(); const accessTokens = new Map<string, { userId: string; expiresAt: number }>(); // Mock users const users = [ { sub: 'user-001', email: 'admin@localhost', preferred_username: 'admin', roles: ['admin', 'developer'], password: 'admin123' }, { sub: 'user-002', email: 'developer@localhost', preferred_username: 'developer', roles: ['developer'], password: 'dev123' }, { sub: 'user-003', email: 'viewer@localhost', preferred_username: 'viewer', roles: ['viewer'], password: 'view123' } ]; // Configuration const CLIENT_ID = 'mimir-local-test'; const CLIENT_SECRET = 'local-test-secret-123'; const ISSUER = 'http://localhost:8888'; /** * GET /oauth2/v1/authorize * Authorization endpoint - shows consent form */ app.get('/oauth2/v1/authorize', (req, res) => { const { response_type, client_id, redirect_uri, state, scope } = req.query; console.log('[OAuth Provider] Authorization request:', { response_type, client_id, redirect_uri, state, scope }); if (response_type !== 'code') { return res.status(400).send('Unsupported response_type. Only "code" is supported.'); } if (client_id !== CLIENT_ID) { return res.status(400).send('Invalid client_id'); } if (!redirect_uri) { return res.status(400).send('redirect_uri is required'); } // Show simple login/consent form res.send(` <!DOCTYPE html> <html> <head> <title>Local OAuth Provider - Login</title> <style> body { font-family: Arial, sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; } h2 { color: #333; } form { background: #f5f5f5; padding: 20px; border-radius: 8px; } label { display: block; margin: 10px 0 5px; font-weight: bold; } input, select { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; } button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; width: 100%; } button:hover { background: #45a049; } .info { background: #e3f2fd; padding: 15px; border-radius: 4px; margin-bottom: 20px; } .user-info { font-size: 12px; color: #666; margin-top: 10px; } </style> </head> <body> <h2>🔐 Local OAuth Provider</h2> <div class="info"> <strong>Test OAuth Login</strong><br> Application: Mimir<br> Redirect: ${redirect_uri} </div> <form method="POST" action="/oauth2/v1/authorize/consent"> <input type="hidden" name="client_id" value="${client_id}"> <input type="hidden" name="redirect_uri" value="${redirect_uri}"> <input type="hidden" name="state" value="${state || ''}"> <input type="hidden" name="scope" value="${scope || ''}"> <label>Select Test User:</label> <select name="user_id" required> ${users.map(u => ` <option value="${u.sub}"> ${u.preferred_username} (${u.email}) - Roles: ${u.roles.join(', ')} </option> `).join('')} </select> <div class="user-info"> <strong>Available users:</strong><br> ${users.map(u => `• ${u.preferred_username} / ${u.password} - [${u.roles.join(', ')}]`).join('<br>')} </div> <button type="submit">Authorize & Continue</button> </form> </body> </html> `); }); /** * POST /oauth2/v1/authorize/consent * User consents and gets redirected with authorization code */ app.post('/oauth2/v1/authorize/consent', (req, res) => { const { client_id, redirect_uri, state, user_id } = req.body; console.log('[OAuth Provider] User consent:', { client_id, redirect_uri, user_id }); // Generate authorization code const authCode = crypto.randomBytes(32).toString('hex'); const expiresAt = Date.now() + 10 * 60 * 1000; // 10 minutes authCodes.set(authCode, { clientId: client_id, redirectUri: redirect_uri, userId: user_id, expiresAt }); console.log('[OAuth Provider] Generated auth code:', authCode.substring(0, 20) + '...'); // Redirect back to app with code const redirectUrl = new URL(redirect_uri); redirectUrl.searchParams.set('code', authCode); if (state) { redirectUrl.searchParams.set('state', state); } console.log('[OAuth Provider] Redirecting to:', redirectUrl.toString()); res.redirect(redirectUrl.toString()); }); /** * POST /oauth2/v1/token * Token exchange endpoint */ app.post('/oauth2/v1/token', (req, res) => { const { grant_type, code, redirect_uri, client_id, client_secret } = req.body; console.log('[OAuth Provider] Token request:', { grant_type, code: code?.substring(0, 20) + '...', client_id }); if (grant_type !== 'authorization_code') { return res.status(400).json({ error: 'unsupported_grant_type', error_description: 'Only authorization_code grant type is supported' }); } if (client_id !== CLIENT_ID || client_secret !== CLIENT_SECRET) { return res.status(401).json({ error: 'invalid_client', error_description: 'Invalid client credentials' }); } const authData = authCodes.get(code); if (!authData) { return res.status(400).json({ error: 'invalid_grant', error_description: 'Invalid or expired authorization code' }); } if (authData.expiresAt < Date.now()) { authCodes.delete(code); return res.status(400).json({ error: 'invalid_grant', error_description: 'Authorization code expired' }); } if (authData.redirectUri !== redirect_uri) { return res.status(400).json({ error: 'invalid_grant', error_description: 'redirect_uri mismatch' }); } // Delete used code (one-time use) authCodes.delete(code); // Generate access token const accessToken = crypto.randomBytes(32).toString('hex'); const expiresIn = 3600; // 1 hour accessTokens.set(accessToken, { userId: authData.userId, expiresAt: Date.now() + expiresIn * 1000 }); console.log('[OAuth Provider] Issued access token for user:', authData.userId); res.json({ access_token: accessToken, token_type: 'Bearer', expires_in: expiresIn, scope: 'openid profile email' }); }); /** * GET /oauth2/v1/userinfo * Userinfo endpoint */ app.get('/oauth2/v1/userinfo', (req, res) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'invalid_token', error_description: 'Missing or invalid authorization header' }); } const token = authHeader.substring(7); const tokenData = accessTokens.get(token); if (!tokenData) { return res.status(401).json({ error: 'invalid_token', error_description: 'Invalid access token' }); } if (tokenData.expiresAt < Date.now()) { accessTokens.delete(token); return res.status(401).json({ error: 'invalid_token', error_description: 'Access token expired' }); } const user = users.find(u => u.sub === tokenData.userId); if (!user) { return res.status(500).json({ error: 'server_error', error_description: 'User not found' }); } console.log('[OAuth Provider] Userinfo request for:', user.email); const { password, ...userProfile } = user; res.json(userProfile); }); /** * GET /.well-known/oauth-authorization-server * OAuth 2.0 Discovery endpoint */ app.get('/.well-known/oauth-authorization-server', (req, res) => { res.json({ issuer: ISSUER, authorization_endpoint: `${ISSUER}/oauth2/v1/authorize`, token_endpoint: `${ISSUER}/oauth2/v1/token`, userinfo_endpoint: `${ISSUER}/oauth2/v1/userinfo`, response_types_supported: ['code'], grant_types_supported: ['authorization_code'], token_endpoint_auth_methods_supported: ['client_secret_post'] }); }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', users: users.length }); }); // Start server const PORT = 8888; app.listen(PORT, () => { console.log(`\n🚀 Local OAuth Provider running on http://localhost:${PORT}`); console.log(`\n📋 Configuration for .env file:`); console.log(`MIMIR_AUTH_PROVIDER=oauth`); console.log(`MIMIR_OAUTH_CLIENT_ID=${CLIENT_ID}`); console.log(`MIMIR_OAUTH_CLIENT_SECRET=${CLIENT_SECRET}`); console.log(`MIMIR_OAUTH_CALLBACK_URL=http://localhost:9042/auth/oauth/callback`); console.log(`\n👥 Test Users:`); users.forEach(u => { console.log(` • ${u.preferred_username} / ${u.password} - [${u.roles.join(', ')}]`); }); console.log(`\n✅ Ready to accept OAuth requests!\n`); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server