adminizer 5.0.0-build.13 → 5.0.0-build.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/{add-B_hn__Ba.js → add-C97ZlnYa.js} +1 -1
- package/assets/add-group-Ck1ckPjg.js +1 -0
- package/assets/{add-user-Bh1iqSem.js → add-user-DrAae33F.js} +1 -1
- package/assets/ai-assistant/agent.es.js +1 -1
- package/assets/app-Lpo3bWGf.css +1 -0
- package/assets/app.js +2 -2
- package/assets/{catalog-DjK8r0Mn.js → catalog-CBGXyd1t.js} +4 -4
- package/assets/{dashboard-CbJIK7ka.js → dashboard-UMjkspUS.js} +1 -1
- package/assets/history-XWYf-Ejp.js +1 -0
- package/assets/{init-user-8aUohVDq.js → init-user-QihBlzjm.js} +1 -1
- package/assets/{list-C6w_xDK2.js → list-Cge_ZbFX.js} +1 -1
- package/assets/{login-CO4laf6m.js → login-TJqrgvWB.js} +1 -1
- package/assets/manifest.json +33 -33
- package/assets/{media-manager-BDbpSSjH.js → media-manager-CHrLjpDc.js} +3 -3
- package/assets/{module-DvPjKyF_.js → module-DxrI93ll.js} +1 -1
- package/assets/{notification-N_b-N0Zu.js → notification-BGZpCz0c.js} +1 -1
- package/assets/{register-DBYIMXGQ.js → register-C9ZkVhe0.js} +1 -1
- package/assets/{ru-rYKdQbWF.js → ru-3AOtVW4L.js} +1 -1
- package/assets/{user-filters-list-xbJoY_5_.js → user-filters-list-C4G5mYJZ.js} +1 -1
- package/assets/{welcome-BUkdkKa5.js → welcome-Az9UFvA4.js} +1 -1
- package/assets/with-app-layout-CrZPDh-O.js +1 -0
- package/controllers/access-rights/permissionOptions.d.ts +2 -0
- package/controllers/access-rights/permissionOptions.js +23 -0
- package/controllers/add.js +1 -1
- package/controllers/addGroup.js +15 -6
- package/controllers/ai/AiAssistantController.js +4 -3
- package/controllers/edit.js +1 -1
- package/controllers/editGroup.js +15 -6
- package/controllers/filter-fields/savedFilters.js +1 -1
- package/controllers/history-actions/HistoryController.js +6 -6
- package/controllers/list.js +1 -1
- package/controllers/login.js +1 -1
- package/controllers/notifications/NotificationController.js +14 -8
- package/controllers/view.js +1 -1
- package/controllers/widgets/filterInfo.js +1 -1
- package/helpers/accessRightsHelper.d.ts +9 -2
- package/helpers/accessRightsHelper.js +79 -12
- package/helpers/inertiaActionsHelper.d.ts +1 -1
- package/helpers/inertiaActionsHelper.js +4 -5
- package/helpers/inertiaAddHelper.d.ts +1 -1
- package/helpers/inertiaAddHelper.js +3 -3
- package/helpers/inertiaGroupHelper.js +10 -2
- package/helpers/inertiaListHelper.d.ts +1 -1
- package/helpers/inertiaListHelper.js +7 -8
- package/interfaces/types.d.ts +18 -0
- package/lib/app-manager/AdminizerApp.d.ts +7 -2
- package/lib/app-manager/AppManager.js +4 -0
- package/lib/notifications/GeneralNotificationService.js +1 -1
- package/lib/notifications/SystemNotificationService.js +1 -1
- package/models/Group.d.ts +2 -1
- package/package.json +2 -2
- package/policies/authPolicies.js +6 -6
- package/system/Router.js +2 -0
- package/assets/add-group-0SdYgiXZ.js +0 -1
- package/assets/app-DcWwG3m4.css +0 -1
- package/assets/history-CZAJgVXP.js +0 -1
- package/assets/with-app-layout-WhRyO5WK.js +0 -1
|
@@ -9,9 +9,10 @@ export class AiAssistantController {
|
|
|
9
9
|
Adminizer.log.warn('AI assistant handler is not initialized');
|
|
10
10
|
return res.json([]);
|
|
11
11
|
}
|
|
12
|
-
const models = handler
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const models = (await Promise.all(handler.getModels().map(async (model) => ({
|
|
13
|
+
model,
|
|
14
|
+
allowed: await req.adminizer.accessRightsHelper.hasPermission(`ai-assistant-${model.id}`, req.user),
|
|
15
|
+
})))).filter(({ allowed }) => allowed).map(({ model }) => model);
|
|
15
16
|
return res.json(models);
|
|
16
17
|
}
|
|
17
18
|
}
|
package/controllers/edit.js
CHANGED
|
@@ -134,7 +134,7 @@ export default async function edit(req, res) {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
const props = inertiaAddHelper(req, modelResource, fields, record);
|
|
137
|
+
const props = await inertiaAddHelper(req, modelResource, fields, record);
|
|
138
138
|
if (req.query?.without_layout) {
|
|
139
139
|
return res.json({
|
|
140
140
|
props: props
|
package/controllers/editGroup.js
CHANGED
|
@@ -44,13 +44,22 @@ export default async function editGroup(req, res) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
}
|
|
48
|
+
for (const token of allTokens) {
|
|
49
|
+
if (req.body[`token-checkbox-${token.id}`] !== true)
|
|
50
|
+
continue;
|
|
51
|
+
if (!token.getOptions) {
|
|
52
|
+
tokensOfThisGroup.push(token.id);
|
|
53
|
+
continue;
|
|
53
54
|
}
|
|
55
|
+
const submittedRights = req.body[`token-record-rights-${token.id}`];
|
|
56
|
+
const rights = Array.isArray(submittedRights)
|
|
57
|
+
? Array.from(new Set(submittedRights.map(String)))
|
|
58
|
+
: [];
|
|
59
|
+
tokensOfThisGroup.push({
|
|
60
|
+
tokenId: token.id,
|
|
61
|
+
rights,
|
|
62
|
+
});
|
|
54
63
|
}
|
|
55
64
|
let updatedGroup;
|
|
56
65
|
try {
|
|
@@ -125,7 +125,7 @@ export async function saveFilter(req, res) {
|
|
|
125
125
|
}
|
|
126
126
|
// Determine visibility and groupIds
|
|
127
127
|
const isAdmin = req.user?.isAdministrator === true;
|
|
128
|
-
const canManageGroupVisibility = isAdmin || req.adminizer.accessRightsHelper.hasPermission(GROUP_FILTER_VISIBILITY_TOKEN, req.user);
|
|
128
|
+
const canManageGroupVisibility = isAdmin || await req.adminizer.accessRightsHelper.hasPermission(GROUP_FILTER_VISIBILITY_TOKEN, req.user);
|
|
129
129
|
let effectiveVisibility = 'private';
|
|
130
130
|
let effectiveGroupIds;
|
|
131
131
|
if (visibility === 'public' && isAdmin) {
|
|
@@ -3,13 +3,13 @@ import { getUiTranslations } from "../../lib/ui-i18n/getUiTranslations.js";
|
|
|
3
3
|
import { HISTORY_UI_TRANSLATION_KEYS } from "../../lib/ui-i18n/uiTranslationKeys.js";
|
|
4
4
|
export class HistoryController {
|
|
5
5
|
static async index(req, res) {
|
|
6
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
6
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
7
7
|
return;
|
|
8
8
|
const adapter = HistoryController.getAdapter(req);
|
|
9
9
|
if (req.method.toUpperCase() === 'GET') {
|
|
10
10
|
const rawModels = adapter.getModels(req.user);
|
|
11
11
|
let users = [];
|
|
12
|
-
const accessToUsersHistory = req.adminizer.accessRightsHelper.enoughPermissions([
|
|
12
|
+
const accessToUsersHistory = await req.adminizer.accessRightsHelper.enoughPermissions([
|
|
13
13
|
`users-history-${adapter.id}`
|
|
14
14
|
], req.user);
|
|
15
15
|
if (accessToUsersHistory)
|
|
@@ -59,7 +59,7 @@ export class HistoryController {
|
|
|
59
59
|
return res.status(405).end();
|
|
60
60
|
}
|
|
61
61
|
static async getAllModelHistory(req, res) {
|
|
62
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
62
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
63
63
|
return;
|
|
64
64
|
const { modelId, modelName } = req.body;
|
|
65
65
|
if (!modelId || !modelName) {
|
|
@@ -80,7 +80,7 @@ export class HistoryController {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
static async getModelFieldsHistory(req, res) {
|
|
83
|
-
if (!HistoryController.checkHistoryPermission(req, res))
|
|
83
|
+
if (!await HistoryController.checkHistoryPermission(req, res))
|
|
84
84
|
return;
|
|
85
85
|
const { historyId } = req.body;
|
|
86
86
|
if (!historyId) {
|
|
@@ -103,12 +103,12 @@ export class HistoryController {
|
|
|
103
103
|
const adapter = req.adminizer.config.history?.adapter ?? 'default';
|
|
104
104
|
return req.adminizer.historyHandler.get(adapter);
|
|
105
105
|
}
|
|
106
|
-
static checkHistoryPermission(req, res) {
|
|
106
|
+
static async checkHistoryPermission(req, res) {
|
|
107
107
|
if (!req.adminizer?.historyHandler) {
|
|
108
108
|
res.status(401).json({ error: 'History system not initialized' });
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
111
|
-
const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`history-${req.adminizer.config.history?.adapter ?? 'default'}`, req.user);
|
|
111
|
+
const hasPermission = await req.adminizer.accessRightsHelper.hasPermission(`history-${req.adminizer.config.history?.adapter ?? 'default'}`, req.user);
|
|
112
112
|
if (!hasPermission) {
|
|
113
113
|
res.sendStatus(403);
|
|
114
114
|
return false;
|
package/controllers/list.js
CHANGED
|
@@ -14,7 +14,7 @@ export default async function list(req, res) {
|
|
|
14
14
|
}
|
|
15
15
|
let dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "list");
|
|
16
16
|
let fields = dataAccessor.getFieldsConfig();
|
|
17
|
-
const header = inertiaListHelper(modelResource, req, fields);
|
|
17
|
+
const header = await inertiaListHelper(modelResource, req, fields);
|
|
18
18
|
const i18nPage = getUiTranslations(req, FILTER_UI_TRANSLATION_KEYS);
|
|
19
19
|
// Parse pagination
|
|
20
20
|
const defaultPageSize = req.adminizer.config.list?.defaultPageSize ?? 50;
|
package/controllers/login.js
CHANGED
|
@@ -61,7 +61,7 @@ export default async function login(req, res) {
|
|
|
61
61
|
//Here we use the captchaSolution key to output messages unrelated to the form fields.
|
|
62
62
|
return inertiaAdminMessage(req, "Profile expired, contact the administrator", 'captchaSolution');
|
|
63
63
|
}
|
|
64
|
-
if (!req.adminizer.accessRightsHelper.hasPermission('access-to-adminpanel', user)) {
|
|
64
|
+
if (!await req.adminizer.accessRightsHelper.hasPermission('access-to-adminpanel', user)) {
|
|
65
65
|
return inertiaAdminMessage(req, "The user is not allowed to enter, please contact the administrator", 'captchaSolution');
|
|
66
66
|
}
|
|
67
67
|
const token = signUser({
|
|
@@ -7,7 +7,7 @@ export class NotificationController {
|
|
|
7
7
|
return;
|
|
8
8
|
if (req.method.toUpperCase() === 'POST') {
|
|
9
9
|
const { s, notificationClass } = req.body;
|
|
10
|
-
const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
10
|
+
const hasPermission = await req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
11
11
|
if (!hasPermission) {
|
|
12
12
|
res.status(403).json({ error: 'Forbidden' });
|
|
13
13
|
return;
|
|
@@ -42,7 +42,7 @@ export class NotificationController {
|
|
|
42
42
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
43
43
|
let activeServices = [];
|
|
44
44
|
for (const service of services) {
|
|
45
|
-
const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user);
|
|
45
|
+
const hasPermission = await req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user);
|
|
46
46
|
if (hasPermission) {
|
|
47
47
|
activeServices.push({
|
|
48
48
|
displayName: req.i18n.__(service.displayName),
|
|
@@ -68,12 +68,12 @@ export class NotificationController {
|
|
|
68
68
|
res.write(': stream-open\n\n');
|
|
69
69
|
const clientId = `user-${req.user?.id}-${Date.now()}`;
|
|
70
70
|
// Function for sending events to the client
|
|
71
|
-
const sendEvent = (event) => {
|
|
71
|
+
const sendEvent = async (event) => {
|
|
72
72
|
// Filtering notifications by user rights
|
|
73
73
|
if (event.type === 'notification') {
|
|
74
74
|
const notificationClass = event.notificationClass;
|
|
75
75
|
// UNIFIED rights check via AccessRightsHelper
|
|
76
|
-
const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
76
|
+
const hasPermission = await req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
77
77
|
if (!hasPermission) {
|
|
78
78
|
return; // The user does not have rights to this notification class
|
|
79
79
|
}
|
|
@@ -88,7 +88,10 @@ export class NotificationController {
|
|
|
88
88
|
};
|
|
89
89
|
// We connect the client to all services
|
|
90
90
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
91
|
-
const allowedServices = services.
|
|
91
|
+
const allowedServices = (await Promise.all(services.map(async (service) => ({
|
|
92
|
+
service,
|
|
93
|
+
allowed: await req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user),
|
|
94
|
+
})))).filter(({ allowed }) => allowed).map(({ service }) => service);
|
|
92
95
|
allowedServices.forEach(service => {
|
|
93
96
|
service.addClient(clientId, sendEvent, req.user);
|
|
94
97
|
// For a system service, add a client to CRUD channels
|
|
@@ -100,7 +103,7 @@ export class NotificationController {
|
|
|
100
103
|
// });
|
|
101
104
|
// }
|
|
102
105
|
});
|
|
103
|
-
sendEvent({
|
|
106
|
+
await sendEvent({
|
|
104
107
|
type: 'connected',
|
|
105
108
|
data: {
|
|
106
109
|
message: 'Connected to unified notification stream',
|
|
@@ -144,7 +147,7 @@ export class NotificationController {
|
|
|
144
147
|
const { notificationClass } = req.params;
|
|
145
148
|
const { limit = 20, skip = 0, unreadOnly = false } = req.query;
|
|
146
149
|
// Checking access rights
|
|
147
|
-
const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
150
|
+
const hasPermission = await req.adminizer.accessRightsHelper.hasPermission(`notification-${notificationClass}`, req.user);
|
|
148
151
|
if (!hasPermission) {
|
|
149
152
|
res.status(403).json({ error: 'Forbidden' });
|
|
150
153
|
return;
|
|
@@ -170,7 +173,10 @@ export class NotificationController {
|
|
|
170
173
|
try {
|
|
171
174
|
// Filtering services by access rights
|
|
172
175
|
const services = req.adminizer.notificationHandler.getAllServices();
|
|
173
|
-
const allowedServices = services.
|
|
176
|
+
const allowedServices = (await Promise.all(services.map(async (service) => ({
|
|
177
|
+
service,
|
|
178
|
+
allowed: await req.adminizer.accessRightsHelper.hasPermission(`notification-${service.notificationClass}`, req.user),
|
|
179
|
+
})))).filter(({ allowed }) => allowed).map(({ service }) => service);
|
|
174
180
|
const allNotifications = [];
|
|
175
181
|
for (const service of allowedServices) {
|
|
176
182
|
const notifications = await service.getNotifications(req.user.id, Number(limit), Number(skip), unreadOnly === 'true');
|
package/controllers/view.js
CHANGED
|
@@ -83,7 +83,7 @@ export default async function view(req, res) {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
const props = inertiaAddHelper(req, modelResource, fields, record, true);
|
|
86
|
+
const props = await inertiaAddHelper(req, modelResource, fields, record, true);
|
|
87
87
|
return req.Inertia.render({
|
|
88
88
|
component: 'add',
|
|
89
89
|
props: props
|
|
@@ -28,7 +28,7 @@ export async function widgetFilterInfoController(req, res) {
|
|
|
28
28
|
if (!filter || !filterService.canViewFilter(filter, user)) {
|
|
29
29
|
return res.status(404).send({ error: req.i18n.__("Not found") });
|
|
30
30
|
}
|
|
31
|
-
if (!req.adminizer.accessRightsHelper.hasPermission(`read-${filter.modelName}-model`, user)) {
|
|
31
|
+
if (!await req.adminizer.accessRightsHelper.hasPermission(`read-${filter.modelName}-model`, user)) {
|
|
32
32
|
return res.sendStatus(403);
|
|
33
33
|
}
|
|
34
34
|
if (req.method.toUpperCase() === "GET") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { User } from "../models/User.js";
|
|
2
|
-
import { AccessRightsToken } from "../interfaces/types.js";
|
|
2
|
+
import { AccessRightsToken, GroupPermissionGrant, PermissionContext } from "../interfaces/types.js";
|
|
3
3
|
import { Adminizer } from "../lib/Adminizer.js";
|
|
4
|
+
export declare function parseGroupPermissionGrant(value: unknown): GroupPermissionGrant | null;
|
|
4
5
|
export declare class AccessRightsHelper {
|
|
5
6
|
private _tokens;
|
|
6
7
|
adminizer: Adminizer;
|
|
@@ -8,13 +9,19 @@ export declare class AccessRightsHelper {
|
|
|
8
9
|
registerToken(accessRightsToken: AccessRightsToken): void;
|
|
9
10
|
unregisterToken(tokenId: string): boolean;
|
|
10
11
|
hasToken(tokenId: string): boolean;
|
|
12
|
+
getToken(tokenId: string): AccessRightsToken | undefined;
|
|
11
13
|
registerTokens(accessRightsTokens: AccessRightsToken[]): void;
|
|
12
14
|
registerModelTokens(modelName: string, department?: string): void;
|
|
13
15
|
getTokens(): AccessRightsToken[];
|
|
14
16
|
getTokensByDepartment(department: string): AccessRightsToken[];
|
|
15
17
|
getAllDepartments(): string[];
|
|
16
18
|
enoughPermissions(tokens: string[], user: User): boolean;
|
|
17
|
-
hasPermission(tokenId: string | undefined, user: User, context?:
|
|
19
|
+
hasPermission(tokenId: string | undefined, user: User, context?: PermissionContext): boolean | Promise<boolean>;
|
|
20
|
+
/** Returns trusted option IDs granted by all groups for a contextual token. */
|
|
21
|
+
getPermissionRights(tokenId: string | undefined, user: User): string[] | null;
|
|
22
|
+
private getRegisteredToken;
|
|
23
|
+
private hasAssignedPermission;
|
|
24
|
+
private runTokenCheck;
|
|
18
25
|
}
|
|
19
26
|
export declare class GroupsAccessRightsHelper {
|
|
20
27
|
static hasAccess(user: User, groupsAccessRights?: string[]): boolean;
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { Adminizer } from "../lib/Adminizer.js";
|
|
2
|
+
export function parseGroupPermissionGrant(value) {
|
|
3
|
+
if (!value ||
|
|
4
|
+
typeof value !== "object" ||
|
|
5
|
+
typeof value.tokenId !== "string" ||
|
|
6
|
+
!Array.isArray(value.rights) ||
|
|
7
|
+
!value.rights.every((right) => typeof right === "string" || typeof right === "number")) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const parsed = value;
|
|
11
|
+
return {
|
|
12
|
+
tokenId: parsed.tokenId.toLowerCase(),
|
|
13
|
+
rights: Array.from(new Set(parsed.rights.map(String))),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
2
16
|
export class AccessRightsHelper {
|
|
3
17
|
_tokens = [];
|
|
4
18
|
adminizer;
|
|
@@ -10,6 +24,12 @@ export class AccessRightsHelper {
|
|
|
10
24
|
if (!accessRightsToken.id || !accessRightsToken.name || !accessRightsToken.description || !accessRightsToken.department) {
|
|
11
25
|
throw new Error("Adminpanel > Can not register token: Missed one or more required parameters");
|
|
12
26
|
}
|
|
27
|
+
if (accessRightsToken.getOptions && typeof accessRightsToken.getOptions !== "function") {
|
|
28
|
+
throw new Error("Adminpanel > Can not register token: getOptions must be a function");
|
|
29
|
+
}
|
|
30
|
+
if (accessRightsToken.check && typeof accessRightsToken.check !== "function") {
|
|
31
|
+
throw new Error("Adminpanel > Can not register token: check must be a function");
|
|
32
|
+
}
|
|
13
33
|
for (let i = 0; i < this._tokens.length; i++) {
|
|
14
34
|
if (this._tokens[i].id === accessRightsToken.id) {
|
|
15
35
|
this._tokens.splice(i, 1);
|
|
@@ -31,6 +51,10 @@ export class AccessRightsHelper {
|
|
|
31
51
|
const normalizedTokenId = tokenId.toLowerCase();
|
|
32
52
|
return this._tokens.some((token) => token.id === normalizedTokenId);
|
|
33
53
|
}
|
|
54
|
+
getToken(tokenId) {
|
|
55
|
+
const normalizedTokenId = tokenId.toLowerCase();
|
|
56
|
+
return this._tokens.find((token) => token.id === normalizedTokenId);
|
|
57
|
+
}
|
|
34
58
|
registerTokens(accessRightsTokens) {
|
|
35
59
|
for (let token of accessRightsTokens) {
|
|
36
60
|
this.registerToken(token);
|
|
@@ -62,30 +86,73 @@ export class AccessRightsHelper {
|
|
|
62
86
|
if (!tokens.length) {
|
|
63
87
|
return true;
|
|
64
88
|
}
|
|
65
|
-
return tokens.some((token) => this.hasPermission(token, user));
|
|
89
|
+
return tokens.some((token) => Boolean(this.hasPermission(token, user)));
|
|
66
90
|
}
|
|
67
91
|
hasPermission(tokenId, user, context) {
|
|
68
|
-
|
|
92
|
+
const token = this.getRegisteredToken(tokenId);
|
|
93
|
+
if (!token) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (!this.hasAssignedPermission(token.id, user)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (!token.check) {
|
|
69
100
|
return true;
|
|
70
101
|
}
|
|
102
|
+
return this.runTokenCheck(token, user, context ?? {});
|
|
103
|
+
}
|
|
104
|
+
/** Returns trusted option IDs granted by all groups for a contextual token. */
|
|
105
|
+
getPermissionRights(tokenId, user) {
|
|
106
|
+
if (!this.adminizer.config.auth.enable) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
71
109
|
if (user.isAdministrator) {
|
|
72
|
-
return
|
|
110
|
+
return null;
|
|
73
111
|
}
|
|
112
|
+
const token = this.getRegisteredToken(tokenId);
|
|
113
|
+
if (!token) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
if (!user.groups) {
|
|
117
|
+
Adminizer.log.error('User has no groups');
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const rights = user.groups.flatMap((group) => group.tokens ?? [])
|
|
121
|
+
.map(parseGroupPermissionGrant)
|
|
122
|
+
.filter((grant) => grant?.tokenId === token.id)
|
|
123
|
+
.flatMap((grant) => grant.rights);
|
|
124
|
+
return Array.from(new Set(rights));
|
|
125
|
+
}
|
|
126
|
+
getRegisteredToken(tokenId) {
|
|
74
127
|
if (!tokenId) {
|
|
75
|
-
Adminizer.log.warn(
|
|
76
|
-
return
|
|
128
|
+
Adminizer.log.warn("AccessRightsHelper > hasPermission: missing accessRightsToken");
|
|
129
|
+
return undefined;
|
|
77
130
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
131
|
+
const token = this.getToken(tokenId);
|
|
132
|
+
if (!token) {
|
|
133
|
+
Adminizer.log.error("Adminpanel > Token is not valid", tokenId);
|
|
134
|
+
}
|
|
135
|
+
return token;
|
|
136
|
+
}
|
|
137
|
+
hasAssignedPermission(tokenId, user) {
|
|
138
|
+
if (!this.adminizer.config.auth.enable || user.isAdministrator) {
|
|
139
|
+
return true;
|
|
83
140
|
}
|
|
84
141
|
if (!user.groups) {
|
|
85
|
-
Adminizer.log.error(
|
|
142
|
+
Adminizer.log.error("User has no groups");
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return user.groups.some((group) => group.tokens?.some((groupToken) => groupToken === tokenId || parseGroupPermissionGrant(groupToken)?.tokenId === tokenId));
|
|
146
|
+
}
|
|
147
|
+
async runTokenCheck(token, user, context) {
|
|
148
|
+
try {
|
|
149
|
+
const rights = this.getPermissionRights(token.id, user) ?? [];
|
|
150
|
+
return await token.check(user, { ...context, rights });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
Adminizer.log.error("AccessRightsHelper > token check failed", token.id, error);
|
|
86
154
|
return false;
|
|
87
155
|
}
|
|
88
|
-
return user.groups.some((group) => group.tokens?.includes(tokenId));
|
|
89
156
|
}
|
|
90
157
|
}
|
|
91
158
|
export class GroupsAccessRightsHelper {
|
|
@@ -7,4 +7,4 @@ export interface Actions {
|
|
|
7
7
|
title: string;
|
|
8
8
|
icon: string;
|
|
9
9
|
}
|
|
10
|
-
export default function inertiaActionsHelper(actionType: ActionType, modelResource: ModelResource, req: ReqType): Actions[]
|
|
10
|
+
export default function inertiaActionsHelper(actionType: ActionType, modelResource: ModelResource, req: ReqType): Promise<Actions[]>;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
export default function inertiaActionsHelper(actionType, modelResource, req) {
|
|
1
|
+
export default async function inertiaActionsHelper(actionType, modelResource, req) {
|
|
2
2
|
let resActions = [];
|
|
3
3
|
if (req.adminizer.menuHelper.hasGlobalActions(modelResource.config, actionType)) {
|
|
4
4
|
const actions = req.adminizer.menuHelper.getGlobalActions(modelResource.config, actionType);
|
|
5
5
|
if (actions && actions.length > 0) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (req.adminizer.accessRightsHelper.hasPermission(action.accessRightsToken, req.user, context)) {
|
|
6
|
+
for (const action of actions) {
|
|
7
|
+
if (await req.adminizer.accessRightsHelper.hasPermission(action.accessRightsToken, req.user)) {
|
|
9
8
|
resActions.push({
|
|
10
9
|
link: action.link,
|
|
11
10
|
id: action.id,
|
|
@@ -14,7 +13,7 @@ export default function inertiaActionsHelper(actionType, modelResource, req) {
|
|
|
14
13
|
icon: action.icon
|
|
15
14
|
});
|
|
16
15
|
}
|
|
17
|
-
}
|
|
16
|
+
}
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
return resActions;
|
|
@@ -23,7 +23,7 @@ interface FieldProps extends Record<string | number | symbol, unknown> {
|
|
|
23
23
|
postLink: string;
|
|
24
24
|
model: string;
|
|
25
25
|
}
|
|
26
|
-
export default function inertiaAddHelper(req: ReqType, modelResource: ModelResource, fields: Fields, record?: Record<string, string | boolean | number | string[]>, view?: boolean): FieldProps
|
|
26
|
+
export default function inertiaAddHelper(req: ReqType, modelResource: ModelResource, fields: Fields, record?: Record<string, string | boolean | number | string[]>, view?: boolean): Promise<FieldProps>;
|
|
27
27
|
export declare function getControlsOptions(fieldConfig: Field["config"], req: ReqType, type: ControlType, defaultControlName: string, context?: string): {
|
|
28
28
|
name: string;
|
|
29
29
|
config: any;
|
|
@@ -14,7 +14,7 @@ function getWeekNumber(date) {
|
|
|
14
14
|
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
15
15
|
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
|
16
16
|
}
|
|
17
|
-
export default function inertiaAddHelper(req, modelResource, fields, record, view = false) {
|
|
17
|
+
export default async function inertiaAddHelper(req, modelResource, fields, record, view = false) {
|
|
18
18
|
const actionType = 'add';
|
|
19
19
|
let props = {
|
|
20
20
|
edit: !!record,
|
|
@@ -36,7 +36,7 @@ export default function inertiaAddHelper(req, modelResource, fields, record, vie
|
|
|
36
36
|
postLink: record ? `${modelResource.uri}/edit/${record.id}` : `${modelResource.uri}/add`,
|
|
37
37
|
model: modelResource.name.toLocaleLowerCase()
|
|
38
38
|
};
|
|
39
|
-
props.actions = inertiaActionsHelper(actionType, modelResource, req);
|
|
39
|
+
props.actions = await inertiaActionsHelper(actionType, modelResource, req);
|
|
40
40
|
const config = req.adminizer.configHelper.getConfig();
|
|
41
41
|
for (const key of Object.keys(fields)) {
|
|
42
42
|
if ((!config.showORMtime) && (key === 'createdAt' || key === 'updatedAt'))
|
|
@@ -118,7 +118,7 @@ export default function inertiaAddHelper(req, modelResource, fields, record, vie
|
|
|
118
118
|
? req.adminizer.modelHandler.resolveModelName(rawRelatedModel)
|
|
119
119
|
: undefined;
|
|
120
120
|
if (relatedModel && req.user) {
|
|
121
|
-
canCreateRelated = req.adminizer.accessRightsHelper.hasPermission(`create-${relatedModel}-model`, req.user);
|
|
121
|
+
canCreateRelated = await req.adminizer.accessRightsHelper.hasPermission(`create-${relatedModel}-model`, req.user);
|
|
122
122
|
}
|
|
123
123
|
else if (relatedModel && !req.adminizer.config.auth?.enable) {
|
|
124
124
|
canCreateRelated = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseGroupPermissionGrant } from "./accessRightsHelper.js";
|
|
1
2
|
export function inertiaGroupHelper(modelResource, req, users, groupedTokens, group, view = false) {
|
|
2
3
|
let props = {
|
|
3
4
|
edit: !!group,
|
|
@@ -55,8 +56,15 @@ export function inertiaGroupHelper(modelResource, req, users, groupedTokens, gro
|
|
|
55
56
|
label: token.name,
|
|
56
57
|
tooltip: token.description,
|
|
57
58
|
name: `token-checkbox-${token.id}`,
|
|
58
|
-
value: group ? (group.tokens
|
|
59
|
-
type: 'checkbox'
|
|
59
|
+
value: group ? Boolean(group.tokens?.some((value) => value === token.id || parseGroupPermissionGrant(value)?.tokenId === token.id)) : false,
|
|
60
|
+
type: 'checkbox',
|
|
61
|
+
options: token.getOptions ? {
|
|
62
|
+
permissionOptions: {
|
|
63
|
+
rights: group?.tokens
|
|
64
|
+
?.map(parseGroupPermissionGrant)
|
|
65
|
+
.find((value) => value?.tokenId === token.id)?.rights ?? [],
|
|
66
|
+
},
|
|
67
|
+
} : undefined,
|
|
60
68
|
});
|
|
61
69
|
}
|
|
62
70
|
props.groupedTokens.push({
|
|
@@ -28,5 +28,5 @@ interface listProps extends Record<string | number | symbol, unknown> {
|
|
|
28
28
|
defaultPageSize: number;
|
|
29
29
|
pageSizeOptions: number[];
|
|
30
30
|
}
|
|
31
|
-
export declare function inertiaListHelper(modelResource: ModelResource, req: ReqType, fields: Fields, activeFilterName?: string): listProps
|
|
31
|
+
export declare function inertiaListHelper(modelResource: ModelResource, req: ReqType, fields: Fields, activeFilterName?: string): Promise<listProps>;
|
|
32
32
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import inertiaActionsHelper from "./inertiaActionsHelper.js";
|
|
2
|
-
export function inertiaListHelper(modelResource, req, fields, activeFilterName) {
|
|
2
|
+
export async function inertiaListHelper(modelResource, req, fields, activeFilterName) {
|
|
3
3
|
const actionType = 'list';
|
|
4
4
|
// Check if filters are enabled for this model
|
|
5
5
|
// Preferred: model-level `models.<Model>.filters`; fallback: legacy top-level `modelFilters.<Model>`
|
|
@@ -31,23 +31,22 @@ export function inertiaListHelper(modelResource, req, fields, activeFilterName)
|
|
|
31
31
|
defaultPageSize: req.adminizer.config.list?.defaultPageSize ?? 50,
|
|
32
32
|
pageSizeOptions: [5, 20, 50],
|
|
33
33
|
};
|
|
34
|
-
if (modelResource.config.add && req.adminizer.accessRightsHelper.hasPermission(`create-${modelResource.name}-model`, req.user
|
|
34
|
+
if (modelResource.config.add && await req.adminizer.accessRightsHelper.hasPermission(`create-${modelResource.name}-model`, req.user)) {
|
|
35
35
|
props.crudActions.createTitle = req.i18n.__('create');
|
|
36
36
|
}
|
|
37
|
-
if (modelResource.config.edit && req.adminizer.accessRightsHelper.hasPermission(`update-${modelResource.name}-model`, req.user
|
|
37
|
+
if (modelResource.config.edit && await req.adminizer.accessRightsHelper.hasPermission(`update-${modelResource.name}-model`, req.user)) {
|
|
38
38
|
props.crudActions.editTitle = req.i18n.__('Edit');
|
|
39
39
|
}
|
|
40
|
-
if (modelResource.config.view && req.adminizer.accessRightsHelper.hasPermission(`read-${modelResource.name}-model`, req.user
|
|
40
|
+
if (modelResource.config.view && await req.adminizer.accessRightsHelper.hasPermission(`read-${modelResource.name}-model`, req.user)) {
|
|
41
41
|
props.crudActions.viewsTitle = req.i18n.__('View');
|
|
42
42
|
}
|
|
43
|
-
if (modelResource.config.remove && req.adminizer.accessRightsHelper.hasPermission(`delete-${modelResource.name}-model`, req.user
|
|
43
|
+
if (modelResource.config.remove && await req.adminizer.accessRightsHelper.hasPermission(`delete-${modelResource.name}-model`, req.user)) {
|
|
44
44
|
props.crudActions.deleteTitle = req.i18n.__('Delete');
|
|
45
45
|
}
|
|
46
|
-
props.actions = inertiaActionsHelper(actionType, modelResource, req);
|
|
46
|
+
props.actions = await inertiaActionsHelper(actionType, modelResource, req);
|
|
47
47
|
if (req.adminizer.menuHelper.hasInlineActions(modelResource.config, 'list')) {
|
|
48
48
|
for (const inlineAction of req.adminizer.menuHelper.getInlineActions(modelResource.config, 'list')) {
|
|
49
|
-
|
|
50
|
-
if (req.adminizer.accessRightsHelper.hasPermission(inlineAction.accessRightsToken, req.user, context)) {
|
|
49
|
+
if (await req.adminizer.accessRightsHelper.hasPermission(inlineAction.accessRightsToken, req.user)) {
|
|
51
50
|
props.inlineActions.push({
|
|
52
51
|
icon: inlineAction.icon,
|
|
53
52
|
id: inlineAction.id,
|
package/interfaces/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractModel } from "../lib/model/AbstractModel.js";
|
|
2
2
|
import { ModelConfig } from "./adminpanelConfig.js";
|
|
3
|
+
import type { User } from "../models/User.js";
|
|
3
4
|
export interface ModelResource {
|
|
4
5
|
name: string;
|
|
5
6
|
config?: ModelConfig;
|
|
@@ -11,7 +12,24 @@ export interface AccessRightsToken {
|
|
|
11
12
|
description: string;
|
|
12
13
|
department: string;
|
|
13
14
|
id: string;
|
|
15
|
+
getOptions?: (user: User) => Promise<PermissionOption[]>;
|
|
16
|
+
check?: (user: User, context?: PermissionContext) => Promise<boolean>;
|
|
14
17
|
}
|
|
18
|
+
export interface PermissionOption {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PermissionContext {
|
|
24
|
+
rights?: string[];
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
/** Serialized as a JSON string in Group.tokens for a contextual token. */
|
|
28
|
+
export interface GroupPermissionGrant {
|
|
29
|
+
tokenId: string;
|
|
30
|
+
rights: string[];
|
|
31
|
+
}
|
|
32
|
+
export type PermissionGrant = string | GroupPermissionGrant;
|
|
15
33
|
export interface PropsField {
|
|
16
34
|
label: string;
|
|
17
35
|
type: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActionType, AdminpanelConfig, AdminpanelIcon, ModelConfig } from "../../interfaces/adminpanelConfig.js";
|
|
2
|
-
import type { AccessRightsToken, INotification, ModelResource } from "../../interfaces/types.js";
|
|
2
|
+
import type { AccessRightsToken, INotification, ModelResource, PermissionContext } from "../../interfaces/types.js";
|
|
3
3
|
import type { AbstractCatalog } from "../catalog/AbstractCatalog.js";
|
|
4
4
|
import type { CatalogTemplateComponentResource } from "../catalog/CatalogTemplateComponentHandler.js";
|
|
5
5
|
import type { AppModelAccess } from "../model/ModelHandler.js";
|
|
@@ -130,9 +130,14 @@ export type AppWidgetResource = AppInfoWidgetResource | AppActionWidgetResource
|
|
|
130
130
|
export interface AppRuntime {
|
|
131
131
|
models: AppModelAccess;
|
|
132
132
|
config: AppRuntimeConfig;
|
|
133
|
+
accessRights: AppRuntimeAccessRights;
|
|
133
134
|
notifications: AppRuntimeNotifications;
|
|
134
135
|
apps: AppRuntimeApps;
|
|
135
136
|
}
|
|
137
|
+
export interface AppRuntimeAccessRights {
|
|
138
|
+
hasPermission(token: string, user: User, context?: PermissionContext): Promise<boolean>;
|
|
139
|
+
getPermissionRights(token: string, user: User): string[] | null;
|
|
140
|
+
}
|
|
136
141
|
export interface AppRuntimeConfig {
|
|
137
142
|
readonly routePrefix: string;
|
|
138
143
|
getModelConfig(modelName: string): ModelConfig | undefined;
|
|
@@ -174,7 +179,7 @@ export interface AppAiAssistantContext {
|
|
|
174
179
|
routePrefix: string;
|
|
175
180
|
getModelResources(): ModelResource[];
|
|
176
181
|
resolveModelResource(modelName: string): ModelResource | undefined;
|
|
177
|
-
hasPermission(token: string, user: User): boolean
|
|
182
|
+
hasPermission(token: string, user: User): boolean | Promise<boolean>;
|
|
178
183
|
createDataAccessor(modelResource: ModelResource, user: User, action: ActionType): DataAccessor;
|
|
179
184
|
/** UI tools available to this user, including methods registered by apps. */
|
|
180
185
|
getUiMethods(user: User): AiAssistantUiMethod[];
|
|
@@ -505,6 +505,10 @@ export class AppManager {
|
|
|
505
505
|
return modelKey ? models[modelKey] : undefined;
|
|
506
506
|
},
|
|
507
507
|
},
|
|
508
|
+
accessRights: {
|
|
509
|
+
hasPermission: async (token, user, context) => await this.adminizer.accessRightsHelper.hasPermission(token, user, context),
|
|
510
|
+
getPermissionRights: (token, user) => this.adminizer.accessRightsHelper.getPermissionRights(token, user),
|
|
511
|
+
},
|
|
508
512
|
notifications: {
|
|
509
513
|
send: (notification) => this.adminizer.sendNotification(notification),
|
|
510
514
|
},
|
|
@@ -23,7 +23,7 @@ export class GeneralNotificationService extends AbstractNotificationService {
|
|
|
23
23
|
const users = await this.userModel().find({});
|
|
24
24
|
for (const user of users) {
|
|
25
25
|
try {
|
|
26
|
-
if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
26
|
+
if (await this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
27
27
|
await this.createUserNotification(notificationDB.id, user.id);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
@@ -19,7 +19,7 @@ export class SystemNotificationService extends AbstractNotificationService {
|
|
|
19
19
|
const users = await this.userModel().find({});
|
|
20
20
|
for (const user of users) {
|
|
21
21
|
try {
|
|
22
|
-
if (this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
22
|
+
if (await this.adminizer.accessRightsHelper.hasPermission(`notification-${this.notificationClass}`, user)) {
|
|
23
23
|
await this.createUserNotification(notificationDB.id, user.id);
|
|
24
24
|
}
|
|
25
25
|
}
|
package/models/Group.d.ts
CHANGED