use session for the post-login redirect, remove redir query string.

This commit is contained in:
Shane Kilkelly 2016-11-22 14:24:36 +00:00
parent da1be67aff
commit 8089bb55a4
6 changed files with 92 additions and 97 deletions

View file

@ -62,17 +62,18 @@ module.exports = AuthenticationController =
if err? if err?
return next(err) return next(err)
if user # `user` is either a user object or false if user # `user` is either a user object or false
redir = AuthenticationController._getRedirectFromSession(req) || "/project"
AuthenticationController.afterLoginSessionSetup req, user, (err) -> AuthenticationController.afterLoginSessionSetup req, user, (err) ->
if err? if err?
return next(err) return next(err)
res.json {redir: req._redir} AuthenticationController._clearRedirectFromSession(req)
res.json {redir: redir}
else else
res.json message: info res.json message: info
)(req, res, next) )(req, res, next)
doPassportLogin: (req, username, password, done) -> doPassportLogin: (req, username, password, done) ->
email = username.toLowerCase() email = username.toLowerCase()
redir = Url.parse(req?.body?.redir or "/project").path
LoginRateLimiter.processLoginRequest email, (err, isAllowed)-> LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
return done(err) if err? return done(err) if err?
if !isAllowed if !isAllowed
@ -90,7 +91,6 @@ module.exports = AuthenticationController =
req.session.justLoggedIn = true req.session.justLoggedIn = true
# capture the request ip for use when creating the session # capture the request ip for use when creating the session
user._login_req_ip = req.ip user._login_req_ip = req.ip
req._redir = redir
return done(null, user) return done(null, user)
else else
AuthenticationController._recordFailedLogin() AuthenticationController._recordFailedLogin()
@ -127,7 +127,7 @@ module.exports = AuthenticationController =
requireLogin: () -> requireLogin: () ->
doRequest = (req, res, next = (error) ->) -> doRequest = (req, res, next = (error) ->) ->
if !AuthenticationController.isUserLoggedIn(req) if !AuthenticationController.isUserLoggedIn(req)
AuthenticationController._redirectToLoginOrRegisterPage(req, res) AuthenticationController._redirectToLoginOrRegisterPage(req, res, next)
else else
req.user = AuthenticationController.getSessionUser(req) req.user = AuthenticationController.getSessionUser(req)
next() next()
@ -156,22 +156,22 @@ module.exports = AuthenticationController =
logger.err user:user, pass:pass, "invalid login details" logger.err user:user, pass:pass, "invalid login details"
return isValid return isValid
_redirectToLoginOrRegisterPage: (req, res)-> _redirectToLoginOrRegisterPage: (req, res, next)->
if req.query.zipUrl? or req.query.project_name? if req.query.zipUrl? or req.query.project_name?
return AuthenticationController._redirectToRegisterPage(req, res) return AuthenticationController._redirectToRegisterPage(req, res, next)
else else
AuthenticationController._redirectToLoginPage(req, res) AuthenticationController._redirectToLoginPage(req, res, next)
_redirectToLoginPage: (req, res) -> _redirectToLoginPage: (req, res, next) ->
logger.log url: req.url, "user not logged in so redirecting to login page" logger.log url: req.url, "user not logged in so redirecting to login page"
req.query.redir = req.path AuthenticationController._setRedirectInSession(req)
url = "/login?#{querystring.stringify(req.query)}" url = "/login?#{querystring.stringify(req.query)}"
res.redirect url res.redirect url
Metrics.inc "security.login-redirect" Metrics.inc "security.login-redirect"
_redirectToRegisterPage: (req, res) -> _redirectToRegisterPage: (req, res, next) ->
logger.log url: req.url, "user not logged in so redirecting to register page" logger.log url: req.url, "user not logged in so redirecting to register page"
req.query.redir = req.path AuthenticationController._setRedirectInSession(req)
url = "/register?#{querystring.stringify(req.query)}" url = "/register?#{querystring.stringify(req.query)}"
res.redirect url res.redirect url
Metrics.inc "security.login-redirect" Metrics.inc "security.login-redirect"
@ -188,3 +188,15 @@ module.exports = AuthenticationController =
_recordFailedLogin: (callback = (error) ->) -> _recordFailedLogin: (callback = (error) ->) ->
Metrics.inc "user.login.failed" Metrics.inc "user.login.failed"
callback() callback()
_setRedirectInSession: (req) ->
target = if Object.keys(req.query) then "#{req.path}?#{querystring.stringify(req.query)}" else req.path
if req.session?
req.session.postLoginRedirect = target
_getRedirectFromSession: (req) ->
return req?.session?.postLoginRedirect || null
_clearRedirectFromSession: (req) ->
if req.session?
delete req.session.postLoginRedirect

