2019-05-29 05:21:06 -04:00
|
|
|
const AuthenticationManager = require('./AuthenticationManager')
|
2021-07-28 04:51:20 -04:00
|
|
|
const SessionManager = require('./SessionManager')
|
2020-08-19 05:11:32 -04:00
|
|
|
const OError = require('@overleaf/o-error')
|
2019-05-29 05:21:06 -04:00
|
|
|
const LoginRateLimiter = require('../Security/LoginRateLimiter')
|
|
|
|
const UserUpdater = require('../User/UserUpdater')
|
2020-10-30 04:10:50 -04:00
|
|
|
const Metrics = require('@overleaf/metrics')
|
2019-05-29 05:21:06 -04:00
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const querystring = require('querystring')
|
2021-07-07 05:38:56 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2019-05-29 05:21:06 -04:00
|
|
|
const basicAuth = require('basic-auth-connect')
|
2019-11-07 05:28:18 -05:00
|
|
|
const crypto = require('crypto')
|
2019-05-29 05:21:06 -04:00
|
|
|
const UserHandler = require('../User/UserHandler')
|
|
|
|
const UserSessionsManager = require('../User/UserSessionsManager')
|
2019-10-22 05:08:49 -04:00
|
|
|
const SessionStoreManager = require('../../infrastructure/SessionStoreManager')
|
2019-05-29 05:21:06 -04:00
|
|
|
const Analytics = require('../Analytics/AnalyticsManager')
|
|
|
|
const passport = require('passport')
|
|
|
|
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
2019-09-25 10:29:10 -04:00
|
|
|
const UrlHelper = require('../Helpers/UrlHelper')
|
2020-04-01 11:07:44 -04:00
|
|
|
const AsyncFormHelper = require('../Helpers/AsyncFormHelper')
|
2019-08-07 10:04:18 -04:00
|
|
|
const _ = require('lodash')
|
2021-03-29 05:20:54 -04:00
|
|
|
const UserAuditLogHandler = require('../User/UserAuditLogHandler')
|
2021-06-21 10:02:43 -04:00
|
|
|
const AnalyticsRegistrationSourceHelper = require('../Analytics/AnalyticsRegistrationSourceHelper')
|
2020-07-27 06:46:27 -04:00
|
|
|
const {
|
2021-04-27 03:52:58 -04:00
|
|
|
acceptsJson,
|
2020-07-27 06:46:27 -04:00
|
|
|
} = require('../../infrastructure/RequestContentTypeDetection')
|
|
|
|
|
|
|
|
function send401WithChallenge(res) {
|
|
|
|
res.setHeader('WWW-Authenticate', 'OverleafLogin')
|
|
|
|
res.sendStatus(401)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2020-08-13 09:42:28 -04:00
|
|
|
const AuthenticationController = {
|
2019-05-29 05:21:06 -04:00
|
|
|
serializeUser(user, callback) {
|
2019-08-07 10:04:18 -04:00
|
|
|
if (!user._id || !user.email) {
|
|
|
|
const err = new Error('serializeUser called with non-user object')
|
|
|
|
logger.warn({ user }, err.message)
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
const lightUser = {
|
|
|
|
_id: user._id,
|
|
|
|
first_name: user.first_name,
|
|
|
|
last_name: user.last_name,
|
|
|
|
isAdmin: user.isAdmin,
|
|
|
|
staffAccess: user.staffAccess,
|
|
|
|
email: user.email,
|
|
|
|
referal_id: user.referal_id,
|
|
|
|
session_created: new Date().toISOString(),
|
|
|
|
ip_address: user._login_req_ip,
|
|
|
|
must_reconfirm: user.must_reconfirm,
|
2021-04-27 03:52:58 -04:00
|
|
|
v1_id: user.overleaf != null ? user.overleaf.id : undefined,
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-08-07 10:04:18 -04:00
|
|
|
callback(null, lightUser)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
deserializeUser(user, cb) {
|
2019-08-07 10:04:18 -04:00
|
|
|
cb(null, user)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
passportLogin(req, res, next) {
|
|
|
|
// This function is middleware which wraps the passport.authenticate middleware,
|
|
|
|
// so we can send back our custom `{message: {text: "", type: ""}}` responses on failure,
|
|
|
|
// and send a `{redir: ""}` response on success
|
2021-04-14 09:17:21 -04:00
|
|
|
passport.authenticate('local', function (err, user, info) {
|
2019-08-07 10:04:18 -04:00
|
|
|
if (err) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return next(err)
|
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
// `user` is either a user object or false
|
|
|
|
return AuthenticationController.finishLogin(user, req, res, next)
|
|
|
|
} else {
|
|
|
|
if (info.redir != null) {
|
|
|
|
return res.json({ redir: info.redir })
|
|
|
|
} else {
|
|
|
|
return res.json({ message: info })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})(req, res, next)
|
|
|
|
},
|
|
|
|
|
|
|
|
finishLogin(user, req, res, next) {
|
|
|
|
if (user === false) {
|
|
|
|
return res.redirect('/login')
|
|
|
|
} // OAuth2 'state' mismatch
|
2020-04-01 11:06:46 -04:00
|
|
|
|
|
|
|
const Modules = require('../../infrastructure/Modules')
|
2021-04-14 09:17:21 -04:00
|
|
|
Modules.hooks.fire(
|
|
|
|
'preFinishLogin',
|
|
|
|
req,
|
|
|
|
res,
|
|
|
|
user,
|
|
|
|
function (error, results) {
|
|
|
|
if (error) {
|
|
|
|
return next(error)
|
|
|
|
}
|
|
|
|
if (results.some(result => result && result.doNotFinish)) {
|
|
|
|
return
|
|
|
|
}
|
2020-04-01 11:06:46 -04:00
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
if (user.must_reconfirm) {
|
|
|
|
return AuthenticationController._redirectToReconfirmPage(
|
|
|
|
req,
|
|
|
|
res,
|
|
|
|
user
|
|
|
|
)
|
2019-08-07 10:04:18 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
|
|
|
|
const redir =
|
|
|
|
AuthenticationController._getRedirectFromSession(req) || '/project'
|
2021-09-09 13:03:29 -04:00
|
|
|
_loginAsyncHandlers(req, user)
|
2021-04-14 09:17:21 -04:00
|
|
|
const userId = user._id
|
|
|
|
UserAuditLogHandler.addEntry(userId, 'login', userId, req.ip, err => {
|
2021-03-29 05:20:54 -04:00
|
|
|
if (err) {
|
|
|
|
return next(err)
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
_afterLoginSessionSetup(req, user, function (err) {
|
|
|
|
if (err) {
|
|
|
|
return next(err)
|
|
|
|
}
|
|
|
|
AuthenticationController._clearRedirectFromSession(req)
|
2021-06-21 10:02:43 -04:00
|
|
|
AnalyticsRegistrationSourceHelper.clearSource(req.session)
|
|
|
|
AnalyticsRegistrationSourceHelper.clearInbound(req.session)
|
2021-04-14 09:17:21 -04:00
|
|
|
AsyncFormHelper.redirect(req, res, redir)
|
|
|
|
})
|
2021-03-29 05:20:54 -04:00
|
|
|
})
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
doPassportLogin(req, username, password, done) {
|
|
|
|
const email = username.toLowerCase()
|
|
|
|
const Modules = require('../../infrastructure/Modules')
|
2021-04-14 09:17:21 -04:00
|
|
|
Modules.hooks.fire(
|
|
|
|
'preDoPassportLogin',
|
|
|
|
req,
|
|
|
|
email,
|
|
|
|
function (err, infoList) {
|
2019-08-07 10:04:18 -04:00
|
|
|
if (err) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return done(err)
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
const info = infoList.find(i => i != null)
|
|
|
|
if (info != null) {
|
|
|
|
return done(null, false, info)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
LoginRateLimiter.processLoginRequest(email, function (err, isAllowed) {
|
|
|
|
if (err) {
|
|
|
|
return done(err)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
if (!isAllowed) {
|
|
|
|
logger.log({ email }, 'too many login requests')
|
|
|
|
return done(null, null, {
|
|
|
|
text: req.i18n.translate('to_many_login_requests_2_mins'),
|
2021-04-27 03:52:58 -04:00
|
|
|
type: 'error',
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
AuthenticationManager.authenticate(
|
|
|
|
{ email },
|
|
|
|
password,
|
|
|
|
function (error, user) {
|
|
|
|
if (error != null) {
|
|
|
|
return done(error)
|
|
|
|
}
|
|
|
|
if (user != null) {
|
|
|
|
// async actions
|
|
|
|
done(null, user)
|
|
|
|
} else {
|
|
|
|
AuthenticationController._recordFailedLogin()
|
|
|
|
logger.log({ email }, 'failed log in')
|
|
|
|
done(null, false, {
|
|
|
|
text: req.i18n.translate('email_or_password_wrong_try_again'),
|
2021-04-27 03:52:58 -04:00
|
|
|
type: 'error',
|
2021-04-14 09:17:21 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
2021-04-14 09:17:21 -04:00
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
ipMatchCheck(req, user) {
|
|
|
|
if (req.ip !== user.lastLoginIp) {
|
|
|
|
NotificationsBuilder.ipMatcherAffiliation(user._id).create(req.ip)
|
|
|
|
}
|
|
|
|
return UserUpdater.updateUser(user._id.toString(), {
|
2021-04-27 03:52:58 -04:00
|
|
|
$set: { lastLoginIp: req.ip },
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
requireLogin() {
|
2021-04-14 09:17:21 -04:00
|
|
|
const doRequest = function (req, res, next) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (next == null) {
|
2021-04-14 09:17:21 -04:00
|
|
|
next = function () {}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-07-28 04:51:20 -04:00
|
|
|
if (!SessionManager.isUserLoggedIn(req.session)) {
|
2020-07-27 06:46:27 -04:00
|
|
|
if (acceptsJson(req)) return send401WithChallenge(res)
|
2019-05-29 05:21:06 -04:00
|
|
|
return AuthenticationController._redirectToLoginOrRegisterPage(req, res)
|
|
|
|
} else {
|
2021-07-28 04:51:20 -04:00
|
|
|
req.user = SessionManager.getSessionUser(req.session)
|
2019-05-29 05:21:06 -04:00
|
|
|
return next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return doRequest
|
|
|
|
},
|
|
|
|
|
2019-06-06 17:14:16 -04:00
|
|
|
requireOauth() {
|
2019-05-29 05:21:06 -04:00
|
|
|
// require this here because module may not be included in some versions
|
|
|
|
const Oauth2Server = require('../../../../modules/oauth2-server/app/src/Oauth2Server')
|
2021-04-14 09:17:21 -04:00
|
|
|
return function (req, res, next) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (next == null) {
|
2021-04-14 09:17:21 -04:00
|
|
|
next = function () {}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
const request = new Oauth2Server.Request(req)
|
|
|
|
const response = new Oauth2Server.Response(res)
|
2021-04-14 09:17:21 -04:00
|
|
|
return Oauth2Server.server.authenticate(
|
|
|
|
request,
|
|
|
|
response,
|
|
|
|
{},
|
|
|
|
function (err, token) {
|
|
|
|
if (err) {
|
|
|
|
// use a 401 status code for malformed header for git-bridge
|
|
|
|
if (
|
|
|
|
err.code === 400 &&
|
|
|
|
err.message === 'Invalid request: malformed authorization header'
|
|
|
|
) {
|
|
|
|
err.code = 401
|
|
|
|
}
|
|
|
|
// send all other errors
|
|
|
|
return res
|
|
|
|
.status(err.code)
|
|
|
|
.json({ error: err.name, error_description: err.message })
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
req.oauth = { access_token: token.accessToken }
|
|
|
|
req.oauth_token = token
|
|
|
|
req.oauth_user = token.user
|
|
|
|
return next()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
validateUserSession: function () {
|
2019-10-22 05:08:49 -04:00
|
|
|
// Middleware to check that the user's session is still good on key actions,
|
|
|
|
// such as opening a a project. Could be used to check that session has not
|
|
|
|
// exceeded a maximum lifetime (req.session.session_created), or for session
|
|
|
|
// hijacking checks (e.g. change of ip address, req.session.ip_address). For
|
|
|
|
// now, just check that the session has been loaded from the session store
|
|
|
|
// correctly.
|
2021-04-14 09:17:21 -04:00
|
|
|
return function (req, res, next) {
|
2019-10-22 05:08:49 -04:00
|
|
|
// check that the session store is returning valid results
|
|
|
|
if (req.session && !SessionStoreManager.hasValidationToken(req)) {
|
|
|
|
// force user to update session
|
|
|
|
req.session.regenerate(() => {
|
|
|
|
// need to destroy the existing session and generate a new one
|
|
|
|
// otherwise they will already be logged in when they are redirected
|
|
|
|
// to the login page
|
2020-07-27 06:46:27 -04:00
|
|
|
if (acceptsJson(req)) return send401WithChallenge(res)
|
2019-10-22 05:08:49 -04:00
|
|
|
AuthenticationController._redirectToLoginOrRegisterPage(req, res)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
_globalLoginWhitelist: [],
|
|
|
|
addEndpointToLoginWhitelist(endpoint) {
|
|
|
|
return AuthenticationController._globalLoginWhitelist.push(endpoint)
|
|
|
|
},
|
|
|
|
|
|
|
|
requireGlobalLogin(req, res, next) {
|
|
|
|
if (
|
2019-08-07 10:04:18 -04:00
|
|
|
AuthenticationController._globalLoginWhitelist.includes(
|
2019-05-29 05:21:06 -04:00
|
|
|
req._parsedUrl.pathname
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return next()
|
|
|
|
}
|
|
|
|
|
2020-12-16 05:37:00 -05:00
|
|
|
if (req.headers.authorization != null) {
|
2021-05-31 04:30:04 -04:00
|
|
|
AuthenticationController.requirePrivateApiAuth()(req, res, next)
|
2021-07-28 04:51:20 -04:00
|
|
|
} else if (SessionManager.isUserLoggedIn(req.session)) {
|
2019-08-07 10:04:18 -04:00
|
|
|
next()
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
|
|
|
logger.log(
|
|
|
|
{ url: req.url },
|
|
|
|
'user trying to access endpoint not in global whitelist'
|
|
|
|
)
|
2020-07-27 06:46:27 -04:00
|
|
|
if (acceptsJson(req)) return send401WithChallenge(res)
|
2019-05-29 05:21:06 -04:00
|
|
|
AuthenticationController.setRedirectInSession(req)
|
2019-08-07 10:04:18 -04:00
|
|
|
res.redirect('/login')
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-08-19 05:09:02 -04:00
|
|
|
validateAdmin(req, res, next) {
|
|
|
|
const adminDomains = Settings.adminDomains
|
|
|
|
if (
|
|
|
|
!adminDomains ||
|
|
|
|
!(Array.isArray(adminDomains) && adminDomains.length)
|
|
|
|
) {
|
|
|
|
return next()
|
|
|
|
}
|
2021-07-28 04:51:20 -04:00
|
|
|
const user = SessionManager.getSessionUser(req.session)
|
2020-08-19 05:09:02 -04:00
|
|
|
if (!(user && user.isAdmin)) {
|
|
|
|
return next()
|
|
|
|
}
|
|
|
|
const email = user.email
|
|
|
|
if (email == null) {
|
|
|
|
return next(
|
|
|
|
new OError('[ValidateAdmin] Admin user without email address', {
|
2021-04-27 03:52:58 -04:00
|
|
|
userId: user._id,
|
2020-08-19 05:09:02 -04:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (!adminDomains.find(domain => email.endsWith(`@${domain}`))) {
|
|
|
|
return next(
|
|
|
|
new OError('[ValidateAdmin] Admin user with invalid email domain', {
|
|
|
|
email: email,
|
2021-04-27 03:52:58 -04:00
|
|
|
userId: user._id,
|
2020-08-19 05:09:02 -04:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return next()
|
|
|
|
},
|
|
|
|
|
2021-05-31 04:30:04 -04:00
|
|
|
requireBasicAuth: function (userDetails) {
|
|
|
|
return basicAuth(function (user, pass) {
|
|
|
|
const expectedPassword = userDetails[user]
|
|
|
|
const isValid =
|
|
|
|
expectedPassword &&
|
|
|
|
expectedPassword.length === pass.length &&
|
|
|
|
crypto.timingSafeEqual(Buffer.from(expectedPassword), Buffer.from(pass))
|
|
|
|
if (!isValid) {
|
2021-06-10 07:26:11 -04:00
|
|
|
logger.err({ user }, 'invalid login details')
|
2021-05-31 04:30:04 -04:00
|
|
|
}
|
|
|
|
return isValid
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
requirePrivateApiAuth() {
|
|
|
|
return AuthenticationController.requireBasicAuth(Settings.httpAuthUsers)
|
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
|
|
|
|
setRedirectInSession(req, value) {
|
|
|
|
if (value == null) {
|
|
|
|
value =
|
|
|
|
Object.keys(req.query).length > 0
|
|
|
|
? `${req.path}?${querystring.stringify(req.query)}`
|
|
|
|
: `${req.path}`
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
req.session != null &&
|
|
|
|
!/^\/(socket.io|js|stylesheets|img)\/.*$/.test(value) &&
|
|
|
|
!/^.*\.(png|jpeg|svg)$/.test(value)
|
|
|
|
) {
|
2019-09-25 10:29:10 -04:00
|
|
|
const safePath = UrlHelper.getSafeRedirectPath(value)
|
2019-05-29 05:21:06 -04:00
|
|
|
return (req.session.postLoginRedirect = safePath)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_redirectToLoginOrRegisterPage(req, res) {
|
|
|
|
if (
|
|
|
|
req.query.zipUrl != null ||
|
|
|
|
req.query.project_name != null ||
|
|
|
|
req.path === '/user/subscription/new'
|
|
|
|
) {
|
2019-08-07 10:04:18 -04:00
|
|
|
AuthenticationController._redirectToRegisterPage(req, res)
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
2019-08-07 10:04:18 -04:00
|
|
|
AuthenticationController._redirectToLoginPage(req, res)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_redirectToLoginPage(req, res) {
|
|
|
|
logger.log(
|
|
|
|
{ url: req.url },
|
|
|
|
'user not logged in so redirecting to login page'
|
|
|
|
)
|
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
|
const url = `/login?${querystring.stringify(req.query)}`
|
|
|
|
res.redirect(url)
|
2019-08-07 10:04:18 -04:00
|
|
|
Metrics.inc('security.login-redirect')
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_redirectToReconfirmPage(req, res, user) {
|
|
|
|
logger.log(
|
|
|
|
{ url: req.url },
|
|
|
|
'user needs to reconfirm so redirecting to reconfirm page'
|
|
|
|
)
|
|
|
|
req.session.reconfirm_email = user != null ? user.email : undefined
|
|
|
|
const redir = '/user/reconfirm'
|
2020-04-01 11:07:44 -04:00
|
|
|
AsyncFormHelper.redirect(req, res, redir)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_redirectToRegisterPage(req, res) {
|
|
|
|
logger.log(
|
|
|
|
{ url: req.url },
|
|
|
|
'user not logged in so redirecting to register page'
|
|
|
|
)
|
|
|
|
AuthenticationController.setRedirectInSession(req)
|
|
|
|
const url = `/register?${querystring.stringify(req.query)}`
|
|
|
|
res.redirect(url)
|
2019-08-07 10:04:18 -04:00
|
|
|
Metrics.inc('security.login-redirect')
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
2019-06-06 08:43:17 -04:00
|
|
|
_recordSuccessfulLogin(userId, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (callback == null) {
|
2021-04-14 09:17:21 -04:00
|
|
|
callback = function () {}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-08-07 10:04:18 -04:00
|
|
|
UserUpdater.updateUser(
|
2019-06-06 08:43:17 -04:00
|
|
|
userId.toString(),
|
2019-05-29 05:21:06 -04:00
|
|
|
{
|
|
|
|
$set: { lastLoggedIn: new Date() },
|
2021-04-27 03:52:58 -04:00
|
|
|
$inc: { loginCount: 1 },
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
2021-04-14 09:17:21 -04:00
|
|
|
function (error) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (error != null) {
|
|
|
|
callback(error)
|
|
|
|
}
|
|
|
|
Metrics.inc('user.login.success')
|
2019-08-07 10:04:18 -04:00
|
|
|
callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
_recordFailedLogin(callback) {
|
|
|
|
Metrics.inc('user.login.failed')
|
2019-06-06 08:43:17 -04:00
|
|
|
if (callback) callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_getRedirectFromSession(req) {
|
|
|
|
let safePath
|
2019-08-07 10:04:18 -04:00
|
|
|
const value = _.get(req, ['session', 'postLoginRedirect'])
|
2019-05-29 05:21:06 -04:00
|
|
|
if (value) {
|
2019-09-25 10:29:10 -04:00
|
|
|
safePath = UrlHelper.getSafeRedirectPath(value)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
return safePath || null
|
|
|
|
},
|
|
|
|
|
|
|
|
_clearRedirectFromSession(req) {
|
|
|
|
if (req.session != null) {
|
2019-08-07 10:04:18 -04:00
|
|
|
delete req.session.postLoginRedirect
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2020-08-13 09:42:28 -04:00
|
|
|
}
|
2020-04-28 09:12:05 -04:00
|
|
|
|
|
|
|
function _afterLoginSessionSetup(req, user, callback) {
|
|
|
|
if (callback == null) {
|
2021-04-14 09:17:21 -04:00
|
|
|
callback = function () {}
|
2020-04-28 09:12:05 -04:00
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
req.login(user, function (err) {
|
2020-04-28 09:12:05 -04:00
|
|
|
if (err) {
|
2020-08-19 05:11:32 -04:00
|
|
|
OError.tag(err, 'error from req.login', {
|
2021-04-27 03:52:58 -04:00
|
|
|
user_id: user._id,
|
2020-08-19 05:11:32 -04:00
|
|
|
})
|
2020-04-28 09:12:05 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
// Regenerate the session to get a new sessionID (cookie value) to
|
|
|
|
// protect against session fixation attacks
|
|
|
|
const oldSession = req.session
|
2021-04-14 09:17:21 -04:00
|
|
|
req.session.destroy(function (err) {
|
2020-04-28 09:12:05 -04:00
|
|
|
if (err) {
|
2020-08-19 05:11:32 -04:00
|
|
|
OError.tag(err, 'error when trying to destroy old session', {
|
2021-04-27 03:52:58 -04:00
|
|
|
user_id: user._id,
|
2020-08-19 05:11:32 -04:00
|
|
|
})
|
2020-04-28 09:12:05 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
req.sessionStore.generate(req)
|
|
|
|
// Note: the validation token is not writable, so it does not get
|
|
|
|
// transferred to the new session below.
|
2021-05-05 09:05:04 -04:00
|
|
|
for (const key in oldSession) {
|
2020-04-28 09:12:05 -04:00
|
|
|
const value = oldSession[key]
|
2021-04-09 04:44:26 -04:00
|
|
|
if (key !== '__tmp' && key !== 'csrfSecret') {
|
2020-04-28 09:12:05 -04:00
|
|
|
req.session[key] = value
|
|
|
|
}
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
req.session.save(function (err) {
|
2020-04-28 09:12:05 -04:00
|
|
|
if (err) {
|
2020-08-19 05:11:32 -04:00
|
|
|
OError.tag(err, 'error saving regenerated session after login', {
|
2021-04-27 03:52:58 -04:00
|
|
|
user_id: user._id,
|
2020-08-19 05:11:32 -04:00
|
|
|
})
|
2020-04-28 09:12:05 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
UserSessionsManager.trackSession(user, req.sessionID, function () {})
|
2020-04-28 09:12:05 -04:00
|
|
|
callback(null)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-09-09 13:03:29 -04:00
|
|
|
function _loginAsyncHandlers(req, user) {
|
2020-04-28 09:12:05 -04:00
|
|
|
UserHandler.setupLoginData(user, err => {
|
|
|
|
if (err != null) {
|
|
|
|
logger.warn({ err }, 'error setting up login data')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
LoginRateLimiter.recordSuccessfulLogin(user.email)
|
|
|
|
AuthenticationController._recordSuccessfulLogin(user._id)
|
|
|
|
AuthenticationController.ipMatchCheck(req, user)
|
2021-09-09 13:03:29 -04:00
|
|
|
Analytics.recordEvent(user._id, 'user-logged-in')
|
|
|
|
Analytics.identifyUser(user._id, req.sessionID)
|
2020-04-28 09:12:05 -04:00
|
|
|
logger.log(
|
|
|
|
{ email: user.email, user_id: user._id.toString() },
|
|
|
|
'successful log in'
|
|
|
|
)
|
|
|
|
req.session.justLoggedIn = true
|
|
|
|
// capture the request ip for use when creating the session
|
|
|
|
return (user._login_req_ip = req.ip)
|
|
|
|
}
|
2020-08-13 09:42:28 -04:00
|
|
|
|
|
|
|
module.exports = AuthenticationController
|