Skip to content

Commit e00bf71

Browse files
authored
fixes 896: don't assume user agent to be always present (#907)
1 parent a3e3cf3 commit e00bf71

File tree

5 files changed

+22
-17
lines changed

5 files changed

+22
-17
lines changed

lib/http/preprocessors.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ const queryOptionsHandler = (_, context) => {
153153
return context.with({ queryOptions: new QueryOptions(options), transitoryData: new Map() });
154154
};
155155

156+
// User Agent in HTTP is optional, if not provided then it is `undefined`.
157+
// `undefined` causes Slonik to fail, we are setting it to `null` here so that
158+
// we don't have check its existence in multiple places.
159+
const userAgentHandler = (_, context) => {
160+
const userAgent = context.headers['user-agent'] ?? null;
161+
return context.with({ userAgent });
162+
};
156163

157-
module.exports = { emptyAuthInjector, authHandler, queryOptionsHandler };
164+
module.exports = { emptyAuthInjector, authHandler, queryOptionsHandler, userAgentHandler };
158165

lib/http/service.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ module.exports = (container) => {
4949
// and for easier transaction management.
5050

5151
const { builder } = require('./endpoint');
52-
const { emptyAuthInjector, authHandler, queryOptionsHandler } = require('./preprocessors');
53-
const endpoint = builder(container, [ emptyAuthInjector, authHandler, queryOptionsHandler ]);
52+
const { emptyAuthInjector, authHandler, queryOptionsHandler, userAgentHandler } = require('./preprocessors');
53+
const endpoint = builder(container, [ emptyAuthInjector, authHandler, queryOptionsHandler, userAgentHandler ]);
5454

5555

5656
////////////////////////////////////////////////////////////////////////////////

lib/resources/entities.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module.exports = (service, endpoint) => {
7474

7575
}));
7676

