This repository has been archived on 2025-12-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
qmi-cloud/server/passport-okta.js
Manuel Romero 149284d265 redirect to
2024-03-12 14:10:36 +01:00

319 lines
9.9 KiB
JavaScript

const passport = require('passport');
const expressSession = require('express-session');
const config = require('./config');
const axios = require('axios');
const qs = require('qs');
const fs = require('qs');
const path = require('path');
// set up database for express session
const MongoStore = require('connect-mongo')(expressSession);
const mongoose = require('mongoose');
const db = require("qmi-cloud-common/mongo");
const sessionStore = config.useMongoDBSessionStore? new MongoStore({
mongooseConnection: mongoose.connection,
url: process.env.MONGO_URI,
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
}) : new expressSession.MemoryStore();
var OpenIDConnectStrategy = require('passport-openidconnect');
const OKTA_DOMAIN = "qlik.okta.com";
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
_findByUpn(user.upn, function (err, user) {
done(err, user);
});
});
var _findByUpn = async function(upn, fn) {
var mongouser = await db.user.getOne({"upn": { $regex: new RegExp(upn, 'i') } });
if (mongouser){
return fn(null, mongouser);
} else {
return fn(null, null);
}
};
const getUserMsGraph = async function(oktaUpn) {
try {
const msConfig = {
client_id: config.creds.azureAdClientId,
client_secret: config.creds.azureAdClientSecret,
scope: "https://graph.microsoft.com/.default",
grant_type: "client_credentials"
}
const msTokenRes = await axios.post('https://login.microsoftonline.com/c21eeb5f-f5a6-44e8-a997-124f2f7a497c/oauth2/v2.0/token', qs.stringify(msConfig), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
const msAccessToken = msTokenRes.data.access_token;
var msUser = await axios({
method: "GET",
headers: {
Authorization: "Bearer "+ msAccessToken
},
url: 'https://graph.microsoft.com/v1.0/users/'+oktaUpn
});
return msUser.data;
} catch (e){
console.log('Passport# Error MS Graph stuff!!');
return null;
}
}
// set up passport
passport.use('oidc', new OpenIDConnectStrategy({
issuer: "https://qlik.okta.com",
authorizationURL: `https://${OKTA_DOMAIN}/oauth2/v1/authorize`,
tokenURL: `https://${OKTA_DOMAIN}/oauth2/v1/token`,
clientID: config.creds.clientID,
clientSecret: config.creds.clientSecret,
callbackURL: config.creds.redirectUrl,
scope: config.creds.scope,
passReqToCallback: true
}, async (req, issuer, profile, context, idToken, accessToken, refreshToken, done) => {
//console.log("OKTA ISSUER ", issuer)
//console.log("OKTA PROFILE ", profile)
//console.log("OKTA context ", context)
//console.log("OKTA idToken ", idToken)
//console.log("OKTA accessToken ", accessToken)
//console.log("OKTA refreshToken ", refreshToken)
var graphUser = await getUserMsGraph(profile.username.toLowerCase());
req.session.oktaAccessToken = accessToken;
if ( !profile.id ) {
return done(new Error("No profile id found"), null);
}
console.log(`Passport# new login from: ${profile.displayName} - sub: ${profile.id}` );
// asynchronous verification, for effect...
process.nextTick(function () {
_findByUpn(profile.username, async function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
"oid": graphUser? graphUser.id : undefined,
"upn": profile.username.toLowerCase(),
"displayName": profile.displayName,
"lastLogin": new Date(),
"sub": profile.id,
"active": true,
"mail": graphUser? graphUser.mail : profile.emails[0].value,
//"jobTitle": jobTitle
});
return done(null, user);
}
db.user.update(user._id, {
"upn": profile.username.toLowerCase(),
"lastLogin": new Date(),
"sub": profile.id,
"active": true,
"mail": graphUser? graphUser.mail : profile.emails[0].value,
//"jobTitle": jobTitle
});
return done(null, user);
});
});
//return done(null, profile);
}));
module.exports.init = function(app, isSecure){
const cookieName = 'qmicloud.sid';
var cookieConf = {
maxAge: config.mongoDBSessionMaxAge * 1000,
httpOnly: true,
secure: isSecure,
sameSite: isSecure? 'none' : undefined
};
console.log("--- SESSION", "Cookie Conf = ", cookieConf , "Cookie Name = " + cookieName);
// set up session middleware
if (config.useMongoDBSessionStore) {
app.use(expressSession({
name: cookieName,
secret: 'secret',
cookie: cookieConf,
store: sessionStore,
resave: true,
saveUninitialized: false
}));
} else {
app.use(expressSession({
name: cookieName,
secret: 'keyboard cat',
cookie: cookieConf,
store: sessionStore,
resave: true,
saveUninitialized: false
}));
}
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.get('/sessioninfo', function(req, res){
if (req.session.oktaAccessToken && req.user ) {
res.json({
"oktaAccessToken": req.session.oktaAccessToken,
"user": req.user
});
} else {
res.status(401).json({});
}
});
app.get('/login',
function(req, res, next) {
req.session.redirectTo = req.query.redirectTo || "/provisions";
passport.authenticate('oidc',
{
response: res, // required
resourceURL: config.resourceURL, // optional. Provide a value if you want to specify the resource.
//customState: 'my_state', // optional. Provide a value if you want to provide custom state value.
failureRedirect: '/error',
session: true,
//assignProperty: "weee",
authInfo: true
}
)(req, res, next);
},
function(req, res) {
res.redirect('/home');
}
);
app.get('/auth/openid/return',
function(req, res, next) {
passport.authenticate('oidc', {
response: res,
failureRedirect: '/error' ,
session: true,
//assignProperty: "weee",
authInfo: true
}
)(req, res, next);
},
function(req, res) {
let redirectTo = req.session && req.session.redirectTo? req.session.redirectTo : "/provisions";
console.log('Passport# We received a return from OKTA, redirectTo -> '+redirectTo);
res.redirect(redirectTo);
}
);
/*app.use('/auth/openid/return', passport.authenticate('oidc', { failureRedirect: '/error' }), (req, res) => {
console.log('Passport# We received a return from OKTA ');
res.redirect('/provisions');
});*/
app.get('/logout', function(req, res){
req.session.destroy(function(err) {
req.logOut();
res.redirect("/home");
});
});
};
function isOktaTokenHeaderAuthenticated(req) {
if (req.headers && req.session && req.headers.oktatoken !== undefined ) {
if ( req.headers.oktatoken === req.session.oktaAccessToken ) {
return true;
} else {
return false;
}
} else {
return false
}
}
async function isApiKeyAuthenticated(req) {
let key = req.query.apiKey || req.get('QMI-ApiKey');
if ( key ) {
var result = await db.apiKey.getOne({"apiKey": key});
if ( result ) {
req.user = result.user;
return true;
} else {
return false;
}
} else {
return false;
}
}
async function _ensureAuthenticated(req) {
return (isOktaTokenHeaderAuthenticated(req) || req.isAuthenticated() || await isApiKeyAuthenticated(req));
}
module.exports.ensureAuthenticatedDoLogin = async function(req, res, next) {
if ( await _ensureAuthenticated(req) ) {
return next();
}
res.redirect(`/login?redirectTo=${req.originalUrl}`);
};
module.exports.ensureAuthenticated = async function(req, res, next) {
if ( await _ensureAuthenticated(req) ) {
return next();
}
res.status(401).send({"error": "Unauthorized"});
};
module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
if ( await _ensureAuthenticated(req) && (req.user.role === 'admin' || req.user.role === 'superadmin') ) {
return next();
}
res.status(401).send({"error": "Unauthorized"});
};
module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
if ( await _ensureAuthenticated(req) ) {
var userId = (req.params.userId === 'me')? req.user._id : req.params.userId;
if ( req.user._id == userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
return next();
} else {
return res.status(401).send("Error: Unauthorized");
}
}
return res.status(401).send("Error: Unauthorized");
};
module.exports.sessionStore = sessionStore;