From f99c78e4d341badf6db4e6a0a8a2adf24314b904 Mon Sep 17 00:00:00 2001
From: Neal Shah <30693865+ProgrammerIn-wonderland@users.noreply.github.com>
Date: Fri, 28 Nov 2025 17:34:26 -0500
Subject: [PATCH] Email read permission for whoami (#2064)
---
extensions/whoami/routes.js | 16 ++++++++++++----
src/backend/src/services/GetUserService.js | 20 ++++++++++++++++++++
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/extensions/whoami/routes.js b/extensions/whoami/routes.js
index d4df8bb45..7d75b7478 100644
--- a/extensions/whoami/routes.js
+++ b/extensions/whoami/routes.js
@@ -1,7 +1,7 @@
// static imports
+import _path from 'fs';
import TimeAgo from 'javascript-time-ago';
import localeEn from 'javascript-time-ago/locale/en';
-import _path from 'fs';
// runtime imports
const { UserActorType, AppUnderUserActorType } = extension.import('core');
@@ -61,6 +61,7 @@ const whoami_common = ({ is_user, user }) => {
extension.get('/whoami', { subdomain: 'api' }, async (req, res, next) => {
const actor = req.actor;
+
if ( ! actor ) {
throw Error('actor not found in context');
}
@@ -101,7 +102,10 @@ extension.get('/whoami', { subdomain: 'api' }, async (req, res, next) => {
// TODO: redundant? GetUserService already puts these values on 'user'
// Get whoami values from other services
- const svc_whoami = req.services.get('whoami');
+ const /** @type {any} */ svc_whoami = req.services.get('whoami');
+
+ const /** @type {any} */ svc_permission = req.services.get('permission');
+
const provider_details = await svc_whoami.get_details({
user: req.user,
actor: actor,
@@ -112,8 +116,12 @@ extension.get('/whoami', { subdomain: 'api' }, async (req, res, next) => {
// When apps call /whoami they should not see these attributes
// delete details.username;
// delete details.uuid;
- delete details.email;
- delete details.unconfirmed_email;
+
+ if ( ! (await svc_permission.check(actor, `user:${details.uuid}:email:read`)) ) {
+ delete details.email;
+ delete details.unconfirmed_email;
+ }
+
delete details.desktop_bg_url;
delete details.desktop_bg_color;
delete details.desktop_bg_fit;
diff --git a/src/backend/src/services/GetUserService.js b/src/backend/src/services/GetUserService.js
index 3d1f4c7ac..b70aab9dd 100644
--- a/src/backend/src/services/GetUserService.js
+++ b/src/backend/src/services/GetUserService.js
@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+const { Actor, UserActorType } = require('./auth/Actor');
+const { PermissionImplicator } = require('./auth/permissionUtils.mjs');
const BaseService = require('./BaseService');
const { DB_READ } = require('./database/consts');
@@ -55,6 +57,24 @@ class GetUserService extends BaseService {
* @returns {Promise} A promise that resolves when the initialization is complete.
*/
async _init () {
+
+ const svc_permission = this.services.get('permission');
+ console.log('reg imppl');
+ svc_permission.register_implicator(PermissionImplicator.create({
+ id: 'user-set-own',
+ shortcut: true,
+ matcher: permission => {
+ return permission.startsWith('user:');
+ },
+ checker: async ({ actor, permission }) => {
+ if ( ! (actor.type instanceof UserActorType) ) {
+ return undefined;
+ }
+ if ( permission === `user:${ actor.type.user.uuid }:email:read` ) {
+ return {};
+ }
+ },
+ }));
}
/**