Просмотр исходного кода

Merge branch 'sk-account-delete-pass'

Shane Kilkelly 10 лет назад
Родитель
Сommit
5c8cdd22cc

+ 18 - 4
services/web/app/coffee/Features/User/UserController.coffee

@@ -15,12 +15,26 @@ settings = require "settings-sharelatex"
 
 module.exports = UserController =
 
-	deleteUser: (req, res)->
+	tryDeleteUser: (req, res, next) ->
 		user_id = AuthenticationController.getLoggedInUserId(req)
-		UserDeleter.deleteUser user_id, (err)->
-			if !err?
+		password = req.body.password
+		logger.log {user_id}, "trying to delete user account"
+		if !password? or password == ''
+			logger.err {user_id}, 'no password supplied for attempt to delete account'
+			return res.sendStatus(403)
+		AuthenticationManager.authenticate {_id: user_id}, password, (err, user) ->
+			if err?
+				logger.err {user_id}, 'error authenticating during attempt to delete account'
+				return next(err)
+			if !user
+				logger.err {user_id}, 'auth failed during attempt to delete account'
+				return res.sendStatus(403)
+			UserDeleter.deleteUser user_id, (err) ->
+				if err?
+					logger.err {user_id}, "error while deleting user account"
+					return next(err)
 				req.session?.destroy()
-			res.sendStatus(200)
+				res.sendStatus(200)
 
 	unsubscribe: (req, res)->
 		user_id = AuthenticationController.getLoggedInUserId(req)

+ 1 - 1
services/web/app/coffee/router.coffee

@@ -92,7 +92,7 @@ module.exports = class Router
 		webRouter.post '/user/sessions/clear', AuthenticationController.requireLogin(), UserController.clearSessions
 
 		webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
-		webRouter.delete '/user', AuthenticationController.requireLogin(), UserController.deleteUser
+		webRouter.post '/user/delete', AuthenticationController.requireLogin(), UserController.tryDeleteUser
 
 		webRouter.get  '/user/personal_info', AuthenticationController.requireLogin(), UserInfoController.getLoggedInUsersPersonalInfo
 		apiRouter.get  '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo

+ 18 - 2
services/web/app/views/user/settings.jade

@@ -150,16 +150,32 @@ block content
 	script(type='text/ng-template', id='deleteAccountModalTemplate')
 		.modal-header
 			h3 #{translate("delete_account")}
-		.modal-body
-			p !{translate("delete_account_warning_message_2")}
+		div.modal-body#delete-account-modal
+			p !{translate("delete_account_warning_message_3")}
 			form(novalidate, name="deleteAccountForm")
+				label #{translate('email')}
 				input.form-control(
 					type="text",
+					autocomplete="off",
 					placeholder="",
 					ng-model="state.deleteText",
 					focus-on="open",
 					ng-keyup="checkValidation()"
 				)
