Added gravatar URL to config to make it configurable (#14490)

refs https://github.com/TryGhost/Toolbox/issues/288

- Allows switching out the Gravatar URL to use placeholder images when working with mocked demo data
This commit is contained in:
Matt Hanley 2022-05-09 12:44:04 +01:00 committed by GitHub
parent cb80be3d0c
commit 62164ecdf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 17 deletions

View File

@ -1,5 +1,6 @@
const Promise = require('bluebird');
const crypto = require('crypto');
const tpl = require('@tryghost/tpl');
class Gravatar {
constructor({config, request}) {
@ -7,21 +8,36 @@ class Gravatar {
this.request = request;
}
url(email, options) {
if (options.default) {
// tpl errors on token `{default}` so we use `{_default}` instead
// but still allow the option to be passed as `default`
options._default = options.default;
}
const defaultOptions = {
size: 250,
_default: 'blank',
rating: 'g'
};
const emailHash = crypto.createHash('md5').update(email.toLowerCase().trim()).digest('hex');
const gravatarUrl = this.config.get('gravatar').url;
return tpl(gravatarUrl, Object.assign(defaultOptions, options, {hash: emailHash}));
}
lookup(userData, timeout) {
let gravatarUrl = '//www.gravatar.com/avatar/' +
crypto.createHash('md5').update(userData.email.toLowerCase().trim()).digest('hex') +
'?s=250';
if (this.config.isPrivacyDisabled('useGravatar')) {
return Promise.resolve();
}
return Promise.resolve(this.request('https:' + gravatarUrl + '&d=404&r=x', {timeout: timeout || 2 * 1000}))
// test existence using a default 404, but return a different default
// so we still have a fallback if the image gets removed from Gravatar
const testUrl = this.url(userData.email, {default: 404, rating: 'x'});
const imageUrl = this.url(userData.email, {default: 'mp', rating: 'x'});
return Promise.resolve(this.request(testUrl, {timeout: timeout || 2 * 1000}))
.then(function () {
gravatarUrl += '&d=mm&r=x';
return {
image: gravatarUrl
image: imageUrl
};
})
.catch({statusCode: 404}, function () {

View File

@ -2,7 +2,7 @@ const ghostBookshelf = require('./base');
const uuid = require('uuid');
const _ = require('lodash');
const config = require('../../shared/config');
const crypto = require('crypto');
const {gravatar} = require('../lib/image');
const Member = ghostBookshelf.Model.extend({
tableName: 'members',
@ -311,8 +311,7 @@ const Member = ghostBookshelf.Model.extend({
// Will not use gravatar if privacy.useGravatar is false in config
attrs.avatar_image = null;
if (attrs.email && !config.isPrivacyDisabled('useGravatar')) {
const emailHash = crypto.createHash('md5').update(attrs.email.toLowerCase().trim()).digest('hex');
attrs.avatar_image = `https://gravatar.com/avatar/${emailHash}?s=250&d=blank`;
attrs.avatar_image = gravatar.url(attrs.email, {size: 250, default: 'blank'});
}
return attrs;

View File

@ -140,5 +140,8 @@
},
"twitter": {
"privateReadOnlyToken": null
},
"gravatar": {
"url": "https://www.gravatar.com/avatar/{hash}?s={size}&r={rating}&d={_default}"
}
}

View File

@ -2,15 +2,38 @@ const should = require('should');
const Gravatar = require('../../../../../core/server/lib/image/gravatar');
describe('lib/image: gravatar', function () {
const gravatarUrl = 'https://www.gravatar.com/avatar/{hash}?s={size}&r={rating}&d={_default}';
it('can build a gravatar url', function () {
const gravatar = new Gravatar({config: {
isPrivacyDisabled: () => false,
get: (config) => {
return config === 'gravatar' ? {
url: gravatarUrl
} : null;
}
}, request: () => {}});
gravatar.url('exists@example.com', {
size: 180,
rating: 'r'
}).should.eql('https://www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=180&r=r&d=blank');
});
it('can successfully lookup a gravatar url', function (done) {
const gravatar = new Gravatar({config: {
isPrivacyDisabled: () => false
isPrivacyDisabled: () => false,
get: (config) => {
return config === 'gravatar' ? {
url: gravatarUrl
} : null;
}
}, request: () => {}});
gravatar.lookup({email: 'exists@example.com'}).then(function (result) {
should.exist(result);
should.exist(result.image);
result.image.should.eql('//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x');
result.image.should.eql('https://www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&r=x&d=mp');
done();
}).catch(done);
@ -18,7 +41,12 @@ describe('lib/image: gravatar', function () {
it('can handle a non existant gravatar', function (done) {
const gravatar = new Gravatar({config: {
isPrivacyDisabled: () => false
isPrivacyDisabled: () => false,
get: (config) => {
return config === 'gravatar' ? {
url: gravatarUrl
} : null;
}
}, request: () => {
return Promise.reject({statusCode: 404});
}});
@ -34,7 +62,12 @@ describe('lib/image: gravatar', function () {
it('will timeout', function () {
const delay = 42;
const gravatar = new Gravatar({config: {
isPrivacyDisabled: () => false
isPrivacyDisabled: () => false,
get: (config) => {
return config === 'gravatar' ? {
url: gravatarUrl
} : null;
}
}, request: (url, options) => {
options.timeout.should.eql(delay);
}});

View File

@ -35,7 +35,7 @@ describe('Unit: models/member', function () {
config.set('privacy:useGravatar', true);
const json = toJSON(member);
json.avatar_image.should.eql(`https://gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=250&d=blank`);
json.avatar_image.should.eql(`https://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=250&r=g&d=blank`);
});
it('avatar_image: skips gravatar when privacy.useGravatar=false', function () {