@softeria/ms-365-mcp-server 0.107.0 → 0.107.1
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/lib/redirect-uri-validation.js +33 -0
- package/dist/server.js +15 -0
- package/docs/deployment.md +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
2
|
+
function isLoopbackHost(host) {
|
|
3
|
+
return LOOPBACK_HOSTS.has(host.toLowerCase());
|
|
4
|
+
}
|
|
5
|
+
function parseAllowlist(raw) {
|
|
6
|
+
if (!raw) return null;
|
|
7
|
+
const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
8
|
+
return list.length > 0 ? list : null;
|
|
9
|
+
}
|
|
10
|
+
function isAllowedRedirectUri(value, allowlist) {
|
|
11
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
12
|
+
let url;
|
|
13
|
+
try {
|
|
14
|
+
url = new URL(value);
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (allowlist && allowlist.length > 0) {
|
|
22
|
+
return allowlist.includes(value);
|
|
23
|
+
}
|
|
24
|
+
if (url.protocol === "http:") {
|
|
25
|
+
return isLoopbackHost(url.hostname);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
isAllowedRedirectUri,
|
|
31
|
+
isLoopbackHost,
|
|
32
|
+
parseAllowlist
|
|
33
|
+
};
|
package/dist/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
microsoftBearerTokenAuthMiddleware,
|
|
16
16
|
refreshAccessToken
|
|
17
17
|
} from "./lib/microsoft-auth.js";
|
|
18
|
+
import { isAllowedRedirectUri, parseAllowlist } from "./lib/redirect-uri-validation.js";
|
|
18
19
|
import { getSecrets } from "./secrets.js";
|
|
19
20
|
import { getCloudEndpoints } from "./cloud-config.js";
|
|
20
21
|
import { requestContext } from "./request-context.js";
|
|
@@ -226,6 +227,20 @@ class MicrosoftGraphServer {
|
|
|
226
227
|
const clientCodeChallenge = url.searchParams.get("code_challenge");
|
|
227
228
|
const clientCodeChallengeMethod = url.searchParams.get("code_challenge_method");
|
|
228
229
|
const state = url.searchParams.get("state");
|
|
230
|
+
const redirectUriParam = url.searchParams.get("redirect_uri");
|
|
231
|
+
if (redirectUriParam) {
|
|
232
|
+
const allowlist = parseAllowlist(process.env.MS365_MCP_ALLOWED_REDIRECT_URIS);
|
|
233
|
+
if (!isAllowedRedirectUri(redirectUriParam, allowlist)) {
|
|
234
|
+
logger.warn("Rejected /authorize request with disallowed redirect_uri", {
|
|
235
|
+
redirect_uri: redirectUriParam
|
|
236
|
+
});
|
|
237
|
+
res.status(400).json({
|
|
238
|
+
error: "invalid_request",
|
|
239
|
+
error_description: "redirect_uri is not allowed"
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
229
244
|
const allowedParams = [
|
|
230
245
|
"response_type",
|
|
231
246
|
"redirect_uri",
|
package/docs/deployment.md
CHANGED
|
@@ -130,6 +130,28 @@ When deploying for an organization, create a dedicated app registration instead
|
|
|
130
130
|
|
|
131
131
|
5. **Store credentials** in Key Vault (see [Azure Key Vault Integration](../README.md#azure-key-vault-integration))
|
|
132
132
|
|
|
133
|
+
## Redirect URI Validation
|
|
134
|
+
|
|
135
|
+
The /authorize endpoint defensively validates client-supplied `redirect_uri` values before forwarding them to Microsoft Entra (CWE-601, Open Redirect). Microsoft Entra also validates the URI against your app registration, but this server-side check rejects obviously dangerous schemes (`javascript:`, `data:`, `file:`, …) and arbitrary remote `http://` origins before the request leaves the server.
|
|
136
|
+
|
|
137
|
+
Default behaviour (no explicit allowlist):
|
|
138
|
+
|
|
139
|
+
- Only `http:` and `https:` schemes are accepted.
|
|
140
|
+
- `http:` is only allowed for loopback hosts (`localhost`, `127.0.0.1`, `::1`).
|
|
141
|
+
- All other `https://` origins are accepted (Entra still has the final say).
|
|
142
|
+
|
|
143
|
+
For production deployments, configure an explicit allowlist via the `MS365_MCP_ALLOWED_REDIRECT_URIS` environment variable. It takes a comma-separated list of exact URIs; only exact string matches pass validation:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Single redirect URI
|
|
147
|
+
MS365_MCP_ALLOWED_REDIRECT_URIS=https://mcp.example.com/auth/callback
|
|
148
|
+
|
|
149
|
+
# Multiple URIs (comma-separated, no spaces required)
|
|
150
|
+
MS365_MCP_ALLOWED_REDIRECT_URIS=https://mcp.example.com/auth/callback,https://staging.example.com/auth/callback
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The list should mirror the redirect URIs registered on your Azure AD app registration. Leaving the variable unset falls back to the default behaviour above, which is appropriate for local development but not recommended for shared/production deployments.
|
|
154
|
+
|
|
133
155
|
## Reverse Proxy / Custom Domain
|
|
134
156
|
|
|
135
157
|
When running behind a reverse proxy, set `MS365_MCP_PUBLIC_URL` so that the OAuth authorize URL handed back to the user's browser is resolvable from outside the server's network:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.107.
|
|
3
|
+
"version": "0.107.1",
|
|
4
4
|
"description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|