+				label #{translate('password')}
+				input.form-control(
+					type="password",
+					autocomplete="off",
+					placeholder="",
+					ng-model="state.password",
+					ng-keyup="checkValidation()"
+				)
+			div(ng-if="state.error")
+				div.alert.alert-danger
+					| #{translate('generic_something_went_wrong')}
+			div(ng-if="state.invalidCredentials")
+				div.alert.alert-danger
+					| #{translate('email_or_password_wrong_try_again')}
 		.modal-footer
 			button.btn.btn-default(
 				ng-click="cancel()"

+ 21 - 5
services/web/public/coffee/main/account-settings.coffee

@@ -29,10 +29,13 @@ define [
 	App.controller "DeleteAccountModalController", [
 		"$scope", "$modalInstance", "$timeout", "$http",
 		($scope,   $modalInstance,   $timeout,   $http) ->
-			$scope.state = 
+			$scope.state =
 				isValid : false
 				deleteText: ""
+				password: ""
 				inflight: false
+				error: false
+				invalidCredentials: false
 
 			$modalInstance.opened.then () ->
 				$timeout () ->
@@ -40,20 +43,33 @@ define [
 				, 700
 
 			$scope.checkValidation = ->
-				$scope.state.isValid = $scope.state.deleteText == $scope.email
+				$scope.state.isValid = $scope.state.deleteText == $scope.email and $scope.state.password.length > 0
 
 			$scope.delete = () ->
 				$scope.state.inflight = true
-
+				$scope.state.error = false
+				$scope.state.invalidCredentials = false
 				$http({
-						method: "DELETE"
-						url: "/user"
+						method: "POST"
+						url: "/user/delete"
 						headers:
 							"X-CSRF-Token": window.csrfToken
+							"Content-Type": 'application/json'
+						data:
+							password: $scope.state.password
 					})
 					.success () ->
 						$modalInstance.close()
+						$scope.state.inflight = false
+						$scope.state.error = false
+						$scope.state.invalidCredentials = false
 						window.location = "/"
+					.error (data, status) ->
+						$scope.state.inflight = false
+						if status == 403
+							$scope.state.invalidCredentials = true
+						else
+							$scope.state.error = true
 
 			$scope.cancel = () ->
 				$modalInstance.dismiss('cancel')

+ 8 - 1
services/web/public/stylesheets/app/account-settings.less

@@ -2,4 +2,11 @@
 	.alert {
 		margin-bottom: 0;
 	}
-}
+}
+
+#delete-account-modal {
+	.alert {
+		margin-top: 25px;
+		margin-bottom: 4px;
+	}
+}

+ 71 - 5
services/web/test/UnitTests/coffee/User/UserControllerTests.coffee

@@ -84,15 +84,81 @@ describe "UserController", ->
 			sendStatus: sinon.stub()
 			json: sinon.stub()
 		@next = sinon.stub()
-	describe "deleteUser", ->
 
-		it "should delete the user", (done)->
+	describe 'tryDeleteUser', ->
 
-			@res.sendStatus = (code)=>
-				@UserDeleter.deleteUser.calledWith(@user_id)
+		beforeEach ->
+			@req.body.password = 'wat'
+			@AuthenticationController.getLoggedInUserId = sinon.stub().returns(@user._id)
+			@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, @user)
+			@UserDeleter.deleteUser = sinon.stub().callsArgWith(1, null)
+
+		it 'should send 200', (done) ->
+			@res.sendStatus = (code) =>
 				code.should.equal 200
 				done()
-			@UserController.deleteUser @req, @res
+			@UserController.tryDeleteUser @req, @res, @next
+
+		it 'should try to authenticate user', (done) ->
+			@res.sendStatus = (code) =>
+				@AuthenticationManager.authenticate.callCount.should.equal 1
+				@AuthenticationManager.authenticate.calledWith({_id: @user._id}, @req.body.password).should.equal true
+				done()
+			@UserController.tryDeleteUser @req, @res, @next
+
+		it 'should delete the user', (done) ->
+			@res.sendStatus = (code) =>
+				@UserDeleter.deleteUser.callCount.should.equal 1
+				@UserDeleter.deleteUser.calledWith(@user._id).should.equal true
+				done()
+			@UserController.tryDeleteUser @req, @res, @next
+
+		describe 'when no password is supplied', ->
+
+			beforeEach ->
+				@req.body.password = ''
+
+			it 'should return 403', (done) ->
+				@res.sendStatus = (code) =>
+					code.should.equal 403
+					done()
+				@UserController.tryDeleteUser @req, @res, @next
+
+		describe 'when authenticate produces an error', ->
+
+			beforeEach ->
+				@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, new Error('woops'))
+
+			it 'should call next with an error', (done) ->
+				@next = (err) =>
+					expect(err).to.not.equal null
+					expect(err).to.be.instanceof Error
+					done()
+				@UserController.tryDeleteUser @req, @res, @next
+
+		describe 'when authenticate does not produce a user', ->
+
+			beforeEach ->
+				@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, null)
+
+			it 'should return 403', (done) ->
+				@res.sendStatus = (code) =>
+					code.should.equal 403
+					done()
+				@UserController.tryDeleteUser @req, @res, @next
+
+		describe 'when deleteUser produces an error', ->
+
+			beforeEach ->
+				@UserDeleter.deleteUser = sinon.stub().callsArgWith(1, new Error('woops'))
+
+			it 'should call next with an error', (done) ->
+				@next = (err) =>
+					expect(err).to.not.equal null
+					expect(err).to.be.instanceof Error
+					done()
+				@UserController.tryDeleteUser @req, @res, @next
+
 
 	describe "unsubscribe", ->