Thunderbird-MailExtension zum Ablegen von E-Mails in DocuWare (.eml + PDF + Anhänge), dynamische Indexfelder aus dem Store-Dialog, Auswahllisten, Identity-Service-Login. Self-distribution-Updates über updates.json auf eigenem Gitea.
167 lines
5.3 KiB
JavaScript
167 lines
5.3 KiB
JavaScript
// 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 };
|