RegistrationTests.js 13 KB

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