fix(providers): validate M365 Copilot web credentials (#5432)

Integrated into release/v3.8.43 (drift-shed: cherry-picked the real change onto the release tip; stale-base drift dropped).
This commit is contained in:
skyzea1
2026-06-30 21:41:17 +08:00
committed by GitHub
parent 802f1d42c5
commit 1fd3bfbe64
5 changed files with 170 additions and 1 deletions

View File

@@ -38,6 +38,7 @@ import {
validateAdaptaWebProvider,
validateClaudeWebProvider,
validateGeminiWebProvider,
validateCopilotM365WebProvider,
validateCopilotWebProvider,
validateT3WebProvider,
validateJulesProvider,
@@ -356,6 +357,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
"adapta-web": validateAdaptaWebProvider,
"claude-web": validateClaudeWebProvider,
"gemini-web": validateGeminiWebProvider,
"copilot-m365-web": validateCopilotM365WebProvider,
"copilot-web": validateCopilotWebProvider,
"t3-web": validateT3WebProvider,
"azure-openai": validateAzureOpenAIProvider,

View File

@@ -291,6 +291,82 @@ export async function validateCopilotWebProvider({ apiKey, providerSpecificData
}
}
function extractM365CredentialParts(raw: string, providerSpecificData: Record<string, unknown>) {
const text = raw.trim();
const parts: Record<string, string> = {};
for (const segment of text.split(/[;\n]/)) {
const index = segment.indexOf("=");
if (index <= 0) continue;
const key = segment.slice(0, index).trim();
const value = segment.slice(index + 1).trim();
if (key && value) parts[key] = value;
}
if (/^wss:\/\/substrate\.office\.com\/m365Copilot\/Chathub\//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] || ""
);
} catch {
// Fall through to the structured key/value parser result.
}
}
return {
accessToken:
parts.access_token ||
parts.accessToken ||
(typeof providerSpecificData.access_token === "string"
? providerSpecificData.access_token
: "") ||
(typeof providerSpecificData.accessToken === "string" ? providerSpecificData.accessToken : ""),
chathubPath:
parts.chathubPath ||
parts.userTenant ||
(typeof providerSpecificData.chathubPath === "string"
? providerSpecificData.chathubPath
: "") ||
(typeof providerSpecificData.userTenant === "string" ? providerSpecificData.userTenant : ""),
};
}
// ── Microsoft 365 Copilot Web token validator ──
export async function validateCopilotM365WebProvider({
apiKey,
providerSpecificData = {},
}: any) {
const { accessToken, chathubPath } = extractM365CredentialParts(
String(apiKey || ""),
providerSpecificData
);
if (!accessToken) {
return {
valid: false,
error: "Could not extract access_token from the Microsoft 365 Copilot credential",
};
}
if (!chathubPath || !chathubPath.includes("@")) {
return {
valid: false,
error: "Could not extract the account-specific Chathub path from the credential",
};
}
// The live provider uses a SignalR WebSocket. The generic web-cookie /models
// probe builds an invalid wss://.../models URL, so validation here confirms
// the captured credential shape and lets the executor perform the live check.
return {
valid: true,
error: null,
warning: "Credential format accepted. The session is verified when the provider sends a chat.",
};
}
// ── t3.chat Web cookie validator ──
export async function validateT3WebProvider({ apiKey, providerSpecificData = {} }: any) {
try {