@unchainedshop/api 5.0.0-alpha.4 → 5.0.0-alpha.5
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/lib/acl.js +4 -0
- package/lib/adminUiPlugins.d.ts +12 -1
- package/lib/adminUiPlugins.js +188 -26
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +2 -0
- package/lib/events.d.ts +1 -0
- package/lib/events.js +1 -0
- package/lib/express/index.js +44 -17
- package/lib/fastify/index.d.ts +2 -0
- package/lib/fastify/index.js +65 -24
- package/lib/mcp/tools/system/handlers/allocateWork.js +2 -2
- package/lib/mcp/tools/users/index.js +1 -1
- package/lib/middleware/createAuthMiddleware.js +2 -0
- package/lib/resolvers/mutations/accounts/loginWithPassword.js +16 -2
- package/lib/resolvers/mutations/accounts/logoutAllSessions.d.ts +3 -1
- package/lib/resolvers/mutations/accounts/logoutAllSessions.js +10 -4
- package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.js +18 -0
- package/lib/resolvers/mutations/bulk/bulkOperation.d.ts +12 -0
- package/lib/resolvers/mutations/bulk/bulkOperation.js +14 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveFilters.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveFilters.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveProducts.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveProducts.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveUsers.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveUsers.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkSetFilterActive.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetFilterActive.js +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetProductStatus.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetProductStatus.js +17 -0
- package/lib/resolvers/mutations/bulk/bulkSetUserRoles.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetUserRoles.js +14 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.js +10 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.js +7 -0
- package/lib/resolvers/mutations/index.d.ts +12 -0
- package/lib/resolvers/mutations/index.js +25 -1
- package/lib/resolvers/mutations/users/removeUser.js +9 -2
- package/lib/resolvers/mutations/users/setRoles.js +9 -2
- package/lib/resolvers/mutations/worker/allocateWork.d.ts +1 -1
- package/lib/resolvers/mutations/worker/allocateWork.js +4 -3
- package/lib/resolvers/queries/index.d.ts +1 -0
- package/lib/resolvers/queries/index.js +2 -0
- package/lib/resolvers/queries/search/globalSearch.d.ts +36 -0
- package/lib/resolvers/queries/search/globalSearch.js +200 -0
- package/lib/resolvers/type/global-search-result-types.d.ts +3 -0
- package/lib/resolvers/type/global-search-result-types.js +15 -0
- package/lib/resolvers/type/index.d.ts +3 -0
- package/lib/resolvers/type/index.js +2 -0
- package/lib/resolvers/type/user-types.d.ts +1 -0
- package/lib/resolvers/type/user-types.js +35 -4
- package/lib/roles/all.js +12 -0
- package/lib/roles/index.js +12 -0
- package/lib/roles/loggedIn.js +1 -1
- package/lib/schema/inputTypes.js +5 -0
- package/lib/schema/mutation.js +74 -1
- package/lib/schema/query.js +15 -0
- package/lib/schema/types/common.js +7 -0
- package/lib/schema/types/search.js +36 -0
- package/lib/schema/types/user.js +4 -0
- package/package.json +25 -25
package/lib/acl.js
CHANGED
package/lib/adminUiPlugins.d.ts
CHANGED
|
@@ -85,13 +85,24 @@ interface StaticAsset {
|
|
|
85
85
|
export interface PreparedPluginAssets {
|
|
86
86
|
routes: Map<string, StaticAsset>;
|
|
87
87
|
validPlugins: AdminUIPluginConfig[];
|
|
88
|
+
importMapTag: string | null;
|
|
89
|
+
importMapJSON: string | null;
|
|
90
|
+
}
|
|
91
|
+
export interface PreparedAdminUIHTML {
|
|
92
|
+
htmlByRoute: Map<string, string>;
|
|
93
|
+
rootHtml: string;
|
|
94
|
+
extHtml: string | null;
|
|
88
95
|
}
|
|
89
96
|
export declare const resolveAdminUIPath: () => string | null;
|
|
90
|
-
export declare const
|
|
97
|
+
export declare const prepareAdminUIHTML: (adminUIPath: string) => PreparedAdminUIHTML;
|
|
98
|
+
export declare const resolveAdminUIHTML: (prepared: PreparedAdminUIHTML, urlPath: string, prefix?: string) => string;
|
|
99
|
+
export declare const injectAdminUIImportMap: (prepared: PreparedAdminUIHTML, urlPath: string, importMapTag: string, prefix?: string) => string;
|
|
100
|
+
export declare const parseEsmBundleExports: (bundle: string) => Set<string> | null;
|
|
91
101
|
export declare function preparePluginAssets(plugins: AdminUIPluginConfig[], log: {
|
|
92
102
|
info: (...args: any[]) => void;
|
|
93
103
|
warn: (...args: any[]) => void;
|
|
94
104
|
}, options?: {
|
|
95
105
|
devMode?: boolean;
|
|
96
106
|
}): PreparedPluginAssets;
|
|
107
|
+
export declare function buildImportMapTag(importMapJSON: string, nonce?: string): string;
|
|
97
108
|
export {};
|
package/lib/adminUiPlugins.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFileSync, statSync } from 'node:fs';
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { join, resolve, sep } from 'node:path';
|
|
4
5
|
const PLUGIN_NAME_RE = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9_-]*[a-z0-9])?)*$/i;
|
|
5
6
|
const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
|
|
7
|
+
const requireFromAPI = createRequire(import.meta.url);
|
|
8
|
+
const loadPluginRuntime = (log) => {
|
|
9
|
+
try {
|
|
10
|
+
const runtime = requireFromAPI('@unchainedshop/admin-ui/plugin-runtime');
|
|
11
|
+
if (!runtime.SHARED_DEP_SHIMS || !runtime.SDK_MODULE_FILES) {
|
|
12
|
+
throw new Error('runtime module does not expose the expected module graph');
|
|
13
|
+
}
|
|
14
|
+
return runtime;
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
log.warn(`admin-ui plugin runtime could not be loaded; shared plugin modules will be unavailable: ${err.message}`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
6
21
|
export const resolveAdminUIPath = () => {
|
|
7
22
|
try {
|
|
8
23
|
const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
|
|
@@ -12,17 +27,82 @@ export const resolveAdminUIPath = () => {
|
|
|
12
27
|
return null;
|
|
13
28
|
}
|
|
14
29
|
};
|
|
30
|
+
const trimTrailingSlashes = (value) => {
|
|
31
|
+
let end = value.length;
|
|
32
|
+
while (end > 0 && value[end - 1] === '/')
|
|
33
|
+
end -= 1;
|
|
34
|
+
return value.slice(0, end);
|
|
35
|
+
};
|
|
36
|
+
const normalizeAdminUIRoute = (urlPath, prefix = '/') => {
|
|
37
|
+
let pathOnly = urlPath.split('?')[0];
|
|
38
|
+
const normalizedPrefix = `/${trimTrailingSlashes(prefix.replace(/^\/+/, ''))}`;
|
|
39
|
+
if (normalizedPrefix !== '/' &&
|
|
40
|
+
(pathOnly === normalizedPrefix || pathOnly.startsWith(`${normalizedPrefix}/`))) {
|
|
41
|
+
pathOnly = pathOnly.slice(normalizedPrefix.length) || '/';
|
|
42
|
+
}
|
|
43
|
+
const withLeadingSlash = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
|
|
44
|
+
return trimTrailingSlashes(withLeadingSlash) || '/';
|
|
45
|
+
};
|
|
46
|
+
export const prepareAdminUIHTML = (adminUIPath) => {
|
|
47
|
+
const htmlByRoute = new Map();
|
|
48
|
+
let extHtml = null;
|
|
49
|
+
for (const entry of readdirSync(adminUIPath, { recursive: true })) {
|
|
50
|
+
const relativePath = String(entry).split(sep).join('/');
|
|
51
|
+
if (relativePath !== 'index.html' && !relativePath.endsWith('/index.html'))
|
|
52
|
+
continue;
|
|
53
|
+
const route = relativePath === 'index.html' ? '/' : `/${relativePath.slice(0, -'/index.html'.length)}`;
|
|
54
|
+
const html = readFileSync(join(adminUIPath, relativePath), 'utf-8');
|
|
55
|
+
if (route === '/ext/[[...slug]]') {
|
|
56
|
+
extHtml = html;
|
|
57
|
+
}
|
|
58
|
+
else if (!route.includes('[')) {
|
|
59
|
+
htmlByRoute.set(route, html);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const rootHtml = htmlByRoute.get('/');
|
|
63
|
+
if (!rootHtml) {
|
|
64
|
+
throw new Error(`admin-ui root index.html not found in ${adminUIPath}`);
|
|
65
|
+
}
|
|
66
|
+
return { htmlByRoute, rootHtml, extHtml };
|
|
67
|
+
};
|
|
68
|
+
export const resolveAdminUIHTML = (prepared, urlPath, prefix = '/') => {
|
|
69
|
+
const route = normalizeAdminUIRoute(urlPath, prefix);
|
|
70
|
+
const exactHtml = prepared.htmlByRoute.get(route);
|
|
71
|
+
if (exactHtml)
|
|
72
|
+
return exactHtml;
|
|
73
|
+
if (prepared.extHtml && (route === '/ext' || route.startsWith('/ext/'))) {
|
|
74
|
+
return prepared.extHtml;
|
|
75
|
+
}
|
|
76
|
+
return prepared.rootHtml;
|
|
77
|
+
};
|
|
78
|
+
export const injectAdminUIImportMap = (prepared, urlPath, importMapTag, prefix = '/') => resolveAdminUIHTML(prepared, urlPath, prefix).replace('</head>', `${importMapTag}</head>`);
|
|
15
79
|
const contentHash = (content) => createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
80
|
+
const listSDKDistFiles = (distPath) => {
|
|
81
|
+
try {
|
|
82
|
+
if (!statSync(distPath).isDirectory())
|
|
83
|
+
return [];
|
|
84
|
+
return readdirSync(distPath, { recursive: true })
|
|
85
|
+
.map((file) => String(file).split(sep).join('/'))
|
|
86
|
+
.filter((file) => file.endsWith('.js'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
export const parseEsmBundleExports = (bundle) => {
|
|
23
93
|
const names = new Set();
|
|
24
|
-
for (const
|
|
25
|
-
|
|
94
|
+
for (const clause of bundle.matchAll(/(?:^|[;\n])\s*export\s*\{([^}]*)\}/g)) {
|
|
95
|
+
for (const entry of clause[1].split(',')) {
|
|
96
|
+
const parts = entry.trim().split(/\s+as\s+/);
|
|
97
|
+
const exported = (parts[1] ?? parts[0]).trim().replace(/^["']|["']$/g, '');
|
|
98
|
+
if (exported)
|
|
99
|
+
names.add(exported);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (/(?:^|[;\n])\s*export\s+default\b/.test(bundle))
|
|
103
|
+
names.add('default');
|
|
104
|
+
for (const decl of bundle.matchAll(/(?:^|[;\n])\s*export\s+(?:async\s+)?(?:const|let|var|function|class)\s+([\w$]+)/g)) {
|
|
105
|
+
names.add(decl[1]);
|
|
26
106
|
}
|
|
27
107
|
return names.size > 0 ? names : null;
|
|
28
108
|
};
|
|
@@ -48,24 +128,41 @@ const collectReferencedComponents = (plugin) => {
|
|
|
48
128
|
}
|
|
49
129
|
return [...names];
|
|
50
130
|
};
|
|
131
|
+
const parseBundleSdkVersion = (bundle) => {
|
|
132
|
+
const match = bundle.match(/^\/\* unchained admin-ui plugin: .+? \(esm, sdk ([^)]+)\) \*\//);
|
|
133
|
+
return match?.[1] ?? null;
|
|
134
|
+
};
|
|
135
|
+
const readHostSdkVersion = () => {
|
|
136
|
+
const adminUIPath = resolveAdminUIPath();
|
|
137
|
+
if (!adminUIPath)
|
|
138
|
+
return null;
|
|
139
|
+
try {
|
|
140
|
+
const pkg = JSON.parse(readFileSync(join(adminUIPath, '..', 'package.json'), 'utf-8'));
|
|
141
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
51
147
|
export function preparePluginAssets(plugins, log, options = {}) {
|
|
52
148
|
const { devMode = false } = options;
|
|
53
149
|
const routes = new Map();
|
|
54
150
|
const devCacheControl = 'no-cache, no-store, must-revalidate';
|
|
55
|
-
const
|
|
151
|
+
const namedPlugins = plugins.filter((p) => {
|
|
56
152
|
if (!PLUGIN_NAME_RE.test(p.name)) {
|
|
57
153
|
log.warn(`Skipping admin-ui plugin with invalid name: "${p.name}"`);
|
|
58
154
|
return false;
|
|
59
155
|
}
|
|
60
156
|
return true;
|
|
61
157
|
});
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
if (
|
|
158
|
+
const validPlugins = namedPlugins.filter((plugin, index) => {
|
|
159
|
+
const lastIndex = namedPlugins.findLastIndex((p) => p.name === plugin.name);
|
|
160
|
+
if (index !== lastIndex) {
|
|
65
161
|
log.warn(`Duplicate admin-ui plugin name "${plugin.name}": later entries override earlier ones`);
|
|
162
|
+
return false;
|
|
66
163
|
}
|
|
67
|
-
|
|
68
|
-
}
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
69
166
|
if (validPlugins.length > 0) {
|
|
70
167
|
const pluginList = validPlugins
|
|
71
168
|
.map((p) => `${p.name}${p.version ? `@${p.version}` : ''}`)
|
|
@@ -86,13 +183,19 @@ export function preparePluginAssets(plugins, log, options = {}) {
|
|
|
86
183
|
}
|
|
87
184
|
}
|
|
88
185
|
const pluginsWithBundles = validPlugins.filter((p) => pluginBundles.has(p.name));
|
|
186
|
+
const hostSdkVersion = pluginsWithBundles.length > 0 ? readHostSdkVersion() : null;
|
|
89
187
|
for (const plugin of pluginsWithBundles) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
188
|
+
const bundle = pluginBundles.get(plugin.name).content;
|
|
189
|
+
const exportNames = parseEsmBundleExports(bundle);
|
|
190
|
+
if (exportNames) {
|
|
191
|
+
const missing = collectReferencedComponents(plugin).filter((name) => !exportNames.has(name));
|
|
192
|
+
if (missing.length > 0) {
|
|
193
|
+
log.warn(`admin-ui plugin "${plugin.name}" references component(s) not exported by its bundle: ${missing.join(', ')}. Exported: ${[...exportNames].join(', ')}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const bundleSdkVersion = parseBundleSdkVersion(bundle);
|
|
197
|
+
if (hostSdkVersion && bundleSdkVersion && bundleSdkVersion !== hostSdkVersion) {
|
|
198
|
+
log.warn(`admin-ui plugin "${plugin.name}" was built against SDK ${bundleSdkVersion} but the running admin-ui is ${hostSdkVersion}. It may still work, but rebuild it against the current @unchainedshop/admin-ui to be safe.`);
|
|
96
199
|
}
|
|
97
200
|
}
|
|
98
201
|
const bundleMtimes = new Map();
|
|
@@ -147,7 +250,7 @@ export function preparePluginAssets(plugins, log, options = {}) {
|
|
|
147
250
|
if (devMode) {
|
|
148
251
|
routes.set(`/admin-plugins/${plugin.name}.js`, {
|
|
149
252
|
content: () => readFileSync(bundlePath, 'utf-8'),
|
|
150
|
-
contentType: '
|
|
253
|
+
contentType: 'text/javascript',
|
|
151
254
|
cacheControl: devCacheControl,
|
|
152
255
|
});
|
|
153
256
|
}
|
|
@@ -155,10 +258,69 @@ export function preparePluginAssets(plugins, log, options = {}) {
|
|
|
155
258
|
const { content } = pluginBundles.get(plugin.name);
|
|
156
259
|
routes.set(`/admin-plugins/${plugin.name}.js`, {
|
|
157
260
|
content,
|
|
158
|
-
contentType: '
|
|
261
|
+
contentType: 'text/javascript',
|
|
159
262
|
cacheControl: IMMUTABLE_CACHE,
|
|
160
263
|
});
|
|
161
264
|
}
|
|
162
265
|
}
|
|
163
|
-
|
|
266
|
+
let importMapTag = null;
|
|
267
|
+
let importMapContent = null;
|
|
268
|
+
if (pluginBundles.size > 0) {
|
|
269
|
+
const runtime = loadPluginRuntime(log);
|
|
270
|
+
const adminUIPath = resolveAdminUIPath();
|
|
271
|
+
const distPath = adminUIPath ? join(adminUIPath, '..', 'dist') : null;
|
|
272
|
+
const sdkFiles = distPath ? listSDKDistFiles(distPath) : [];
|
|
273
|
+
if (runtime && distPath && sdkFiles.length > 0) {
|
|
274
|
+
const sdkFileHashes = new Map();
|
|
275
|
+
for (const file of sdkFiles) {
|
|
276
|
+
const filePath = join(distPath, file);
|
|
277
|
+
if (devMode) {
|
|
278
|
+
routes.set(`/admin-ui-sdk/${file}`, {
|
|
279
|
+
content: () => readFileSync(filePath, 'utf-8'),
|
|
280
|
+
contentType: 'text/javascript',
|
|
281
|
+
cacheControl: devCacheControl,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
286
|
+
sdkFileHashes.set(file, contentHash(content));
|
|
287
|
+
routes.set(`/admin-ui-sdk/${file}`, {
|
|
288
|
+
content,
|
|
289
|
+
contentType: 'text/javascript',
|
|
290
|
+
cacheControl: IMMUTABLE_CACHE,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const imports = {};
|
|
295
|
+
for (const [specifier, file] of [
|
|
296
|
+
...Object.entries(runtime.SDK_MODULE_FILES),
|
|
297
|
+
...Object.entries(runtime.SHARED_DEP_SHIMS),
|
|
298
|
+
]) {
|
|
299
|
+
if (!sdkFiles.includes(file)) {
|
|
300
|
+
log.warn(`admin-ui plugin runtime: expected SDK file "${file}" for "${specifier}" not found in ${distPath}`);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const version = sdkFileHashes.get(file);
|
|
304
|
+
imports[specifier] = `/admin-ui-sdk/${file}${version ? `?v=${version}` : ''}`;
|
|
305
|
+
}
|
|
306
|
+
const importMapJSON = JSON.stringify({ imports });
|
|
307
|
+
const importMapHash = contentHash(importMapJSON);
|
|
308
|
+
routes.set('/admin-ui-importmap.json', {
|
|
309
|
+
content: importMapJSON,
|
|
310
|
+
contentType: 'application/json',
|
|
311
|
+
cacheControl: devMode ? devCacheControl : 'public, max-age=0, must-revalidate',
|
|
312
|
+
etag: `"${importMapHash}"`,
|
|
313
|
+
});
|
|
314
|
+
importMapContent = importMapJSON;
|
|
315
|
+
importMapTag = `<script type="importmap" data-unchained-admin-ui>${importMapJSON}</script>`;
|
|
316
|
+
}
|
|
317
|
+
else if (runtime) {
|
|
318
|
+
log.warn('admin-ui plugin runtime: SDK dist files not found; plugins depending on shared modules will fail to load');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { routes, validPlugins, importMapTag, importMapJSON: importMapContent };
|
|
322
|
+
}
|
|
323
|
+
export function buildImportMapTag(importMapJSON, nonce) {
|
|
324
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
|
|
325
|
+
return `<script type="importmap" data-unchained-admin-ui${nonceAttr}>${importMapJSON}</script>`;
|
|
164
326
|
}
|
package/lib/errors.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare const CurrencyNotFoundError: any;
|
|
|
19
19
|
export declare const DeliverProviderNotFoundError: any;
|
|
20
20
|
export declare const LanguageNotFoundError: any;
|
|
21
21
|
export declare const UserNotFoundError: any;
|
|
22
|
+
export declare const LastAdminError: any;
|
|
22
23
|
export declare const UserWebAuthnCredentialsNotFoundError: any;
|
|
23
24
|
export declare const WebAuthnDisabledError: any;
|
|
24
25
|
export declare const UserWeb3AddressNotFoundError: any;
|
|
@@ -56,6 +57,7 @@ export declare const EnrollmentWrongStatusError: any;
|
|
|
56
57
|
export declare const EnrollmentNotFoundError: any;
|
|
57
58
|
export declare const WarehousingProviderNotFoundError: any;
|
|
58
59
|
export declare const InvalidIdError: any;
|
|
60
|
+
export declare const BulkOperationTooLargeError: any;
|
|
59
61
|
export declare const ProviderConfigurationInvalid: any;
|
|
60
62
|
export declare const TokenWrongStatusError: any;
|
|
61
63
|
export declare const TokenNotFoundError: any;
|
package/lib/errors.js
CHANGED
|
@@ -29,6 +29,7 @@ export const CurrencyNotFoundError = createError('CurrencyNotFoundError', 'Curre
|
|
|
29
29
|
export const DeliverProviderNotFoundError = createError('DeliverProviderNotFoundError', 'Delivery provider not found');
|
|
30
30
|
export const LanguageNotFoundError = createError('LanguageNotFoundError', 'Language not found');
|
|
31
31
|
export const UserNotFoundError = createError('UserNotFoundError', 'User not found');
|
|
32
|
+
export const LastAdminError = createError('LastAdminError', 'At least one active administrator is required. Assign another administrator before removing this account or revoking its admin role.');
|
|
32
33
|
export const UserWebAuthnCredentialsNotFoundError = createError('UserWebAuthnCredentialsNotFoundError', 'User WebAuth Credentials not found');
|
|
33
34
|
export const WebAuthnDisabledError = createError('WebAuthnDisabledError', 'WebAuthn disabled');
|
|
34
35
|
export const UserWeb3AddressNotFoundError = createError('UserWeb3AddressNotFoundError', 'Web3 Account not found for User');
|
|
@@ -66,6 +67,7 @@ export const EnrollmentWrongStatusError = createError('EnrollmentWrongStatusErro
|
|
|
66
67
|
export const EnrollmentNotFoundError = createError('EnrollmentNotFoundError', 'Enrollment not found');
|
|
67
68
|
export const WarehousingProviderNotFoundError = createError('WarehousingProviderNotFoundError', 'Warehousing provider not found');
|
|
68
69
|
export const InvalidIdError = createError('InvalidIdError', 'Invalid ID provided');
|
|
70
|
+
export const BulkOperationTooLargeError = createError('BulkOperationTooLargeError', 'Bulk operation exceeds the maximum number of entities');
|
|
69
71
|
export const ProviderConfigurationInvalid = createError('ProviderConfigurationInvalid', 'Provider Configuration invalid (check if the Adapter Key exists)');
|
|
70
72
|
export const TokenWrongStatusError = createError('TokenWrongStatusError', 'The current status of the token does not allow this operation');
|
|
71
73
|
export const TokenNotFoundError = createError('TokenNotFoundError', 'Token not found');
|
package/lib/events.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const API_EVENTS: {
|
|
2
2
|
readonly API_LOGIN_TOKEN_CREATED: "API_LOGIN_TOKEN_CREATED";
|
|
3
|
+
readonly API_LOGIN_FAILED: "API_LOGIN_FAILED";
|
|
3
4
|
readonly API_LOGOUT: "API_LOGOUT";
|
|
4
5
|
readonly ACL_DENIED: "ACL_DENIED";
|
|
5
6
|
readonly ACL_GRANTED_SENSITIVE: "ACL_GRANTED_SENSITIVE";
|
package/lib/events.js
CHANGED
package/lib/express/index.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
1
9
|
import e from 'express';
|
|
2
10
|
import cookieParser from 'cookie-parser';
|
|
3
11
|
import { pluginRegistry } from '@unchainedshop/core';
|
|
4
12
|
import { createHash } from 'node:crypto';
|
|
5
|
-
import {
|
|
6
|
-
import { join } from 'node:path';
|
|
13
|
+
import { runWithAuditContext } from '@unchainedshop/events';
|
|
7
14
|
import { getCurrentContextResolver } from "../context.js";
|
|
8
15
|
import { createAuthContext } from "../middleware/createAuthMiddleware.js";
|
|
9
16
|
import createMCPMiddleware from "./createMCPMiddleware.js";
|
|
10
17
|
import { connectChat } from "./chatHandler.js";
|
|
11
18
|
import { mountRoutes } from "./mountRoutes.js";
|
|
12
19
|
import { createBackchannelLogoutRoute } from "../handlers/createBackchannelLogoutHandler.js";
|
|
13
|
-
import { preparePluginAssets, resolveAdminUIPath, } from "../adminUiPlugins.js";
|
|
20
|
+
import { preparePluginAssets, prepareAdminUIHTML, resolveAdminUIHTML, injectAdminUIImportMap, buildImportMapTag, resolveAdminUIPath, } from "../adminUiPlugins.js";
|
|
14
21
|
export const adminUIRouter = (enabled = true, theme, plugins = []) => {
|
|
15
22
|
const router = e.Router();
|
|
16
23
|
const adminUIPath = resolveAdminUIPath();
|
|
@@ -22,7 +29,8 @@ export const adminUIRouter = (enabled = true, theme, plugins = []) => {
|
|
|
22
29
|
let css = '/* default theme */';
|
|
23
30
|
if (theme) {
|
|
24
31
|
try {
|
|
25
|
-
const
|
|
32
|
+
const themeModuleUrl = import.meta.resolve('@unchainedshop/admin-ui/theme');
|
|
33
|
+
const { generateThemeCSS } = await import(__rewriteRelativeImportExtension(themeModuleUrl));
|
|
26
34
|
css = generateThemeCSS(theme);
|
|
27
35
|
}
|
|
28
36
|
catch {
|
|
@@ -45,7 +53,7 @@ export const adminUIRouter = (enabled = true, theme, plugins = []) => {
|
|
|
45
53
|
});
|
|
46
54
|
const log = { info: console.info, warn: console.warn };
|
|
47
55
|
const devMode = process.env.NODE_ENV !== 'production';
|
|
48
|
-
const { routes: pluginRoutes } = preparePluginAssets(plugins, log, { devMode });
|
|
56
|
+
const { routes: pluginRoutes, importMapTag, importMapJSON, } = preparePluginAssets(plugins, log, { devMode });
|
|
49
57
|
for (const [path, asset] of pluginRoutes) {
|
|
50
58
|
router.get(path, (req, res) => {
|
|
51
59
|
if (asset.etag) {
|
|
@@ -63,16 +71,23 @@ export const adminUIRouter = (enabled = true, theme, plugins = []) => {
|
|
|
63
71
|
.send(body);
|
|
64
72
|
});
|
|
65
73
|
}
|
|
66
|
-
router.use(e.static(adminUIPath));
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
router.use(e.static(adminUIPath, importMapTag ? { index: false } : undefined));
|
|
75
|
+
if (importMapJSON) {
|
|
76
|
+
const preparedHTML = prepareAdminUIHTML(adminUIPath);
|
|
77
|
+
router.get(/(.*)/, (req, res) => {
|
|
78
|
+
if (devMode)
|
|
79
|
+
res.set('Cache-Control', 'no-cache');
|
|
80
|
+
const nonce = res.locals?.cspNonce;
|
|
81
|
+
const tag = nonce ? buildImportMapTag(importMapJSON, nonce) : importMapTag;
|
|
82
|
+
return res.type('text/html').send(injectAdminUIImportMap(preparedHTML, req.path, tag));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const preparedHTML = prepareAdminUIHTML(adminUIPath);
|
|
87
|
+
router.get(/(.*)/, (req, res) => {
|
|
88
|
+
return res.type('text/html').send(resolveAdminUIHTML(preparedHTML, req.path));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
76
91
|
}
|
|
77
92
|
return router;
|
|
78
93
|
};
|
|
@@ -112,7 +127,7 @@ const createAddContextMiddleware = (authConfig, trustProxy = false) => async fun
|
|
|
112
127
|
};
|
|
113
128
|
const authContext = await createAuthContext(authContextParams, authConfig);
|
|
114
129
|
const context = getCurrentContextResolver();
|
|
115
|
-
|
|
130
|
+
const unchainedContext = await context({
|
|
116
131
|
setHeader,
|
|
117
132
|
getHeader,
|
|
118
133
|
remoteAddress,
|
|
@@ -124,6 +139,12 @@ const createAddContextMiddleware = (authConfig, trustProxy = false) => async fun
|
|
|
124
139
|
impersonatorId: authContext.impersonatorId,
|
|
125
140
|
tokenVersion: authContext.tokenVersion,
|
|
126
141
|
}, req, res);
|
|
142
|
+
req.unchainedContext = unchainedContext;
|
|
143
|
+
req._auditContext = {
|
|
144
|
+
userId: unchainedContext.userId,
|
|
145
|
+
userName: unchainedContext.user?.username || unchainedContext.user?.emails?.[0]?.address,
|
|
146
|
+
remoteAddress,
|
|
147
|
+
};
|
|
127
148
|
next();
|
|
128
149
|
}
|
|
129
150
|
catch (error) {
|
|
@@ -152,7 +173,13 @@ export const connect = async (expressApp, { graphqlHandler, unchainedAPI, }, { a
|
|
|
152
173
|
}
|
|
153
174
|
expressApp.use(cookieParser());
|
|
154
175
|
expressApp.use(createAddContextMiddleware(authConfig, trustProxy || allowRemoteToLocalhostSecureCookies));
|
|
155
|
-
expressApp.use(graphqlHandler.graphqlEndpoint,
|
|
176
|
+
expressApp.use(graphqlHandler.graphqlEndpoint, (req, res) => {
|
|
177
|
+
const auditCtx = req._auditContext;
|
|
178
|
+
if (auditCtx) {
|
|
179
|
+
return runWithAuditContext(auditCtx, () => graphqlHandler.handle(req, res));
|
|
180
|
+
}
|
|
181
|
+
return graphqlHandler.handle(req, res);
|
|
182
|
+
});
|
|
156
183
|
expressApp.use(MCP_API_PATH, e.json({ limit: '10mb' }));
|
|
157
184
|
expressApp.use(MCP_API_PATH, createMCPMiddleware);
|
|
158
185
|
if (chat) {
|
package/lib/fastify/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface AdminUIRouterOptions {
|
|
|
11
11
|
enabled?: boolean;
|
|
12
12
|
theme?: AdminUIThemeConfig;
|
|
13
13
|
plugins?: AdminUIPluginConfig[];
|
|
14
|
+
importMapTag?: string | null;
|
|
15
|
+
importMapJSON?: string | null;
|
|
14
16
|
}
|
|
15
17
|
export declare const unchainedLogger: (prefix: string) => FastifyBaseLogger;
|
|
16
18
|
export declare const connect: (fastify: FastifyInstance, { graphqlHandler, unchainedAPI, }: {
|
package/lib/fastify/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { runWithAuditContext } from '@unchainedshop/events';
|
|
1
10
|
import { getCurrentContextResolver } from "../context.js";
|
|
2
11
|
import { createAuthContext } from "../middleware/createAuthMiddleware.js";
|
|
3
12
|
import { pluginRegistry } from '@unchainedshop/core';
|
|
@@ -7,10 +16,9 @@ import mcpHandler from "./mcpHandler.js";
|
|
|
7
16
|
import { connectChat } from "./chatHandler.js";
|
|
8
17
|
import { mountRoutes } from "./mountRoutes.js";
|
|
9
18
|
import { createHash } from 'node:crypto';
|
|
10
|
-
import {
|
|
11
|
-
import { join } from 'node:path';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
12
20
|
import { createBackchannelLogoutRoute } from "../handlers/createBackchannelLogoutHandler.js";
|
|
13
|
-
import { preparePluginAssets, resolveAdminUIPath, } from "../adminUiPlugins.js";
|
|
21
|
+
import { preparePluginAssets, prepareAdminUIHTML, resolveAdminUIHTML, injectAdminUIImportMap, buildImportMapTag, resolveAdminUIPath, } from "../adminUiPlugins.js";
|
|
14
22
|
const resolveUserRemoteAddress = (req, trustProxy = false) => {
|
|
15
23
|
let remoteAddress;
|
|
16
24
|
if (trustProxy) {
|
|
@@ -46,7 +54,7 @@ const createMiddlewareHook = (authConfig, trustProxy = false) => async function
|
|
|
46
54
|
};
|
|
47
55
|
const authContext = await createAuthContext(authContextParams, authConfig);
|
|
48
56
|
const context = getCurrentContextResolver();
|
|
49
|
-
|
|
57
|
+
const unchainedContext = await context({
|
|
50
58
|
setHeader,
|
|
51
59
|
getHeader,
|
|
52
60
|
remoteAddress,
|
|
@@ -58,6 +66,12 @@ const createMiddlewareHook = (authConfig, trustProxy = false) => async function
|
|
|
58
66
|
impersonatorId: authContext.impersonatorId,
|
|
59
67
|
tokenVersion: authContext.tokenVersion,
|
|
60
68
|
}, req, reply);
|
|
69
|
+
req.unchainedContext = unchainedContext;
|
|
70
|
+
req._auditContext = {
|
|
71
|
+
userId: unchainedContext.userId,
|
|
72
|
+
userName: unchainedContext.user?.username || unchainedContext.user?.emails?.[0]?.address,
|
|
73
|
+
remoteAddress,
|
|
74
|
+
};
|
|
61
75
|
};
|
|
62
76
|
export const unchainedLogger = (prefix) => {
|
|
63
77
|
const logger = createLogger(prefix);
|
|
@@ -103,10 +117,15 @@ export const connect = async (fastify, { graphqlHandler, unchainedAPI, }, { allo
|
|
|
103
117
|
url: graphqlHandler.graphqlEndpoint,
|
|
104
118
|
method: ['GET', 'POST', 'OPTIONS'],
|
|
105
119
|
handler: async (req, reply) => {
|
|
106
|
-
|
|
120
|
+
const auditCtx = req._auditContext;
|
|
121
|
+
const handle = () => graphqlHandler.handleNodeRequestAndResponse(req, reply, {
|
|
107
122
|
req,
|
|
108
123
|
reply,
|
|
109
124
|
});
|
|
125
|
+
if (auditCtx) {
|
|
126
|
+
return runWithAuditContext(auditCtx, handle);
|
|
127
|
+
}
|
|
128
|
+
return handle();
|
|
110
129
|
},
|
|
111
130
|
});
|
|
112
131
|
fastify.route({
|
|
@@ -130,7 +149,8 @@ export const connect = async (fastify, { graphqlHandler, unchainedAPI, }, { allo
|
|
|
130
149
|
let css = '/* default theme */';
|
|
131
150
|
if (adminUIOptions?.theme) {
|
|
132
151
|
try {
|
|
133
|
-
const
|
|
152
|
+
const themeModuleUrl = import.meta.resolve('@unchainedshop/admin-ui/theme');
|
|
153
|
+
const { generateThemeCSS } = await import(__rewriteRelativeImportExtension(themeModuleUrl));
|
|
134
154
|
css = generateThemeCSS(adminUIOptions.theme);
|
|
135
155
|
}
|
|
136
156
|
catch {
|
|
@@ -152,7 +172,7 @@ export const connect = async (fastify, { graphqlHandler, unchainedAPI, }, { allo
|
|
|
152
172
|
.send(css);
|
|
153
173
|
});
|
|
154
174
|
const devMode = process.env.NODE_ENV !== 'production';
|
|
155
|
-
const { routes: pluginRoutes } = preparePluginAssets(adminUIPlugins, fastify.log, {
|
|
175
|
+
const { routes: pluginRoutes, importMapTag, importMapJSON, } = preparePluginAssets(adminUIPlugins, fastify.log, {
|
|
156
176
|
devMode,
|
|
157
177
|
});
|
|
158
178
|
for (const [path, asset] of pluginRoutes) {
|
|
@@ -177,6 +197,8 @@ export const connect = async (fastify, { graphqlHandler, unchainedAPI, }, { allo
|
|
|
177
197
|
enabled: true,
|
|
178
198
|
prefix: adminUIOptions?.prefix || '/',
|
|
179
199
|
plugins: adminUIPlugins,
|
|
200
|
+
importMapTag,
|
|
201
|
+
importMapJSON,
|
|
180
202
|
});
|
|
181
203
|
}
|
|
182
204
|
};
|
|
@@ -209,24 +231,43 @@ export const adminUIRouter = async (fastify, opts) => {
|
|
|
209
231
|
if (fastifyStatic) {
|
|
210
232
|
const adminUIPath = resolveAdminUIPath();
|
|
211
233
|
if (adminUIPath) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
234
|
+
if (opts.importMapJSON) {
|
|
235
|
+
const preparedHTML = prepareAdminUIHTML(adminUIPath);
|
|
236
|
+
await fastify.register(fastifyStatic, {
|
|
237
|
+
root: adminUIPath,
|
|
238
|
+
prefix: opts.prefix || '/',
|
|
239
|
+
wildcard: false,
|
|
240
|
+
index: false,
|
|
241
|
+
});
|
|
242
|
+
fastify.setNotFoundHandler(async (request, reply) => {
|
|
243
|
+
if (request.method === 'GET' && !request.url.includes('.')) {
|
|
244
|
+
if (process.env.NODE_ENV !== 'production')
|
|
245
|
+
reply.header('Cache-Control', 'no-cache');
|
|
246
|
+
const nonce = reply.cspNonce?.script;
|
|
247
|
+
const tag = nonce ? buildImportMapTag(opts.importMapJSON, nonce) : opts.importMapTag;
|
|
248
|
+
return reply
|
|
249
|
+
.type('text/html')
|
|
250
|
+
.send(injectAdminUIImportMap(preparedHTML, request.url, tag, opts.prefix));
|
|
225
251
|
}
|
|
226
|
-
return reply.
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
252
|
+
return reply.code(404).send({ error: 'Not Found' });
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const preparedHTML = prepareAdminUIHTML(adminUIPath);
|
|
257
|
+
await fastify.register(fastifyStatic, {
|
|
258
|
+
root: adminUIPath,
|
|
259
|
+
prefix: opts.prefix || '/',
|
|
260
|
+
wildcard: false,
|
|
261
|
+
});
|
|
262
|
+
fastify.setNotFoundHandler(async (request, reply) => {
|
|
263
|
+
if (request.method === 'GET' && !request.url.includes('.')) {
|
|
264
|
+
return reply
|
|
265
|
+
.type('text/html')
|
|
266
|
+
.send(resolveAdminUIHTML(preparedHTML, request.url, opts.prefix));
|
|
267
|
+
}
|
|
268
|
+
return reply.code(404).send({ error: 'Not Found' });
|
|
269
|
+
});
|
|
270
|
+
}
|
|
230
271
|
return;
|
|
231
272
|
}
|
|
232
273
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildObfuscatedFieldsFilter } from '@unchainedshop/utils';
|
|
2
|
+
import { WorkerDirector } from '@unchainedshop/core';
|
|
2
3
|
const allocateWork = async (context, options) => {
|
|
3
|
-
const { modules } = context;
|
|
4
4
|
const removePrivateFieldsFromWork = buildObfuscatedFieldsFilter(context.options.worker?.blacklistedVariables);
|
|
5
|
-
const work = await
|
|
5
|
+
const work = await WorkerDirector.allocateWork(context, {
|
|
6
6
|
types: options.types || [],
|
|
7
7
|
worker: options.worker || '',
|
|
8
8
|
});
|