diff --git a/client/src/components/users/UserAvatar/UserAvatar.jsx b/client/src/components/users/UserAvatar/UserAvatar.jsx index 323e717b..7abd5226 100755 --- a/client/src/components/users/UserAvatar/UserAvatar.jsx +++ b/client/src/components/users/UserAvatar/UserAvatar.jsx @@ -52,6 +52,13 @@ const UserAvatar = React.memo( const [t] = useTranslation(); + let avatarUrl = null; + if (user.avatar) { + avatarUrl = user.avatar.thumbnailUrls.cover180; + } else if (user.gravatarUrl) { + avatarUrl = user.gravatarUrl; + } + const contentNode = ( - {!user.avatar && {initials(user.name).slice(0, 2)}} + {!avatarUrl && {initials(user.name).slice(0, 2)}} {withCreatorIndicator && +} ); diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 60341a02..1c330338 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -84,6 +84,10 @@ services: # - SMTP_PASSWORD= # - SMTP_FROM="Demo Demo" # - SMTP_TLS_REJECT_UNAUTHORIZED=false + + # Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk). + # Use a proxy you control for privacy, or leave commented out or empty to disable. + # GRAVATAR_BASE_URL=https://www.gravatar.com/avatar/ depends_on: postgres: condition: service_healthy diff --git a/docker-compose.yml b/docker-compose.yml index fe9960f2..cae21f3b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -104,6 +104,10 @@ services: # - SMTP_PASSWORD__FILE=/run/secrets/smtp_password # - SMTP_FROM="Demo Demo" # - SMTP_TLS_REJECT_UNAUTHORIZED=false + + # Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk). + # Use a proxy you control for privacy, or leave commented out or empty to disable. + # GRAVATAR_BASE_URL=https://www.gravatar.com/avatar/ depends_on: postgres: condition: service_healthy diff --git a/server/.env.sample b/server/.env.sample index 2c64a033..0c9f3b86 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -75,3 +75,7 @@ SECRET_KEY=notsecretkey # SMTP_PASSWORD= # SMTP_FROM="Demo Demo" # SMTP_TLS_REJECT_UNAUTHORIZED=false + +# Using Gravatar directly exposes user IPs and hashed emails to a third party (GDPR risk). +# Use a proxy you control for privacy, or leave commented out or empty to disable. +# GRAVATAR_BASE_URL=https://www.gravatar.com/avatar/ diff --git a/server/api/helpers/users/build-gravatar-url.js b/server/api/helpers/users/build-gravatar-url.js new file mode 100644 index 00000000..408fc654 --- /dev/null +++ b/server/api/helpers/users/build-gravatar-url.js @@ -0,0 +1,26 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +const crypto = require('crypto'); + +module.exports = { + sync: true, + + inputs: { + record: { + type: 'ref', + required: true, + }, + }, + + fn(inputs) { + if (!sails.config.custom.gravatarBaseUrl) { + return null; + } + + const hash = crypto.createHash('md5').update(inputs.record.email).digest('hex'); + return `${sails.config.custom.gravatarBaseUrl}${hash}?s=180&d=initials`; + }, +}; diff --git a/server/api/helpers/users/present-one.js b/server/api/helpers/users/present-one.js index 856d1d20..226df8d9 100644 --- a/server/api/helpers/users/present-one.js +++ b/server/api/helpers/users/present-one.js @@ -36,6 +36,12 @@ module.exports = { termsType: sails.hooks.terms.getTypeByUserRole(inputs.record.role), }; + const gravatarUrl = sails.helpers.users.buildGravatarUrl(inputs.record); + + if (gravatarUrl) { + data.gravatarUrl = gravatarUrl; + } + if (inputs.user) { const isForCurrentUser = inputs.record.id === inputs.user.id; const isForAdmin = inputs.user.role === User.Roles.ADMIN; diff --git a/server/config/custom.js b/server/config/custom.js index 296377cf..4cebc225 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -105,4 +105,6 @@ module.exports.custom = { smtpPassword: process.env.SMTP_PASSWORD, smtpFrom: process.env.SMTP_FROM, smtpTlsRejectUnauthorized: process.env.SMTP_TLS_REJECT_UNAUTHORIZED !== 'false', + + gravatarBaseUrl: process.env.GRAVATAR_BASE_URL, };