From bb80cdf5a6157ca1f3a276e12e9faae9a4739cb7 Mon Sep 17 00:00:00 2001
From: dh_ackergaul <dh_ackergaul@dh-software.de>
Date: Di, 23 Jun 2026 11:16:18 +0200
Subject: [PATCH] Update emvheya - 23.6.2026, 11:16:10 [JD]

---
 manufacturer/_furnview/furnplan-web/api/services/OAuth2AuthenticationService.js |  153 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 153 insertions(+), 0 deletions(-)

diff --git a/manufacturer/_furnview/furnplan-web/api/services/OAuth2AuthenticationService.js b/manufacturer/_furnview/furnplan-web/api/services/OAuth2AuthenticationService.js
new file mode 100644
index 0000000..e328e21
--- /dev/null
+++ b/manufacturer/_furnview/furnplan-web/api/services/OAuth2AuthenticationService.js
@@ -0,0 +1,153 @@
+const uuid = require("node-uuid").v4;
+
+const { BaseAuthenticationService } = require("./BaseAuthenticationService");
+
+module.exports.OAuth2AuthenticationService = class extends BaseAuthenticationService {
+    constructor() {
+        super();
+
+        this.discover();
+    }
+
+    async discover() {
+        try {
+            this.openId = await import("@dh-software/opus-x-openid-client-helper");
+
+            this.config = await this.openId.getConfig(sails.config.oauth, true);
+
+            console.log(`Discovered OAuth Service: ${this.config.serverMetadata().issuer}`);
+        }
+        catch (error) {
+            console.error(`Unable to discover OAuth Server at ${sails.config.oauth.issuer}. Retrying...`, error);
+
+            this.config = undefined;
+
+            setTimeout(() => this.discover(), 3000);
+        }
+    }
+
+    async login(req, res) {
+        try {
+            const { codeVerifier, state, url } = await this.openId.getAuthenticationAttempt(this.config, sails.config.oauth.redirectUri);
+
+            req.auth_cookie.auth = {
+                codeVerifier,
+                state,
+                originalUrl: req.query.oriReq || "/"
+            };
+
+            return res.redirect(url.href);
+        }
+        catch (e) {
+            Winston.error("OAuth login initiation failed:", e);
+
+            return res.serverError();
+        }
+    }
+
+    async logout(req, res) {
+        try {
+            const uri = await this.openId.buildLogoutURL(this.config, sails.config.oauth.postLogoutRedirectURIs[0], req.user.get("oauth.idToken"));
+
+            return res.json({ url: uri.toString() });
+        }
+        catch (error) {
+            console.error("Unable to logout:", error);
+
+            return res.serverError();
+        }
+    }
+
+    async callback (req, res) {
+        const oauthSession = req.auth_cookie.auth;
+
+        if (!oauthSession || req.query.state !== oauthSession.state) {
+            Winston.warn("OAuth callback state mismatch or missing session");
+            return res.redirect("/auth/login");
+        }
+
+        try {
+            const callbackUrl = new URL(
+                req.url,
+                sails.config.oauth.redirectUri
+            );
+
+            const { userInfo, tokenSet } = await this.openId.callback(this.config, oauthSession.codeVerifier, oauthSession.state, callbackUrl);
+
+            if (!userInfo.sub || !userInfo.customer_no) {
+                console.error("OAuth callback missing required userInfo 'sub' or 'customer_no':", userInfo);
+
+                delete req.auth_cookie.auth;
+
+                return res.redirect("/auth/login");
+            }
+
+            const opusSessionId = uuid();
+
+            const user = {
+                _id: opusSessionId,
+                permissions: ["anonymous", "basic", "dhp"],
+                canLogout: true,
+                isOpusUser: true,
+                canSeeEverything: false,
+                customerNo: userInfo.customer_no,
+                username: userInfo.username,
+                opusSessionId: opusSessionId,
+                data: {},
+                oauth: {
+                    idToken: tokenSet.id_token,
+                    userInfo: userInfo,
+                }
+            };
+
+            const configuration = await UseCaseConfiguration.findOne({
+                customerNo: user.customerNo,
+                externalConf: true
+            });
+
+            if (configuration) user.data.config = configuration.id;
+
+            await Session.update({ _id: req.session._id }, { $addToSet: { users: user } });
+
+            console.log("OAuth login granted for subject:", userInfo.sub, "username:", userInfo.username, "customerNo:", userInfo.customer_no);
+
+            const redirectTo = new URL("http://" + req.headers["host"] + req.auth_cookie.auth.originalUrl);
+
+            redirectTo.searchParams.set("session", user.opusSessionId);
+
+            return res.redirect(redirectTo.toString());
+        }
+        catch (e) {
+            console.error("OAuth login callback failed: ", e);
+
+            return res.redirect("/auth/login");
+        }
+        finally {
+            delete req.auth_cookie.auth;
+        }
+    }
+
+    async backchannelLogout (req, res) {
+        try {
+            const claims = await this.openId.validateLogoutToken(this.config, req.body.logout_token);
+            const session = await Session.findOne({ "users.oauth.userInfo.sub": claims.sub });
+            const user = session.users.find((user) => user?.get("oauth")?.userInfo?.sub === claims.sub);
+
+            const { userInfo } = user.get("oauth");
+
+            await Session.update({ _id: session._id }, { $pull: { users: { "oauth.userInfo.sub": claims.sub } } });
+
+            if (req.session) {
+                // delete temporary session which was created as a fallback (config/http.js) when the OAuth server called the logout callback
+                await Session.deleteOne({ _id: req.session._id });
+            }
+
+            console.log("OAuth logout granted for subject:", userInfo.sub, "username:", userInfo.username, "customerNo:", userInfo.customer_no);
+        }
+        catch (error) {
+            console.warn("Unable to single sign off:", error);
+        }
+
+        return res.send("ok");
+    }
+};

--
Gitblit v1.9.3