77-
service.post('/projects/:id/datasets/:name/entities', endpoint(async ({ Datasets, Entities }, { auth, body, headers, params }) => {
77+
service.post('/projects/:id/datasets/:name/entities', endpoint(async ({ Datasets, Entities }, { auth, body, params, userAgent }) => {
7878

7979
const dataset = await Datasets.get(params.id, params.name).then(getOrNotFound);
8080

@@ -85,13 +85,13 @@ module.exports = (service, endpoint) => {
8585
const partial = await Entity.fromJson(body, properties, dataset);
8686

8787
const sourceId = await Entities.createSource();
88-
const entity = await Entities.createNew(dataset, partial, null, sourceId, headers['user-agent']);
88+
const entity = await Entities.createNew(dataset, partial, null, sourceId, userAgent);
8989

9090
// Entities.createNew doesn't return enough information for a full response so re-fetch.
9191
return Entities.getById(dataset.id, entity.uuid).then(getOrNotFound);
9292
}));
9393

94-
service.patch('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Datasets, Entities }, { auth, body, headers, params, query }) => {
94+
service.patch('/projects/:id/datasets/:name/entities/:uuid', endpoint(async ({ Datasets, Entities }, { auth, body, params, query, userAgent }) => {
9595

9696
const dataset = await Datasets.get(params.id, params.name).then(getOrNotFound);
9797

@@ -105,7 +105,7 @@ module.exports = (service, endpoint) => {
105105
const partial = Entity.fromJson(body, properties, dataset, entity);
106106

107107
const sourceId = await Entities.createSource();
108-
return Entities.createVersion(dataset, entity, partial.def.data, partial.def.label, sourceId, headers['user-agent']);
108+
return Entities.createVersion(dataset, entity, partial.def.data, partial.def.label, sourceId, userAgent);
109109
}));
110110

111111
service.delete('/projects/:projectId/datasets/:name/entities/:uuid', endpoint(async ({ Datasets, Entities }, { auth, params, queryOptions }) => {

lib/resources/sessions.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const { success } = require('../util/http');
1515

1616
module.exports = (service, endpoint) => {
1717

18-
service.post('/sessions', endpoint(({ Audits, Users, Sessions, bcrypt }, { body, headers }) => {
18+
service.post('/sessions', endpoint(({ Audits, Users, Sessions, bcrypt }, { body, userAgent }) => {
1919
const { email, password } = body;
2020

2121
if (isBlank(email) || isBlank(password))
@@ -33,9 +33,7 @@ module.exports = (service, endpoint) => {
3333
// Logging here rather than defining Sessions.create.audit, because
3434
// Sessions.create.audit would require auth. Logging here also makes
3535
// it easy to access `headers`.
36-
Audits.log(user.actor, 'user.session.create', user.actor, {
37-
userAgent: headers['user-agent']
38-
})
36+
Audits.log(user.actor, 'user.session.create', user.actor, { userAgent })
3937
]))
4038
.then(([ session ]) => (_, response) => {
4139
response.cookie('__Host-session', session.token, { path: '/', expires: session.expiresAt,

lib/resources/submissions.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ module.exports = (service, endpoint) => {
8181
};
8282

8383
// Nonstandard REST; OpenRosa-specific API.
84-
service.post(path, multipart.any(), multerUtf, endpoint.openRosa(({ Forms, Submissions, SubmissionAttachments }, { params, files, auth, query, headers }) =>
84+
service.post(path, multipart.any(), multerUtf, endpoint.openRosa(({ Forms, Submissions, SubmissionAttachments }, { params, files, auth, query, userAgent }) =>
8585
Submission.fromXml(findMultipart(files).buffer)
8686
.then((partial) => getForm(auth, params, partial.xmlFormId, Forms, partial.def.version)
8787
.catch(forceAuthFailed)
@@ -124,7 +124,7 @@ module.exports = (service, endpoint) => {
124124
}
125125
// NEW SUBMISSION REQUEST: create a new submission and attachments.
126126
return Promise.all([
127-
Submissions.createNew(partial, form, query.deviceID, headers['user-agent']),
127+
Submissions.createNew(partial, form, query.deviceID, userAgent),
128128
Forms.getBinaryFields(form.def.id)
129129
]).then(([ saved, binaryFields ]) => SubmissionAttachments.create(saved, form, binaryFields, files));
130130
});
@@ -177,7 +177,7 @@ module.exports = (service, endpoint) => {
177177
// and repeated for draft/nondraft.
178178

179179
const restSubmission = (base, draft, getForm) => {
180-
service.post(`${base}/submissions`, endpoint(({ Forms, Submissions, SubmissionAttachments }, { params, auth, query, headers }, request) =>
180+
service.post(`${base}/submissions`, endpoint(({ Forms, Submissions, SubmissionAttachments }, { params, auth, query, userAgent }, request) =>
181181
Submission.fromXml(request)
182182
.then((partial) => getForm(params, Forms, partial.def.version)
183183
.then((form) => auth.canOrReject('submission.create', form))
@@ -186,15 +186,15 @@ module.exports = (service, endpoint) => {
186186
return reject(Problem.user.unexpectedValue({ field: 'form id', value: partial.xmlFormId, reason: 'did not match the form ID in the URL' }));
187187

188188
return Promise.all([
189-
Submissions.createNew(partial, form, query.deviceID, headers['user-agent']),
189+
Submissions.createNew(partial, form, query.deviceID, userAgent),
190190
Forms.getBinaryFields(form.def.id)
191191
])
192192
.then(([ submission, binaryFields ]) =>
193193
SubmissionAttachments.create(submission, form, binaryFields)
194194
.then(always(submission)));
195195
}))));
196196

197-
service.put(`${base}/submissions/:instanceId`, endpoint(({ Forms, Submissions, SubmissionAttachments }, { params, auth, query, headers }, request) =>
197+
service.put(`${base}/submissions/:instanceId`, endpoint(({ Forms, Submissions, SubmissionAttachments }, { params, auth, query, userAgent }, request) =>
198198
Submission.fromXml(request).then((partial) => {
199199
if (partial.xmlFormId !== params.formId)
200200
return reject(Problem.user.unexpectedValue({ field: 'form id', value: partial.xmlFormId, reason: 'did not match the form ID in the URL' }));
@@ -211,7 +211,7 @@ module.exports = (service, endpoint) => {
211211
.then(getOrNotFound) // this request exists just to check existence and fail the whole request.
212212
])
213213
.then(([ deprecated, form ]) => Promise.all([
214-
Submissions.createVersion(partial, deprecated, form, query.deviceID, headers['user-agent']),
214+
Submissions.createVersion(partial, deprecated, form, query.deviceID, userAgent),
215215
Forms.getBinaryFields(form.def.id),
216216
SubmissionAttachments.getForFormAndInstanceId(form.id, deprecatedId, draft),
217217
])

0 commit comments

Comments
 (0)