mirror of
https://github.com/overleaf/overleaf.git
synced 2024-09-16 02:52:31 -04:00
Merge branch 'master' of https://github.com/sharelatex/web-sharelatex
This commit is contained in:
commit
763f16f43c
19 changed files with 319 additions and 126 deletions
|
@ -12,9 +12,12 @@ basicAuth = require('basic-auth-connect')
|
|||
|
||||
module.exports = AuthenticationController =
|
||||
login: (req, res, next = (error) ->) ->
|
||||
email = req.body?.email?.toLowerCase()
|
||||
password = req.body?.password
|
||||
redir = Url.parse(req.body?.redir or "/project").path
|
||||
AuthenticationController.doLogin req.body, req, res, next
|
||||
|
||||
doLogin: (options, req, res, next) ->
|
||||
email = options.email?.toLowerCase()
|
||||
password = options.password
|
||||
redir = Url.parse(options.redir or "/project").path
|
||||
LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
|
||||
if !isAllowed
|
||||
logger.log email:email, "too many login requests"
|
||||
|
|
|
@ -15,8 +15,6 @@ templates.registered =
|
|||
|
||||
<p><a href="<%= setNewPasswordUrl %>">Click here to set your password and log in.</a></p>
|
||||
|
||||
<p>Once you have reset your password you can <a href="#{settings.siteUrl}/login">log in here</a>.</p>
|
||||
|
||||
<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
|
||||
"""
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
PasswordResetHandler = require("./PasswordResetHandler")
|
||||
RateLimiter = require("../../infrastructure/RateLimiter")
|
||||
AuthenticationController = require("../Authentication/AuthenticationController")
|
||||
UserGetter = require("../User/UserGetter")
|
||||
logger = require "logger-sharelatex"
|
||||
|
||||
module.exports =
|
||||
|
@ -37,14 +39,19 @@ module.exports =
|
|||
title:"set_password"
|
||||
passwordResetToken: req.session.resetToken
|
||||
|
||||
setNewUserPassword: (req, res)->
|
||||
setNewUserPassword: (req, res, next)->
|
||||
{passwordResetToken, password} = req.body
|
||||
if !password? or password.length == 0 or !passwordResetToken? or passwordResetToken.length == 0
|
||||
return res.sendStatus 400
|
||||
delete req.session.resetToken
|
||||
PasswordResetHandler.setNewUserPassword passwordResetToken?.trim(), password?.trim(), (err, found) ->
|
||||
PasswordResetHandler.setNewUserPassword passwordResetToken?.trim(), password?.trim(), (err, found, user_id) ->
|
||||
return next(err) if err?
|
||||
if found
|
||||
res.sendStatus 200
|
||||
if req.body.login_after
|
||||
UserGetter.getUser user_id, {email: 1}, (err, user) ->
|
||||
return next(err) if err?
|
||||
AuthenticationController.doLogin {email:user.email, password: password}, req, res, next
|
||||
else
|
||||
res.sendStatus 200
|
||||
else
|
||||
res.send 404, {message: req.i18n.translate("password_reset_token_expired")}
|
||||
res.sendStatus 404
|
||||
|
|
|
@ -23,11 +23,11 @@ module.exports =
|
|||
return callback(error) if error?
|
||||
callback null, true
|
||||
|
||||
setNewUserPassword: (token, password, callback = (error, found) ->)->
|
||||
setNewUserPassword: (token, password, callback = (error, found, user_id) ->)->
|
||||
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, user_id)->
|
||||
if err then return callback(err)
|
||||
if !user_id?
|
||||
return callback null, false
|
||||
return callback null, false, null
|
||||
AuthenticationManager.setUserPassword user_id, password, (err) ->
|
||||
if err then return callback(err)
|
||||
callback null, true
|
||||
callback null, true, user_id
|
|
@ -8,10 +8,7 @@ metrics = require("../../infrastructure/Metrics")
|
|||
Url = require("url")
|
||||
AuthenticationManager = require("../Authentication/AuthenticationManager")
|
||||
UserUpdater = require("./UserUpdater")
|
||||
EmailHandler = require("../Email/EmailHandler")
|
||||
OneTimeTokenHandler = require "../Security/OneTimeTokenHandler"
|
||||
settings = require "settings-sharelatex"
|
||||
crypto = require "crypto"
|
||||
|
||||
module.exports =
|
||||
|
||||
|
@ -85,32 +82,12 @@ module.exports =
|
|||
if !email? or email == ""
|
||||
res.sendStatus 422 # Unprocessable Entity
|
||||
return
|
||||
logger.log {email}, "registering new user"
|
||||
UserRegistrationHandler.registerNewUser {
|
||||
email: email
|
||||
password: crypto.randomBytes(32).toString("hex")
|
||||
}, (err, user)->
|
||||
if err? and err?.message != "EmailAlreadyRegistered"
|
||||
return next(err)
|
||||
|
||||
if err?.message == "EmailAlreadyRegistered"
|
||||
logger.log {email}, "user already exists, resending welcome email"
|
||||
|
||||
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
|
||||
OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
|
||||
return next(err) if err?
|
||||
|
||||
setNewPasswordUrl = "#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}&email=#{encodeURIComponent(email)}"
|
||||
|
||||
EmailHandler.sendEmail "registered", {
|
||||
to: user.email
|
||||
setNewPasswordUrl: setNewPasswordUrl
|
||||
}, () ->
|
||||
|
||||
res.json {
|
||||
email: user.email
|
||||
setNewPasswordUrl: setNewPasswordUrl
|
||||
}
|
||||
UserRegistrationHandler.registerNewUserAndSendActivationEmail email, (error, user, setNewPasswordUrl) ->
|
||||
return next(error) if error?
|
||||
res.json {
|
||||
email: user.email
|
||||
setNewPasswordUrl: setNewPasswordUrl
|
||||
}
|
||||
|
||||
changePassword : (req, res, next = (error) ->)->
|
||||
metrics.inc "user.password-change"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
UserLocator = require("./UserLocator")
|
||||
UserGetter = require("./UserGetter")
|
||||
ErrorController = require("../Errors/ErrorController")
|
||||
logger = require("logger-sharelatex")
|
||||
Settings = require("settings-sharelatex")
|
||||
fs = require('fs')
|
||||
|
@ -20,11 +22,33 @@ module.exports =
|
|||
sharedProjectData: sharedProjectData
|
||||
newTemplateData: newTemplateData
|
||||
new_email:req.query.new_email || ""
|
||||
|
||||
activateAccountPage: (req, res) ->
|
||||
# An 'activation' is actually just a password reset on an account that
|
||||
# was set with a random password originally.
|
||||
if !req.query?.user_id? or !req.query?.token?
|
||||
return ErrorController.notFound(req, res)
|
||||
|
||||
UserGetter.getUser req.query.user_id, {email: 1, loginCount: 1}, (error, user) ->
|
||||
return next(error) if error?
|
||||
if !user
|
||||
return ErrorController.notFound(req, res)
|
||||
if user.loginCount > 0
|
||||
# Already seen this user, so account must be activate
|
||||
# This lets users keep clicking the 'activate' link in their email
|
||||
# as a way to log in which, if I know our users, they will.
|
||||
res.redirect "/login?email=#{encodeURIComponent(user.email)}"
|
||||
else
|
||||
res.render 'user/activate',
|
||||
title: 'activate_account'
|
||||
email: user.email,
|
||||
token: req.query.token
|
||||
|
||||
loginPage : (req, res)->
|
||||
res.render 'user/login',
|
||||
title: 'login',
|
||||
redir: req.query.redir
|
||||
redir: req.query.redir,
|
||||
email: req.query.email
|
||||
|
||||
settingsPage : (req, res, next)->
|
||||
logger.log user: req.session.user, "loading settings page"
|
||||
|
|
|
@ -5,8 +5,12 @@ AuthenticationManager = require("../Authentication/AuthenticationManager")
|
|||
NewsLetterManager = require("../Newsletter/NewsletterManager")
|
||||
async = require("async")
|
||||
logger = require("logger-sharelatex")
|
||||
crypto = require("crypto")
|
||||
EmailHandler = require("../Email/EmailHandler")
|
||||
OneTimeTokenHandler = require "../Security/OneTimeTokenHandler"
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports =
|
||||
module.exports = UserRegistrationHandler =
|
||||
validateEmail : (email) ->
|
||||
re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
return re.test(email)
|
||||
|
@ -58,6 +62,31 @@ module.exports =
|
|||
], (err)->
|
||||
logger.log user: user, "registered"
|
||||
callback(err, user)
|
||||
|
||||
registerNewUserAndSendActivationEmail: (email, callback = (error, user, setNewPasswordUrl) ->) ->
|
||||
logger.log {email}, "registering new user"
|
||||
UserRegistrationHandler.registerNewUser {
|
||||
email: email
|
||||
password: crypto.randomBytes(32).toString("hex")
|
||||
}, (err, user)->
|
||||
if err? and err?.message != "EmailAlreadyRegistered"
|
||||
return next(err)
|
||||
|
||||
if err?.message == "EmailAlreadyRegistered"
|
||||
logger.log {email}, "user already exists, resending welcome email"
|
||||
|
||||
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
|
||||
OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
|
||||
return next(err) if err?
|
||||
|
||||
setNewPasswordUrl = "#{settings.siteUrl}/user/activate?token=#{token}&user_id=#{user._id}"
|
||||
|
||||
EmailHandler.sendEmail "registered", {
|
||||
to: user.email
|
||||
setNewPasswordUrl: setNewPasswordUrl
|
||||
}, () ->
|
||||
|
||||
callback null, user, setNewPasswordUrl
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ module.exports = class Router
|
|||
webRouter.get '/blog', BlogController.getIndexPage
|
||||
webRouter.get '/blog/*', BlogController.getPage
|
||||
|
||||
webRouter.get '/user/activate', UserPagesController.activateAccountPage
|
||||
|
||||
webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage
|
||||
webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings
|
||||
webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
],
|
||||
shouldSendCallback: function(data) {
|
||||
// only send a fraction of errors
|
||||
var sampleRate = 0.05;
|
||||
var sampleRate = 0.01;
|
||||
return (Math.random() <= sampleRate);
|
||||
},
|
||||
dataCallback: function(data) {
|
||||
|
|
64
services/web/app/views/user/activate.jade
Normal file
64
services/web/app/views/user/activate.jade
Normal file
|
@ -0,0 +1,64 @@
|
|||
extends ../layout
|
||||
|
||||
block content
|
||||
.content.content-alt
|
||||
.container
|
||||
.row
|
||||
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
|
||||
.alert.alert-success #{translate("nearly_activated")}
|
||||
.row
|
||||
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
|
||||
.card
|
||||
.page-header
|
||||
h1 #{translate("please_set_a_password")}
|
||||
form(
|
||||
async-form="activate",
|
||||
name="activationForm",
|
||||
action="/user/password/set",
|
||||
method="POST",
|
||||
ng-cloak
|
||||
)
|
||||
input(name='_csrf', type='hidden', value=csrfToken)
|
||||
input(
|
||||
type="hidden",
|
||||
name="passwordResetToken",
|
||||
value=token
|
||||
)
|
||||
input(name='login_after', type='hidden', value="true")
|
||||
.alert.alert-danger(ng-show="activationForm.response.error")
|
||||
| #{translate("activation_token_expired")}
|
||||
|
||||
.form-group
|
||||
label(for='email') #{translate("email")}
|
||||
input.form-control(
|
||||
type='email',
|
||||
name='email',
|
||||
placeholder="email@example.com"
|
||||
required,
|
||||
ng-model="email",
|
||||
ng-init="email = #{JSON.stringify(email)}",
|
||||
ng-model-options="{ updateOn: 'blur' }",
|
||||
disabled
|
||||
)
|
||||
.form-group
|
||||
label(for='password') #{translate("password")}
|
||||
input.form-control#passwordField(
|
||||
type='password',
|
||||
name='password',
|
||||
placeholder="********",
|
||||
required,
|
||||
ng-model="password",
|
||||
complex-password,
|
||||
focus="true"
|
||||
)
|
||||
span.small.text-primary(ng-show="activationForm.password.$error.complexPassword", ng-bind-html="complexPasswordErrorMessage")
|
||||
.actions
|
||||
button.btn-primary.btn(
|
||||
type='submit'
|
||||
ng-disabled="activationForm.inflight || activationForm.password.$error.required|| activationForm.password.$error.complexPassword"
|
||||
)
|
||||
span(ng-show="!activationForm.inflight") #{translate("activate")}
|
||||
span(ng-show="activationForm.inflight") #{translate("activating")}...
|
||||
|
||||
script(type='text/javascript').
|
||||
window.passwordStrengthOptions = !{JSON.stringify(settings.passwordStrengthOptions || {})}
|
|
@ -20,6 +20,7 @@ block content
|
|||
placeholder='email@example.com',
|
||||
ng-model="email",
|
||||
ng-model-options="{ updateOn: 'blur' }",
|
||||
ng-init="email = #{JSON.stringify(email)}",
|
||||
focus="true"
|
||||
)
|
||||
span.small.text-primary(ng-show="loginForm.email.$invalid && loginForm.email.$dirty")
|
||||
|
|
|
@ -16,10 +16,11 @@ block content
|
|||
ng-cloak
|
||||
)
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
form-messages(for="passwordResetForm")
|
||||
.alert.alert-success(ng-show="passwordResetForm.response.success")
|
||||
| #{translate("password_has_been_reset")}.
|
||||
a(href='/login') #{translate("login_here")}
|
||||
.alert.alert-success(ng-show="passwordResetForm.response.success")
|
||||
| #{translate("password_has_been_reset")}.
|
||||
a(href='/login') #{translate("login_here")}
|
||||
.alert.alert-danger(ng-show="passwordResetForm.response.error")
|
||||
| #{translate("password_reset_token_expired")}
|
||||
|
||||
.form-group
|
||||
input.form-control#passwordField(
|
||||
|
|
|
@ -3,10 +3,11 @@ define(function() {
|
|||
var LOG_WRAP_LIMIT = 79;
|
||||
var LATEX_WARNING_REGEX = /^LaTeX Warning: (.*)$/;
|
||||
var HBOX_WARNING_REGEX = /^(Over|Under)full \\(v|h)box/;
|
||||
var BIBER_WARNING_REGEX = /^Package biblatex Warning: (.*)$/;
|
||||
var NATBIB_WARNING_REGEX = /^Package natbib Warning: (.*)$/;
|
||||
var PACKAGE_WARNING_REGEX = /^(Package \b.+\b Warning:.*)$/;
|
||||
// This is used to parse the line number from common latex warnings
|
||||
var LINES_REGEX = /lines? ([0-9]+)/;
|
||||
// This is used to parse the package name from the package warnings
|
||||
var PACKAGE_REGEX = /^Package (\b.+\b) Warning/;
|
||||
|
||||
var LogText = function(text) {
|
||||
this.text = text.replace(/(\r\n)|\r/g, "\n");
|
||||
|
@ -101,10 +102,8 @@ define(function() {
|
|||
this.parseSingleWarningLine(LATEX_WARNING_REGEX);
|
||||
} else if (this.currentLineIsHboxWarning()) {
|
||||
this.parseHboxLine();
|
||||
} else if (this.currentLineIsBiberWarning()) {
|
||||
this.parseBiberWarningLine();
|
||||
} else if (this.currentLineIsNatbibWarning()) {
|
||||
this.parseSingleWarningLine(NATBIB_WARNING_REGEX);
|
||||
} else if (this.currentLineIsPackageWarning()) {
|
||||
this.parseMultipleWarningLine();
|
||||
} else {
|
||||
this.parseParensForFilenames();
|
||||
}
|
||||
|
@ -140,12 +139,8 @@ define(function() {
|
|||
return !!(this.currentLine.match(LATEX_WARNING_REGEX));
|
||||
};
|
||||
|
||||
this.currentLineIsBiberWarning = function () {
|
||||
return !!(this.currentLine.match(BIBER_WARNING_REGEX));
|
||||
};
|
||||
|
||||
this.currentLineIsNatbibWarning = function () {
|
||||
return !!(this.currentLine.match(NATBIB_WARNING_REGEX));
|
||||
this.currentLineIsPackageWarning = function () {
|
||||
return !!(this.currentLine.match(PACKAGE_WARNING_REGEX));
|
||||
};
|
||||
|
||||
this.currentLineIsHboxWarning = function() {
|
||||
|
@ -169,22 +164,31 @@ define(function() {
|
|||
});
|
||||
};
|
||||
|
||||
this.parseBiberWarningLine = function() {
|
||||
// Biber warnings are multiple lines, let's parse the first line
|
||||
var warningMatch = this.currentLine.match(BIBER_WARNING_REGEX);
|
||||
this.parseMultipleWarningLine = function() {
|
||||
// Some package warnings are multiple lines, let's parse the first line
|
||||
var warningMatch = this.currentLine.match(PACKAGE_WARNING_REGEX);
|
||||
if (!warningMatch) return; // Something strange happened, return early
|
||||
|
||||
// Now loop over the other output and just grab the message part
|
||||
// Each line is prefiex with: (biblatex)
|
||||
var warning_lines = [warningMatch[1]];
|
||||
while (((this.currentLine = this.log.nextLine()) !== false) &&
|
||||
(warningMatch = this.currentLine.match(/^\(biblatex\)[\s]+(.*)$/))) {
|
||||
warning_lines.push(warningMatch[1])
|
||||
var lineMatch = this.currentLine.match(LINES_REGEX);
|
||||
var line = lineMatch ? parseInt(lineMatch[1], 10) : null;
|
||||
var packageMatch = this.currentLine.match(PACKAGE_REGEX);
|
||||
var packageName = packageMatch[1];
|
||||
|
||||
// Regex to get rid of the unnecesary (packagename) prefix in most multi-line warnings
|
||||
var prefixRegex = new RegExp("(?:\\(" + packageName + "\\))*[\\s]*(.*)", "i");
|
||||
|
||||
// After every warning message there's a blank line, let's use it
|
||||
while (!!(this.currentLine = this.log.nextLine())) {
|
||||
lineMatch = this.currentLine.match(LINES_REGEX);
|
||||
line = lineMatch ? parseInt(lineMatch[1], 10) : line;
|
||||
warningMatch = this.currentLine.match(prefixRegex)
|
||||
warning_lines.push(warningMatch[1]);
|
||||
}
|
||||
|
||||
var raw_message = warning_lines.join(' ');
|
||||
this.data.push({
|
||||
line : null, // Unfortunately, biber doesn't return a line number
|
||||
line : line,
|
||||
file : this.currentFilePath,
|
||||
level : "warning",
|
||||
message : raw_message,
|
||||
|
|
|
@ -71,7 +71,7 @@ describe "AuthenticationManager", ->
|
|||
@bcrypt.genSalt = sinon.stub().callsArgWith(1, null, @salt)
|
||||
@bcrypt.hash = sinon.stub().callsArgWith(2, null, @hashedPassword)
|
||||
@db.users.update = sinon.stub().callsArg(2)
|
||||
@AuthenticationManager.setUserPassword(@user_id, @unencryptedPassword, @callback)
|
||||
@AuthenticationManager.setUserPassword(@user_id, @password, @callback)
|
||||
|
||||
it "should update the user's password in the database", ->
|
||||
@db.users.update
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
should = require('chai').should()
|
||||
expect = require("chai").expect
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
|
@ -21,6 +22,8 @@ describe "PasswordResetController", ->
|
|||
"./PasswordResetHandler":@PasswordResetHandler
|
||||
"logger-sharelatex": log:->
|
||||
"../../infrastructure/RateLimiter":@RateLimiter
|
||||
"../Authentication/AuthenticationController": @AuthenticationController = {}
|
||||
"../User/UserGetter": @UserGetter = {}
|
||||
|
||||
@email = "bob@bob.com "
|
||||
@token = "my security token that was emailed to me"
|
||||
|
@ -101,7 +104,7 @@ describe "PasswordResetController", ->
|
|||
|
||||
it "should send 404 if the token didn't work", (done)->
|
||||
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, false)
|
||||
@res.send = (code)=>
|
||||
@res.sendStatus = (code)=>
|
||||
code.should.equal 404
|
||||
done()
|
||||
@PasswordResetController.setNewUserPassword @req, @res
|
||||
|
@ -131,6 +134,19 @@ describe "PasswordResetController", ->
|
|||
@req.session.should.not.have.property 'resetToken'
|
||||
done()
|
||||
@PasswordResetController.setNewUserPassword @req, @res
|
||||
|
||||
it "should login user if login_after is set", (done) ->
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, { email: "joe@example.com" })
|
||||
@PasswordResetHandler.setNewUserPassword.callsArgWith(2, null, true, @user_id = "user-id-123")
|
||||
@req.body.login_after = "true"
|
||||
@AuthenticationController.doLogin = (options, req, res, next)=>
|
||||
@UserGetter.getUser.calledWith(@user_id).should.equal true
|
||||
expect(options).to.deep.equal {
|
||||
email: "joe@example.com",
|
||||
password: @password
|
||||
}
|
||||
done()
|
||||
@PasswordResetController.setNewUserPassword @req, @res
|
||||
|
||||
describe "renderSetPasswordForm", ->
|
||||
|
||||
|
|
|
@ -80,8 +80,9 @@ describe "PasswordResetHandler", ->
|
|||
it "should set the user password", (done)->
|
||||
@OneTimeTokenHandler.getValueFromTokenAndExpire.callsArgWith(1, null, @user_id)
|
||||
@AuthenticationManager.setUserPassword.callsArgWith(2)
|
||||
@PasswordResetHandler.setNewUserPassword @token, @password, (err, found) =>
|
||||
@PasswordResetHandler.setNewUserPassword @token, @password, (err, found, user_id) =>
|
||||
found.should.equal true
|
||||
user_id.should.equal @user_id
|
||||
@AuthenticationManager.setUserPassword.calledWith(@user_id, @password).should.equal true
|
||||
done()
|
||||
|
||||
|
|
|
@ -40,10 +40,6 @@ describe "UserController", ->
|
|||
autoAllocate:sinon.stub()
|
||||
@UserUpdater =
|
||||
changeEmailAddress:sinon.stub()
|
||||
@EmailHandler =
|
||||
sendEmail:sinon.stub().callsArgWith(2)
|
||||
@OneTimeTokenHandler =
|
||||
getNewToken: sinon.stub()
|
||||
@settings =
|
||||
siteUrl: "sharelatex.example.com"
|
||||
@UserController = SandboxedModule.require modulePath, requires:
|
||||
|
@ -57,9 +53,6 @@ describe "UserController", ->
|
|||
"../Authentication/AuthenticationManager": @AuthenticationManager
|
||||
"../Referal/ReferalAllocator":@ReferalAllocator
|
||||
"../Subscription/SubscriptionDomainHandler":@SubscriptionDomainHandler
|
||||
"../Email/EmailHandler": @EmailHandler
|
||||
"../Security/OneTimeTokenHandler": @OneTimeTokenHandler
|
||||
"crypto": @crypto = {}
|
||||
"settings-sharelatex": @settings
|
||||
"logger-sharelatex": {log:->}
|
||||
|
||||
|
@ -175,51 +168,23 @@ describe "UserController", ->
|
|||
|
||||
describe "register", ->
|
||||
beforeEach ->
|
||||
@req.body.email = @user.email = "email@example.com"
|
||||
@crypto.randomBytes = sinon.stub().returns({toString: () => @password = "mock-password"})
|
||||
@OneTimeTokenHandler.getNewToken.callsArgWith(2, null, @token = "mock-token")
|
||||
@UserRegistrationHandler.registerNewUserAndSendActivationEmail = sinon.stub().callsArgWith(1, null, @user, @url = "mock/url")
|
||||
@req.body.email = @user.email = @email = "email@example.com"
|
||||
@UserController.register @req, @res
|
||||
|
||||
describe "with a new user", ->
|
||||
beforeEach ->
|
||||
@UserRegistrationHandler.registerNewUser.callsArgWith(1, null, @user)
|
||||
@UserController.register @req, @res
|
||||
|
||||
it "should ask the UserRegistrationHandler to register user", ->
|
||||
@UserRegistrationHandler.registerNewUser
|
||||
.calledWith({
|
||||
email: @req.body.email
|
||||
password: @password
|
||||
}).should.equal true
|
||||
|
||||
it "should generate a new password reset token", ->
|
||||
@OneTimeTokenHandler.getNewToken
|
||||
.calledWith(@user_id, expiresIn: 7 * 24 * 60 * 60)
|
||||
.should.equal true
|
||||
|
||||
it "should send a registered email", ->
|
||||
@EmailHandler.sendEmail
|
||||
.calledWith("registered", {
|
||||
to: @user.email
|
||||
setNewPasswordUrl: "#{@settings.siteUrl}/user/password/set?passwordResetToken=#{@token}&email=#{encodeURIComponent(@user.email)}"
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should return the user", ->
|
||||
@res.json
|
||||
.calledWith({
|
||||
email: @user.email
|
||||
setNewPasswordUrl: "#{@settings.siteUrl}/user/password/set?passwordResetToken=#{@token}&email=#{encodeURIComponent(@user.email)}"
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "with a user that already exists", ->
|
||||
beforeEach ->
|
||||
@UserRegistrationHandler.registerNewUser.callsArgWith(1, new Error("EmailAlreadyRegistered"), @user)
|
||||
@UserController.register @req, @res
|
||||
|
||||
it "should still generate a new password token and email", ->
|
||||
@OneTimeTokenHandler.getNewToken.called.should.equal true
|
||||
@EmailHandler.sendEmail.called.should.equal true
|
||||
it "should register the user and send them an email", ->
|
||||
@UserRegistrationHandler.registerNewUserAndSendActivationEmail
|
||||
.calledWith(@email)
|
||||
.should.equal true
|
||||
|
||||
it "should return the user and activation url", ->
|
||||
console.log @res.json.args
|
||||
@res.json
|
||||
.calledWith({
|
||||
email: @email,
|
||||
setNewPasswordUrl: @url
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
describe "changePassword", ->
|
||||
|
||||
|
|
|
@ -12,18 +12,25 @@ describe "UserPagesController", ->
|
|||
|
||||
@settings = {}
|
||||
@user =
|
||||
_id:"kwjewkl"
|
||||
_id: @user_id = "kwjewkl"
|
||||
features:{}
|
||||
email: "joe@example.com"
|
||||
|
||||
@UserLocator =
|
||||
findById: sinon.stub().callsArgWith(1, null, @user)
|
||||
@UserGetter =
|
||||
getUser: sinon.stub().callsArgWith(2, null, @user)
|
||||
@dropboxStatus = {}
|
||||
@DropboxHandler =
|
||||
getUserRegistrationStatus : sinon.stub().callsArgWith(1, null, @dropboxStatus)
|
||||
@ErrorController =
|
||||
notFound: sinon.stub()
|
||||
@UserPagesController = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": log:->
|
||||
"./UserLocator": @UserLocator
|
||||
"./UserGetter": @UserGetter
|
||||
"../Errors/ErrorController": @ErrorController
|
||||
'../Dropbox/DropboxHandler': @DropboxHandler
|
||||
@req =
|
||||
query:{}
|
||||
|
@ -103,4 +110,44 @@ describe "UserPagesController", ->
|
|||
@res.render = (page, opts)=>
|
||||
opts.user.should.equal @user
|
||||
done()
|
||||
@UserPagesController.settingsPage @req, @res
|
||||
@UserPagesController.settingsPage @req, @res
|
||||
|
||||
describe "activateAccountPage", ->
|
||||
beforeEach ->
|
||||
@req.query.user_id = @user_id
|
||||
@req.query.token = @token = "mock-token-123"
|
||||
|
||||
it "should 404 without a user_id", (done) ->
|
||||
delete @req.query.user_id
|
||||
@ErrorController.notFound = () ->
|
||||
done()
|
||||
@UserPagesController.activateAccountPage @req, @res
|
||||
|
||||
it "should 404 without a token", (done) ->
|
||||
delete @req.query.token
|
||||
@ErrorController.notFound = () ->
|
||||
done()
|
||||
@UserPagesController.activateAccountPage @req, @res
|
||||
|
||||
it "should 404 without a valid user_id", (done) ->
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, null)
|
||||
@ErrorController.notFound = () ->
|
||||
done()
|
||||
@UserPagesController.activateAccountPage @req, @res
|
||||
|
||||
it "should redirect activated users to login", (done) ->
|
||||
@user.loginCount = 1
|
||||
@res.redirect = (url) =>
|
||||
@UserGetter.getUser.calledWith(@user_id).should.equal true
|
||||
url.should.equal "/login?email=#{encodeURIComponent(@user.email)}"
|
||||
done()
|
||||
@UserPagesController.activateAccountPage @req, @res
|
||||
|
||||
it "render the activation page if the user has not logged in before", (done) ->
|
||||
@user.loginCount = 0
|
||||
@res.render = (page, opts) =>
|
||||
page.should.equal "user/activate"
|
||||
opts.email.should.equal @user.email
|
||||
opts.token.should.equal @token
|
||||
done()
|
||||
@UserPagesController.activateAccountPage @req, @res
|
|
@ -10,7 +10,7 @@ describe "UserRegistrationHandler", ->
|
|||
|
||||
beforeEach ->
|
||||
@user =
|
||||
_id: "31j2lk21kjl"
|
||||
_id: @user_id = "31j2lk21kjl"
|
||||
@User =
|
||||
findOne:sinon.stub()
|
||||
update: sinon.stub().callsArgWith(2)
|
||||
|
@ -20,12 +20,20 @@ describe "UserRegistrationHandler", ->
|
|||
setUserPassword: sinon.stub().callsArgWith(2)
|
||||
@NewsLetterManager =
|
||||
subscribe: sinon.stub().callsArgWith(1)
|
||||
@EmailHandler =
|
||||
sendEmail:sinon.stub().callsArgWith(2)
|
||||
@OneTimeTokenHandler =
|
||||
getNewToken: sinon.stub()
|
||||
@handler = SandboxedModule.require modulePath, requires:
|
||||
"../../models/User": {User:@User}
|
||||
"./UserCreator": @UserCreator
|
||||
"../Authentication/AuthenticationManager":@AuthenticationManager
|
||||
"../Newsletter/NewsletterManager":@NewsLetterManager
|
||||
"logger-sharelatex": @logger = { log: sinon.stub() }
|
||||
"crypto": @crypto = {}
|
||||
"../Email/EmailHandler": @EmailHandler
|
||||
"../Security/OneTimeTokenHandler": @OneTimeTokenHandler
|
||||
"settings-sharelatex": @settings = {siteUrl: "http://sl.example.com"}
|
||||
|
||||
@passingRequest = {email:"something@email.com", password:"123"}
|
||||
|
||||
|
@ -128,4 +136,50 @@ describe "UserRegistrationHandler", ->
|
|||
it "should call the ReferalAllocator", (done)->
|
||||
done()
|
||||
|
||||
describe "registerNewUserAndSendActivationEmail", ->
|
||||
beforeEach ->
|
||||
@email = "email@example.com"
|
||||
@crypto.randomBytes = sinon.stub().returns({toString: () => @password = "mock-password"})
|
||||
@OneTimeTokenHandler.getNewToken.callsArgWith(2, null, @token = "mock-token")
|
||||
@handler.registerNewUser = sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "with a new user", ->
|
||||
beforeEach ->
|
||||
@handler.registerNewUser.callsArgWith(1, null, @user)
|
||||
@handler.registerNewUserAndSendActivationEmail @email, @callback
|
||||
|
||||
it "should ask the UserRegistrationHandler to register user", ->
|
||||
@handler.registerNewUser
|
||||
.calledWith({
|
||||
email: @email
|
||||
password: @password
|
||||
}).should.equal true
|
||||
|
||||
it "should generate a new password reset token", ->
|
||||
|
||||
@OneTimeTokenHandler.getNewToken
|
||||
.calledWith(@user_id, expiresIn: 7 * 24 * 60 * 60)
|
||||
.should.equal true
|
||||
|
||||
it "should send a registered email", ->
|
||||
@EmailHandler.sendEmail
|
||||
.calledWith("registered", {
|
||||
to: @user.email
|
||||
setNewPasswordUrl: "#{@settings.siteUrl}/user/activate?token=#{@token}&user_id=#{@user_id}"
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should return the user", ->
|
||||
@callback
|
||||
.calledWith(null, @user, "#{@settings.siteUrl}/user/activate?token=#{@token}&user_id=#{@user_id}")
|
||||
.should.equal true
|
||||
|
||||
describe "with a user that already exists", ->
|
||||
beforeEach ->
|
||||
@handler.registerNewUser.callsArgWith(1, new Error("EmailAlreadyRegistered"), @user)
|
||||
@handler.registerNewUserAndSendActivationEmail @email, @callback
|
||||
|
||||
it "should still generate a new password token and email", ->
|
||||
@OneTimeTokenHandler.getNewToken.called.should.equal true
|
||||
@EmailHandler.sendEmail.called.should.equal true
|
Loading…
Reference in a new issue