@intranefr/superbackend 1.5.0 → 1.5.1
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/.env.example +5 -0
- package/README.md +11 -0
- package/index.js +23 -0
- package/package.json +7 -2
- package/src/admin/endpointRegistry.js +120 -0
- package/src/controllers/admin.controller.js +22 -5
- package/src/controllers/adminBlockDefinitions.controller.js +127 -0
- package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
- package/src/controllers/adminCache.controller.js +342 -0
- package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
- package/src/controllers/adminCrons.controller.js +388 -0
- package/src/controllers/adminDbBrowser.controller.js +124 -0
- package/src/controllers/adminEjsVirtual.controller.js +13 -3
- package/src/controllers/adminHeadless.controller.js +9 -2
- package/src/controllers/adminHealthChecks.controller.js +570 -0
- package/src/controllers/adminI18n.controller.js +51 -29
- package/src/controllers/adminLlm.controller.js +126 -2
- package/src/controllers/adminPages.controller.js +720 -0
- package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
- package/src/controllers/adminProxy.controller.js +113 -0
- package/src/controllers/adminRateLimits.controller.js +138 -0
- package/src/controllers/adminRbac.controller.js +803 -0
- package/src/controllers/adminScripts.controller.js +93 -2
- package/src/controllers/adminSeoConfig.controller.js +71 -48
- package/src/controllers/blogAdmin.controller.js +279 -0
- package/src/controllers/blogAiAdmin.controller.js +224 -0
- package/src/controllers/blogAutomationAdmin.controller.js +141 -0
- package/src/controllers/blogInternal.controller.js +26 -0
- package/src/controllers/blogPublic.controller.js +89 -0
- package/src/controllers/fileManager.controller.js +190 -0
- package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
- package/src/controllers/healthChecksPublic.controller.js +196 -0
- package/src/controllers/metrics.controller.js +64 -4
- package/src/controllers/orgAdmin.controller.js +80 -0
- package/src/middleware/internalCronAuth.js +29 -0
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +756 -48
- package/src/models/BlockDefinition.js +27 -0
- package/src/models/BlogAutomationLock.js +14 -0
- package/src/models/BlogAutomationRun.js +39 -0
- package/src/models/BlogPost.js +42 -0
- package/src/models/CacheEntry.js +26 -0
- package/src/models/ConsoleEntry.js +32 -0
- package/src/models/ConsoleLog.js +23 -0
- package/src/models/ContextBlockDefinition.js +33 -0
- package/src/models/CronExecution.js +47 -0
- package/src/models/CronJob.js +70 -0
- package/src/models/ExternalDbConnection.js +49 -0
- package/src/models/FileEntry.js +22 -0
- package/src/models/HealthAutoHealAttempt.js +57 -0
- package/src/models/HealthCheck.js +132 -0
- package/src/models/HealthCheckRun.js +51 -0
- package/src/models/HealthIncident.js +49 -0
- package/src/models/Page.js +95 -0
- package/src/models/PageCollection.js +42 -0
- package/src/models/ProxyEntry.js +66 -0
- package/src/models/RateLimitCounter.js +19 -0
- package/src/models/RateLimitMetricBucket.js +20 -0
- package/src/models/RbacGrant.js +25 -0
- package/src/models/RbacGroup.js +16 -0
- package/src/models/RbacGroupMember.js +13 -0
- package/src/models/RbacGroupRole.js +13 -0
- package/src/models/RbacRole.js +25 -0
- package/src/models/RbacUserRole.js +13 -0
- package/src/routes/adminBlog.routes.js +21 -0
- package/src/routes/adminBlogAi.routes.js +16 -0
- package/src/routes/adminBlogAutomation.routes.js +27 -0
- package/src/routes/adminCache.routes.js +20 -0
- package/src/routes/adminConsoleManager.routes.js +302 -0
- package/src/routes/adminCrons.routes.js +25 -0
- package/src/routes/adminDbBrowser.routes.js +65 -0
- package/src/routes/adminEjsVirtual.routes.js +2 -1
- package/src/routes/adminHeadless.routes.js +2 -1
- package/src/routes/adminHealthChecks.routes.js +28 -0
- package/src/routes/adminI18n.routes.js +4 -3
- package/src/routes/adminLlm.routes.js +4 -2
- package/src/routes/adminPages.routes.js +55 -0
- package/src/routes/adminProxy.routes.js +15 -0
- package/src/routes/adminRateLimits.routes.js +17 -0
- package/src/routes/adminRbac.routes.js +38 -0
- package/src/routes/adminSeoConfig.routes.js +5 -4
- package/src/routes/adminUiComponents.routes.js +2 -1
- package/src/routes/blogInternal.routes.js +14 -0
- package/src/routes/blogPublic.routes.js +9 -0
- package/src/routes/fileManager.routes.js +62 -0
- package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
- package/src/routes/healthChecksPublic.routes.js +9 -0
- package/src/routes/log.routes.js +43 -60
- package/src/routes/metrics.routes.js +4 -2
- package/src/routes/orgAdmin.routes.js +1 -0
- package/src/routes/pages.routes.js +123 -0
- package/src/routes/proxy.routes.js +46 -0
- package/src/routes/rbac.routes.js +47 -0
- package/src/routes/webhook.routes.js +2 -1
- package/src/routes/workflows.routes.js +4 -0
- package/src/services/blockDefinitionsAi.service.js +247 -0
- package/src/services/blog.service.js +99 -0
- package/src/services/blogAutomation.service.js +978 -0
- package/src/services/blogCronsBootstrap.service.js +184 -0
- package/src/services/blogPublishing.service.js +58 -0
- package/src/services/cacheLayer.service.js +696 -0
- package/src/services/consoleManager.service.js +700 -0
- package/src/services/consoleOverride.service.js +6 -1
- package/src/services/cronScheduler.service.js +350 -0
- package/src/services/dbBrowser.service.js +536 -0
- package/src/services/ejsVirtual.service.js +102 -32
- package/src/services/fileManager.service.js +475 -0
- package/src/services/fileManagerStoragePolicy.service.js +285 -0
- package/src/services/healthChecks.service.js +650 -0
- package/src/services/healthChecksBootstrap.service.js +109 -0
- package/src/services/healthChecksScheduler.service.js +106 -0
- package/src/services/llmDefaults.service.js +190 -0
- package/src/services/migrationAssets/s3.js +2 -2
- package/src/services/pages.service.js +602 -0
- package/src/services/pagesContext.service.js +331 -0
- package/src/services/pagesContextBlocksAi.service.js +349 -0
- package/src/services/proxy.service.js +535 -0
- package/src/services/rateLimiter.service.js +623 -0
- package/src/services/rbac.service.js +212 -0
- package/src/services/scriptsRunner.service.js +1 -1
- package/src/services/uiComponentsAi.service.js +6 -19
- package/src/services/workflow.service.js +23 -8
- package/src/utils/orgRoles.js +14 -0
- package/src/utils/rbac/engine.js +60 -0
- package/src/utils/rbac/rightsRegistry.js +29 -0
- package/views/admin-blog-automation.ejs +877 -0
- package/views/admin-blog-edit.ejs +542 -0
- package/views/admin-blog.ejs +399 -0
- package/views/admin-cache.ejs +681 -0
- package/views/admin-console-manager.ejs +680 -0
- package/views/admin-crons.ejs +645 -0
- package/views/admin-db-browser.ejs +445 -0
- package/views/admin-ejs-virtual.ejs +16 -10
- package/views/admin-file-manager.ejs +942 -0
- package/views/admin-health-checks.ejs +725 -0
- package/views/admin-i18n.ejs +59 -5
- package/views/admin-llm.ejs +99 -1
- package/views/admin-organizations.ejs +163 -1
- package/views/admin-pages.ejs +2424 -0
- package/views/admin-proxy.ejs +491 -0
- package/views/admin-rate-limiter.ejs +625 -0
- package/views/admin-rbac.ejs +1331 -0
- package/views/admin-scripts.ejs +1 -1
- package/views/admin-seo-config.ejs +61 -7
- package/views/admin-ui-components.ejs +57 -25
- package/views/admin-workflows.ejs +7 -7
- package/views/file-manager.ejs +866 -0
- package/views/pages/blocks/contact.ejs +27 -0
- package/views/pages/blocks/cta.ejs +18 -0
- package/views/pages/blocks/faq.ejs +20 -0
- package/views/pages/blocks/features.ejs +19 -0
- package/views/pages/blocks/hero.ejs +13 -0
- package/views/pages/blocks/html.ejs +5 -0
- package/views/pages/blocks/image.ejs +14 -0
- package/views/pages/blocks/testimonials.ejs +26 -0
- package/views/pages/blocks/text.ejs +10 -0
- package/views/pages/layouts/default.ejs +51 -0
- package/views/pages/layouts/minimal.ejs +42 -0
- package/views/pages/layouts/sidebar.ejs +54 -0
- package/views/pages/partials/footer.ejs +13 -0
- package/views/pages/partials/header.ejs +12 -0
- package/views/pages/partials/sidebar.ejs +8 -0
- package/views/pages/runtime/page.ejs +10 -0
- package/views/pages/templates/article.ejs +20 -0
- package/views/pages/templates/default.ejs +12 -0
- package/views/pages/templates/landing.ejs +14 -0
- package/views/pages/templates/listing.ejs +15 -0
- package/views/partials/admin-image-upload-modal.ejs +221 -0
- package/views/partials/dashboard/nav-items.ejs +11 -0
- package/views/partials/llm-provider-model-picker.ejs +183 -0
- package/src/routes/llmUi.routes.js +0 -26
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const OrganizationMember = require('../models/OrganizationMember');
|
|
4
|
+
const RbacUserRole = require('../models/RbacUserRole');
|
|
5
|
+
const RbacGroup = require('../models/RbacGroup');
|
|
6
|
+
const RbacGroupMember = require('../models/RbacGroupMember');
|
|
7
|
+
const RbacGroupRole = require('../models/RbacGroupRole');
|
|
8
|
+
const RbacGrant = require('../models/RbacGrant');
|
|
9
|
+
const { matches } = require('../utils/rbac/engine');
|
|
10
|
+
|
|
11
|
+
function normalizeId(input) {
|
|
12
|
+
if (!input) return null;
|
|
13
|
+
const str = String(input);
|
|
14
|
+
if (!mongoose.Types.ObjectId.isValid(str)) return null;
|
|
15
|
+
return new mongoose.Types.ObjectId(str);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeRight(input) {
|
|
19
|
+
return String(input || '').trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildExplainItem(grant, source) {
|
|
23
|
+
if (!grant) return null;
|
|
24
|
+
return {
|
|
25
|
+
source,
|
|
26
|
+
effect: grant.effect,
|
|
27
|
+
right: grant.right,
|
|
28
|
+
subjectType: grant.subjectType,
|
|
29
|
+
subjectId: String(grant.subjectId),
|
|
30
|
+
scopeType: grant.scopeType,
|
|
31
|
+
scopeId: grant.scopeId ? String(grant.scopeId) : null,
|
|
32
|
+
id: String(grant._id),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractMatches(grants, requiredRight) {
|
|
37
|
+
const denies = [];
|
|
38
|
+
const allows = [];
|
|
39
|
+
|
|
40
|
+
for (const g of grants || []) {
|
|
41
|
+
if (!g) continue;
|
|
42
|
+
if (!matches(requiredRight, g.right)) continue;
|
|
43
|
+
|
|
44
|
+
if (g.effect === 'deny') {
|
|
45
|
+
denies.push(g);
|
|
46
|
+
} else {
|
|
47
|
+
allows.push(g);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { denies, allows };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getUserOrgIds(userId) {
|
|
55
|
+
const uid = normalizeId(userId);
|
|
56
|
+
if (!uid) return [];
|
|
57
|
+
const rows = await OrganizationMember.find({ userId: uid, status: 'active' }).select('orgId').lean();
|
|
58
|
+
return rows.map((r) => String(r.orgId));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function getEffectiveGrants({ userId, orgId }) {
|
|
62
|
+
const uid = normalizeId(userId);
|
|
63
|
+
const oid = normalizeId(orgId);
|
|
64
|
+
if (!uid || !oid) {
|
|
65
|
+
return {
|
|
66
|
+
grants: [],
|
|
67
|
+
layers: { org: [], group: [], role: [], user: [] },
|
|
68
|
+
explain: [],
|
|
69
|
+
context: { roles: [], groups: [] },
|
|
70
|
+
orgMember: null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const orgMember = await OrganizationMember.findOne({ userId: uid, orgId: oid, status: 'active' }).lean();
|
|
75
|
+
if (!orgMember) {
|
|
76
|
+
return {
|
|
77
|
+
grants: [],
|
|
78
|
+
layers: { org: [], group: [], role: [], user: [] },
|
|
79
|
+
explain: [],
|
|
80
|
+
context: { roles: [], groups: [] },
|
|
81
|
+
orgMember: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const [userRoleLinks, groupLinks, orgGrantsGlobal, orgGrantsOrg] = await Promise.all([
|
|
86
|
+
RbacUserRole.find({ userId: uid }).select('roleId').lean(),
|
|
87
|
+
RbacGroupMember.find({ userId: uid }).select('groupId').lean(),
|
|
88
|
+
RbacGrant.find({ subjectType: 'org', subjectId: oid, scopeType: 'global' }).lean(),
|
|
89
|
+
RbacGrant.find({ subjectType: 'org', subjectId: oid, scopeType: 'org', scopeId: oid }).lean(),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const directRoleIds = userRoleLinks.map((r) => r.roleId).filter(Boolean);
|
|
93
|
+
const groupIds = groupLinks.map((g) => g.groupId).filter(Boolean);
|
|
94
|
+
|
|
95
|
+
const groups = groupIds.length
|
|
96
|
+
? await RbacGroup.find({ _id: { $in: groupIds }, status: 'active' }).select('_id isGlobal orgId').lean()
|
|
97
|
+
: [];
|
|
98
|
+
|
|
99
|
+
const allowedGroupIds = [];
|
|
100
|
+
for (const g of groups) {
|
|
101
|
+
if (g.isGlobal) {
|
|
102
|
+
allowedGroupIds.push(g._id);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (g.orgId && String(g.orgId) === String(oid)) {
|
|
106
|
+
allowedGroupIds.push(g._id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const groupRoleLinks = allowedGroupIds.length
|
|
111
|
+
? await RbacGroupRole.find({ groupId: { $in: allowedGroupIds } }).select('groupId roleId').lean()
|
|
112
|
+
: [];
|
|
113
|
+
|
|
114
|
+
const groupRoleIds = groupRoleLinks.map((l) => l.roleId).filter(Boolean);
|
|
115
|
+
const effectiveRoleIds = Array.from(new Set([...directRoleIds, ...groupRoleIds]));
|
|
116
|
+
|
|
117
|
+
const [userGrantsGlobal, userGrantsOrg, roleGrantsGlobal, roleGrantsOrg, groupGrantsGlobal, groupGrantsOrg] = await Promise.all([
|
|
118
|
+
RbacGrant.find({ subjectType: 'user', subjectId: uid, scopeType: 'global' }).lean(),
|
|
119
|
+
RbacGrant.find({ subjectType: 'user', subjectId: uid, scopeType: 'org', scopeId: oid }).lean(),
|
|
120
|
+
effectiveRoleIds.length ? RbacGrant.find({ subjectType: 'role', subjectId: { $in: effectiveRoleIds }, scopeType: 'global' }).lean() : [],
|
|
121
|
+
effectiveRoleIds.length ? RbacGrant.find({ subjectType: 'role', subjectId: { $in: effectiveRoleIds }, scopeType: 'org', scopeId: oid }).lean() : [],
|
|
122
|
+
allowedGroupIds.length ? RbacGrant.find({ subjectType: 'group', subjectId: { $in: allowedGroupIds }, scopeType: 'global' }).lean() : [],
|
|
123
|
+
allowedGroupIds.length ? RbacGrant.find({ subjectType: 'group', subjectId: { $in: allowedGroupIds }, scopeType: 'org', scopeId: oid }).lean() : [],
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
const layers = {
|
|
127
|
+
org: [...orgGrantsGlobal, ...orgGrantsOrg],
|
|
128
|
+
group: [...groupGrantsGlobal, ...groupGrantsOrg],
|
|
129
|
+
role: [...roleGrantsGlobal, ...roleGrantsOrg],
|
|
130
|
+
user: [...userGrantsGlobal, ...userGrantsOrg],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const all = [...layers.org, ...layers.group, ...layers.role, ...layers.user];
|
|
134
|
+
|
|
135
|
+
const explain = [];
|
|
136
|
+
for (const g of userGrantsGlobal) explain.push(buildExplainItem(g, 'user:global'));
|
|
137
|
+
for (const g of userGrantsOrg) explain.push(buildExplainItem(g, 'user:org'));
|
|
138
|
+
for (const g of roleGrantsGlobal) explain.push(buildExplainItem(g, 'role:global'));
|
|
139
|
+
for (const g of roleGrantsOrg) explain.push(buildExplainItem(g, 'role:org'));
|
|
140
|
+
for (const g of groupGrantsGlobal) explain.push(buildExplainItem(g, 'group:global'));
|
|
141
|
+
for (const g of groupGrantsOrg) explain.push(buildExplainItem(g, 'group:org'));
|
|
142
|
+
for (const g of orgGrantsGlobal) explain.push(buildExplainItem(g, 'org:global'));
|
|
143
|
+
for (const g of orgGrantsOrg) explain.push(buildExplainItem(g, 'org:org'));
|
|
144
|
+
|
|
145
|
+
const context = {
|
|
146
|
+
groups: groups.map((g) => ({
|
|
147
|
+
id: String(g._id),
|
|
148
|
+
isGlobal: !!g.isGlobal,
|
|
149
|
+
orgId: g.orgId ? String(g.orgId) : null,
|
|
150
|
+
})),
|
|
151
|
+
roles: [
|
|
152
|
+
...directRoleIds.map((rid) => ({ roleId: String(rid), source: 'user' })),
|
|
153
|
+
...groupRoleLinks.map((l) => ({ roleId: String(l.roleId), source: 'group', groupId: String(l.groupId) })),
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return { grants: all, layers, explain: explain.filter(Boolean), context, orgMember };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function checkRight({ userId, orgId, right }) {
|
|
161
|
+
const r = normalizeRight(right);
|
|
162
|
+
if (!r) {
|
|
163
|
+
return { allowed: false, reason: 'invalid_right', explain: [], context: null, decisionLayer: null };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { layers, explain, context, orgMember } = await getEffectiveGrants({ userId, orgId });
|
|
167
|
+
if (!orgMember) {
|
|
168
|
+
return { allowed: false, reason: 'not_org_member', explain: [], context: null, decisionLayer: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const denyMatches = [];
|
|
172
|
+
for (const [layerName, grants] of Object.entries(layers || {})) {
|
|
173
|
+
const { denies } = extractMatches(grants, r);
|
|
174
|
+
for (const d of denies) denyMatches.push({ layerName, grant: d });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (denyMatches.length) {
|
|
178
|
+
const matchedIds = new Set(denyMatches.map((m) => String(m.grant._id)));
|
|
179
|
+
const filteredExplain = explain.filter((e) => matchedIds.has(String(e.id)));
|
|
180
|
+
return {
|
|
181
|
+
allowed: false,
|
|
182
|
+
reason: 'denied',
|
|
183
|
+
decisionLayer: 'deny',
|
|
184
|
+
explain: filteredExplain,
|
|
185
|
+
context,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const allowPriority = ['org', 'group', 'role', 'user'];
|
|
190
|
+
for (const layer of allowPriority) {
|
|
191
|
+
const { allows } = extractMatches(layers?.[layer] || [], r);
|
|
192
|
+
if (!allows.length) continue;
|
|
193
|
+
|
|
194
|
+
const matchedIds = new Set(allows.map((a) => String(a._id)));
|
|
195
|
+
const filteredExplain = explain.filter((e) => matchedIds.has(String(e.id)));
|
|
196
|
+
return {
|
|
197
|
+
allowed: true,
|
|
198
|
+
reason: 'allowed',
|
|
199
|
+
decisionLayer: layer,
|
|
200
|
+
explain: filteredExplain,
|
|
201
|
+
context,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { allowed: false, reason: 'no_match', explain: [], context, decisionLayer: null };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
getUserOrgIds,
|
|
210
|
+
getEffectiveGrants,
|
|
211
|
+
checkRight,
|
|
212
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const UiComponent = require('../models/UiComponent');
|
|
2
|
-
const { getSettingValue } = require('./globalSettings.service');
|
|
3
2
|
const llmService = require('./llm.service');
|
|
3
|
+
const { resolveLlmProviderModel } = require('./llmDefaults.service');
|
|
4
4
|
const { createAuditEvent } = require('./audit.service');
|
|
5
5
|
|
|
6
6
|
const ALLOWED_FIELDS = new Set(['html', 'css', 'js', 'usageMarkdown']);
|
|
@@ -94,24 +94,11 @@ function computeWarnings(nextFields) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
async function resolveLlmDefaults({ providerKey, model }) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const envProvider = String(process.env.DEFAULT_LLM_PROVIDER_KEY || '').trim();
|
|
104
|
-
const envModel = String(process.env.DEFAULT_LLM_MODEL || '').trim();
|
|
105
|
-
|
|
106
|
-
const resolvedProviderKey = uiProvider || settingProvider || envProvider;
|
|
107
|
-
if (!resolvedProviderKey) {
|
|
108
|
-
const err = new Error('Missing LLM providerKey (configure uiComponents.ai.providerKey or DEFAULT_LLM_PROVIDER_KEY, or send from UI)');
|
|
109
|
-
err.code = 'VALIDATION';
|
|
110
|
-
throw err;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const resolvedModel = uiModel || settingModel || envModel || 'x-ai/grok-code-fast-1';
|
|
114
|
-
return { providerKey: resolvedProviderKey, model: resolvedModel };
|
|
97
|
+
return resolveLlmProviderModel({
|
|
98
|
+
systemKey: 'uiComponents.proposeEdit',
|
|
99
|
+
providerKey,
|
|
100
|
+
model,
|
|
101
|
+
});
|
|
115
102
|
}
|
|
116
103
|
|
|
117
104
|
function buildSystemPrompt({ targets }) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const Workflow = require('../models/Workflow');
|
|
2
2
|
const WorkflowExecution = require('../models/WorkflowExecution');
|
|
3
|
-
const llmService = require('./llm.service');
|
|
4
3
|
const { NodeVM } = require('vm2');
|
|
4
|
+
const llmService = require('./llm.service');
|
|
5
|
+
const { resolveLlmProviderModel } = require('./llmDefaults.service');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Workflow Service
|
|
@@ -163,13 +164,27 @@ class WorkflowService {
|
|
|
163
164
|
|
|
164
165
|
async handleLLM(node) {
|
|
165
166
|
const prompt = this.interpolate(node.prompt);
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
const providerKeyRaw = node.provider;
|
|
168
|
+
const modelRaw = node.model;
|
|
169
|
+
|
|
170
|
+
const resolved = (!providerKeyRaw || !String(providerKeyRaw).trim() || !modelRaw || !String(modelRaw).trim())
|
|
171
|
+
? await resolveLlmProviderModel({
|
|
172
|
+
systemKey: 'workflow.node.llm',
|
|
173
|
+
providerKey: providerKeyRaw,
|
|
174
|
+
model: modelRaw,
|
|
175
|
+
})
|
|
176
|
+
: { providerKey: String(providerKeyRaw).trim(), model: String(modelRaw || '').trim() };
|
|
177
|
+
|
|
178
|
+
const response = await llmService.callAdhoc(
|
|
179
|
+
{
|
|
180
|
+
providerKey: resolved.providerKey,
|
|
181
|
+
messages: [{ role: 'user', content: prompt }],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
model: resolved.model || undefined,
|
|
185
|
+
temperature: node.temperature !== undefined ? parseFloat(node.temperature) : 0.7,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
173
188
|
return response.content;
|
|
174
189
|
}
|
|
175
190
|
|
package/src/utils/orgRoles.js
CHANGED
|
@@ -142,6 +142,18 @@ async function getOrgRoleLevel(role) {
|
|
|
142
142
|
return hierarchy[r] || 0;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
async function isRoleAtLeast(role, requiredRole) {
|
|
146
|
+
const level = await getOrgRoleLevel(role);
|
|
147
|
+
const requiredLevel = await getOrgRoleLevel(requiredRole);
|
|
148
|
+
return level >= requiredLevel;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function isRoleHigherThan(role, otherRole) {
|
|
152
|
+
const level = await getOrgRoleLevel(role);
|
|
153
|
+
const otherLevel = await getOrgRoleLevel(otherRole);
|
|
154
|
+
return level > otherLevel;
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
function clearOrgRolesCache() {
|
|
146
158
|
cached = null;
|
|
147
159
|
}
|
|
@@ -152,5 +164,7 @@ module.exports = {
|
|
|
152
164
|
getDefaultOrgRole,
|
|
153
165
|
isValidOrgRole,
|
|
154
166
|
getOrgRoleLevel,
|
|
167
|
+
isRoleAtLeast,
|
|
168
|
+
isRoleHigherThan,
|
|
155
169
|
clearOrgRolesCache,
|
|
156
170
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function normalizeRight(input) {
|
|
2
|
+
return String(input || '').trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function escapeRegex(str) {
|
|
6
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function patternToRegex(pattern) {
|
|
10
|
+
const parts = normalizeRight(pattern).split('*').map(escapeRegex);
|
|
11
|
+
return new RegExp('^' + parts.join('.*') + '$');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function matches(requiredRight, grantedPattern) {
|
|
15
|
+
const required = normalizeRight(requiredRight);
|
|
16
|
+
const pattern = normalizeRight(grantedPattern);
|
|
17
|
+
if (!required || !pattern) return false;
|
|
18
|
+
if (pattern === required) return true;
|
|
19
|
+
if (!pattern.includes('*')) return false;
|
|
20
|
+
return patternToRegex(pattern).test(required);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function evaluateEffects(entries, requiredRight) {
|
|
24
|
+
const required = normalizeRight(requiredRight);
|
|
25
|
+
if (!required) {
|
|
26
|
+
return { allowed: false, reason: 'invalid_required_right' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const denies = [];
|
|
30
|
+
const allows = [];
|
|
31
|
+
|
|
32
|
+
for (const e of entries || []) {
|
|
33
|
+
if (!e) continue;
|
|
34
|
+
const right = normalizeRight(e.right);
|
|
35
|
+
const effect = normalizeRight(e.effect || 'allow');
|
|
36
|
+
if (!right) continue;
|
|
37
|
+
if (!matches(required, right)) continue;
|
|
38
|
+
|
|
39
|
+
if (effect === 'deny') {
|
|
40
|
+
denies.push(e);
|
|
41
|
+
} else {
|
|
42
|
+
allows.push(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (denies.length) {
|
|
47
|
+
return { allowed: false, reason: 'denied', matched: denies };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (allows.length) {
|
|
51
|
+
return { allowed: true, reason: 'allowed', matched: allows };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { allowed: false, reason: 'no_match' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
matches,
|
|
59
|
+
evaluateEffects,
|
|
60
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const DEFAULT_RIGHTS = [
|
|
2
|
+
'rbac:roles:read',
|
|
3
|
+
'rbac:roles:write',
|
|
4
|
+
'rbac:groups:read',
|
|
5
|
+
'rbac:groups:write',
|
|
6
|
+
'rbac:grants:read',
|
|
7
|
+
'rbac:grants:write',
|
|
8
|
+
'rbac:test',
|
|
9
|
+
'file_manager:*',
|
|
10
|
+
'file_manager:access',
|
|
11
|
+
'file_manager:drives:read',
|
|
12
|
+
'file_manager:files:read',
|
|
13
|
+
'file_manager:files:upload',
|
|
14
|
+
'file_manager:files:download',
|
|
15
|
+
'file_manager:files:update',
|
|
16
|
+
'file_manager:files:delete',
|
|
17
|
+
'file_manager:files:share',
|
|
18
|
+
'backoffice:*',
|
|
19
|
+
'backoffice:dashboard:access',
|
|
20
|
+
'*',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function listRights() {
|
|
24
|
+
return Array.from(new Set(DEFAULT_RIGHTS)).sort();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
listRights,
|
|
29
|
+
};
|