diff --git a/package-lock.json b/package-lock.json index fc03b7d06a..d92f1c864a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25893,49 +25893,6 @@ "throttleit": "^1.0.0" } }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -28212,14 +28169,6 @@ "node": ">= 0.6" } }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", @@ -35066,7 +35015,6 @@ "recurly": "^4.0.0", "referer-parser": "0.0.3", "request": "^2.88.2", - "request-promise-native": "^1.0.8", "requestretry": "^6.0.0", "rimraf": "2.2.6", "rolling-rate-limiter": "^0.2.10", @@ -35169,6 +35117,7 @@ "terser-webpack-plugin": "^5.3.1", "timekeeper": "^2.2.0", "to-string-loader": "^1.2.0", + "tough-cookie": "^4.0.0", "typescript": "^4.5.5", "val-loader": "^4.0.0", "webpack": "^5.71.0", @@ -42853,7 +42802,6 @@ "recurly": "^4.0.0", "referer-parser": "0.0.3", "request": "^2.88.2", - "request-promise-native": "^1.0.8", "requestretry": "^6.0.0", "requirejs": "^2.3.6", "rimraf": "2.2.6", @@ -42870,6 +42818,7 @@ "terser-webpack-plugin": "^5.3.1", "timekeeper": "^2.2.0", "to-string-loader": "^1.2.0", + "tough-cookie": "^4.0.0", "tsscmp": "^1.0.6", "typescript": "^4.5.5", "underscore": "^1.13.1", @@ -61749,35 +61698,6 @@ "throttleit": "^1.0.0" } }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, "requestretry": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.1.2.tgz", @@ -63594,11 +63514,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", diff --git a/services/web/modules/launchpad/test/acceptance/src/LaunchpadTests.js b/services/web/modules/launchpad/test/acceptance/src/LaunchpadTests.js index 5b1b1d053f..15d2d82253 100644 --- a/services/web/modules/launchpad/test/acceptance/src/LaunchpadTests.js +++ b/services/web/modules/launchpad/test/acceptance/src/LaunchpadTests.js @@ -9,9 +9,10 @@ describe('Launchpad', function () { const user = new UserHelper() it('should show the launchpad page', async function () { - const response = await user.request.get('/launchpad') - expect(response.statusCode).to.equal(200) - const $ = cheerio.load(response.body) + const response = await user.fetch('/launchpad') + expect(response.status).to.equal(200) + const body = await response.text() + const $ = cheerio.load(body) expect($('h2').first().text()).to.equal('Create the first Admin account') expect($('form[name="email"]').first()).to.exist expect($('form[name="password"]').first()).to.exist @@ -19,45 +20,54 @@ describe('Launchpad', function () { it('should allow for creation of the first admin user', async function () { // Load the launchpad page - const initialPageResponse = await user.request.get('/launchpad') - expect(initialPageResponse.statusCode).to.equal(200) - const $ = cheerio.load(initialPageResponse.body) + const initialPageResponse = await user.fetch('/launchpad') + expect(initialPageResponse.status).to.equal(200) + const initialPageBody = await initialPageResponse.text() + const $ = cheerio.load(initialPageBody) expect($('h2').first().text()).to.equal('Create the first Admin account') expect($('form[name="email"]').first()).to.exist expect($('form[name="password"]').first()).to.exist // Submit the form let csrfToken = await user.getCsrfToken() - const postResponse = await user.request.post({ - url: '/launchpad/register_admin', - json: { + const postResponse = await user.fetch('/launchpad/register_admin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ _csrf: csrfToken, email: adminEmail, password: adminPassword, - }, + }), }) - expect(postResponse.statusCode).to.equal(200) - expect(postResponse.body).to.deep.equal({ redir: '/launchpad' }) + expect(postResponse.status).to.equal(200) + const postBody = await postResponse.json() + expect(postBody).to.deep.equal({ redir: '/launchpad' }) // Try to load the page again - const secondPageResponse = await user.request.get('/launchpad', { - simple: false, - }) - expect(secondPageResponse.statusCode).to.equal(302) - expect(secondPageResponse.headers.location).to.equal('/login') + const secondPageResponse = await user.fetch('/launchpad') + expect(secondPageResponse.status).to.equal(302) + expect(secondPageResponse.headers.get('location')).to.equal( + UserHelper.url('/login').toString() + ) // Forbid submitting the form again csrfToken = await user.getCsrfToken() - const badPostResponse = await user.request.post({ - url: '/launchpad/register_admin', - json: { + const badPostResponse = await user.fetch('/launchpad/register_admin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ _csrf: csrfToken, email: adminEmail + '1', password: adminPassword + '1', - }, - simple: false, + }), }) - expect(badPostResponse.statusCode).to.equal(403) + expect(badPostResponse.status).to.equal(403) // Log in as this new admin user const adminUser = await UserHelper.loginUser({ diff --git a/services/web/package.json b/services/web/package.json index 4d3765c8b8..a1e3ccb00f 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -206,7 +206,6 @@ "recurly": "^4.0.0", "referer-parser": "0.0.3", "request": "^2.88.2", - "request-promise-native": "^1.0.8", "requestretry": "^6.0.0", "rimraf": "2.2.6", "rolling-rate-limiter": "^0.2.10", @@ -309,6 +308,7 @@ "terser-webpack-plugin": "^5.3.1", "timekeeper": "^2.2.0", "to-string-loader": "^1.2.0", + "tough-cookie": "^4.0.0", "typescript": "^4.5.5", "val-loader": "^4.0.0", "webpack": "^5.71.0", diff --git a/services/web/test/acceptance/src/BetaProgramTests.js b/services/web/test/acceptance/src/BetaProgramTests.js index 0e6bb5a4f8..b1122bf628 100644 --- a/services/web/test/acceptance/src/BetaProgramTests.js +++ b/services/web/test/acceptance/src/BetaProgramTests.js @@ -13,31 +13,21 @@ describe('BetaProgram', function () { }) }) it('should opt in', async function () { - const response = await userHelper.request.post('/beta/opt-in', { - simple: false, - }) - expect(response.statusCode).to.equal(302) - response.statusCode.should.equal(302) - expect(response.headers.location).to.equal('/beta/participate') - const user = ( - await UserHelper.getUser({ - email, - }) - ).user + const response = await userHelper.fetch('/beta/opt-in', { method: 'POST' }) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/beta/participate').toString() + ) + const user = (await UserHelper.getUser({ email })).user expect(user.betaProgram).to.equal(true) }) it('should opt out', async function () { - const response = await userHelper.request.post('/beta/opt-out', { - simple: false, - }) - expect(response.statusCode).to.equal(302) - response.statusCode.should.equal(302) - expect(response.headers.location).to.equal('/beta/participate') - const user = ( - await UserHelper.getUser({ - email, - }) - ).user + const response = await userHelper.fetch('/beta/opt-out', { method: 'POST' }) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/beta/participate').toString() + ) + const user = (await UserHelper.getUser({ email })).user expect(user.betaProgram).to.equal(false) }) }) diff --git a/services/web/test/acceptance/src/PasswordResetTests.js b/services/web/test/acceptance/src/PasswordResetTests.js index c2388abe91..b7405a60df 100644 --- a/services/web/test/acceptance/src/PasswordResetTests.js +++ b/services/web/test/acceptance/src/PasswordResetTests.js @@ -13,10 +13,9 @@ describe('PasswordReset', function () { // generate the token await userHelper.getCsrfToken() - response = await userHelper.request.post('/user/password/reset', { - form: { - email, - }, + response = await userHelper.fetch('/user/password/reset', { + method: 'POST', + body: new URLSearchParams({ email }), }) token = ( @@ -32,20 +31,20 @@ describe('PasswordReset', function () { email, password: userHelper.getDefaultPassword(), }) - response = await userHelper.request.get( - `/user/password/set?passwordResetToken=${token}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?passwordResetToken=${token}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/set${emailQuery}` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url(`/user/password/set${emailQuery}`).toString() ) // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'a-password', - }, + }), }) userHelper = await UserHelper.getUser({ email }) user = userHelper.user @@ -75,20 +74,20 @@ describe('PasswordReset', function () { email: otherUserEmail, password: userHelper.getDefaultPassword(), }) - response = await userHelper.request.get( - `/user/password/set?passwordResetToken=${token}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?passwordResetToken=${token}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/set${emailQuery}` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url(`/user/password/set${emailQuery}`).toString() ) // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'a-password', - }, + }), }) userHelper = await UserHelper.getUser({ email }) user = userHelper.user @@ -110,20 +109,20 @@ describe('PasswordReset', function () { }) describe('when not logged in', function () { beforeEach(async function () { - response = await userHelper.request.get( - `/user/password/set?passwordResetToken=${token}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?passwordResetToken=${token}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/set${emailQuery}` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url(`/user/password/set${emailQuery}`).toString() ) // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'a-password', - }, + }), }) userHelper = await UserHelper.getUser({ email }) user = userHelper.user @@ -144,24 +143,23 @@ describe('PasswordReset', function () { }) describe('password checks', function () { beforeEach(async function () { - response = await userHelper.request.get( - `/user/password/set?passwordResetToken=${token}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?passwordResetToken=${token}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/set${emailQuery}` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url(`/user/password/set${emailQuery}`).toString() ) }) it('without a password should return 400 and not log the change', async function () { // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) userHelper = await UserHelper.getUser({ email }) const auditLog = userHelper.getAuditLogWithoutNoise() @@ -170,14 +168,14 @@ describe('PasswordReset', function () { it('without a valid password should return 400 and not log the change', async function () { // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'short', - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) userHelper = await UserHelper.getUser({ email }) const auditLog = userHelper.getAuditLogWithoutNoise() @@ -187,41 +185,45 @@ describe('PasswordReset', function () { it('should flag email in password', async function () { const localPart = email.split('@').shift() // send bad password - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ passwordResetToken: token, password: localPart, email, - }, - json: true, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) - expect(response.body).to.deep.equal({ + expect(response.status).to.equal(400) + const body = await response.json() + expect(body).to.deep.equal({ message: { text: 'password contains part of email address' }, }) }) it('should be able to retry after providing an invalid password', async function () { // send bad password - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'short', - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) // send good password - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'SomeThingVeryStrong!11', - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) userHelper = await UserHelper.getUser({ email }) const auditLog = userHelper.getAuditLogWithoutNoise() @@ -230,17 +232,16 @@ describe('PasswordReset', function () { it('when the password is the same as current, should return 400 and log the change', async function () { // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: userHelper.getDefaultPassword(), - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) - expect(JSON.parse(response.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response.status).to.equal(400) + const body = await response.json() + expect(body.message.key).to.equal('password-must-be-different') userHelper = await UserHelper.getUser({ email }) const auditLog = userHelper.getAuditLogWithoutNoise() @@ -251,80 +252,81 @@ describe('PasswordReset', function () { describe('multiple attempts to set the password, reaching attempt limit', async function () { beforeEach(async function () { - response = await userHelper.request.get( - `/user/password/set?passwordResetToken=${token}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?passwordResetToken=${token}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/set${emailQuery}` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url(`/user/password/set${emailQuery}`).toString() ) }) it('should allow multiple attempts with same-password error, then deny further attempts', async function () { const sendSamePasswordRequest = async function () { - return userHelper.request.post('/user/password/set', { - form: { + return userHelper.fetch('/user/password/set', { + method: 'POST', + headers: { + Accept: 'application/json', + }, + body: new URLSearchParams({ passwordResetToken: token, password: userHelper.getDefaultPassword(), - }, - simple: false, + }), }) } // Three attempts at setting the password, all rejected for being the same as // the current password const response1 = await sendSamePasswordRequest() - expect(response1.statusCode).to.equal(400) - expect(JSON.parse(response1.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response1.status).to.equal(400) + const body1 = await response1.json() + expect(body1.message.key).to.equal('password-must-be-different') const response2 = await sendSamePasswordRequest() - expect(response2.statusCode).to.equal(400) - expect(JSON.parse(response2.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response2.status).to.equal(400) + const body2 = await response2.json() + expect(body2.message.key).to.equal('password-must-be-different') const response3 = await sendSamePasswordRequest() - expect(response3.statusCode).to.equal(400) - expect(JSON.parse(response3.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response3.status).to.equal(400) + const body3 = await response3.json() + expect(body3.message.key).to.equal('password-must-be-different') // Fourth attempt is rejected because the token has been used too many times const response4 = await sendSamePasswordRequest() - expect(response4.statusCode).to.equal(404) - expect(JSON.parse(response4.body).message.key).to.equal('token-expired') + expect(response4.status).to.equal(404) + const body4 = await response4.json() + expect(body4.message.key).to.equal('token-expired') }) it('should allow multiple attempts with same-password error, then set the password', async function () { const sendSamePasswordRequest = async function () { - return userHelper.request.post('/user/password/set', { - form: { + return userHelper.fetch('/user/password/set', { + method: 'POST', + headers: { + Accept: 'application/json', + }, + body: new URLSearchParams({ passwordResetToken: token, password: userHelper.getDefaultPassword(), - }, - simple: false, + }), }) } // Two attempts at setting the password, all rejected for being the same as // the current password const response1 = await sendSamePasswordRequest() - expect(response1.statusCode).to.equal(400) - expect(JSON.parse(response1.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response1.status).to.equal(400) + const body1 = await response1.json() + expect(body1.message.key).to.equal('password-must-be-different') const response2 = await sendSamePasswordRequest() - expect(response2.statusCode).to.equal(400) - expect(JSON.parse(response2.body).message.key).to.equal( - 'password-must-be-different' - ) + expect(response2.status).to.equal(400) + const body2 = await response2.json() + expect(body2.message.key).to.equal('password-must-be-different') // Third attempt is succeeds - const response3 = await userHelper.request.post('/user/password/set', { - form: { + const response3 = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, password: 'some-new-password', - }, - simple: false, + }), }) - expect(response3.statusCode).to.equal(200) + expect(response3.status).to.equal(200) // Check the user and audit log userHelper = await UserHelper.getUser({ email }) user = userHelper.user @@ -342,83 +344,79 @@ describe('PasswordReset', function () { describe('without a valid token', function () { it('no token should redirect to page to re-request reset token', async function () { - response = await userHelper.request.get( - `/user/password/set?&email=${email}`, - { simple: false } + response = await userHelper.fetch(`/user/password/set?&email=${email}`) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/user/password/reset').toString() ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/user/password/reset') }) it('should show error for invalid tokens and return 404 if used', async function () { const invalidToken = 'not-real-token' - response = await userHelper.request.get( - `/user/password/set?&passwordResetToken=${invalidToken}&email=${email}`, - { simple: false } + response = await userHelper.fetch( + `/user/password/set?&passwordResetToken=${invalidToken}&email=${email}` ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/user/password/reset?error=token_expired` + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/user/password/reset?error=token_expired').toString() ) // send reset request - response = await userHelper.request.post('/user/password/set', { - form: { + response = await userHelper.fetch('/user/password/set', { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: invalidToken, password: 'a-password', - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(404) + expect(response.status).to.equal(404) }) }) describe('password reset', function () { it('should return 200 if email field is valid', async function () { - response = await userHelper.request.post(`/user/password/reset`, { - form: { - email, - }, + response = await userHelper.fetch(`/user/password/reset`, { + method: 'POST', + body: new URLSearchParams({ email }), }) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) }) it('should return 400 if email field is missing', async function () { - response = await userHelper.request.post(`/user/password/reset`, { - form: { - mail: email, - }, - simple: false, + response = await userHelper.fetch(`/user/password/reset`, { + method: 'POST', + body: new URLSearchParams({ mail: email }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) }) describe('password set', function () { it('should return 200 if password and passwordResetToken fields are valid', async function () { - response = await userHelper.request.post(`/user/password/set`, { - form: { + response = await userHelper.fetch(`/user/password/set`, { + method: 'POST', + body: new URLSearchParams({ password: 'new-password', passwordResetToken: token, - }, + }), }) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) }) it('should return 400 if password field is missing', async function () { - response = await userHelper.request.post(`/user/password/set`, { - form: { + response = await userHelper.fetch(`/user/password/set`, { + method: 'POST', + body: new URLSearchParams({ passwordResetToken: token, - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) it('should return 400 if passwordResetToken field is missing', async function () { - response = await userHelper.request.post(`/user/password/set`, { - form: { + response = await userHelper.fetch(`/user/password/set`, { + method: 'POST', + body: new URLSearchParams({ password: 'new-password', - }, - simple: false, + }), }) - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) }) }) diff --git a/services/web/test/acceptance/src/PasswordUpdateTests.js b/services/web/test/acceptance/src/PasswordUpdateTests.js index cdf271caf5..2872684903 100644 --- a/services/web/test/acceptance/src/PasswordUpdateTests.js +++ b/services/web/test/acceptance/src/PasswordUpdateTests.js @@ -23,19 +23,19 @@ describe('PasswordUpdate', function () { }) describe('success', function () { beforeEach(async function () { - response = await userHelper.request.post('/user/password/update', { - form: { + response = await userHelper.fetch('/user/password/update', { + method: 'POST', + body: new URLSearchParams({ currentPassword: password, newPassword1: 'new-password', newPassword2: 'new-password', - }, - simple: false, + }), }) userHelper = await UserHelper.getUser({ email }) user = userHelper.user }) it('should return 200', async function () { - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) }) it('should update the audit log', function () { const auditLog = userHelper.getAuditLogWithoutNoise() @@ -50,17 +50,17 @@ describe('PasswordUpdate', function () { describe('errors', function () { describe('missing current password', function () { beforeEach(async function () { - response = await userHelper.request.post('/user/password/update', { - form: { + response = await userHelper.fetch('/user/password/update', { + method: 'POST', + body: new URLSearchParams({ newPassword1: 'new-password', newPassword2: 'new-password', - }, - simple: false, + }), }) userHelper = await UserHelper.getUser({ email }) }) it('should return 500', async function () { - expect(response.statusCode).to.equal(500) + expect(response.status).to.equal(500) }) it('should not update audit log', async function () { const auditLog = userHelper.getAuditLogWithoutNoise() @@ -69,18 +69,18 @@ describe('PasswordUpdate', function () { }) describe('wrong current password', function () { beforeEach(async function () { - response = await userHelper.request.post('/user/password/update', { - form: { + response = await userHelper.fetch('/user/password/update', { + method: 'POST', + body: new URLSearchParams({ currentPassword: 'wrong-password', newPassword1: 'new-password', newPassword2: 'new-password', - }, - simple: false, + }), }) userHelper = await UserHelper.getUser({ email }) }) it('should return 400', async function () { - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) it('should not update audit log', async function () { const auditLog = userHelper.getAuditLogWithoutNoise() @@ -89,22 +89,26 @@ describe('PasswordUpdate', function () { }) describe('newPassword1 does not match newPassword2', function () { beforeEach(async function () { - response = await userHelper.request.post('/user/password/update', { - form: { + response = await userHelper.fetch('/user/password/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ currentPassword: password, newPassword1: 'new-password', newPassword2: 'oops-password', - }, - json: true, - simple: false, + }), }) userHelper = await UserHelper.getUser({ email }) }) it('should return 400', async function () { - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) it('should return error message', async function () { - expect(response.body.message).to.equal('Passwords do not match') + const body = await response.json() + expect(body.message).to.equal('Passwords do not match') }) it('should not update audit log', async function () { const auditLog = userHelper.getAuditLogWithoutNoise() @@ -113,22 +117,26 @@ describe('PasswordUpdate', function () { }) describe('new password is not valid', function () { beforeEach(async function () { - response = await userHelper.request.post('/user/password/update', { - form: { + response = await userHelper.fetch('/user/password/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ currentPassword: password, newPassword1: 'short', newPassword2: 'short', - }, - json: true, - simple: false, + }), }) userHelper = await UserHelper.getUser({ email }) }) it('should return 400', async function () { - expect(response.statusCode).to.equal(400) + expect(response.status).to.equal(400) }) it('should return error message', async function () { - expect(response.body.message).to.equal('password is too short') + const body = await response.json() + expect(body.message).to.equal('password is too short') }) it('should not update audit log', async function () { const auditLog = userHelper.getAuditLogWithoutNoise() diff --git a/services/web/test/acceptance/src/PrimaryEmailCheckTests.js b/services/web/test/acceptance/src/PrimaryEmailCheckTests.js index d05f89bb05..e6aaf1b78e 100644 --- a/services/web/test/acceptance/src/PrimaryEmailCheckTests.js +++ b/services/web/test/acceptance/src/PrimaryEmailCheckTests.js @@ -43,19 +43,18 @@ describe('PrimaryEmailCheck', function () { describe('redirections', function () { describe('when the user has signed up recently', function () { it("shouldn't be redirected from project list to the primary email check page", async function () { - const response = await userHelper.request.get( - '/project' + SPLIT_TEST_QUERY - ) - expect(response.statusCode).to.equal(200) + const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY) + expect(response.status).to.equal(200) }) it('should be redirected from the primary email check page to the project list', async function () { - const response = await userHelper.request.get( - '/user/emails/primary-email-check' + SPLIT_TEST_QUERY, - { simple: false } + const response = await userHelper.fetch( + '/user/emails/primary-email-check' + SPLIT_TEST_QUERY + ) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/project').toString() ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/project') }) }) @@ -68,19 +67,18 @@ describe('PrimaryEmailCheck', function () { }) it("shouldn't be redirected from project list to the primary email check page", async function () { - const response = await userHelper.request.get( - '/project' + SPLIT_TEST_QUERY - ) - expect(response.statusCode).to.equal(200) + const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY) + expect(response.status).to.equal(200) }) it('should be redirected from the primary email check page to the project list', async function () { - const response = await userHelper.request.get( - '/user/emails/primary-email-check' + SPLIT_TEST_QUERY, - { simple: false } + const response = await userHelper.fetch( + '/user/emails/primary-email-check' + SPLIT_TEST_QUERY + ) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/project').toString() ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/project') }) }) @@ -100,19 +98,18 @@ describe('PrimaryEmailCheck', function () { }) it("shouldn't be redirected from project list to the primary email check page", async function () { - const response = await userHelper.request.get( - '/project' + SPLIT_TEST_QUERY - ) - expect(response.statusCode).to.equal(200) + const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY) + expect(response.status).to.equal(200) }) it('should be redirected from the primary email check page to the project list', async function () { - const response = await userHelper.request.get( - '/user/emails/primary-email-check' + SPLIT_TEST_QUERY, - { simple: false } + const response = await userHelper.fetch( + '/user/emails/primary-email-check' + SPLIT_TEST_QUERY + ) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/project').toString() ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/project') }) }) @@ -125,21 +122,18 @@ describe('PrimaryEmailCheck', function () { }) it('should be redirected from project list to the primary email check page', async function () { - const response = await userHelper.request.get( - '/project' + SPLIT_TEST_QUERY, - { simple: false } - ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - '/user/emails/primary-email-check' + const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/user/emails/primary-email-check').toString() ) }) it('can visit the primary email check page', async function () { - const response = await userHelper.request.get( + const response = await userHelper.fetch( '/user/emails/primary-email-check' ) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) }) }) }) @@ -154,34 +148,32 @@ describe('PrimaryEmailCheck', function () { $set: { lastPrimaryEmailCheck: new Date(time) }, }) - checkResponse = await userHelper.request.post( + checkResponse = await userHelper.fetch( '/user/emails/primary-email-check' + SPLIT_TEST_QUERY, - { - form: {}, - simple: false, - } + { method: 'POST' } ) }) it('should be redirected to the project list page', function () { - expect(checkResponse.statusCode).to.equal(302) - expect(checkResponse.headers.location).to.equal('/project') + expect(checkResponse.status).to.equal(302) + expect(checkResponse.headers.get('location')).to.equal( + UserHelper.url('/project').toString() + ) }) it("shouldn't be redirected from project list to the primary email check page any longer", async function () { - const response = await userHelper.request.get( - '/project' + SPLIT_TEST_QUERY - ) - expect(response.statusCode).to.equal(200) + const response = await userHelper.fetch('/project' + SPLIT_TEST_QUERY) + expect(response.status).to.equal(200) }) it('visiting the primary email check page should redirect to the project list page', async function () { - const response = await userHelper.request.get( - '/user/emails/primary-email-check', - { simple: false } + const response = await userHelper.fetch( + '/user/emails/primary-email-check' + ) + expect(response.status).to.equal(302) + expect(response.headers.get('location')).to.equal( + UserHelper.url('/project').toString() ) - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/project') }) }) }) diff --git a/services/web/test/acceptance/src/ProjectDuplicateNameTests.js b/services/web/test/acceptance/src/ProjectDuplicateNameTests.js index 55057d1136..d19bbb097e 100644 --- a/services/web/test/acceptance/src/ProjectDuplicateNameTests.js +++ b/services/web/test/acceptance/src/ProjectDuplicateNameTests.js @@ -668,15 +668,14 @@ describe('ProjectDuplicateNames', function () { }) it('should handle characters that would cause an invalid regular expression', async function () { const projectName = 'Example (test' - response = await userHelper.request.post('/project/new', { - simple: false, - form: { projectName }, + response = await userHelper.fetch('/project/new', { + method: 'POST', + body: new URLSearchParams([['projectName', projectName]]), }) - expect(response.statusCode).to.equal(200) // can create project - response = await userHelper.request.get( - `/project/${JSON.parse(response.body).project_id}` - ) - expect(response.statusCode).to.equal(200) // can open project + const body = await response.json() + expect(response.status).to.equal(200) // can create project + response = await userHelper.fetch(`/project/${body.project_id}`) + expect(response.status).to.equal(200) // can open project }) }) }) diff --git a/services/web/test/acceptance/src/helpers/UserHelper.js b/services/web/test/acceptance/src/helpers/UserHelper.js index e9dbde88a4..a55584dc48 100644 --- a/services/web/test/acceptance/src/helpers/UserHelper.js +++ b/services/web/test/acceptance/src/helpers/UserHelper.js @@ -1,4 +1,5 @@ const { expect } = require('chai') +const { CookieJar } = require('tough-cookie') const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager') const Settings = require('@overleaf/settings') const InstitutionsAPI = require('../../../../app/src/Features/Institutions/InstitutionsAPI') @@ -6,7 +7,7 @@ const UserCreator = require('../../../../app/src/Features/User/UserCreator') const UserGetter = require('../../../../app/src/Features/User/UserGetter') const UserUpdater = require('../../../../app/src/Features/User/UserUpdater') const moment = require('moment') -const request = require('request-promise-native') +const fetch = require('node-fetch') const { db } = require('../../../../app/src/infrastructure/mongodb') const { ObjectId } = require('mongodb') const { @@ -82,43 +83,33 @@ class UserHelper { // used to store mongo user object once created/loaded this.user = null // cookie jar - this.jar = request.jar() - // create new request instance - this.request = request.defaults({}) - // initialize request instance with default options - this.setRequestDefaults({ - baseUrl: UserHelper.baseUrl(), - followRedirect: false, - jar: this.jar, - resolveWithFullResponse: true, - }) + this.jar = new CookieJar() } - /* Set defaults for request object. Applied over existing defaults. - * @param {object} [defaults] - */ - setRequestDefaults(defaults = {}) { - // request-promise instance for making requests - this.request = this.request.defaults(defaults) - } - - /** - * Make a request for the user and run expectations on the response. - * @param {object} [requestOptions] options to pass to request - * @param {object} [responseExpectations] expectations: - * - {Int} statusCode the expected status code - * - {RegEx} message a matcher for the message - */ - async expectErrorOnRequest(requestOptions, responseExpectations) { - let error - try { - await this.request(requestOptions) - } catch (e) { - error = e + async fetch(url, opts = {}) { + url = UserHelper.url(url) + const headers = {} + const cookieString = this.jar.getCookieStringSync(url) + if (cookieString) { + headers.Cookie = cookieString } - expect(error).to.exist - expect(error.statusCode).to.equal(responseExpectations.statusCode) - expect(error.message).to.match(responseExpectations.message) + if (this._csrfToken) { + headers['x-csrf-token'] = this._csrfToken + } + const response = await fetch(url, { + redirect: 'manual', + ...opts, + headers: { ...headers, ...opts.headers }, + }) + + // From https://www.npmjs.com/package/node-fetch#extract-set-cookie-header + const cookies = response.headers.raw()['set-cookie'] + if (cookies != null) { + for (const cookie of cookies) { + this.jar.setCookieSync(cookie, url) + } + } + return response } /* async http api call methods */ @@ -128,12 +119,8 @@ class UserHelper { */ async getCsrfToken() { // get csrf token from api and store - const response = await this.request.get('/dev/csrf') - this._csrfToken = response.body - // use csrf token for requests - this.setRequestDefaults({ - headers: { 'x-csrf-token': this._csrfToken }, - }) + const response = await this.fetch('/dev/csrf') + this._csrfToken = await response.text() } /** @@ -142,13 +129,11 @@ class UserHelper { * @returns {object} http response */ async logout(options = {}) { - // do not throw exception on 302 - options.simple = false // post logout - const response = await this.request.post('/logout', options) + const response = await this.fetch('/logout', { method: 'POST', ...options }) if ( - response.statusCode !== 302 || - !response.headers.location.includes('/login') + response.status !== 302 || + !response.headers.get('location').includes('/login') ) { throw new Error('logout failed') } @@ -168,6 +153,13 @@ class UserHelper { return `http://${process.env.HTTP_TEST_HOST || 'localhost'}:23000` } + /** + * Generates a full URL given a path + */ + static url(path) { + return new URL(path, UserHelper.baseUrl()) + } + /* static async instantiation methods */ /** @@ -247,17 +239,30 @@ class UserHelper { const userHelper = new UserHelper() const loginPath = Settings.enableLegacyLogin ? '/login/legacy' : '/login' await userHelper.getCsrfToken() - const response = await userHelper.request.post(loginPath, { - json: { + const response = await userHelper.fetch(loginPath, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ 'g-recaptcha-response': 'valid', ...userData, - }, + }), }) - if (response.statusCode !== 200 || response.body.redir !== '/project') { + if (!response.ok) { const error = new Error('login failed') error.response = response throw error } + + const body = await response.json() + if (body.redir !== '/project') { + const error = new Error('login failed') + error.response = response + throw error + } + userHelper.user = await UserGetter.promises.getUser({ email: userData.email, }) @@ -274,10 +279,10 @@ class UserHelper { * @returns {Boolean} */ async isLoggedIn() { - const response = await this.request.get('/user/sessions', { - followRedirect: true, + const response = await this.fetch('/user/sessions', { + redirect: 'follow', }) - return response.request.path === '/user/sessions' + return !response.redirected } /** @@ -292,8 +297,16 @@ class UserHelper { const userHelper = new UserHelper() await userHelper.getCsrfToken() userData = userHelper.getDefaultEmailPassword(userData) - options.json = userData - const { body } = await userHelper.request.post('/register', options) + const response = await userHelper.fetch('/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(userData), + ...options, + }) + const body = await response.json() if (body.message && body.message.type === 'error') { throw new Error(`register api error: ${body.message.text}`) } @@ -321,14 +334,11 @@ class UserHelper { } async addEmail(email) { - const response = await this.request.post({ - form: { - email, - }, - simple: false, - uri: '/user/emails', + const response = await this.fetch('/user/emails', { + method: 'POST', + body: new URLSearchParams([['email', email]]), }) - expect(response.statusCode).to.equal(204) + expect(response.status).to.equal(204) } async addEmailAndConfirm(userId, email) { @@ -393,16 +403,12 @@ class UserHelper { } async confirmEmail(userId, email) { - let response // UserHelper.createUser does not create a confirmation token - response = await this.request.post({ - form: { - email, - }, - simple: false, - uri: '/user/emails/resend_confirmation', + let response = await this.fetch('/user/emails/resend_confirmation', { + method: 'POST', + body: new URLSearchParams([['email', email]]), }) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) const tokenData = await db.tokens .find({ use: 'email_confirmation', @@ -411,14 +417,11 @@ class UserHelper { usedAt: { $exists: false }, }) .next() - response = await this.request.post({ - form: { - token: tokenData.token, - }, - simple: false, - uri: '/user/emails/confirm', + response = await this.fetch('/user/emails/confirm', { + method: 'POST', + body: new URLSearchParams([['token', tokenData.token]]), }) - expect(response.statusCode).to.equal(200) + expect(response.status).to.equal(200) } }