Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#6250): use fetch in couch-request #9746

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
27 changes: 14 additions & 13 deletions api/src/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ const dataContext = require('./services/data-context');
const { roles, users } = require('@medic/user-management')(config, db, dataContext);

const contentLengthRegex = /^content-length$/i;
const contentTypeRegex = /^content-type$/i;

const get = (path, headers) => {
const getHeaders = { ...headers };
Object
.keys(getHeaders)
.filter(header => contentLengthRegex.test(header))
.filter(header => contentLengthRegex.test(header) || contentTypeRegex.test(header))
.forEach(header => delete getHeaders[header]);

const url = new URL(path, environment.serverUrlNoAuth);
Expand Down Expand Up @@ -51,7 +52,7 @@ module.exports = {
getUserCtx: req => {
return get('/_session', req.headers)
.catch(err => {
if (err.statusCode === 401) {
if (err.status === 401) {
throw { code: 401, message: 'Not logged in', err: err };
}
throw err;
Expand Down Expand Up @@ -83,12 +84,12 @@ module.exports = {
* @return {Object} {username: username, password: password}
*/
basicAuthCredentials: req => {
const authHeader = req && req.headers && req.headers.authorization;
const authHeader = req?.headers?.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
return false;
}
try {
const [username, password] = Buffer.from(authHeader.split(' ')[1], 'base64').toString().split(':');
const [username, password] = atob(authHeader.split(' ')[1]).split(':');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: why change this? Looks like atob is marked as "legacy" in favor of Buffer.from...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, my bad. I'll switch it back!

return { username, password };
} catch (err) {
throw Error('Corrupted Auth header');
Expand All @@ -101,16 +102,16 @@ module.exports = {
* @param {Object} Credentials object as created by basicAuthCredentials
*/
validateBasicAuth: ({ username, password }) => {
const authUrl = new URL(environment.serverUrlNoAuth);
authUrl.username = username;
authUrl.password = password;
return request.head({
uri: authUrl.toString(),
resolveWithFullResponse: true
})
return request
.get({
uri: environment.serverUrlNoAuth,
auth: { username, password },
simple: false,
json: false,
})
.then(res => {
if (res.statusCode !== 200) {
return Promise.reject(new Error(`Expected 200 got ${res.statusCode}`));
if (!res.ok) {
return Promise.reject(new Error(`Expected 200 got ${res.status}`));
}
return username;
});
Expand Down
16 changes: 6 additions & 10 deletions api/src/controllers/login.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const path = require('path');
const request = require('@medic/couch-request');
const _ = require('lodash');
const url = require('node:url');
const auth = require('../auth');
const environment = require('@medic/environment');
Expand Down Expand Up @@ -160,22 +159,19 @@ const render = (page, req, extras = {}) => {
};

const getSessionCookie = res => {
return _.find(
res.headers['set-cookie'],
cookie => cookie.indexOf('AuthSession') === 0
);
return res.headers.getSetCookie().find(cookie => cookie.indexOf('AuthSession') === 0);
};

const createSession = req => {
const user = req.body.user;
const password = req.body.password;

return request.post({
url: new URL('/_session', environment.serverUrlNoAuth).toString(),
json: true,
resolveWithFullResponse: true,
simple: false, // doesn't throw an error on non-200 responses
body: { name: user, password: password },
auth: { user: user, pass: password },
auth: { username: user, password: password },
});
};

Expand Down Expand Up @@ -242,7 +238,7 @@ const getUserCtxRetry = async (options, retry = 10) => {

const createSessionRetry = (req, retry=10) => {
return createSession(req).then(sessionRes => {
if (sessionRes.statusCode === 200) {
if (sessionRes.status === 200) {
return sessionRes;
}

Expand Down Expand Up @@ -303,8 +299,8 @@ const renderLogin = (req) => {
const login = async (req, res) => {
try {
const sessionRes = await createSession(req);
if (sessionRes.statusCode !== 200) {
res.status(sessionRes.statusCode).json({ error: 'Not logged in' });
if (sessionRes.status !== 200) {
res.status(sessionRes.status).json({ error: 'Not logged in' });
} else {
const redirectUrl = await setCookies(req, res, sessionRes);
res.status(302).send(redirectUrl);
Expand Down
11 changes: 6 additions & 5 deletions api/src/db-batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ const runBatch = (ddoc, view, viewParams, iteratee) => {
});
// using request here instead of PouchDB because
// PouchDB doesn't support startkey_docid: #5319
return request.get({
url: fullUrl,
json: true,
auth: { user: environment.username, pass: environment.password },
})
return request
.get({
url: fullUrl,
json: true,
auth: { username: environment.username, password: environment.password },
})
.then(result => {
logger.info(` Processing doc ${result.offset}`);
let nextPage;
Expand Down
2 changes: 1 addition & 1 deletion api/src/migrations/fix-user-db-security.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {
return p.then(() => {
return userDb.setSecurity(userDb.getDbName(username), username)
.catch(err => {
if (err.statusCode !== 404) {
if (err.status !== 404) {
throw err;
}
// db not found is ok
Expand Down
4 changes: 2 additions & 2 deletions api/src/migrations/restrict-access-to-audit-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const addMemberToDb = () => {
pathname: `${environment.db}-audit/_security`,
}),
auth: {
user: environment.username,
pass: environment.password
username: environment.username,
password: environment.password
},
json: true,
body: securityObject
Expand Down
4 changes: 2 additions & 2 deletions api/src/migrations/restrict-access-to-sentinel-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const addSecurityToDb = () => {
pathname: `${environment.db}-sentinel/_security`,
}),
auth: {
user: environment.username,
pass: environment.password
username: environment.username,
password: environment.password
},
json: true,
body: securityObject
Expand Down
4 changes: 2 additions & 2 deletions api/src/migrations/restrict-access-to-vault-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const addSecurityToDb = () => {
pathname: `${environment.db}-vault/_security`,
}),
auth: {
user: environment.username,
pass: environment.password
username: environment.username,
password: environment.password
},
json: true,
body: securityObject
Expand Down
29 changes: 15 additions & 14 deletions api/src/services/africas-talking.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,21 @@ const parseResponseBody = body => {
const sendMessage = (credentials, message) => {
const url = getUrl(credentials.username === 'sandbox');
logger.debug(`Sending message to "${url}"`);
return request.post({
url: url,
simple: false,
form: {
username: credentials.username,
from: credentials.from,
to: message.to,
message: message.content
},
headers: {
apikey: credentials.apiKey,
Accept: 'application/json'
}
})
return request
.post({
url: url,
json: false,
form: {
username: credentials.username,
from: credentials.from,
to: message.to,
message: message.content
},
headers: {
apikey: credentials.apiKey,
Accept: 'application/json'
}
})
.then(body => {
const result = parseResponseBody(body);
if (!result) {
Expand Down
6 changes: 2 additions & 4 deletions api/src/services/rapidpro.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ const getCredentials = () => {

const getRequestOptions = ({ apiToken, host }={}) => ({
baseUrl: host,
json: true,
headers: {
Authorization: `Token ${apiToken}`,
Accept: 'application/json',
},
});

Expand Down Expand Up @@ -108,7 +106,7 @@ const sendMessage = (credentials, message) => {
})
.catch(err => {
logger.error(`Error thrown when trying to send message: %o`, err);
if (err?.statusCode === 400) {
if (err?.status === 400) {
// source https://rapidpro.io/api/v2/
// 400: The request failed due to invalid parameters.
// Do not retry with the same values, and the body of the response will contain details.
Expand Down Expand Up @@ -168,7 +166,7 @@ const getRemoteStates = (credentials, messages) => {
stateUpdates.push(stateUpdate);
})
.catch(err => {
if (err && err.statusCode === 429) {
if (err && err.status === 429) {
// rate limited, throw error to halt recursive polling
throttled = true;
}
Expand Down
4 changes: 2 additions & 2 deletions api/src/services/user-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ module.exports = {
pathname: `${dbName}/_security`,
}),
auth: {
user: environment.username,
pass: environment.password
username: environment.username,
password: environment.password
},
json: true,
body: {
Expand Down
17 changes: 11 additions & 6 deletions api/tests/mocha/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('Auth', () => {
describe('check', () => {

it('returns error when not logged in', () => {
const get = sinon.stub(request, 'get').rejects({ statusCode: 401 });
const get = sinon.stub(request, 'get').rejects({ status: 401 });
return auth.check({ }).catch(err => {
chai.expect(get.callCount).to.equal(1);
chai.expect(get.args[0][0].url).to.equal('http://abc.com/_session');
Expand Down Expand Up @@ -139,12 +139,11 @@ describe('Auth', () => {
host: 'localhost:5988',
'user-agent': 'curl/8.6.0',
accept: '*/*',
'content-type': 'application/json',
},
}]]);
});

it('should clean content-length headers before forwarding', async () => {
it('should clean content-length and content-type headers before forwarding', async () => {
sinon.stub(request, 'get').resolves({ userCtx: { name: 'theuser', roles: ['userrole'] }});

req.headers['content-length'] = 100;
Expand All @@ -153,6 +152,13 @@ describe('Auth', () => {
req.headers['content-Length'] = 82;
req.headers['CONTENT-LENGTH'] = 240;

req.headers['content-type'] = 'application/json';
req.headers['Content-Type'] = 'image/jpeg';
req.headers['Content-type'] = 'x-www-form-urlencoded';
req.headers['content-Type'] = 'multipart/form-data';
req.headers['CONTENT-TYPE'] = 'text/html';


const result = await auth.getUserCtx(req);
chai.expect(result).to.deep.equal({ name: 'theuser', roles: ['userrole'] });
chai.expect(request.get.args).to.deep.equal([[{
Expand All @@ -162,18 +168,17 @@ describe('Auth', () => {
host: 'localhost:5988',
'user-agent': 'curl/8.6.0',
accept: '*/*',
'content-type': 'application/json',
},
}]]);
});

it('should throw a custom 401 error', async () => {
sinon.stub(request, 'get').rejects({ statusCode: 401, error: 'not logged in' });
sinon.stub(request, 'get').rejects({ status: 401, error: 'not logged in' });

await chai.expect(auth.getUserCtx(req)).to.be.rejected.and.eventually.deep.equal({
code: 401,
message: 'Not logged in',
err: { statusCode: 401, error: 'not logged in' }
err: { status: 401, error: 'not logged in' }
});

chai.expect(request.get.callCount).to.equal(1);
Expand Down
Loading
Loading