dev(data-access): re-add protected app support

Adds support for the "protected apps" feature to the new `apps` driver
that will replace the existing `es:apps` driver.
This commit is contained in:
KernelDeimos
2026-01-20 13:21:33 -05:00
committed by Eric Dubé
parent d08a828226
commit 3fe304ebac
@@ -4,6 +4,7 @@ import APIError from '../../api/APIError.js';
import config from '../../config.js';
import { app_name_exists, refresh_apps_cache } from '../../helpers.js';
import { AppUnderUserActorType, UserActorType } from '../../services/auth/Actor.js';
import { PermissionUtil } from '../../services/auth/permissionUtils.mjs';
import BaseService from '../../services/BaseService.js';
import { DB_READ, DB_WRITE } from '../../services/database/consts.js';
import { Context } from '../../util/context.js';
@@ -190,6 +191,12 @@ export default class AppService extends BaseService {
}
}
// Check protected app access before adding to results
if ( await this.#check_protected_app_access(app, row.owner_user_id) ) {
// App should be filtered out (not accessible)
continue;
}
client_safe_apps.push(app);
}
@@ -311,6 +318,14 @@ export default class AppService extends BaseService {
}
}
// Check protected app access
if ( await this.#check_protected_app_access(app, row.owner_user_id) ) {
// App should not be accessible
throw APIError.create('entity_not_found', null, {
identifier: uid || JSON.stringify(id),
});
}
return app;
}
@@ -555,7 +570,7 @@ export default class AppService extends BaseService {
const app_owner = old_app.app_owner;
const app_owner_uid = app_owner?.uid;
if ( ! app_owner_uid || app_owner_uid !== app.uid ) {
if ( !app_owner_uid || app_owner_uid !== app.uid ) {
throw APIError.create('forbidden');
}
}
@@ -917,4 +932,55 @@ export default class AppService extends BaseService {
values,
};
}
/**
* Checks if a protected app should be filtered out (not accessible to the current actor).
* Returns true if the app should be filtered out, false if it's accessible.
*
* @param {Object} app - The app object with protected, uid, and owner fields
* @param {number} owner_user_id - The database ID of the app owner (for accurate comparison)
* @returns {Promise<boolean>} true if app should be filtered out, false if accessible
*/
async #check_protected_app_access (app, owner_user_id) {
// If it's not a protected app, no worries - allow it
if ( ! app.protected ) {
return false;
}
const actor = Context.get('actor');
const services = this.services;
// If actor is this app itself, allow it
if (
actor.type instanceof AppUnderUserActorType &&
app.uid === actor.type.app.uid
) {
return false;
}
// If actor is owner of this app, allow it
// Compare using owner_user_id from database for accuracy
if (
actor.type instanceof UserActorType &&
owner_user_id &&
owner_user_id === actor.type.user.id
) {
return false;
}
// Now we need to check for permission
const app_uid = app.uid;
const svc_permission = services.get('permission');
const permission_to_check = `app:uid#${app_uid}:access`;
const reading = await svc_permission.scan(actor, permission_to_check);
const options = PermissionUtil.reading_to_options(reading);
// If they have permission, allow it
if ( options.length > 0 ) {
return false;
}
// No access - filter it out
return true;
}
}