RegistrationTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /* eslint-disable
  2. handle-callback-err
  3. */
  4. const { expect } = require('chai')
  5. const async = require('async')
  6. const metrics = require('./helpers/metrics')
  7. const User = require('./helpers/User')
  8. const UserPromises = require('./helpers/User').promises
  9. const redis = require('./helpers/redis')
  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. let userA
  63. beforeEach(function () {
  64. userA = new UserPromises()
  65. })
  66. function loginRateLimited(line) {
  67. return line.includes('rate_limit_hit') && line.includes('login')
  68. }
  69. async function getLoginRateLimitHitMetricValue() {
  70. return await metrics.promises.getMetric(loginRateLimited)
  71. }
  72. let beforeCount
  73. beforeEach('get baseline metric value', async function () {
  74. beforeCount = await getLoginRateLimitHitMetricValue()
  75. })
  76. beforeEach('setup csrf token', async function () {
  77. await userA.getCsrfToken()
  78. })
  79. describe('pushing an account just below the rate limit', function () {
  80. async function doLoginAttempts(user, n, pushInto) {
  81. while (n--) {
  82. const { body } = await user.doRequest('POST', {
  83. url: '/login',
  84. json: {
  85. email: user.email,
  86. password: 'invalid-password',
  87. },
  88. })
  89. const message = body && body.message && body.message.text
  90. pushInto.push(message)
  91. }
  92. }
  93. let results = []
  94. beforeEach('do 9 login attempts', async function () {
  95. results = []
  96. await doLoginAttempts(userA, 9, results)
  97. })
  98. it('should not record any rate limited requests', async function () {
  99. const afterCount = await getLoginRateLimitHitMetricValue()
  100. expect(afterCount).to.equal(beforeCount)
  101. })
  102. it('should produce the correct responses so far', function () {
  103. expect(results.length).to.equal(9)
  104. expect(results).to.deep.equal(
  105. Array(9).fill('Your email or password is incorrect. Please try again')
  106. )
  107. })
  108. describe('pushing the account past the limit', function () {
  109. beforeEach('do 6 login attempts', async function () {
  110. await doLoginAttempts(userA, 6, results)
  111. })
  112. it('should record 5 rate limited requests', async function () {
  113. const afterCount = await getLoginRateLimitHitMetricValue()
  114. expect(afterCount).to.equal(beforeCount + 5)
  115. })
  116. it('should produce the correct responses', function () {
  117. expect(results.length).to.equal(15)
  118. expect(results).to.deep.equal(
  119. Array(10)
  120. .fill('Your email or password is incorrect. Please try again')
  121. .concat(
  122. Array(5).fill(
  123. 'This account has had too many login requests. Please wait 2 minutes before trying to log in again'
  124. )
  125. )
  126. )
  127. })
  128. describe('logging in with another user', function () {
  129. let userB
  130. beforeEach(function () {
  131. userB = new UserPromises()
  132. })
  133. beforeEach('update baseline metric value', async function () {
  134. beforeCount = await getLoginRateLimitHitMetricValue()
  135. })
  136. beforeEach('setup csrf token', async function () {
  137. await userB.getCsrfToken()
  138. })
  139. let messages = []
  140. beforeEach('do bad login', async function () {
  141. messages = []
  142. await doLoginAttempts(userB, 1, messages)
  143. })
  144. it('should not rate limit their request', function () {
  145. expect(messages).to.deep.equal([
  146. 'Your email or password is incorrect. Please try again',
  147. ])
  148. })
  149. it('should not record any further rate limited requests', async function () {
  150. const afterCount = await getLoginRateLimitHitMetricValue()
  151. expect(afterCount).to.equal(beforeCount)
  152. })
  153. })
  154. })
  155. describe('performing a valid login for clearing the limit', function () {
  156. beforeEach('do login', async function () {
  157. await userA.login()
  158. })
  159. it('should log the user in', async function () {
  160. const { response } = await userA.doRequest('GET', '/project')
  161. expect(response.statusCode).to.equal(200)
  162. })
  163. it('should not record any rate limited requests', async function () {
  164. const afterCount = await getLoginRateLimitHitMetricValue()
  165. expect(afterCount).to.equal(beforeCount)
  166. })
  167. describe('logging out and performing more invalid login requests', function () {
  168. beforeEach('logout', async function () {
  169. await userA.logout()
  170. })
  171. beforeEach('fetch new csrf token', async function () {
  172. await userA.getCsrfToken()
  173. })
  174. let results = []
  175. beforeEach('do 9 login attempts', async function () {
  176. results = []
  177. await doLoginAttempts(userA, 9, results)
  178. })
  179. it('should not record any rate limited requests yet', async function () {
  180. const afterCount = await getLoginRateLimitHitMetricValue()
  181. expect(afterCount).to.equal(beforeCount)
  182. })
  183. it('should not emit any rate limited responses yet', function () {
  184. expect(results.length).to.equal(9)
  185. expect(results).to.deep.equal(
  186. Array(9).fill(
  187. 'Your email or password is incorrect. Please try again'
  188. )
  189. )
  190. })
  191. })
  192. })
  193. })
  194. })
  195. describe('CSRF protection', function () {
  196. before(function () {
  197. if (!Features.hasFeature('public-registration')) {
  198. this.skip()
  199. }
  200. })
  201. beforeEach(function () {
  202. this.user = new User()
  203. this.email = `test+${Math.random()}@example.com`
  204. this.password = 'password11'
  205. })
  206. afterEach(function (done) {
  207. this.user.fullDeleteUser(this.email, done)
  208. })
  209. it('should register with the csrf token', function (done) {
  210. this.user.request.get('/login', (err, res, body) => {
  211. this.user.getCsrfToken(error => {
  212. this.user.request.post(
  213. {
  214. url: '/register',
  215. json: {
  216. email: this.email,
  217. password: this.password,
  218. },
  219. headers: {
  220. 'x-csrf-token': this.user.csrfToken,
  221. },
  222. },
  223. (error, response, body) => {
  224. expect(err != null).to.equal(false)
  225. expect(response.statusCode).to.equal(200)
  226. return done()
  227. }
  228. )
  229. })
  230. })
  231. })
  232. it('should fail with no csrf token', function (done) {
  233. this.user.request.get('/login', (err, res, body) => {
  234. this.user.getCsrfToken(error => {
  235. this.user.request.post(
  236. {
  237. url: '/register',
  238. json: {
  239. email: this.email,
  240. password: this.password,
  241. },
  242. headers: {
  243. 'x-csrf-token': '',
  244. },
  245. },
  246. (error, response, body) => {
  247. expect(response.statusCode).to.equal(403)
  248. return done()
  249. }
  250. )
  251. })
  252. })
  253. })
  254. it('should fail with a stale csrf token', function (done) {
  255. this.user.request.get('/login', (err, res, body) => {
  256. this.user.getCsrfToken(error => {
  257. const oldCsrfToken = this.user.csrfToken
  258. this.user.logout(err => {
  259. this.user.request.post(
  260. {
  261. url: '/register',
  262. json: {
  263. email: this.email,
  264. password: this.password,
  265. },
  266. headers: {
  267. 'x-csrf-token': oldCsrfToken,
  268. },
  269. },
  270. (error, response, body) => {
  271. expect(response.statusCode).to.equal(403)
  272. return done()
  273. }
  274. )
  275. })
  276. })
  277. })
  278. })
  279. })
  280. describe('Register', function () {
  281. before(function () {
  282. if (!Features.hasFeature('public-registration')) {
  283. this.skip()
  284. }
  285. })
  286. beforeEach(function () {
  287. this.user = new User()
  288. })
  289. it('Set emails attribute', function (done) {
  290. this.user.register((error, user) => {
  291. expect(error).to.not.exist
  292. user.email.should.equal(this.user.email)
  293. user.emails.should.exist
  294. user.emails.should.be.a('array')
  295. user.emails.length.should.equal(1)
  296. user.emails[0].email.should.equal(this.user.email)
  297. return done()
  298. })
  299. })
  300. })
  301. describe('Register with bonus referal id', function () {
  302. before(function () {
  303. if (!Features.hasFeature('public-registration')) {
  304. this.skip()
  305. }
  306. })
  307. beforeEach(function (done) {
  308. this.user1 = new User()
  309. this.user2 = new User()
  310. async.series(
  311. [
  312. cb => this.user1.register(cb),
  313. cb =>
  314. this.user2.registerWithQuery(
  315. `?r=${this.user1.referal_id}&rm=d&rs=b`,
  316. cb
  317. ),
  318. ],
  319. done
  320. )
  321. })
  322. it('Adds a referal when an id is supplied and the referal source is "bonus"', function (done) {
  323. this.user1.get((error, user) => {
  324. expect(error).to.not.exist
  325. user.refered_user_count.should.eql(1)
  326. return done()
  327. })
  328. })
  329. })
  330. describe('LoginViaRegistration', function () {
  331. beforeEach(function (done) {
  332. this.timeout(60000)
  333. this.user1 = new User()
  334. this.user2 = new User()
  335. async.series(
  336. [
  337. cb => this.user1.login(cb),
  338. cb => this.user1.logout(cb),
  339. cb => redis.clearUserSessions(this.user1, cb),
  340. cb => this.user2.login(cb),
  341. cb => this.user2.logout(cb),
  342. cb => redis.clearUserSessions(this.user2, cb),
  343. ],
  344. done
  345. )
  346. this.project_id = null
  347. })
  348. describe('[Security] Trying to register/login as another user', function () {
  349. before(function () {
  350. if (!Features.hasFeature('public-registration')) {
  351. this.skip()
  352. }
  353. })
  354. it('should not allow sign in with secondary email', function (done) {
  355. const secondaryEmail = 'acceptance-test-secondary@example.com'
  356. this.user1.addEmail(secondaryEmail, err => {
  357. this.user1.loginWith(secondaryEmail, err => {
  358. expect(err != null).to.equal(false)
  359. this.user1.isLoggedIn((err, isLoggedIn) => {
  360. expect(isLoggedIn).to.equal(false)
  361. return done()
  362. })
  363. })
  364. })
  365. })
  366. it('should have user1 login and create a project, which user2 cannot access', function (done) {
  367. let projectId
  368. async.series(
  369. [
  370. // user1 logs in and creates a project which only they can access
  371. cb => {
  372. this.user1.login(err => {
  373. expect(err).not.to.exist
  374. cb()
  375. })
  376. },
  377. cb => {
  378. this.user1.createProject('Private Project', (err, id) => {
  379. expect(err).not.to.exist
  380. projectId = id
  381. cb()
  382. })
  383. },
  384. cb => expectProjectAccess(this.user1, projectId, cb),
  385. cb => expectNoProjectAccess(this.user2, projectId, cb),
  386. // should prevent user2 from login/register with user1 email address
  387. cb => {
  388. tryLoginThroughRegistrationForm(
  389. this.user2,
  390. this.user1.email,
  391. 'totally_not_the_right_password',
  392. (err, response, body) => {
  393. expect(body.redir != null).to.equal(false)
  394. expect(body.message != null).to.equal(true)
  395. expect(body.message).to.have.all.keys('type', 'text')
  396. expect(body.message.type).to.equal('error')
  397. cb()
  398. }
  399. )
  400. },
  401. // check user still can't access the project
  402. cb => expectNoProjectAccess(this.user2, projectId, done),
  403. ],
  404. done
  405. )
  406. })
  407. })
  408. })
  409. })