thunderbird2docuware/lib/auth.js

167 lines
5.3 KiB
JavaScript
Raw Normal View History

// 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 <token>
//
// Fallback: Findet sich kein Identity Service (alte On-Prem-Server), wird das
// klassische Cookie-Logon versucht.
//
// Token wird im Speicher gehalten (TOKEN) und von authHeaders() ausgegeben.
const DW_PUBLIC_CLIENT_ID = "docuware.platform.net.client";
const DW_SCOPE = "docuware.platform";
let TOKEN = null; // { accessToken, refreshToken, expiresAt, 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? */
hasValidToken() {
return !!(TOKEN && TOKEN.accessToken && TOKEN.expiresAt > Date.now() + 5000);
},
/** 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,
refreshToken: data.refresh_token || null,
expiresAt: Date.now() + (Number(data.expires_in) || 3600) * 1000,
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());
}
},
async _safeText(res) {
return res.text().catch(() => "");
},
};
if (typeof module !== "undefined") module.exports = { Auth };