digitransit-ui/server/passport-openid-connect/Strategy.js
2025-06-24 14:20:48 +03:00

195 lines
5.3 KiB
JavaScript

/* eslint-disable no-console, strict, no-unused-vars, prefer-destructuring, consistent-return */
'use strict';
const { Issuer, Strategy, custom } = require('openid-client');
const util = require('util');
const process = require('process');
const User = require('./User').User;
const debugLogging = process.env.DEBUGLOGGING;
const callbackPath = '/oid_callback';
const OICStrategy = function strategy(config) {
this.name = 'passport-openid-connect';
this.config = config || {};
this.client = null;
this.tokenSet = null;
this.init().then(() => {
console.log(
'Initialization of OpenID Connect discovery process completed.',
);
});
};
util.inherits(OICStrategy, Strategy);
custom.setHttpOptionsDefaults({
timeout: 10000,
});
OICStrategy.prototype.init = function init() {
if (!this.config.issuerHost) {
throw new Error(
'Could not find requried config options issuerHost in openid-passport strategy initalization',
);
}
console.log('OIDC: discover');
return Issuer.discover(this.config.issuerHost)
.then(issuer => {
this.client = new issuer.Client(this.config);
this.client[custom.clock_tolerance] = 30;
})
.catch(err => {
console.log('OpenID Connect discovery failed');
console.error('OIDC error: ', err);
process.abort();
});
};
OICStrategy.prototype.authenticate = function auth(req, opts) {
const redirectUri = this.createRedirectUrl(req);
if (opts.callback) {
if (debugLogging) {
console.log('calling auth callback');
}
return this.callback(req, opts);
}
if (opts.refresh) {
return this.refresh(req);
}
const cookieLang = req.cookies.lang || 'fi';
const { ssoValidTo, ssoToken } = req.session;
const authurl =
ssoValidTo && ssoValidTo > Math.floor(new Date().getTime() / 1000)
? this.createAuthUrl(redirectUri, cookieLang, ssoToken)
: this.createAuthUrl(redirectUri, cookieLang);
if (debugLogging) {
console.log(`ssoToken: ${ssoToken} authUrl: ${authurl}`);
}
this.redirect(authurl);
};
OICStrategy.prototype.getUserInfo = function getuinfo() {
if (debugLogging) {
console.log('passport getUserInfo');
}
return this.client.userinfo(this.tokenSet.access_token).then(userinfo => {
this.userinfo = userinfo;
if (debugLogging) {
console.log(`got userInfo: ${JSON.stringify(userinfo)}`);
}
});
};
OICStrategy.prototype.callback = function cb(req, opts) {
if (debugLogging) {
console.log(`path=${req.path} query=${req.query}`);
}
const redirectUri = this.createRedirectUrl(req);
return this.client
.callback(redirectUri, req.query, {
state: req.query.state,
})
.then(tokenSet => {
req.session.ssoToken = null;
req.session.ssoValidTo = null;
this.tokenSet = tokenSet;
if (debugLogging) {
console.log(`got tokenSet: ${JSON.stringify(tokenSet)}`);
}
return this.getUserInfo();
})
.then(() => {
const user = new User(this.userinfo);
user.token = this.tokenSet;
user.idtoken = this.tokenSet.claims;
if (debugLogging) {
console.log(`set user: ${JSON.stringify(user)}`);
}
if (this.config.sessionCallback) {
this.config.sessionCallback(user.data.sub, req.session.id);
}
this.success(user);
})
.catch(err => {
console.error('Error processing callback', err);
req.session.ssoToken = null;
req.session.ssoValidTo = null;
this.fail(err);
});
};
OICStrategy.prototype.refresh = function refresh(req) {
if (debugLogging) {
console.log('Refreshing tokens');
}
return this.client
.refresh(req.user.token.refresh_token)
.then(tokenSet => {
this.tokenSet = tokenSet;
if (debugLogging) {
console.log(`got tokenSet: ${JSON.stringify(tokenSet)}`);
}
return this.getUserInfo();
})
.then(() => {
const user = new User(this.userinfo);
user.token = this.tokenSet;
user.idtoken = this.tokenSet.claims;
if (debugLogging) {
console.log(`set user: ${JSON.stringify(user)}`);
}
this.success(user);
})
.catch(err => {
console.error('Error refreshing tokens', err);
req.logout({}, () => {
req.session.destroy();
this.fail(err);
});
});
};
OICStrategy.prototype.createAuthUrl = function createAuthUrl(
redirectUri,
lang,
ssoToken,
) {
if (debugLogging) {
console.log(`createAuthUrl, ssotoken=${JSON.stringify(ssoToken)}`);
}
const params = {
response_type: 'code',
client_id: this.config.client_id,
redirect_uri: redirectUri,
scope: this.config.scope,
state: process.hrtime()[1],
ui_locales: lang,
};
if (ssoToken) {
return this.client.authorizationUrl({
...params,
sso_token: ssoToken,
prompt: 'none',
});
}
return this.client.authorizationUrl(params);
};
OICStrategy.prototype.createRedirectUrl = function createRedirectUrl(req) {
const host = req.headers['x-forwarded-host'] || req.headers.host;
if (req.secure) {
return `https://${host}${callbackPath}`;
}
return `http://${host}${callbackPath}`;
};
OICStrategy.serializeUser = function serializeUser(user, cb) {
cb(null, user.serialize());
};
OICStrategy.deserializeUser = function deserializeUser(packed, cb) {
cb(null, User.unserialize(packed));
};
exports.Strategy = OICStrategy;