// DocuWare-Authentifizierung. // // Primär: DocuWare Identity Service (OpenID Connect). // Neuere DocuWare-Versionen / DocuWare Cloud haben das alte Cookie-Logon // (/Account/Logon) abgeschaltet (HTTP 410) und verlangen ein OAuth-Token. // Wir nutzen den Resource-Owner-Password-Grant (ROPC) mit dem von DocuWare // vorinstallierten ÖFFENTLICHEN Client "docuware.platform.net.client" -- // d.h. KEINE eigene App-Registrierung und KEIN Client-Secret nötig. // // Ablauf: // 1. GET {platform}/Home/IdentityServiceInfo -> Identity-Service-URL // 2. GET {identity}/.well-known/openid-configuration -> token_endpoint // 3. POST {token_endpoint} grant_type=password ... -> access_token // 4. Folgeaufrufe der Platform-API mit Authorization: Bearer // // Fallback: Findet sich kein Identity Service (alte On-Prem-Server), wird das // klassische Cookie-Logon versucht. // // Token wird NUR im Speicher gehalten (TOKEN), nie persistiert. Im Hintergrund- // skript überlebt es die TB-Sitzung und wird per Messaging an die Fenster gereicht // (siehe background.js). Das Passwort wird ausschließlich zum Holen des Tokens // verwendet und danach verworfen. const DW_PUBLIC_CLIENT_ID = "docuware.platform.net.client"; const DW_SCOPE = "docuware.platform"; const COOKIE_TTL_MS = 8 * 60 * 60 * 1000; // Cookie-Session pragmatisch ~8h gültig let TOKEN = null; // { accessToken, expiresAt, mode, tokenEndpoint } const Auth = { /** * Meldet sich an DocuWare an. Wirft bei Fehlschlag. * @returns {Promise<{mode:string}>} */ async logon(settings) { const identityUrl = await this._discoverIdentityService(settings); if (identityUrl) { await this._logonIdentity(settings, identityUrl); return { mode: "identity" }; } // Fallback: altes Cookie-Logon (nur ältere On-Prem-Server). await this._logonCookie(settings); return { mode: "cookie" }; }, /** Zusätzliche Header für Requests. Bearer-Token, falls vorhanden. */ authHeaders() { if (TOKEN && TOKEN.accessToken) { return { Authorization: `Bearer ${TOKEN.accessToken}` }; } return {}; }, /** Aktuell ein Token vorhanden und (noch) gültig? (auch Cookie-Session) */ hasValidToken() { return !!( TOKEN && (TOKEN.accessToken || TOKEN.mode === "cookie") && TOKEN.expiresAt > Date.now() + 5000 ); }, /** Serialisierbare Token-Sicht für den Transfer per Messaging. */ currentToken() { if (!TOKEN) return null; return { accessToken: TOKEN.accessToken || null, expiresAt: TOKEN.expiresAt, mode: TOKEN.mode }; }, /** * Übernimmt ein vom Broker (Hintergrundskript) geliefertes Token in diesen * Kontext, damit authHeaders()/credentials greifen. Kein Passwort nötig. */ setToken(t) { if (!t || (!t.accessToken && t.mode !== "cookie")) { TOKEN = null; return; } TOKEN = { accessToken: t.accessToken || null, expiresAt: t.expiresAt || Date.now() + COOKIE_TTL_MS, mode: t.mode || (t.accessToken ? "identity" : "cookie"), }; }, /** Token verwerfen (z.B. beim Wechsel der Zugangsdaten). */ reset() { TOKEN = null; }, // --- Identity Service ----------------------------------------------------- /** * Ermittelt die Identity-Service-URL. Liefert null, wenn der Server keinen * Identity Service meldet (dann Cookie-Fallback). */ async _discoverIdentityService(settings) { const platform = Settings.platformUrl(settings); let res; try { res = await fetch(`${platform}/Home/IdentityServiceInfo`, { method: "GET", headers: { Accept: "application/json" }, }); } catch (_) { return null; } if (!res.ok) return null; const info = await res.json().catch(() => ({})); const url = info.IdentityServiceUrl || info.identityServiceUrl || info.Url || info.url || null; return url ? url.replace(/\/+$/, "") : null; }, async _logonIdentity(settings, identityUrl) { // OpenID-Discovery -> token_endpoint. const discoRes = await fetch(`${identityUrl}/.well-known/openid-configuration`, { method: "GET", headers: { Accept: "application/json" }, }); if (!discoRes.ok) { throw new Error( `Identity Service nicht erreichbar (HTTP ${discoRes.status} bei ${identityUrl}).` ); } const disco = await discoRes.json(); const tokenEndpoint = disco.token_endpoint; if (!tokenEndpoint) throw new Error("token_endpoint im Identity Service nicht gefunden."); const body = new URLSearchParams({ grant_type: "password", scope: DW_SCOPE, client_id: DW_PUBLIC_CLIENT_ID, username: settings.username, password: settings.password, }); const res = await fetch(tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: body.toString(), }); const data = await res.json().catch(() => ({})); if (!res.ok || !data.access_token) { const detail = data.error_description || data.error || (await this._safeText(res)); throw new Error(`Identity-Logon fehlgeschlagen (HTTP ${res.status}). ${detail || ""}`.trim()); } TOKEN = { accessToken: data.access_token, expiresAt: Date.now() + (Number(data.expires_in) || 3600) * 1000, mode: "identity", tokenEndpoint, }; }, // --- Cookie-Fallback (alte Server) ---------------------------------------- async _logonCookie(settings) { const platform = Settings.platformUrl(settings); const body = new URLSearchParams({ UserName: settings.username, Password: settings.password, Organization: settings.organization, RedirectToMyselfInCaseOfError: "false", RememberMe: "false", }); const res = await fetch(`${platform}/Account/Logon`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: body.toString(), }); if (!res.ok) { const text = await this._safeText(res); throw new Error(`Logon fehlgeschlagen (HTTP ${res.status}). ${text}`.trim()); } // Cookie-Session: kein Bearer-Token, Folgeaufrufe per credentials:"include". // Wir merken nur, dass eine Session besteht (für hasValidToken/Sitzungs-Cache). TOKEN = { accessToken: null, expiresAt: Date.now() + COOKIE_TTL_MS, mode: "cookie" }; }, async _safeText(res) { return res.text().catch(() => ""); }, }; if (typeof module !== "undefined") module.exports = { Auth };