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");
|
}
|
};
|