diff --git a/adminforth/commands/createApp/templates/index.ts.hbs b/adminforth/commands/createApp/templates/index.ts.hbs
index d612a963a..56215c8db 100644
--- a/adminforth/commands/createApp/templates/index.ts.hbs
+++ b/adminforth/commands/createApp/templates/index.ts.hbs
@@ -26,6 +26,13 @@ export const admin = new AdminForth({
return "Please use adminforth as username and adminforth as password"
}
},
+ websocketTopicAuth: async (topic: string, adminUser: AdminUser) => {
+ if (!adminUser) {
+ // don't allow anonymous users to subscribe
+ return false;
+ }
+ return true;
+ }
},
customization: {
brandName: "{{appName}}",
diff --git a/adminforth/dataConnectors/baseConnector.ts b/adminforth/dataConnectors/baseConnector.ts
index c51b77e6e..8128aadce 100644
--- a/adminforth/dataConnectors/baseConnector.ts
+++ b/adminforth/dataConnectors/baseConnector.ts
@@ -3,13 +3,14 @@ import {
AdminForthResourceColumn,
IAdminForthSort, IAdminForthSingleFilter, IAdminForthAndOrFilter,
AdminForthConfig,
- IAggregationRule, IGroupByRule, IGroupByDateTrunc,
+ IAggregationRule, IGroupByRule, IGroupByDateTrunc
} from "../types/Back.js";
-
+import type { AdminUser } from "../types/Common.js"
import { suggestIfTypo } from "../modules/utils.js";
-import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from "../types/Common.js";
+import { interpretResource } from "../modules/restApi.js";
+import { ActionCheckSource, AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum } from "../types/Common.js";
import { randomUUID } from "crypto";
import dayjs from "dayjs";
import { afLogger } from '../modules/logger.js';
@@ -24,11 +25,32 @@ type AdminForthFilterNormalizationResult = {
};
async function publishShowPageUpdate(resource: AdminForthResource, recordId: string, updates: Record) {
- await global.adminforth.websocket.publish(`/showPage/${resource.resourceId}/${String(recordId)}`, {
- resourceId: resource.resourceId,
- recordId,
- updates,
- });
+ await global.adminforth.websocket.publish(`/showPage/${resource.resourceId}/${String(recordId)}`,
+ {
+ resourceId: resource.resourceId,
+ recordId,
+ updates,
+ },
+ async (adminUser: AdminUser): Promise => {
+ if (!adminUser) {
+ // anonymous clients should never receive record updates
+ return false;
+ }
+ try {
+ const { allowedActions } = await interpretResource(
+ adminUser,
+ resource,
+ { requestBody: null, pk: recordId },
+ ActionCheckSource.ShowRequest,
+ global.adminforth,
+ );
+ return allowedActions[AllowedActionsEnum.show] === true;
+ } catch (e) {
+ afLogger.error(`Error while checking show access for ${resource.resourceId} record ${recordId}, assuming update should not be sent: ${e}`);
+ return false;
+ }
+ }
+ );
}
diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts
index 797ac3b2d..01f611dce 100644
--- a/adminforth/modules/configValidator.ts
+++ b/adminforth/modules/configValidator.ts
@@ -28,6 +28,7 @@ import {
AdminForthResourcePages,
AdminForthDataTypes,
Predicate,
+ AdminUser,
} from "../types/Common.js";
import AdminForth from "adminforth";
import { AdminForthConfigMenuItem } from "adminforth";
@@ -1216,6 +1217,17 @@ export default class ConfigValidator implements IConfigValidator {
}
}
+ if (!newConfig.auth.websocketTopicAuth) {
+ newConfig.auth.websocketTopicAuth = async (topic: string, adminUser: AdminUser) => {
+ afLogger.warn('websocketTopicAuth is not provided. Public access to websocket topics (except /opentopic) is blocked.');
+ if (!adminUser) {
+ // don't allow anonymous users to subscribe
+ return false;
+ }
+ return true;
+ }
+ }
+
newConfig.auth.rateLimit = newConfig.auth.rateLimit || [...DEFAULT_AUTH_RATE_LIMIT];
if (!Array.isArray(newConfig.auth.rateLimit)) {
errors.push(`auth.rateLimit must be an array of strings in format "500/5m"`);