beforeCreate: function(instance){ if(instance.providerId === 'saml'){ return instance.sequelize.Promise.join( //generate a certificate pem.createCertificateAsync({ commonName: Optional.ofNullable(config.get('server.rootUrl')) .map(function(rootUrl){ return url.format(rootUrl).hostname; }) .orElse('localhost'), days: 7300, selfSigned: true, keyBitsize: 2048 }) .then(function(generated){ return instance.sequelize.models.samlServiceProviderMetadata.create({ privateKey: generated.clientKey, x509SigningCert: generated.certificate, directoryId: instance.directoryId, tenantId: instance.tenantId }) .then(function(metadata){ instance.set('samlServiceProviderMetadataId', metadata.id); }); }), //create empty statement mapping rules instance.sequelize.models.attributeStatementMappingRules.create({tenantId: instance.tenantId}) .then(function(rules){ instance.set('attributeStatementMappingRulesId', rules.id); }) ); } },
function parseExpandParam(expandParam) { return Optional.ofNullable(expandParam) .filter(_.negate(_.isEmpty)) //split the different parts, e.g: 'tenant,groups(offset:0,limit:10)' // ==> ['tenant', 'groups(offset:0,limit:10)'] .map(_.method('split', /,(?!offset|limit)/)) .map(function (expandStrings) { return _(expandStrings) .map(function (expandString) { //separate the association name and the pagination parts const expandParts = /^([^(]*)\(([^)]*)\)*$/.exec(expandString); let associationName, pagination; if (expandParts) { //pagination (limit or offset) was specified associationName = expandParts[1]; pagination = _(expandParts[2].split(',')) .map(_.method('split', ':')) .fromPairs() .mapValues(parseInt) .defaults(defaultPagination) .value(); ApiError.assert(_.keysIn(pagination).length === 2, ApiError, 400, 400, 'Invalid expansion pagination: %s', expandParts[2]); } else { //no option, the whole param is the association name associationName = expandString; pagination = defaultPagination; } return [associationName, pagination]; }) .fromPairs() .value(); }) .orElseGet(_.stubObject); }
.then(function (apiKey) { done( null, Optional.ofNullable(apiKey) .filter(_.flow(_.property('secret'), _.partial(_.eq, providedSecret))) .orElse(false) ); return null; })
controller.getCurrent = function (req, res) { res.status(302) .location( Optional.ofNullable(config.get('server.rootUrl')) .map(_.method('concat', '/v1/accounts/', req.user.accountId)) .orElse(req.user.accountId) ) .json(); return null; };
exports.send = function(account, directory, template, tokenId, additionalPlaceHolderValues){ var placeHolderValues = _.merge( //selection of account & directory fields {account: _.pick(_.defaults(account, {directory: _.pick(directory, directoryFields)}), accountFields)}, //token related fields (url, name value pair...) Optional.ofNullable(tokenId).map(_.bindKey(template, 'getUrlTokens')).orElseGet(_.stubObject), //custom fields additionalPlaceHolderValues ); var emailFields = _.defaults ( {}, //configuration fields config.get('email.fields'), //mandrill fields Optional.ofNullable(template.mandrillTemplate) .filter(() => _.eq(transportName, 'nodemailer-mandrill-transport')) .map(_.partial(getMandrillFields, _, placeHolderValues)) .orElseGet(_.stubObject), //template fields { from: template.fromName + '<'+template.fromEmailAddress+'>', to: account.email, subject: template.subject, text: _.template(template.textBody, templateSettings)(placeHolderValues) }, //Add html alternative only if the mime type is text/html Optional.of(template) .filter(_.flow(_.property('mimeType'), _.partial(_.eq, 'text/html'))) .map(function(t){ return {html: _.template(t.htmlBody, templateSettings)(placeHolderValues)}; }) .orElseGet(_.stubObject) ); return transporterSendEmail(emailFields) .tap(_.partial(logger.info, 'Email sent:')) .catch(_.partial(logger.error, 'Could not send email:')); };
getLookupAccountStore: function (organizationName) { //if organizationName is specified, try to find an organization //mapped to this application with that name. //Else return this application return Optional.ofNullable(organizationName) .map(o => this.getOrganizations({ attributes: ['id'], where: {name: o}, limit: 1 }) .then(_.head) .tap(_.partial(ApiError.assert, _, ApiError, 404, 2014, 'Organization %s is not linked to application %s', organizationName, this.id))) .orElse(this.sequelize.Promise.resolve(this)); }
controller.createFactor = function (req, res) { const accountId = req.swagger.params.id.value; const factorAttributes = req.swagger.params.attributes.value; //if accountName is not set, take the account email BluebirdPromise.resolve( Optional.ofNullable(factorAttributes.accountName) .orElseGet(() => models.account.findByPk(accountId, {attributes: ['email']}) .tap(ApiError.assertFound) .get('email')) ).then(email => { factorAttributes.accountName = email; return controllerHelper.createAndExpand( models.factor, {tenantId: req.user.tenantId, accountId: accountId}, req, res); }); };
.then(requireMfa => { var secondFactor = Optional.ofNullable(factorTypeGetter) .map(_.method('call', null, result)) .orElse(null); //ask for a second factor if requested if(!_.isEmpty(requireMfa) && !_.includes(requireMfa, secondFactor)){ req.authInfo.require_mfa = requireMfa; //add 2nd factors into the request scope req.authInfo.scope = scopeHelper.pathsToScope(_.merge( scopeHelper.scopeToPaths(req.authInfo.scope), idSiteHelper.getFactorsScope(accountId) )); req.authInfo.isNewSub = isNewSub; return setAuthorizationBearer(req, this) .then(() => original.call(this, result)); } else { //the user is authenticated: redirect back to the application return getApiKey( req.authInfo.sub, { model: models.tenant, include: [{ model: models.idSite, limit: 1 }] } ) .then(function(apiKey) { return idSiteHelper.getJwtResponse( apiKey, accountHref, { isNewSub: isNewSub || req.authInfo.isNewSub || false, status: "AUTHENTICATED", cb_uri: req.authInfo.cb_uri, irt: req.authInfo.init_jti, state: req.authInfo.state, mfa: secondFactor, inv_href: req.authInfo.inv_href, email: req.authInfo.email } ); }) //don't redirect directly to the app, redirect first to cloudpass so it can set a cookie .then(jwtResponse => this.redirect(hrefHelper.getRootUrl(req.authInfo.app_href) + "/sso?jwtResponse=" + jwtResponse)); } })
.orElseGet(() => //look for orgId in authInfo (account settings or missing 2nd factor) Optional.ofNullable(req.authInfo.org_href) .orElseGet(() => { //ros => request organization selection if (req.authInfo.ros) { return models.account.build({id: accountId}).countOrganizations() .then(_.cond([ //no org => no need for selection [_.partial(_.eq, 0), _.constant(null)], //only one org => just take this one [_.partial(_.eq, 1), () => models.account.build({id: accountId}).getOrganizations({ limit: 1, attributes: ['id'] }).get(0).get('href')], //more than one: selection needed [_.stubTrue, _.stubFalse] ])); } else { //no org requested return null; } })
var _ = require('lodash'); var Optional = require('optional-js'); var winston = require('winston'); var scopeHelper = require('./scopeHelper'); var hrefHelper = require('./hrefHelper'); var logger = winston.loggers.get('email'); var templateSettings = {interpolate : /\${([\w\.]+?)}/g}; var accountFields = ['givenName', 'surname', 'fullName', 'username', 'email', 'failedLoginAttempts', 'directory']; var directoryFields = ['name']; // create reusable transporter object var transportName = config.get('email.transport.name'); var transportOptions = config.get('email.transport.options'); var transporter = nodemailer.createTransport( Optional.ofNullable(transportName) .map(function(pluginName){return require(pluginName)(transportOptions);}) .orElse(transportOptions) ); var transporterSendEmail = BluebirdPromise.promisify(transporter.sendMail, {context: transporter}); function getMandrillFields(mandrillTemplate, placeHolderValues){ return { //subject, content & from fields are provided by the template subject: null, text: null, html: null, from: null, mandrillOptions: { template_name: mandrillTemplate, template_content: [],
.flatMap(hrefSplit => Optional.ofNullable(_.find(_.values(models.sequelize.models), _.matchesProperty('options.name.plural', hrefSplit[0]))) .map(_.method('build', {id: hrefSplit[1]}))
return function(result) { //check if an account has been returned let accountHref = accountHrefGetter(result, req); if (accountHref) { var accountId = /\/accounts\/(.*)$/.exec(accountHref)[1]; //request a 2nd factor if the user is not already authenticated // and the user already configured any or the application requested it BluebirdPromise.resolve( Optional.ofNullable(req.authInfo.authenticated ? [] : req.authInfo.require_mfa) .orElseGet(() => models.factor.findAll({ where: { accountId, status: 'ENABLED', verificationStatus: 'VERIFIED' } }) .map(_.property('type')) .then(_.uniq) ) ) .then(requireMfa => { var secondFactor = Optional.ofNullable(factorTypeGetter) .map(_.method('call', null, result)) .orElse(null); //ask for a second factor if requested if(!_.isEmpty(requireMfa) && !_.includes(requireMfa, secondFactor)){ req.authInfo.require_mfa = requireMfa; //add 2nd factors into the request scope req.authInfo.scope = scopeHelper.pathsToScope(_.merge( scopeHelper.scopeToPaths(req.authInfo.scope), idSiteHelper.getFactorsScope(accountId) )); req.authInfo.isNewSub = isNewSub; return setAuthorizationBearer(req, this) .then(() => original.call(this, result)); } else { //the user is authenticated: redirect back to the application return getApiKey( req.authInfo.sub, { model: models.tenant, include: [{ model: models.idSite, limit: 1 }] } ) .then(function(apiKey) { return idSiteHelper.getJwtResponse( apiKey, accountHref, { isNewSub: isNewSub || req.authInfo.isNewSub || false, status: "AUTHENTICATED", cb_uri: req.authInfo.cb_uri, irt: req.authInfo.init_jti, state: req.authInfo.state, mfa: secondFactor, inv_href: req.authInfo.inv_href, email: req.authInfo.email } ); }) //don't redirect directly to the app, redirect first to cloudpass so it can set a cookie .then(jwtResponse => this.redirect(hrefHelper.getRootUrl(req.authInfo.app_href) + "/sso?jwtResponse=" + jwtResponse)); } }) .catch(req.next); } else { return original.call(this, result); } };
.then(requireMfa => { const secondFactor = Optional.ofNullable(factorTypeGetter) .map(_.method('call', null, result)) .orElse(req.authInfo.verified_mfa); //ask for a second factor if requested if (!_.isEmpty(requireMfa) && !_.includes(requireMfa, secondFactor)) { req.authInfo.require_mfa = requireMfa; //add 2nd factors into the request scope req.authInfo.scope = scopeHelper.pathsToScope(_.merge( scopeHelper.scopeToPaths(req.authInfo.scope), idSiteHelper.getFactorsScope(accountId) )); req.authInfo.isNewSub = isNewSub; return setAuthorizationBearer(req, this) .then(() => original.call(this, result)); } else { //the user is authenticated // check if organization selection was requested return BluebirdPromise.resolve( Optional.ofNullable(orgHrefGetter) .map(_.method('call', null, result)) .orElseGet(() => //look for orgId in authInfo (account settings or missing 2nd factor) Optional.ofNullable(req.authInfo.org_href) .orElseGet(() => { //ros => request organization selection if (req.authInfo.ros) { return models.account.build({id: accountId}).countOrganizations() .then(_.cond([ //no org => no need for selection [_.partial(_.eq, 0), _.constant(null)], //only one org => just take this one [_.partial(_.eq, 1), () => models.account.build({id: accountId}).getOrganizations({ limit: 1, attributes: ['id'] }).get(0).get('href')], //more than one: selection needed [_.stubTrue, _.stubFalse] ])); } else { //no org requested return null; } }) )) .then(orgHref => { if (orgHref === false) { //organization requested but not chosen yet //add available organizations to the scope req.authInfo.scope = idSiteHelper.getAccountOrganizationsScope(accountId); req.authInfo.verified_mfa = secondFactor; req.authInfo.isNewSub = isNewSub; return setAuthorizationBearer(req, this).then(() => original.call(this, result)); } else { return getApiKey( req.authInfo.sub, { model: models.tenant, include: [{ model: models.idSite, limit: 1 }] } ) .then(function (apiKey) { return idSiteHelper.getJwtResponse( apiKey, accountHref, { isNewSub: isNewSub || req.authInfo.isNewSub || false, status: "AUTHENTICATED", cb_uri: req.authInfo.cb_uri, irt: req.authInfo.init_jti, state: req.authInfo.state, mfa: secondFactor, inv_href: req.authInfo.inv_href, email: req.authInfo.email, org_href: orgHref } ); }) //don't redirect directly to the app, redirect first to cloudpass so it can set a cookie .then(jwtResponse => this.redirect(hrefHelper.getRootUrl(req.authInfo.app_href) + "/sso?jwtResponse=" + jwtResponse)); } }); } })
v => Optional.ofNullable(v).map(_.property('href')).map(hrefHelper.resolveHref).orElse(v)
getFacebookAppSecret() { return Optional.ofNullable(process.env.FACEBOOK_CLIENT_SECRET) .orElseThrow(() => new Error('Could not get env variable FACEBOOK_CLIENT_SECRET')); }
afterAuthentication(result => Optional.ofNullable(result.href).filter(_.constant(result.status === 'ENABLED')).orElseGet(_.stubFalse), true)
verifyPassword: function(password){ return Optional.ofNullable(this.get('password', {role: 'passwordHashing'})) .map(hash => bcrypt.compareAsync(password, hash)) .orElse(BluebirdPromise.resolve(false)); },
const getHrefGroup = (groupId, defaultResult) => href => Optional.of(href) .map(hrefPattern.exec.bind(hrefPattern)) .map(res => res[groupId]) .orElse(defaultResult); //the root URL is only protocol + host exports.getRootUrl = getHrefGroup(2, ''); //the base URL also includes the API version (/v1) exports.getBaseUrl = getHrefGroup(1, '/v1'); //unqualifyHref removes the base URL exports.unqualifyHref = href => href.replace(hrefPattern, '$3'); //returned the configured base URL exports.baseUrl = Optional.ofNullable(config.get('server.rootUrl')) .map(_.method('concat', '/v1/')) .orElse('/'); exports.resolveHref = href => Optional.of(href) .map(exports.unqualifyHref) .map(_.method('split', '/')) .map(_.compact) .filter(_.matchesProperty('length', 2)) .flatMap(hrefSplit => Optional.ofNullable(_.find(_.values(models.sequelize.models), _.matchesProperty('options.name.plural', hrefSplit[0]))) .map(_.method('build', {id: hrefSplit[1]})) ).orElse(null);
req => Optional.ofNullable(req.headers.authorization) .map(_.bindKey(authorization, 'parse')) .filter(auth => auth.scheme === 'Bearer') .map(_.property('token')) .orElse(null),