RegistrationTests.js 12 KB

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