diff --git a/src/controller/account.js b/src/controller/account.js index 9c9c388c..f0399537 100644 --- a/src/controller/account.js +++ b/src/controller/account.js @@ -44,6 +44,15 @@ class AccountController { requirePassword = true; } + if (updates.profile) { + validateProfile(updates.profile); + + fields.profile = { + image: updates.profile.image.trim(), + text: updates.profile.text + }; + } + if (requirePassword) { if (!password) { throw new InvalidRequestError('Password required'); @@ -67,4 +76,28 @@ class AccountController { } } +function validateProfile(profile) { + // TODO: replace all of these errors with a standard errorcode + field checker + if (profile.toString() !== '[object Object]') + throw new InvalidRequestError('Invalid profile'); + if (typeof profile.text !== 'string') + throw new InvalidRequestError('Invalid profile'); + if (typeof profile.image !== 'string') + throw new InvalidRequestError('Invalid profile'); + if (profile.text.length > 255) + throw new InvalidRequestError('Profile text must not exceed 255 characters'); + if (profile.image.length > 255) + throw new InvalidRequestError('Profile image URL must not exceed 255 characters'); + + if (profile.image.trim() === '') return true; + + const url = parseURL(profile.image); + if (!url.host) + throw new InvalidRequestError('Invalid profile image URL'); + if (url.protocol !== 'https:') + throw new InvalidRequestError('Profile image URL must start with "https:"'); + + return true; +} + export { AccountController }; diff --git a/test/web/routes/account/data.js b/test/web/routes/account/data.js index a4da56dd..ca77e5e3 100644 --- a/test/web/routes/account/data.js +++ b/test/web/routes/account/data.js @@ -278,6 +278,33 @@ describe('AccountDataRoute', () => { }); }); + it('updates profile', () => { + accountDB.expects('updateByName').withArgs( + 'test', + { + profile: { + text: 'testing', + image: 'https://example.com/image.jpg' + } + } + ); + + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: 'testing', + image: 'https://example.com/image.jpg' + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 204); + + accountDB.verify(); + }); + }); + it('rejects invalid email address', () => { return request('PATCH', `${URL_BASE}/account/data/test`, { body: { @@ -377,6 +404,133 @@ describe('AccountDataRoute', () => { }); }); + it('rejects invalid profile', () => { + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: 'not valid' + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Invalid profile' + ); + + accountDB.verify(); + }); + }); + + it('rejects wrongly typed profile text', () => { + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: ['wrong'], + image: 'https://example.com' + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Invalid profile' + ); + + accountDB.verify(); + }); + }); + + it('rejects too long profile text', () => { + let longText = ''; for (let i = 0; i < 256; i++) longText += 'a'; + + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: longText, + image: 'https://example.com' + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Profile text must not exceed 255 characters' + ); + + accountDB.verify(); + }); + }); + + it('rejects wrongly typed profile image', () => { + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: 'test', + image: 42 + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Invalid profile' + ); + + accountDB.verify(); + }); + }); + + it('rejects too long profile image', () => { + let longText = 'https://'; for (let i = 0; i < 256; i++) longText += 'a'; + + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: 'test', + image: longText + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Profile image URL must not exceed 255 characters' + ); + + accountDB.verify(); + }); + }); + + it('rejects non-https profile image', () => { + return request('PATCH', `${URL_BASE}/account/data/test`, { + body: { + updates: { + profile: { + text: 'test', + image: 'http://example.com/image.jpg' + } + } + } + }).then(res => { + assert.strictEqual(res.statusCode, 400); + assert.strictEqual( + JSON.parse(res.body).error, + 'Profile image URL must start with "https:"' + ); + + accountDB.verify(); + }); + }); + checkDefaults('/account/data/test', 'PATCH'); });