feat: store user-agent (extracted OBS version only) for each track when uploading

This commit is contained in:
Paul Bienkowski 2020-11-23 17:51:22 +01:00
parent 2da013583b
commit 3b1376b661
5 changed files with 84 additions and 4 deletions

View file

@ -254,4 +254,31 @@ function* parseObsver2(body) {
} }
} }
module.exports = { parseTrackPoints, detectFormat, parseObsver1, parseObsver2, replaceDollarNewlinesHack }; /**
* This function normalizes a User-Agent header for storage in the database. It
* make sure that we only store the user-agent if it matches the pattern
* `OBS/*`, and extracts that part of the user agent, if it contains more
* information. This is the only part we are interested in, the
* remainder is too privacy sensitive to keep.
*/
function normalizeUserAgent(userAgent) {
if (!userAgent) {
return null;
}
const match = userAgent.match(/\bOBS\/[^\s]+/);
if (match) {
return match[0];
}
return null;
}
module.exports = {
detectFormat,
normalizeUserAgent,
parseObsver1,
parseObsver2,
parseTrackPoints,
replaceDollarNewlinesHack,
};

View file

@ -1,4 +1,11 @@
const { parseTrackPoints, parseObsver1, detectFormat, parseObsver2, replaceDollarNewlinesHack } = require('./tracks'); const {
detectFormat,
normalizeUserAgent,
parseObsver1,
parseObsver2,
parseTrackPoints,
replaceDollarNewlinesHack,
} = require('./tracks');
const { test1, test2, test3 } = require('./_tracks_testdata'); const { test1, test2, test3 } = require('./_tracks_testdata');
@ -92,3 +99,44 @@ describe('detectFormat', () => {
expect(detectFormat('')).toBe('invalid'); expect(detectFormat('')).toBe('invalid');
}); });
}); });
describe('normalizeUserAgent', () => {
it('is a function', () => {
expect(typeof normalizeUserAgent).toBe('function');
});
it('ignores falsy values', () => {
expect(normalizeUserAgent(null)).toBe(null);
expect(normalizeUserAgent('')).toBe(null);
});
it('ignores normal browser agents', () => {
const browserAgents = [
'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.3',
'Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A5370a Safari/604.1',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1',
];
for (const browserAgent of browserAgents) {
expect(normalizeUserAgent(browserAgent)).toBe(null);
}
});
it('detects OBS versions', () => {
const agents = ['OBS/123', 'OBS/2', 'OBS/1.2.3.4.5-rc123'];
for (const agent of agents) {
expect(normalizeUserAgent(agent)).toBe(agent);
}
});
it('extracts OBS versions from extended formats', () => {
const agents = ['foo OBS/123', 'OBS/123 bar', 'foo OBS/123 bar'];
for (const agent of agents) {
expect(normalizeUserAgent(agent)).toBe('OBS/123');
}
});
});

View file

@ -9,6 +9,7 @@ const TrackSchema = new mongoose.Schema(
description: String, description: String,
body: String, body: String,
visible: Boolean, visible: Boolean,
uploadedByUserAgent: String,
numEvents: { type: Number, default: 0 }, numEvents: { type: Number, default: 0 },
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }], comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },

View file

@ -7,7 +7,7 @@ const User = mongoose.model('User');
const busboy = require('connect-busboy'); const busboy = require('connect-busboy');
const auth = require('../auth'); const auth = require('../auth');
const currentTracks = new Map(); const currentTracks = new Map();
const { parseTrackPoints } = require('../../logic/tracks'); const { parseTrackPoints, normalizeUserAgent } = require('../../logic/tracks');
const wrapRoute = require('../../_helpers/wrapRoute'); const wrapRoute = require('../../_helpers/wrapRoute');
// Preload track objects on routes with ':track' // Preload track objects on routes with ':track'
@ -216,6 +216,7 @@ router.post(
if (track.body) { if (track.body) {
trackData.points = Array.from(parseTrackPoints(track.body)); trackData.points = Array.from(parseTrackPoints(track.body));
track.uploadedByUserAgent = normalizeUserAgent(req.headers['user-agent']);
} }
track.visible = track.author.areTracksVisibleForAll; track.visible = track.author.areTracksVisibleForAll;
@ -242,6 +243,7 @@ router.post(
const trackData = new TrackData(); const trackData = new TrackData();
track.trackData = trackData._id; track.trackData = trackData._id;
track.author = user; track.author = user;
track.uploadedByUserAgent = normalizeUserAgent(req.headers['user-agent']);
await track.save(); await track.save();
await trackData.save(); await trackData.save();
@ -372,6 +374,7 @@ router.put(
req.track.trackData = trackData._id; req.track.trackData = trackData._id;
} }
trackData.points = Array.from(parseTrackPoints(req.track.body)); trackData.points = Array.from(parseTrackPoints(req.track.body));
req.track.uploadedByUserAgent = normalizeUserAgent(req.headers['user-agent']);
await trackData.save(); await trackData.save();
} }

View file

@ -2,7 +2,8 @@ const jwt = require('express-jwt');
const secret = require('../config').secret; const secret = require('../config').secret;
function getTokenFromHeader(req) { function getTokenFromHeader(req) {
const [tokenType, token] = req.headers.authorization?.split(' ') || []; const authorization = req.headers.authorization;
const [tokenType, token] = (authorization && authorization.split(' ')) || [];
if (tokenType === 'Token' || tokenType === 'Bearer') { if (tokenType === 'Token' || tokenType === 'Bearer') {
return token; return token;