View file

@ -20,7 +20,6 @@ module.exports =
res.render 'user/register', res.render 'user/register',
title: 'register' title: 'register'
redir: req.query.redir
sharedProjectData: sharedProjectData sharedProjectData: sharedProjectData
newTemplateData: newTemplateData newTemplateData: newTemplateData
new_email:req.query.new_email || "" new_email:req.query.new_email || ""
@ -51,7 +50,6 @@ module.exports =
loginPage : (req, res)-> loginPage : (req, res)->
res.render 'user/login', res.render 'user/login',
title: 'login', title: 'login',
redir: req.query.redir,
email: req.query.email email: req.query.email
settingsPage : (req, res, next)-> settingsPage : (req, res, next)->

View file

@ -10,7 +10,6 @@ block content
h1 #{translate("log_in")} h1 #{translate("log_in")}
form(async-form="login", name="loginForm", action='/login', method="POST", ng-cloak) form(async-form="login", name="loginForm", action='/login', method="POST", ng-cloak)
input(name='_csrf', type='hidden', value=csrfToken) input(name='_csrf', type='hidden', value=csrfToken)
input(name='redir', type='hidden', value=redir)
form-messages(for="loginForm") form-messages(for="loginForm")
.form-group .form-group
input.form-control( input.form-control(

View file

@ -91,7 +91,10 @@ describe "AuthenticationController", ->
@info = null @info = null
@req.login = sinon.stub().callsArgWith(1, null) @req.login = sinon.stub().callsArgWith(1, null)
@res.json = sinon.stub() @res.json = sinon.stub()
@req.session = @session = {passport: {user: @user}} @req.session = @session = {
passport: {user: @user},
postLoginRedirect: "/path/to/redir/to"
}
@req.session.destroy = sinon.stub().callsArgWith(0, null) @req.session.destroy = sinon.stub().callsArgWith(0, null)
@req.session.save = sinon.stub().callsArgWith(0, null) @req.session.save = sinon.stub().callsArgWith(0, null)
@req.sessionStore = {generate: sinon.stub()} @req.sessionStore = {generate: sinon.stub()}
@ -114,11 +117,11 @@ describe "AuthenticationController", ->
describe 'when authenticate produces a user', -> describe 'when authenticate produces a user', ->
beforeEach -> beforeEach ->
@req._redir = 'some_redirect' @req.session.postLoginRedirect = 'some_redirect'
@passport.authenticate.callsArgWith(1, null, @user, @info) @passport.authenticate.callsArgWith(1, null, @user, @info)
afterEach -> afterEach ->
delete @req._redir delete @req.session.postLoginRedirect
it 'should call req.login', () -> it 'should call req.login', () ->
@AuthenticationController.passportLogin @req, @res, @next @AuthenticationController.passportLogin @req, @res, @next
@ -128,7 +131,7 @@ describe "AuthenticationController", ->
it 'should send a json response with redirect', () -> it 'should send a json response with redirect', () ->
@AuthenticationController.passportLogin @req, @res, @next @AuthenticationController.passportLogin @req, @res, @next
@res.json.callCount.should.equal 1 @res.json.callCount.should.equal 1
@res.json.calledWith({redir: @req._redir}).should.equal true @res.json.calledWith({redir: 'some_redirect'}).should.equal true
describe 'when session.save produces an error', () -> describe 'when session.save produces an error', () ->
beforeEach -> beforeEach ->
@ -230,7 +233,8 @@ describe "AuthenticationController", ->
@req.body = @req.body =
email: @email email: @email
password: @password password: @password
redir: @redir = "/path/to/redir/to" session:
postLoginRedirect: "/path/to/redir/to"
@cb = sinon.stub() @cb = sinon.stub()
describe "when the users rate limit", -> describe "when the users rate limit", ->
@ -313,17 +317,6 @@ describe "AuthenticationController", ->
.calledWith(email: @email.toLowerCase(), "failed log in") .calledWith(email: @email.toLowerCase(), "failed log in")
.should.equal true .should.equal true
describe "with a URL to a different domain", ->
beforeEach ->
@LoginRateLimiter.processLoginRequest.callsArgWith(1, null, true)
@req.body.redir = "http://www.facebook.com/test"
@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, @user)
@cb = sinon.stub()
@AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb)
it "should only redirect to the local path", ->
expect(@req._redir).to.equal "/test"
describe "getLoggedInUserId", -> describe "getLoggedInUserId", ->
beforeEach -> beforeEach ->
@ -488,8 +481,8 @@ describe "AuthenticationController", ->
@AuthenticationController._redirectToRegisterPage(@req, @res) @AuthenticationController._redirectToRegisterPage(@req, @res)
it "should redirect to the register page with a query string attached", -> it "should redirect to the register page with a query string attached", ->
@res.redirectedTo @req.session.postLoginRedirect.should.equal '/target/url?extra_query=foo'
.should.equal "/register?extra_query=foo&redir=%2Ftarget%2Furl" @res.redirectedTo.should.equal "/register?extra_query=foo"
it "should log out a message", -> it "should log out a message", ->
@logger.log @logger.log
@ -504,7 +497,8 @@ describe "AuthenticationController", ->
@AuthenticationController._redirectToLoginPage(@req, @res) @AuthenticationController._redirectToLoginPage(@req, @res)
it "should redirect to the register page with a query string attached", -> it "should redirect to the register page with a query string attached", ->
@res.redirectedTo.should.equal "/login?extra_query=foo&redir=%2Ftarget%2Furl" @req.session.postLoginRedirect.should.equal '/target/url?extra_query=foo'
@res.redirectedTo.should.equal "/login?extra_query=foo"
describe "_recordSuccessfulLogin", -> describe "_recordSuccessfulLogin", ->
@ -535,3 +529,30 @@ describe "AuthenticationController", ->
it "should call the callback", -> it "should call the callback", ->
@callback.called.should.equal true @callback.called.should.equal true
describe '_setRedirectInSession', ->
beforeEach ->
@req = {session: {}}
@req.path = "/somewhere"
@req.query = {one: "1"}
it 'should set redirect property on session', ->
@AuthenticationController._setRedirectInSession(@req)
expect(@req.session.postLoginRedirect).to.equal "/somewhere?one=1"
describe '_getRedirectFromSession', ->
beforeEach ->
@req = {session: {postLoginRedirect: "/a?b=c"}}
it 'should get redirect property from session', ->
expect(@AuthenticationController._getRedirectFromSession(@req)).to.equal "/a?b=c"
describe '_clearRedirectFromSession', ->
beforeEach ->
@req = {session: {postLoginRedirect: "/a?b=c"}}
it 'should remove the redirect property from session', ->
@AuthenticationController._clearRedirectFromSession(@req)
expect(@req.session.postLoginRedirect).to.equal undefined

