|
@@ -1,12 +1,3 @@
|
|
|
-/*
|
|
|
|
|
- * decaffeinate suggestions:
|
|
|
|
|
- * DS101: Remove unnecessary use of Array.from
|
|
|
|
|
- * DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
|
- * DS103: Rewrite code to no longer use __guard__
|
|
|
|
|
- * DS205: Consider reworking code to avoid use of IIFEs
|
|
|
|
|
- * DS207: Consider shorter variations of null checks
|
|
|
|
|
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
|
- */
|
|
|
|
|
const AuthenticationManager = require('./AuthenticationManager')
|
|
const AuthenticationManager = require('./AuthenticationManager')
|
|
|
const LoginRateLimiter = require('../Security/LoginRateLimiter')
|
|
const LoginRateLimiter = require('../Security/LoginRateLimiter')
|
|
|
const UserUpdater = require('../User/UserUpdater')
|
|
const UserUpdater = require('../User/UserUpdater')
|
|
@@ -22,9 +13,15 @@ const passport = require('passport')
|
|
|
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
|
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
|
|
const SudoModeHandler = require('../SudoMode/SudoModeHandler')
|
|
const SudoModeHandler = require('../SudoMode/SudoModeHandler')
|
|
|
const { URL } = require('url')
|
|
const { URL } = require('url')
|
|
|
|
|
+const _ = require('lodash')
|
|
|
|
|
|
|
|
const AuthenticationController = (module.exports = {
|
|
const AuthenticationController = (module.exports = {
|
|
|
serializeUser(user, callback) {
|
|
serializeUser(user, callback) {
|
|
|
|
|
+ if (!user._id || !user.email) {
|
|
|
|
|
+ const err = new Error('serializeUser called with non-user object')
|
|
|
|
|
+ logger.warn({ user }, err.message)
|
|
|
|
|
+ return callback(err)
|
|
|
|
|
+ }
|
|
|
const lightUser = {
|
|
const lightUser = {
|
|
|
_id: user._id,
|
|
_id: user._id,
|
|
|
first_name: user.first_name,
|
|
first_name: user.first_name,
|
|
@@ -38,27 +35,27 @@ const AuthenticationController = (module.exports = {
|
|
|
must_reconfirm: user.must_reconfirm,
|
|
must_reconfirm: user.must_reconfirm,
|
|
|
v1_id: user.overleaf != null ? user.overleaf.id : undefined
|
|
v1_id: user.overleaf != null ? user.overleaf.id : undefined
|
|
|
}
|
|
}
|
|
|
- return callback(null, lightUser)
|
|
|
|
|
|
|
+ callback(null, lightUser)
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
deserializeUser(user, cb) {
|
|
deserializeUser(user, cb) {
|
|
|
- return cb(null, user)
|
|
|
|
|
|
|
+ cb(null, user)
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
afterLoginSessionSetup(req, user, callback) {
|
|
afterLoginSessionSetup(req, user, callback) {
|
|
|
if (callback == null) {
|
|
if (callback == null) {
|
|
|
callback = function() {}
|
|
callback = function() {}
|
|
|
}
|
|
}
|
|
|
- return req.login(user, function(err) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ req.login(user, function(err) {
|
|
|
|
|
+ if (err) {
|
|
|
logger.warn({ user_id: user._id, err }, 'error from req.login')
|
|
logger.warn({ user_id: user._id, err }, 'error from req.login')
|
|
|
return callback(err)
|
|
return callback(err)
|
|
|
}
|
|
}
|
|
|
// Regenerate the session to get a new sessionID (cookie value) to
|
|
// Regenerate the session to get a new sessionID (cookie value) to
|
|
|
// protect against session fixation attacks
|
|
// protect against session fixation attacks
|
|
|
const oldSession = req.session
|
|
const oldSession = req.session
|
|
|
- return req.session.destroy(function(err) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ req.session.destroy(function(err) {
|
|
|
|
|
+ if (err) {
|
|
|
logger.warn(
|
|
logger.warn(
|
|
|
{ user_id: user._id, err },
|
|
{ user_id: user._id, err },
|
|
|
'error when trying to destroy old session'
|
|
'error when trying to destroy old session'
|
|
@@ -72,10 +69,8 @@ const AuthenticationController = (module.exports = {
|
|
|
req.session[key] = value
|
|
req.session[key] = value
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // copy to the old `session.user` location, for backward-comptability
|
|
|
|
|
- req.session.user = req.session.passport.user
|
|
|
|
|
- return req.session.save(function(err) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ req.session.save(function(err) {
|
|
|
|
|
+ if (err) {
|
|
|
logger.warn(
|
|
logger.warn(
|
|
|
{ user_id: user._id },
|
|
{ user_id: user._id },
|
|
|
'error saving regenerated session after login'
|
|
'error saving regenerated session after login'
|
|
@@ -83,7 +78,7 @@ const AuthenticationController = (module.exports = {
|
|
|
return callback(err)
|
|
return callback(err)
|
|
|
}
|
|
}
|
|
|
UserSessionsManager.trackSession(user, req.sessionID, function() {})
|
|
UserSessionsManager.trackSession(user, req.sessionID, function() {})
|
|
|
- return callback(null)
|
|
|
|
|
|
|
+ callback(null)
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
@@ -93,8 +88,8 @@ const AuthenticationController = (module.exports = {
|
|
|
// This function is middleware which wraps the passport.authenticate middleware,
|
|
// This function is middleware which wraps the passport.authenticate middleware,
|
|
|
// so we can send back our custom `{message: {text: "", type: ""}}` responses on failure,
|
|
// so we can send back our custom `{message: {text: "", type: ""}}` responses on failure,
|
|
|
// and send a `{redir: ""}` response on success
|
|
// and send a `{redir: ""}` response on success
|
|
|
- return passport.authenticate('local', function(err, user, info) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ passport.authenticate('local', function(err, user, info) {
|
|
|
|
|
+ if (err) {
|
|
|
return next(err)
|
|
return next(err)
|
|
|
}
|
|
}
|
|
|
if (user) {
|
|
if (user) {
|
|
@@ -116,60 +111,49 @@ const AuthenticationController = (module.exports = {
|
|
|
} // OAuth2 'state' mismatch
|
|
} // OAuth2 'state' mismatch
|
|
|
if (user.must_reconfirm) {
|
|
if (user.must_reconfirm) {
|
|
|
return AuthenticationController._redirectToReconfirmPage(req, res, user)
|
|
return AuthenticationController._redirectToReconfirmPage(req, res, user)
|
|
|
- } else {
|
|
|
|
|
- const redir =
|
|
|
|
|
- AuthenticationController._getRedirectFromSession(req) || '/project'
|
|
|
|
|
- AuthenticationController._loginAsyncHandlers(req, user)
|
|
|
|
|
- return AuthenticationController.afterLoginSessionSetup(
|
|
|
|
|
- req,
|
|
|
|
|
- user,
|
|
|
|
|
- function(err) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
- return next(err)
|
|
|
|
|
- }
|
|
|
|
|
- return SudoModeHandler.activateSudoMode(user._id, function(err) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
- logger.err(
|
|
|
|
|
- { err, user_id: user._id },
|
|
|
|
|
- 'Error activating Sudo Mode on login, continuing'
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- AuthenticationController._clearRedirectFromSession(req)
|
|
|
|
|
- if (
|
|
|
|
|
- __guard__(
|
|
|
|
|
- req.headers != null ? req.headers['accept'] : undefined,
|
|
|
|
|
- x => x.match(/^application\/json.*$/)
|
|
|
|
|
- )
|
|
|
|
|
- ) {
|
|
|
|
|
- return res.json({ redir })
|
|
|
|
|
- } else {
|
|
|
|
|
- return res.redirect(redir)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ const redir =
|
|
|
|
|
+ AuthenticationController._getRedirectFromSession(req) || '/project'
|
|
|
|
|
+ AuthenticationController._loginAsyncHandlers(req, user)
|
|
|
|
|
+ AuthenticationController.afterLoginSessionSetup(req, user, function(err) {
|
|
|
|
|
+ if (err) {
|
|
|
|
|
+ return next(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ SudoModeHandler.activateSudoMode(user._id, function(err) {
|
|
|
|
|
+ if (err) {
|
|
|
|
|
+ logger.err(
|
|
|
|
|
+ { err, user_id: user._id },
|
|
|
|
|
+ 'Error activating Sudo Mode on login, continuing'
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ AuthenticationController._clearRedirectFromSession(req)
|
|
|
|
|
+ if (
|
|
|
|
|
+ _.get(req, ['headers', 'accept'], '').match(/^application\/json.*$/)
|
|
|
|
|
+ ) {
|
|
|
|
|
+ res.json({ redir })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res.redirect(redir)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
doPassportLogin(req, username, password, done) {
|
|
doPassportLogin(req, username, password, done) {
|
|
|
const email = username.toLowerCase()
|
|
const email = username.toLowerCase()
|
|
|
const Modules = require('../../infrastructure/Modules')
|
|
const Modules = require('../../infrastructure/Modules')
|
|
|
- return Modules.hooks.fire('preDoPassportLogin', req, email, function(
|
|
|
|
|
|
|
+ Modules.hooks.fire('preDoPassportLogin', req, email, function(
|
|
|
err,
|
|
err,
|
|
|
infoList
|
|
infoList
|
|
|
) {
|
|
) {
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ if (err) {
|
|
|
return done(err)
|
|
return done(err)
|
|
|
}
|
|
}
|
|
|
const info = infoList.find(i => i != null)
|
|
const info = infoList.find(i => i != null)
|
|
|
if (info != null) {
|
|
if (info != null) {
|
|
|
return done(null, false, info)
|
|
return done(null, false, info)
|
|
|
}
|
|
}
|
|
|
- return LoginRateLimiter.processLoginRequest(email, function(
|
|
|
|
|
- err,
|
|
|
|
|
- isAllowed
|
|
|
|
|
- ) {
|
|
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ LoginRateLimiter.processLoginRequest(email, function(err, isAllowed) {
|
|
|
|
|
+ if (err) {
|
|
|
return done(err)
|
|
return done(err)
|
|
|
}
|
|
}
|
|
|
if (!isAllowed) {
|
|
if (!isAllowed) {
|
|
@@ -179,7 +163,7 @@ const AuthenticationController = (module.exports = {
|
|
|
type: 'error'
|
|
type: 'error'
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
- return AuthenticationManager.authenticate({ email }, password, function(
|
|
|
|
|
|
|
+ AuthenticationManager.authenticate({ email }, password, function(
|
|
|
error,
|
|
error,
|
|
|
user
|
|
user
|
|
|
) {
|
|
) {
|
|
@@ -188,11 +172,11 @@ const AuthenticationController = (module.exports = {
|
|
|
}
|
|
}
|
|
|
if (user != null) {
|
|
if (user != null) {
|
|
|
// async actions
|
|
// async actions
|
|
|
- return done(null, user)
|
|
|
|
|
|
|
+ done(null, user)
|
|
|
} else {
|
|
} else {
|
|
|
AuthenticationController._recordFailedLogin()
|
|
AuthenticationController._recordFailedLogin()
|
|
|
logger.log({ email }, 'failed log in')
|
|
logger.log({ email }, 'failed log in')
|
|
|
- return done(null, false, {
|
|
|
|
|
|
|
+ done(null, false, {
|
|
|
text: req.i18n.translate('email_or_password_wrong_try_again'),
|
|
text: req.i18n.translate('email_or_password_wrong_try_again'),
|
|
|
type: 'error'
|
|
type: 'error'
|
|
|
})
|
|
})
|
|
@@ -228,29 +212,15 @@ const AuthenticationController = (module.exports = {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
setInSessionUser(req, props) {
|
|
setInSessionUser(req, props) {
|
|
|
- return (() => {
|
|
|
|
|
- const result = []
|
|
|
|
|
- for (let key in props) {
|
|
|
|
|
- const value = props[key]
|
|
|
|
|
- if (
|
|
|
|
|
- __guard__(
|
|
|
|
|
- __guard__(req != null ? req.session : undefined, x1 => x1.passport),
|
|
|
|
|
- x => x.user
|
|
|
|
|
- ) != null
|
|
|
|
|
- ) {
|
|
|
|
|
- req.session.passport.user[key] = value
|
|
|
|
|
- }
|
|
|
|
|
- if (
|
|
|
|
|
- __guard__(req != null ? req.session : undefined, x2 => x2.user) !=
|
|
|
|
|
- null
|
|
|
|
|
- ) {
|
|
|
|
|
- result.push((req.session.user[key] = value))
|
|
|
|
|
- } else {
|
|
|
|
|
- result.push(undefined)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return result
|
|
|
|
|
- })()
|
|
|
|
|
|
|
+ const sessionUser = AuthenticationController.getSessionUser(req)
|
|
|
|
|
+ if (!sessionUser) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ for (let key in props) {
|
|
|
|
|
+ const value = props[key]
|
|
|
|
|
+ sessionUser[key] = value
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
isUserLoggedIn(req) {
|
|
isUserLoggedIn(req) {
|
|
@@ -278,18 +248,9 @@ const AuthenticationController = (module.exports = {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
getSessionUser(req) {
|
|
getSessionUser(req) {
|
|
|
- if (__guard__(req != null ? req.session : undefined, x => x.user) != null) {
|
|
|
|
|
- return req.session.user
|
|
|
|
|
- } else if (
|
|
|
|
|
- __guard__(
|
|
|
|
|
- __guard__(req != null ? req.session : undefined, x2 => x2.passport),
|
|
|
|
|
- x1 => x1.user
|
|
|
|
|
- )
|
|
|
|
|
- ) {
|
|
|
|
|
- return req.session.passport.user
|
|
|
|
|
- } else {
|
|
|
|
|
- return null
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const sessionUser = _.get(req, ['session', 'user'])
|
|
|
|
|
+ const sessionPassportUser = _.get(req, ['session', 'passport', 'user'])
|
|
|
|
|
+ return sessionUser || sessionPassportUser || null
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
requireLogin() {
|
|
requireLogin() {
|
|
@@ -321,7 +282,7 @@ const AuthenticationController = (module.exports = {
|
|
|
err,
|
|
err,
|
|
|
token
|
|
token
|
|
|
) {
|
|
) {
|
|
|
- if (err != null) {
|
|
|
|
|
|
|
+ if (err) {
|
|
|
// use a 401 status code for malformed header for git-bridge
|
|
// use a 401 status code for malformed header for git-bridge
|
|
|
if (
|
|
if (
|
|
|
err.code === 400 &&
|
|
err.code === 400 &&
|
|
@@ -349,7 +310,7 @@ const AuthenticationController = (module.exports = {
|
|
|
|
|
|
|
|
requireGlobalLogin(req, res, next) {
|
|
requireGlobalLogin(req, res, next) {
|
|
|
if (
|
|
if (
|
|
|
- Array.from(AuthenticationController._globalLoginWhitelist).includes(
|
|
|
|
|
|
|
+ AuthenticationController._globalLoginWhitelist.includes(
|
|
|
req._parsedUrl.pathname
|
|
req._parsedUrl.pathname
|
|
|
)
|
|
)
|
|
|
) {
|
|
) {
|
|
@@ -357,16 +318,16 @@ const AuthenticationController = (module.exports = {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (req.headers['authorization'] != null) {
|
|
if (req.headers['authorization'] != null) {
|
|
|
- return AuthenticationController.httpAuth(req, res, next)
|
|
|
|
|
|
|
+ AuthenticationController.httpAuth(req, res, next)
|
|
|
} else if (AuthenticationController.isUserLoggedIn(req)) {
|
|
} else if (AuthenticationController.isUserLoggedIn(req)) {
|
|
|
- return next()
|
|
|
|
|
|
|
+ next()
|
|
|
} else {
|
|
} else {
|
|
|
logger.log(
|
|
logger.log(
|
|
|
{ url: req.url },
|
|
{ url: req.url },
|
|
|
'user trying to access endpoint not in global whitelist'
|
|
'user trying to access endpoint not in global whitelist'
|
|
|
)
|
|
)
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
- return res.redirect('/login')
|
|
|
|
|
|
|
+ res.redirect('/login')
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -401,9 +362,9 @@ const AuthenticationController = (module.exports = {
|
|
|
req.query.project_name != null ||
|
|
req.query.project_name != null ||
|
|
|
req.path === '/user/subscription/new'
|
|
req.path === '/user/subscription/new'
|
|
|
) {
|
|
) {
|
|
|
- return AuthenticationController._redirectToRegisterPage(req, res)
|
|
|
|
|
|
|
+ AuthenticationController._redirectToRegisterPage(req, res)
|
|
|
} else {
|
|
} else {
|
|
|
- return AuthenticationController._redirectToLoginPage(req, res)
|
|
|
|
|
|
|
+ AuthenticationController._redirectToLoginPage(req, res)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -415,7 +376,7 @@ const AuthenticationController = (module.exports = {
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
const url = `/login?${querystring.stringify(req.query)}`
|
|
const url = `/login?${querystring.stringify(req.query)}`
|
|
|
res.redirect(url)
|
|
res.redirect(url)
|
|
|
- return Metrics.inc('security.login-redirect')
|
|
|
|
|
|
|
+ Metrics.inc('security.login-redirect')
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
_redirectToReconfirmPage(req, res, user) {
|
|
_redirectToReconfirmPage(req, res, user) {
|
|
@@ -425,14 +386,10 @@ const AuthenticationController = (module.exports = {
|
|
|
)
|
|
)
|
|
|
req.session.reconfirm_email = user != null ? user.email : undefined
|
|
req.session.reconfirm_email = user != null ? user.email : undefined
|
|
|
const redir = '/user/reconfirm'
|
|
const redir = '/user/reconfirm'
|
|
|
- if (
|
|
|
|
|
- __guard__(req.headers != null ? req.headers['accept'] : undefined, x =>
|
|
|
|
|
- x.match(/^application\/json.*$/)
|
|
|
|
|
- )
|
|
|
|
|
- ) {
|
|
|
|
|
- return res.json({ redir })
|
|
|
|
|
|
|
+ if (_.get(req, ['headers', 'accept'], '').match(/^application\/json.*$/)) {
|
|
|
|
|
+ res.json({ redir })
|
|
|
} else {
|
|
} else {
|
|
|
- return res.redirect(redir)
|
|
|
|
|
|
|
+ res.redirect(redir)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -444,14 +401,14 @@ const AuthenticationController = (module.exports = {
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
const url = `/register?${querystring.stringify(req.query)}`
|
|
const url = `/register?${querystring.stringify(req.query)}`
|
|
|
res.redirect(url)
|
|
res.redirect(url)
|
|
|
- return Metrics.inc('security.login-redirect')
|
|
|
|
|
|
|
+ Metrics.inc('security.login-redirect')
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
_recordSuccessfulLogin(userId, callback) {
|
|
_recordSuccessfulLogin(userId, callback) {
|
|
|
if (callback == null) {
|
|
if (callback == null) {
|
|
|
callback = function() {}
|
|
callback = function() {}
|
|
|
}
|
|
}
|
|
|
- return UserUpdater.updateUser(
|
|
|
|
|
|
|
+ UserUpdater.updateUser(
|
|
|
userId.toString(),
|
|
userId.toString(),
|
|
|
{
|
|
{
|
|
|
$set: { lastLoggedIn: new Date() },
|
|
$set: { lastLoggedIn: new Date() },
|
|
@@ -462,7 +419,7 @@ const AuthenticationController = (module.exports = {
|
|
|
callback(error)
|
|
callback(error)
|
|
|
}
|
|
}
|
|
|
Metrics.inc('user.login.success')
|
|
Metrics.inc('user.login.success')
|
|
|
- return callback()
|
|
|
|
|
|
|
+ callback()
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
},
|
|
},
|
|
@@ -474,10 +431,7 @@ const AuthenticationController = (module.exports = {
|
|
|
|
|
|
|
|
_getRedirectFromSession(req) {
|
|
_getRedirectFromSession(req) {
|
|
|
let safePath
|
|
let safePath
|
|
|
- const value = __guard__(
|
|
|
|
|
- req != null ? req.session : undefined,
|
|
|
|
|
- x => x.postLoginRedirect
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ const value = _.get(req, ['session', 'postLoginRedirect'])
|
|
|
if (value) {
|
|
if (value) {
|
|
|
safePath = AuthenticationController._getSafeRedirectPath(value)
|
|
safePath = AuthenticationController._getSafeRedirectPath(value)
|
|
|
}
|
|
}
|
|
@@ -486,7 +440,7 @@ const AuthenticationController = (module.exports = {
|
|
|
|
|
|
|
|
_clearRedirectFromSession(req) {
|
|
_clearRedirectFromSession(req) {
|
|
|
if (req.session != null) {
|
|
if (req.session != null) {
|
|
|
- return delete req.session.postLoginRedirect
|
|
|
|
|
|
|
+ delete req.session.postLoginRedirect
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -500,9 +454,3 @@ const AuthenticationController = (module.exports = {
|
|
|
return safePath
|
|
return safePath
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
-
|
|
|
|
|
-function __guard__(value, transform) {
|
|
|
|
|
- return typeof value !== 'undefined' && value !== null
|
|
|
|
|
- ? transform(value)
|
|
|
|
|
- : undefined
|
|
|
|
|
-}
|
|
|