fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078) (#7166)

* fix(providers): accept m365.cloud.microsoft for copilot-m365-web token (#7078)

* test: regression for #7078 m365.cloud.microsoft token extraction

* fix(7078): match on url.hostname and anchor path with startsWith

* test(7078): cover explicit :443 port via url.hostname
This commit is contained in:
Adam
2026-07-18 22:34:56 +08:00
committed by GitHub
parent 40e097cc7a
commit b71790bb7e
2 changed files with 62 additions and 5 deletions

View File

@@ -292,7 +292,7 @@ export async function validateCopilotWebProvider({ apiKey, providerSpecificData
}
}
function extractM365CredentialParts(raw: string, providerSpecificData: Record<string, unknown>) {
export function extractM365CredentialParts(raw: string, providerSpecificData: Record<string, unknown>) {
const text = raw.trim();
const parts: Record<string, string> = {};
@@ -304,13 +304,22 @@ function extractM365CredentialParts(raw: string, providerSpecificData: Record<st
if (key && value) parts[key] = value;
}
if (/^wss:\/\/substrate\.office\.com\/m365Copilot\/Chathub\//i.test(text)) {
// Accept the current M365 web endpoint (m365.cloud.microsoft, including
// regional subdomains) plus the two legacy hosts (substrate.office.com,
// copilot.microsoft.com). The path still carries /m365Copilot/Chathub/<tenant>,
// so extraction is unchanged. (OmniRoute issue #7078)
if (/^wss:\/\//i.test(text)) {
try {
const url = new URL(text);
parts.access_token ||= url.searchParams.get("access_token") || "";
parts.chathubPath ||= decodeURIComponent(
url.pathname.split("/m365Copilot/Chathub/")[1] || ""
const hostOk = /^(?:[\w-]+\.)*(?:m365\.cloud\.microsoft|copilot\.microsoft\.com|substrate\.office\.com)$/i.test(
url.hostname
);
if (hostOk && url.pathname.startsWith("/m365Copilot/Chathub/")) {
parts.access_token ||= url.searchParams.get("access_token") || "";
parts.chathubPath ||= decodeURIComponent(
url.pathname.split("/m365Copilot/Chathub/")[1] || ""
);
}
} catch {
// Fall through to the structured key/value parser result.
}

View File

@@ -0,0 +1,48 @@
// Issue #7078 — Microsoft 365 Copilot web token extraction must accept the current
// m365.cloud.microsoft endpoint (and legacy substrate.office.com / copilot.microsoft.com),
// not just the old substrate.office.com WS host. Verifies access_token + chathubPath parse.
import test from "node:test";
import assert from "node:assert/strict";
const B = await import("../../src/lib/providers/validation/webProvidersB.ts");
const extract = (raw: string) =>
(B as Record<string, any>).extractM365CredentialParts(raw, {});
test("#7078 m365.cloud.microsoft wss URL extracts access_token + chathubPath", () => {
const raw =
"wss://m365.cloud.microsoft/m365Copilot/Chathub/user@tenant.example.com?access_token=TOKEN123";
const parts = extract(raw);
assert.equal(parts.accessToken, "TOKEN123");
assert.equal(parts.chathubPath, "user@tenant.example.com");
});
test("#7078 regional subdomain m365.cloud.microsoft also accepted", () => {
const raw =
"wss://eu.m365.cloud.microsoft/m365Copilot/Chathub/user@tenant?access_token=TOKEN456";
const parts = extract(raw);
assert.equal(parts.accessToken, "TOKEN456");
assert.equal(parts.chathubPath, "user@tenant");
});
test("#7078 legacy substrate.office.com still works (no regression)", () => {
const raw =
"wss://substrate.office.com/m365Copilot/Chathub/user@tenant?access_token=LEGACY";
const parts = extract(raw);
assert.equal(parts.accessToken, "LEGACY");
assert.equal(parts.chathubPath, "user@tenant");
});
test("#7078 key/value string form still parsed", () => {
const raw = "access_token=KV;chathubPath=user@tenant";
const parts = extract(raw);
assert.equal(parts.accessToken, "KV");
assert.equal(parts.chathubPath, "user@tenant");
});
test("#7078 m365.cloud.microsoft with explicit :443 port still extracts (hostname, not host)", () => {
const raw =
"wss://m365.cloud.microsoft:443/m365Copilot/Chathub/user@tenant?access_token=TOKENPORT";
const parts = extract(raw);
assert.equal(parts.accessToken, "TOKENPORT");
assert.equal(parts.chathubPath, "user@tenant");
});