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,
};