View file

@ -56,14 +56,6 @@ describe "UserPagesController", ->
done() done()
@UserPagesController.registerPage @req, @res @UserPagesController.registerPage @req, @res
it "should set the redirect", (done)->
redirect = "/go/here/please"
@req.query.redir = redirect
@res.render = (page, opts)=>
opts.redir.should.equal redirect
done()
@UserPagesController.registerPage @req, @res
it "should set sharedProjectData", (done)-> it "should set sharedProjectData", (done)->
@req.query.project_name = "myProject" @req.query.project_name = "myProject"
@req.query.user_first_name = "user_first_name_here" @req.query.user_first_name = "user_first_name_here"
@ -98,14 +90,6 @@ describe "UserPagesController", ->
done() done()
@UserPagesController.loginPage @req, @res @UserPagesController.loginPage @req, @res
it "should set the redirect", (done)->
redirect = "/go/here/please"
@req.query.redir = redirect
@res.render = (page, opts)=>
opts.redir.should.equal redirect
done()
@UserPagesController.loginPage @req, @res
describe 'sessionsPage', -> describe 'sessionsPage', ->
beforeEach -> beforeEach ->

View file

@ -60,7 +60,7 @@ tryAcceptInvite = (user, invite, callback=(err, response, body)->) ->
token: invite.token token: invite.token
}, callback }, callback
tryRegisterUser = (user, email, redir, callback=(err, response, body)->) -> tryRegisterUser = (user, email, callback=(err, response, body)->) ->
user.getCsrfToken (error) => user.getCsrfToken (error) =>
return callback(error) if error? return callback(error) if error?
user.request.post { user.request.post {
@ -68,7 +68,6 @@ tryRegisterUser = (user, email, redir, callback=(err, response, body)->) ->
json: json:
email: email email: email
password: "some_weird_password" password: "some_weird_password"
redir: redir
}, callback }, callback
tryFollowLoginLink = (user, loginLink, callback=(err, response, body)->) -> tryFollowLoginLink = (user, loginLink, callback=(err, response, body)->) ->
@ -76,7 +75,7 @@ tryFollowLoginLink = (user, loginLink, callback=(err, response, body)->) ->
return callback(error) if error? return callback(error) if error?
user.request.get loginLink, callback user.request.get loginLink, callback
tryLoginUser = (user, redir, callback=(err, response, body)->) -> tryLoginUser = (user, callback=(err, response, body)->) ->
user.getCsrfToken (error) => user.getCsrfToken (error) =>
return callback(error) if error? return callback(error) if error?
user.request.post { user.request.post {
@ -84,7 +83,6 @@ tryLoginUser = (user, redir, callback=(err, response, body)->) ->
json: json:
email: user.email email: user.email
password: user.password password: user.password
redir: redir
}, callback }, callback
tryGetInviteList = (user, projectId, callback=(err, response, body)->) -> tryGetInviteList = (user, projectId, callback=(err, response, body)->) ->
@ -143,35 +141,34 @@ expectInviteRedirectToRegister = (user, link, callback=(err,result)->) ->
tryFollowInviteLink user, link, (err, response, body) -> tryFollowInviteLink user, link, (err, response, body) ->
expect(err).to.be.oneOf [null, undefined] expect(err).to.be.oneOf [null, undefined]
expect(response.statusCode).to.equal 302 expect(response.statusCode).to.equal 302
expect(response.headers.location).to.match new RegExp("^/register\?.*redir=.*$") expect(response.headers.location).to.match new RegExp("^/register.*$")
# follow redirect to register page and extract the redirectUrl from form # follow redirect to register page and extract the redirectUrl from form
user.request.get response.headers.location, (err, response, body) -> user.request.get response.headers.location, (err, response, body) ->
redirectUrl = body.match(/input name="redir" type="hidden" value="([^"]*)"/m)?[1] # redirectUrl = body.match(/input name="redir" type="hidden" value="([^"]*)"/m)?[1]
loginUrl = body.match(/href="([^"]*)">\s*Login here/m)?[1] # loginUrl = body.match(/href="([^"]*)">\s*Login here/m)?[1]
expect(redirectUrl).to.not.be.oneOf [null, undefined] # expect(redirectUrl).to.not.be.oneOf [null, undefined]
expect(loginUrl).to.not.be.oneOf [null, undefined] # expect(loginUrl).to.not.be.oneOf [null, undefined]
callback(null, redirectUrl, loginUrl) callback(null)
expectLoginPage = (user, loginLink, callback=(err, result)->) -> expectLoginPage = (user, callback=(err, result)->) ->
tryFollowLoginLink user, loginLink, (err, response, body) -> tryFollowLoginLink user, "/login", (err, response, body) ->
expect(err).to.be.oneOf [null, undefined] expect(err).to.be.oneOf [null, undefined]
expect(response.statusCode).to.equal 200 expect(response.statusCode).to.equal 200
expect(body).to.match new RegExp("<title>Login - .*</title>") expect(body).to.match new RegExp("<title>Login - .*</title>")
redirectUrl = body.match(/input name="redir" type="hidden" value="([^"]*)"/m)?[1] callback(null)
callback(null, redirectUrl)
expectLoginRedirectToInvite = (user, redir, link, callback=(err, result)->) -> expectLoginRedirectToInvite = (user, link, callback=(err, result)->) ->
tryLoginUser user, redir, (err, response, body) -> tryLoginUser user, (err, response, body) ->
expect(err).to.be.oneOf [null, undefined] expect(err).to.be.oneOf [null, undefined]
expect(response.statusCode).to.equal 200 expect(response.statusCode).to.equal 200
expect(link).to.match new RegExp("^.*#{body.redir}\?.*$") # expect(link).to.match new RegExp("^.*#{body.redir}\?.*$")
callback(null, null) callback(null, null)
expectRegistrationRedirectToInvite = (user, email, redir, link, callback=(err, result)->) -> expectRegistrationRedirectToInvite = (user, email, link, callback=(err, result)->) ->
tryRegisterUser user, email, redir, (err, response, body) -> tryRegisterUser user, email, (err, response, body) ->
expect(err).to.be.oneOf [null, undefined] expect(err).to.be.oneOf [null, undefined]
expect(response.statusCode).to.equal 200 expect(response.statusCode).to.equal 200
expect(link).to.match new RegExp("^.*#{body.redir}\?.*$") # expect(link).to.match new RegExp("^.*#{body.redir}\?.*$")
callback(null, null) callback(null, null)
expectInviteRedirectToProject = (user, link, invite, callback=(err,result)->) -> expectInviteRedirectToProject = (user, link, invite, callback=(err,result)->) ->
@ -424,6 +421,7 @@ describe "ProjectInviteTests", ->
(cb) => revokeInvite(@sendingUser, @projectId, @invite._id, cb) (cb) => revokeInvite(@sendingUser, @projectId, @invite._id, cb)
], done ], done
# # # #
describe 'registration prompt workflow with valid token', -> describe 'registration prompt workflow with valid token', ->
it 'should redirect to the register page', (done) -> it 'should redirect to the register page', (done) ->
@ -433,16 +431,14 @@ describe "ProjectInviteTests", ->
it 'should allow user to accept the invite if the user registers a new account', (done) -> it 'should allow user to accept the invite if the user registers a new account', (done) ->
Async.series [ Async.series [
(cb) => (cb) => expectInviteRedirectToRegister @user, @link, cb
expectInviteRedirectToRegister @user, @link, (err, redirectUrl) => (cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", @link, cb
@_redir = redirectUrl
cb()
(cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", @_redir, @link, cb
(cb) => expectInvitePage @user, @link, cb (cb) => expectInvitePage @user, @link, cb
(cb) => expectAcceptInviteAndRedirect @user, @invite, cb (cb) => expectAcceptInviteAndRedirect @user, @invite, cb
(cb) => expectProjectAccess @user, @invite.projectId, cb (cb) => expectProjectAccess @user, @invite.projectId, cb
], done ], done
# # # #
describe 'registration prompt workflow with non-valid token', -> describe 'registration prompt workflow with non-valid token', ->
before (done)-> before (done)->
@ -457,11 +453,8 @@ describe "ProjectInviteTests", ->
it 'should display invalid-invite if the user registers a new account', (done) -> it 'should display invalid-invite if the user registers a new account', (done) ->
badLink = @link.replace(@invite.token, 'not_a_real_token') badLink = @link.replace(@invite.token, 'not_a_real_token')
Async.series [ Async.series [
(cb) => (cb) => expectInviteRedirectToRegister @user, badLink, cb
expectInviteRedirectToRegister @user, badLink, (err, redirectUrl) => (cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", badLink, cb
@_redir = redirectUrl
cb()
(cb) => expectRegistrationRedirectToInvite @user, "some_email@example.com", @_redir, badLink, cb
(cb) => expectInvalidInvitePage @user, badLink, cb (cb) => expectInvalidInvitePage @user, badLink, cb
(cb) => expectNoProjectAccess @user, @invite.projectId, cb (cb) => expectNoProjectAccess @user, @invite.projectId, cb
], done ], done
@ -479,16 +472,9 @@ describe "ProjectInviteTests", ->
it 'should allow the user to login to view the invite', (done) -> it 'should allow the user to login to view the invite', (done) ->
Async.series [ Async.series [
(cb) => (cb) => expectInviteRedirectToRegister @user, @link, cb
expectInviteRedirectToRegister @user, @link, (err, redirectUrl, loginUrl) => (cb) => expectLoginPage @user, cb
@_redir = redirectUrl (cb) => expectLoginRedirectToInvite @user, @link, cb
@_loginLink = loginUrl
cb()
(cb) =>
expectLoginPage @user, @_loginLink, (err, redirectUrl) =>
expect(@_redir).to.equal redirectUrl
cb()
(cb) => expectLoginRedirectToInvite @user, @_redir, @link, cb
(cb) => expectInvitePage @user, @link, cb (cb) => expectInvitePage @user, @link, cb
(cb) => expectNoProjectAccess @user, @invite.projectId, cb (cb) => expectNoProjectAccess @user, @invite.projectId, cb
], done ], done
@ -515,15 +501,10 @@ describe "ProjectInviteTests", ->
badLink = @link.replace(@invite.token, 'not_a_real_token') badLink = @link.replace(@invite.token, 'not_a_real_token')
Async.series [ Async.series [
(cb) => (cb) =>
expectInviteRedirectToRegister @user, badLink, (err, redirectUrl, loginUrl) => expectInviteRedirectToRegister @user, badLink, cb
@_redir = redirectUrl
@_loginLink = loginUrl
cb()
(cb) => (cb) =>
expectLoginPage @user, @_loginLink, (err, redirectUrl) => expectLoginPage @user, cb
expect(@_redir).to.equal redirectUrl (cb) => expectLoginRedirectToInvite @user, badLink, cb
cb()
(cb) => expectLoginRedirectToInvite @user, @_redir, badLink, cb
(cb) => expectInvalidInvitePage @user, badLink, cb (cb) => expectInvalidInvitePage @user, badLink, cb
(cb) => expectNoProjectAccess @user, @invite.projectId, cb (cb) => expectNoProjectAccess @user, @invite.projectId, cb
], done ], done