UNPKG

@softeria/ms-365-mcp-server

Version:

A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API

165 lines (164 loc) 5.41 kB
import logger from "../logger.js"; import { getCloudEndpoints } from "../cloud-config.js"; function buildWwwAuthenticate(req, error, description) { const protocol = req.secure ? "https" : "http"; const origin = `${protocol}://${req.get("host")}`; const resourceMetadata = `${origin}/.well-known/oauth-protected-resource`; return `Bearer resource_metadata="${resourceMetadata}", error="${error}", error_description="${description}"`; } function isJwtExpired(token) { const parts = token.split("."); if (parts.length !== 3) return false; try { const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8")); if (typeof payload.exp !== "number") return false; return payload.exp * 1e3 < Date.now(); } catch { return false; } } const microsoftBearerTokenAuthMiddleware = (opts = {}) => (req, res, next) => { if (opts.trustProxyAuth) { next(); return; } const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith("Bearer ")) { res.status(401).set( "WWW-Authenticate", buildWwwAuthenticate(req, "invalid_token", "Missing or malformed Authorization header") ).json({ error: "invalid_token", error_description: "Missing or malformed Authorization header" }); return; } const accessToken = authHeader.substring(7); if (isJwtExpired(accessToken)) { res.status(401).set( "WWW-Authenticate", buildWwwAuthenticate(req, "invalid_token", "The access token has expired") ).json({ error: "invalid_token", error_description: "The access token has expired" }); return; } req.microsoftAuth = { accessToken }; next(); }; class OAuthUpstreamError extends Error { constructor(status, raw, body) { const suffix = body.error_description ? ` - ${body.error_description}` : ""; super(`OAuth upstream error: ${body.error}${suffix}`); this.name = "OAuthUpstreamError"; this.status = status; this.body = body; this.raw = raw; } } function parseUpstreamOAuthError(raw) { try { const json = JSON.parse(raw); if (json !== null && typeof json === "object" && typeof json.error === "string") { return json; } } catch { } return null; } function toOAuthErrorResponse(error) { if (error instanceof OAuthUpstreamError) { const body = { error: error.body.error }; if (error.body.error_description) body.error_description = error.body.error_description; if (error.body.suberror) body.suberror = error.body.suberror; return { status: 400, body }; } return { status: 500, body: { error: "server_error", error_description: "Internal server error during token exchange" } }; } async function exchangeCodeForToken(code, redirectUri, clientId, clientSecret, tenantId = "common", codeVerifier, cloudType = "global") { const cloudEndpoints = getCloudEndpoints(cloudType); const params = new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: redirectUri, client_id: clientId }); if (clientSecret) { params.append("client_secret", clientSecret); } if (codeVerifier) { params.append("code_verifier", codeVerifier); } const response = await fetch(`${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params }); if (!response.ok) { const raw = await response.text(); const parsed = parseUpstreamOAuthError(raw); if (parsed) { logger.warn(`Token endpoint upstream OAuth error: ${parsed.error}`, { status: response.status, error: parsed.error, suberror: parsed.suberror, error_codes: parsed.error_codes, correlation_id: parsed.correlation_id }); throw new OAuthUpstreamError(response.status, raw, parsed); } logger.error(`Failed to exchange code for token: ${raw}`); throw new Error(`Failed to exchange code for token: ${raw}`); } return response.json(); } async function refreshAccessToken(refreshToken, clientId, clientSecret, tenantId = "common", cloudType = "global") { const cloudEndpoints = getCloudEndpoints(cloudType); const params = new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: clientId }); if (clientSecret) { params.append("client_secret", clientSecret); } const response = await fetch(`${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params }); if (!response.ok) { const raw = await response.text(); const parsed = parseUpstreamOAuthError(raw); if (parsed) { logger.warn(`Token endpoint upstream OAuth error: ${parsed.error}`, { status: response.status, error: parsed.error, suberror: parsed.suberror, error_codes: parsed.error_codes, correlation_id: parsed.correlation_id }); throw new OAuthUpstreamError(response.status, raw, parsed); } logger.error(`Failed to refresh token: ${raw}`); throw new Error(`Failed to refresh token: ${raw}`); } return response.json(); } export { OAuthUpstreamError, exchangeCodeForToken, microsoftBearerTokenAuthMiddleware, refreshAccessToken, toOAuthErrorResponse };