RegistrationTests.js 13 KB

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