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.js
2021-03-31 15:29:37 +02:00

269 lines
9.8 KiB
JavaScript

const passport = require('passport');
const expressSession = require('express-session');
const config = require('./config');
// set up database for express session
const MongoStore = require('connect-mongo')(expressSession);
const mongoose = require('mongoose');
const db = require("qmi-cloud-common/mongo");
const axios = require('axios');
const path = require('path');
const fs = require('fs');
// Start QuickStart here
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
/******************************************************************************
* Set up passport in the app
******************************************************************************/
//-----------------------------------------------------------------------------
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
//-----------------------------------------------------------------------------
passport.serializeUser(function(user, done) {
done(null, user.oid);
});
passport.deserializeUser(function(oid, done) {
_findByOid(oid, function (err, user) {
done(err, user);
});
});
var _findByOid = async function(oid, fn) {
var mongouser = await db.user.getOne({"oid": oid});
if (mongouser && mongouser.oid === oid){
return fn(null, mongouser);
} else {
return fn(null, null);
}
};
//-----------------------------------------------------------------------------
// Use the OIDCStrategy within Passport.
//
// Strategies in passport require a `verify` function, which accepts credentials
// (in this case, the `oid` claim in id_token), and invoke a callback to find
// the corresponding user object.
//
// The following are the accepted prototypes for the `verify` function
// (1) function(iss, sub, done)
// (2) function(iss, sub, profile, done)
// (3) function(iss, sub, profile, access_token, refresh_token, done)
// (4) function(iss, sub, profile, access_token, refresh_token, params, done)
// (5) function(iss, sub, profile, jwtClaims, access_token, refresh_token, params, done)
// (6) prototype (1)-(5) with an additional `req` parameter as the first parameter
//
// To do prototype (6), passReqToCallback must be set to true in the config.
//-----------------------------------------------------------------------------
passport.use(new OIDCStrategy({
identityMetadata: config.creds.identityMetadata,
clientID: config.creds.clientID,
responseType: config.creds.responseType,
responseMode: config.creds.responseMode,
redirectUrl: config.creds.redirectUrl,
allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl,
clientSecret: config.creds.clientSecret,
validateIssuer: config.creds.validateIssuer,
isB2C: config.creds.isB2C,
issuer: config.creds.issuer,
passReqToCallback: config.creds.passReqToCallback,
scope: config.creds.scope,
loggingLevel: config.creds.loggingLevel,
nonceLifetime: config.creds.nonceLifetime,
nonceMaxAmount: config.creds.nonceMaxAmount,
useCookieInsteadOfSession: config.creds.useCookieInsteadOfSession,
cookieEncryptionKeys: config.creds.cookieEncryptionKeys,
clockSkew: config.creds.clockSkew,
},
function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
if ( !profile.oid ) {
return done(new Error("No oid found"), null);
}
//console.log("accessToken", accessToken);
//console.log("iss", iss);
//console.log("sub", sub);
//console.log("refreshToken", refreshToken);
//console.log("jwtClaims", jwtClaims);
//console.log("params", params);
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName})` );
//Save user photo
axios({
method: 'get',
url: 'https://graph.microsoft.com/v1.0/me/photo/$value',
responseType: 'stream',
headers: { 'Authorization' : 'Bearer '+accessToken }
}).then(function (response) {
response.data.pipe(fs.createWriteStream(path.resolve(__dirname, '..', 'photos', `${profile.oid}.jpg`)));
}).catch(function(err){
console.log('Passport# Warning: No picture found');
});
// asynchronous verification, for effect...
process.nextTick(function () {
_findByOid(profile.oid, async function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
"oid": profile.oid,
"upn": profile.upn,
"displayName": profile.displayName,
"lastLogin": new Date()
});
return done(null, user);
}
db.user.update(user._id, {"lastLogin": new Date()});
return done(null, user);
});
});
}
));
module.exports.init = function(app){
// set up session middleware
if (config.useMongoDBSessionStore) {
//mongoose.connect(config.databaseUri);
app.use(expressSession({
secret: 'secret',
cookie: {maxAge: config.mongoDBSessionMaxAge * 1000},
store: new MongoStore({
mongooseConnection: mongoose.connection,
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
}),
resave: true,
saveUninitialized: false
}));
} else {
app.use(expressSession({ secret: 'keyboard cat', 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('/login',
function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
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: '/',
session: false
}
)(req, res, next);
},
function(req, res) {
res.redirect('/');
}
);
// 'GET returnURL'
// `passport.authenticate` will try to authenticate the content returned in
// query (such as authorization code). If authentication fails, user will be
// redirected to '/' (home page); otherwise, it passes to the next middleware.
app.get('/auth/openid/return',
function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/'
}
)(req, res, next);
},
function(req, res) {
console.log('Passport# We received a return from AzureAD.');
res.redirect('/provisions');
}
);
// 'POST returnURL'
// `passport.authenticate` will try to authenticate the content returned in
// body (such as authorization code). If authentication fails, user will be
// redirected to '/' (home page); otherwise, it passes to the next middleware.
app.post('/auth/openid/return',
function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
failureRedirect: '/'
}
)(req, res, next);
},
function(req, res) {
console.log('Passport# We received a return from AzureAD.');
res.redirect('/provisions');
}
);
// 'logout' route, logout from passport, and destroy the session with AAD.
app.get('/logout', function(req, res){
req.session.destroy(function(err) {
req.logOut();
res.redirect(config.destroySessionUrl);
});
});
};
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;
}
}
module.exports.ensureAuthenticatedDoLogin = async function(req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
return next();
}
res.redirect('/login');
};
module.exports.ensureAuthenticated = async function(req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
return next();
}
res.status(401).send({"error": "Unauthorized"});
};
module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
if ( ( await isApiKeyAuthenticated(req) || req.isAuthenticated()) && (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 isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
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");
};