319 lines
9.9 KiB
JavaScript
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;
|