Skip to main content
Glama
razvan1326

Academiadepolitie.com MCP Server

by razvan1326
oauth.js6.16 kB
/** * OAuth 2.1 Implementation pentru Remote MCP * Conform specificațiilor Claude Remote Connectors */ import jwt from 'jsonwebtoken'; import crypto from 'crypto'; // Store pentru auth codes și tokens (în producție folosește Redis/DB) const authStore = new Map(); const tokenStore = new Map(); /** * Middleware pentru autentificare request-uri */ export async function authenticateRequest(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'unauthorized', error_description: 'Missing or invalid authorization header' }); } const token = authHeader.substring(7); try { // Verifică JWT token (generat de PHP oauth-bridge.php) const decoded = jwt.verify(token, process.env.JWT_SECRET); // Token-urile PHP sunt self-contained, nu au nevoie de store // Verificăm doar dacă nu a expirat if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) { throw new Error('Token expired'); } // Adaugă user info la request conform structurii PHP req.user = { userId: decoded.userId || decoded.sub, username: decoded.username, email: decoded.email, permissions: ['read', 'write', 'tools'], // Default permissions scope: 'mcp_access' }; next(); } catch (error) { return res.status(401).json({ error: 'invalid_token', error_description: error.message }); } } /** * Authorization endpoint - inițiază OAuth flow */ export async function handleAuthorize(req, res) { const { response_type, client_id, redirect_uri, state, code_challenge, code_challenge_method, scope } = req.query; // Validări if (response_type !== 'code') { return res.status(400).json({ error: 'unsupported_response_type', error_description: 'Only authorization code flow is supported' }); } if (!code_challenge || code_challenge_method !== 'S256') { return res.status(400).json({ error: 'invalid_request', error_description: 'PKCE with S256 is required' }); } // Verifică client_id if (client_id !== process.env.OAUTH_CLIENT_ID) { return res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client' }); } // În producție, aici ar fi UI pentru login/consent // Pentru demo, generăm direct auth code const authCode = crypto.randomBytes(32).toString('base64url'); // Salvează auth code cu PKCE challenge authStore.set(authCode, { clientId: client_id, redirectUri: redirect_uri, codeChallenge: code_challenge, scope: scope || 'read write', expiresAt: Date.now() + 600000, // 10 minute userId: null // Se va seta după autentificare user }); // Pentru demo, redirect direct cu code // În producție, aici ar fi login form const redirectUrl = new URL(redirect_uri); redirectUrl.searchParams.append('code', authCode); if (state) { redirectUrl.searchParams.append('state', state); } res.redirect(redirectUrl.toString()); } /** * Token endpoint - schimbă auth code pentru access token */ export async function handleToken(req, res) { const { grant_type, code, redirect_uri, code_verifier, client_id, client_secret, refresh_token } = req.body; // Verifică client credentials if (client_id !== process.env.OAUTH_CLIENT_ID || client_secret !== process.env.OAUTH_CLIENT_SECRET) { return res.status(401).json({ error: 'invalid_client', error_description: 'Client authentication failed' }); } if (grant_type === 'authorization_code') { // Exchange auth code pentru token const authData = authStore.get(code); if (!authData) { return res.status(400).json({ error: 'invalid_grant', error_description: 'Invalid or expired authorization code' }); } // Verifică PKCE const challenge = crypto .createHash('sha256') .update(code_verifier) .digest('base64url'); if (challenge !== authData.codeChallenge) { return res.status(400).json({ error: 'invalid_grant', error_description: 'PKCE verification failed' }); } // Verifică redirect_uri if (redirect_uri !== authData.redirectUri) { return res.status(400).json({ error: 'invalid_grant', error_description: 'Redirect URI mismatch' }); } // Șterge auth code (single use) authStore.delete(code); // Generează tokens const accessToken = generateAccessToken(authData); const refreshToken = generateRefreshToken(authData); // Salvează în token store tokenStore.set(accessToken, { userId: authData.userId, scope: authData.scope, expiresAt: Date.now() + 3600000 // 1 oră }); res.json({ access_token: accessToken, token_type: 'Bearer', expires_in: 3600, refresh_token: refreshToken, scope: authData.scope }); } else if (grant_type === 'refresh_token') { // Refresh token flow // TODO: Implementează refresh token logic res.status(501).json({ error: 'unsupported_grant_type', error_description: 'Refresh token not yet implemented' }); } else { res.status(400).json({ error: 'unsupported_grant_type', error_description: 'Only authorization_code and refresh_token are supported' }); } } /** * Generează access token JWT */ function generateAccessToken(authData) { // În producție, userId vine din sesiunea de autentificare const payload = { iss: 'https://mcp.academiadepolitie.com', aud: 'mcp-api', user_id: authData.userId || 4001, // Hardcodat pentru test scope: authData.scope, permissions: ['read', 'write', 'tools'] }; return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' }); } /** * Generează refresh token */ function generateRefreshToken(authData) { return crypto.randomBytes(32).toString('base64url'); }

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/razvan1326/mcp-server'

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