RegistrationTests.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /* eslint-disable
  2. handle-callback-err
  3. */
  4. const { expect } = require('chai')
  5. const { assert } = require('chai')
  6. const async = require('async')
  7. const User = require('./helpers/User')
  8. const redis = require('./helpers/redis')
  9. const _ = require('lodash')
  10. const Features = require('../../../app/src/infrastructure/Features')
  11. // Currently this is testing registration via the 'public-registration' module,
  12. // whereas in production we're using the 'overleaf-integration' module.
  13. // Expectations
  14. const expectProjectAccess = function(user, projectId, callback) {
  15. // should have access to project
  16. if (callback == null) {
  17. callback = function(err, result) {}
  18. }
  19. user.openProject(projectId, err => {
  20. expect(err).to.be.oneOf([null, undefined])
  21. return callback()
  22. })
  23. }
  24. const expectNoProjectAccess = function(user, projectId, callback) {
  25. // should not have access to project page
  26. if (callback == null) {
  27. callback = function(err, result) {}
  28. }
  29. user.openProject(projectId, err => {
  30. expect(err).to.be.instanceof(Error)
  31. return callback()
  32. })
  33. }
  34. // Actions
  35. const tryLoginThroughRegistrationForm = function(
  36. user,
  37. email,
  38. password,
  39. callback
  40. ) {
  41. if (callback == null) {
  42. callback = function(err, response, body) {}
  43. }
  44. user.getCsrfToken(err => {
  45. if (err != null) {
  46. return callback(err)
  47. }
  48. user.request.post(
  49. {
  50. url: '/register',
  51. json: {
  52. email,
  53. password
  54. }
  55. },
  56. callback
  57. )
  58. })
  59. }
  60. describe('Registration', function() {
  61. describe('LoginRateLimit', function() {
  62. beforeEach(function() {
  63. this.user = new User()
  64. this.badEmail = 'bademail@example.com'
  65. this.badPassword = 'badpassword'
  66. })
  67. it('should rate limit login attempts after 10 within two minutes', function(done) {
  68. this.user.request.get('/login', (err, res, body) => {
  69. async.timesSeries(
  70. 15,
  71. (n, cb) => {
  72. this.user.getCsrfToken(error => {
  73. if (error != null) {
  74. return cb(error)
  75. }
  76. this.user.request.post(
  77. {
  78. url: '/login',
  79. json: {
  80. email: this.badEmail,
  81. password: this.badPassword
  82. }
  83. },
  84. (err, response, body) => {
  85. const message = body && body.message && body.message.text
  86. return cb(null, message)
  87. }
  88. )
  89. })
  90. },
  91. (err, results) => {
  92. // ten incorrect-credentials messages, then five rate-limit messages
  93. expect(results.length).to.equal(15)
  94. assert.deepEqual(
  95. results,
  96. _.concat(
  97. _.fill(
  98. [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  99. 'Your email or password is incorrect. Please try again'
  100. ),
  101. _.fill(
  102. [1, 2, 3, 4, 5],
  103. 'This account has had too many login requests. Please wait 2 minutes before trying to log in again'
  104. )
  105. )
  106. )
  107. return done()
  108. }
  109. )
  110. })
  111. })
  112. })
  113. describe('CSRF protection', function() {
  114. before(function() {
  115. if (!Features.hasFeature('public-registration')) {
  116. this.skip()
  117. }
  118. })
  119. beforeEach(function() {
  120. this.user = new User()
  121. this.email = `test+${Math.random()}@example.com`
  122. this.password = 'password11'
  123. })
  124. afterEach(function(done) {
  125. this.user.fullDeleteUser(this.email, done)
  126. })
  127. it('should register with the csrf token', function(done) {
  128. this.user.request.get('/login', (err, res, body) => {
  129. this.user.getCsrfToken(error => {
  130. this.user.request.post(
  131. {
  132. url: '/register',
  133. json: {
  134. email: this.email,
  135. password: this.password
  136. },
  137. headers: {
  138. 'x-csrf-token': this.user.csrfToken
  139. }
  140. },
  141. (error, response, body) => {
  142. expect(err != null).to.equal(false)
  143. expect(response.statusCode).to.equal(200)
  144. return done()
  145. }
  146. )
  147. })
  148. })
  149. })
  150. it('should fail with no csrf token', function(done) {
  151. this.user.request.get('/login', (err, res, body) => {
  152. this.user.getCsrfToken(error => {
  153. this.user.request.post(
  154. {
  155. url: '/register',
  156. json: {
  157. email: this.email,
  158. password: this.password
  159. },
  160. headers: {
  161. 'x-csrf-token': ''
  162. }
  163. },
  164. (error, response, body) => {
  165. expect(response.statusCode).to.equal(403)
  166. return done()
  167. }
  168. )
  169. })
  170. })
  171. })
  172. it('should fail with a stale csrf token', function(done) {
  173. this.user.request.get('/login', (err, res, body) => {
  174. this.user.getCsrfToken(error => {
  175. const oldCsrfToken = this.user.csrfToken
  176. this.user.logout(err => {
  177. this.user.request.post(
  178. {
  179. url: '/register',
  180. json: {
  181. email: this.email,
  182. password: this.password
  183. },
  184. headers: {
  185. 'x-csrf-token': oldCsrfToken
  186. }
  187. },
  188. (error, response, body) => {
  189. expect(response.statusCode).to.equal(403)
  190. return done()
  191. }
  192. )
  193. })
  194. })
  195. })
  196. })
  197. })
  198. describe('Register', function() {
  199. before(function() {
  200. if (!Features.hasFeature('public-registration')) {
  201. this.skip()
  202. }
  203. })
  204. beforeEach(function() {
  205. this.user = new User()
  206. })
  207. it('Set emails attribute', function(done) {
  208. this.user.register((error, user) => {
  209. expect(error).to.not.exist
  210. user.email.should.equal(this.user.email)
  211. user.emails.should.exist
  212. user.emails.should.be.a('array')
  213. user.emails.length.should.equal(1)
  214. user.emails[0].email.should.equal(this.user.email)
  215. return done()
  216. })
  217. })
  218. })
  219. describe('Register with bonus referal id', function() {
  220. before(function() {
  221. if (!Features.hasFeature('public-registration')) {
  222. this.skip()
  223. }
  224. })
  225. beforeEach(function(done) {
  226. this.user1 = new User()
  227. this.user2 = new User()
  228. async.series(
  229. [
  230. cb => this.user1.register(cb),
  231. cb =>
  232. this.user2.registerWithQuery(
  233. `?r=${this.user1.referal_id}&rm=d&rs=b`,
  234. cb
  235. )
  236. ],
  237. done
  238. )
  239. })
  240. it('Adds a referal when an id is supplied and the referal source is "bonus"', function(done) {
  241. this.user1.get((error, user) => {
  242. expect(error).to.not.exist
  243. user.refered_user_count.should.eql(1)
  244. return done()
  245. })
  246. })
  247. })
  248. describe('LoginViaRegistration', function() {
  249. beforeEach(function(done) {
  250. this.timeout(60000)
  251. this.user1 = new User()
  252. this.user2 = new User()
  253. async.series(
  254. [
  255. cb => this.user1.login(cb),
  256. cb => this.user1.logout(cb),
  257. cb => redis.clearUserSessions(this.user1, cb),
  258. cb => this.user2.login(cb),
  259. cb => this.user2.logout(cb),
  260. cb => redis.clearUserSessions(this.user2, cb)
  261. ],
  262. done
  263. )
  264. this.project_id = null
  265. })
  266. describe('[Security] Trying to register/login as another user', function() {
  267. before(function() {
  268. if (!Features.hasFeature('public-registration')) {
  269. this.skip()
  270. }
  271. })
  272. it('should not allow sign in with secondary email', function(done) {
  273. const secondaryEmail = 'acceptance-test-secondary@example.com'
  274. this.user1.addEmail(secondaryEmail, err => {
  275. this.user1.loginWith(secondaryEmail, err => {
  276. expect(err != null).to.equal(false)
  277. this.user1.isLoggedIn((err, isLoggedIn) => {
  278. expect(isLoggedIn).to.equal(false)
  279. return done()
  280. })
  281. })
  282. })
  283. })
  284. it('should have user1 login and create a project, which user2 cannot access', function(done) {
  285. let projectId
  286. async.series(
  287. [
  288. // user1 logs in and creates a project which only they can access
  289. cb => {
  290. this.user1.login(err => {
  291. expect(err).not.to.exist
  292. cb()
  293. })
  294. },
  295. cb => {
  296. this.user1.createProject('Private Project', (err, id) => {
  297. expect(err).not.to.exist
  298. projectId = id
  299. cb()
  300. })
  301. },
  302. cb => expectProjectAccess(this.user1, projectId, cb),
  303. cb => expectNoProjectAccess(this.user2, projectId, cb),
  304. // should prevent user2 from login/register with user1 email address
  305. cb => {
  306. tryLoginThroughRegistrationForm(
  307. this.user2,
  308. this.user1.email,
  309. 'totally_not_the_right_password',
  310. (err, response, body) => {
  311. expect(body.redir != null).to.equal(false)
  312. expect(body.message != null).to.equal(true)
  313. expect(body.message).to.have.all.keys('type', 'text')
  314. expect(body.message.type).to.equal('error')
  315. cb()
  316. }
  317. )
  318. },
  319. // check user still can't access the project
  320. cb => expectNoProjectAccess(this.user2, projectId, done)
  321. ],
  322. done
  323. )
  324. })
  325. })
  326. })
  327. })