@unchainedshop/api 4.7.2 → 4.8.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/lib/acl.js +1 -29
- package/lib/api-index.d.ts +0 -1
- package/lib/chat/generateImageHandler.js +5 -6
- package/lib/context.d.ts +0 -1
- package/lib/context.js +3 -8
- package/lib/events.d.ts +0 -2
- package/lib/events.js +0 -2
- package/lib/express/createMCPMiddleware.js +1 -1
- package/lib/express/index.d.ts +9 -6
- package/lib/express/index.js +125 -52
- package/lib/fastify/index.d.ts +9 -6
- package/lib/fastify/index.js +113 -51
- package/lib/fastify/mcpHandler.js +1 -1
- package/lib/loaders/buildTextMap.js +13 -15
- package/lib/mcp/tools/assortment/assortmentManagement.d.ts +2 -2
- package/lib/mcp/tools/assortment/assortmentManagement.js +2 -2
- package/lib/mcp/tools/assortment/handlers/getAssortment.js +1 -1
- package/lib/mcp/tools/assortment/schemas.d.ts +141 -322
- package/lib/mcp/tools/assortment/schemas.js +96 -179
- package/lib/mcp/tools/filter/filterManagement.d.ts +2 -2
- package/lib/mcp/tools/filter/filterManagement.js +2 -2
- package/lib/mcp/tools/filter/schemas.d.ts +75 -184
- package/lib/mcp/tools/filter/schemas.js +51 -87
- package/lib/mcp/tools/localization/getLocalizationsConfig.d.ts +1 -1
- package/lib/mcp/tools/localization/localizationManagement.d.ts +2 -2
- package/lib/mcp/tools/localization/localizationManagement.js +2 -2
- package/lib/mcp/tools/localization/schemas.d.ts +47 -144
- package/lib/mcp/tools/localization/schemas.js +36 -51
- package/lib/mcp/tools/order/handlers/getTopCustomers.d.ts +0 -2
- package/lib/mcp/tools/order/orderManagement.d.ts +2 -2
- package/lib/mcp/tools/order/orderManagement.js +2 -2
- package/lib/mcp/tools/order/schemas.d.ts +75 -184
- package/lib/mcp/tools/order/schemas.js +51 -113
- package/lib/mcp/tools/product/handlers/getProduct.js +1 -1
- package/lib/mcp/tools/product/productManagement.d.ts +2 -2
- package/lib/mcp/tools/product/productManagement.js +2 -2
- package/lib/mcp/tools/product/schemas.d.ts +317 -642
- package/lib/mcp/tools/product/schemas.js +191 -287
- package/lib/mcp/tools/provider/providerManagement.d.ts +2 -2
- package/lib/mcp/tools/provider/providerManagement.js +2 -2
- package/lib/mcp/tools/provider/schemas.d.ts +47 -142
- package/lib/mcp/tools/provider/schemas.js +32 -67
- package/lib/mcp/tools/quotation/handlers/getQuotation.d.ts +0 -2
- package/lib/mcp/tools/quotation/handlers/listQuotations.d.ts +0 -2
- package/lib/mcp/tools/quotation/handlers/makeQuotationProposal.d.ts +0 -2
- package/lib/mcp/tools/quotation/handlers/rejectQuotation.d.ts +0 -2
- package/lib/mcp/tools/quotation/handlers/verifyQuotation.d.ts +0 -2
- package/lib/mcp/tools/quotation/quotationManagement.d.ts +2 -2
- package/lib/mcp/tools/quotation/quotationManagement.js +2 -2
- package/lib/mcp/tools/quotation/schemas.d.ts +40 -111
- package/lib/mcp/tools/quotation/schemas.js +29 -54
- package/lib/mcp/tools/system/handlers/countEvents.d.ts +1 -8
- package/lib/mcp/tools/system/handlers/countWork.d.ts +1 -10
- package/lib/mcp/tools/system/handlers/countWork.js +0 -1
- package/lib/mcp/tools/system/handlers/index.d.ts +4 -4
- package/lib/mcp/tools/system/handlers/listEvents.d.ts +1 -12
- package/lib/mcp/tools/system/handlers/listWork.d.ts +1 -14
- package/lib/mcp/tools/system/handlers/listWork.js +0 -1
- package/lib/mcp/tools/system/schemas.d.ts +84 -179
- package/lib/mcp/tools/system/schemas.js +67 -188
- package/lib/mcp/tools/system/systemManagement.d.ts +1 -1
- package/lib/mcp/tools/system/systemManagement.js +1 -1
- package/lib/mcp/tools/users/handlers/addUserEmail.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/createUser.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/enrollUser.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/getCurrentUser.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/getUser.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/listUsers.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/setUserTags.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/setUserUsername.d.ts +0 -2
- package/lib/mcp/tools/users/handlers/updateUser.d.ts +0 -2
- package/lib/mcp/tools/users/schemas.d.ts +163 -296
- package/lib/mcp/tools/users/schemas.js +71 -152
- package/lib/mcp/tools/users/usersManagement.d.ts +2 -2
- package/lib/mcp/tools/users/usersManagement.js +2 -2
- package/lib/mcp/utils/getNormalizedQuotationDetails.d.ts +0 -2
- package/lib/mcp/utils/getNormalizedUserDetails.d.ts +0 -2
- package/lib/mcp/utils/normalizeMediaUrl.js +1 -1
- package/lib/mcp/utils/sanitizeLocalizationEntityData.d.ts +1 -1
- package/lib/mcp/utils/sharedSchemas.d.ts +39 -28
- package/lib/mcp/utils/sharedSchemas.js +47 -28
- package/lib/mcp/utils/validateIsoCode.d.ts +1 -1
- package/lib/resolvers/mutations/accounts/changePassword.js +1 -4
- package/lib/resolvers/mutations/index.d.ts +6 -1
- package/lib/resolvers/mutations/index.js +12 -2
- package/lib/resolvers/mutations/orders/updateCart.js +1 -7
- package/lib/resolvers/queries/orders/orders.js +2 -2
- package/lib/resolvers/type/index.d.ts +1 -0
- package/lib/resolvers/type/order/order-delivery-pickup-types.d.ts +1 -0
- package/lib/resolvers/type/order/order-delivery-pickup-types.js +7 -0
- package/lib/roles/index.js +0 -1
- package/lib/roles/loggedIn.js +0 -1
- package/lib/schema/mutation.js +44 -6
- package/lib/schema/types/index.js +2 -2
- package/lib/schema/types/order/delivery.js +2 -0
- package/package.json +17 -8
package/lib/acl.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import { Roles } from '@unchainedshop/roles';
|
|
2
|
-
import { emit } from '@unchainedshop/events';
|
|
3
2
|
import { NoPermissionError, PermissionSystemError } from "./errors.js";
|
|
4
|
-
import { API_EVENTS } from "./events.js";
|
|
5
|
-
const SENSITIVE_ACTION_PREFIXES = [
|
|
6
|
-
'manageUser',
|
|
7
|
-
'login',
|
|
8
|
-
'logout',
|
|
9
|
-
'reset',
|
|
10
|
-
'forgot',
|
|
11
|
-
'impersonate',
|
|
12
|
-
'updateUser',
|
|
13
|
-
'createUser',
|
|
14
|
-
];
|
|
15
3
|
const defaultOptions = {
|
|
16
4
|
showKey: true,
|
|
17
5
|
mapArgs: (...args) => args,
|
|
@@ -31,27 +19,11 @@ export const ensureIsFunction = (fn, action, options, key) => {
|
|
|
31
19
|
});
|
|
32
20
|
}
|
|
33
21
|
};
|
|
34
|
-
const isSensitiveAction = (action) => {
|
|
35
|
-
return SENSITIVE_ACTION_PREFIXES.some((prefix) => action.startsWith(prefix));
|
|
36
|
-
};
|
|
37
22
|
const checkAction = async (context, action, args = emptyArray, options = emptyObject) => {
|
|
38
23
|
const { key } = options || emptyObject;
|
|
39
24
|
const hasPermission = await Roles.userHasPermission(context, action, args);
|
|
40
|
-
if (hasPermission)
|
|
41
|
-
if (isSensitiveAction(action)) {
|
|
42
|
-
await emit(API_EVENTS.ACL_GRANTED_SENSITIVE, {
|
|
43
|
-
userId: context.userId,
|
|
44
|
-
action,
|
|
45
|
-
key,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
25
|
+
if (hasPermission)
|
|
48
26
|
return;
|
|
49
|
-
}
|
|
50
|
-
await emit(API_EVENTS.ACL_DENIED, {
|
|
51
|
-
userId: context.userId,
|
|
52
|
-
action,
|
|
53
|
-
key,
|
|
54
|
-
});
|
|
55
27
|
const keyText = key && key !== '' ? ` in "${key}"` : '';
|
|
56
28
|
throw new NoPermissionError({
|
|
57
29
|
userId: context.userId,
|
package/lib/api-index.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export * from './loaders/index.ts';
|
|
|
8
8
|
export * from './errors.ts';
|
|
9
9
|
export * as acl from './acl.ts';
|
|
10
10
|
export * as roles from './roles/index.ts';
|
|
11
|
-
export type { OIDCProviderConfig, AuthConfig } from './auth.ts';
|
|
12
11
|
export { createContextResolver, getCurrentContextResolver, setCurrentContextResolver };
|
|
13
12
|
export type UnchainedServerOptions = {
|
|
14
13
|
roles?: any;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z } from 'zod/v4-mini';
|
|
2
2
|
import mime from 'mime/lite';
|
|
3
3
|
import { createLogger } from '@unchainedshop/logger';
|
|
4
4
|
const logger = createLogger('unchained:api:mcp');
|
|
@@ -12,9 +12,9 @@ try {
|
|
|
12
12
|
catch {
|
|
13
13
|
}
|
|
14
14
|
const inputSchema = z.object({
|
|
15
|
-
prompt: z.string().describe('The prompt to generate the image from'),
|
|
15
|
+
prompt: z.string().check(z.describe('The prompt to generate the image from')),
|
|
16
16
|
size: z
|
|
17
|
-
.enum([
|
|
17
|
+
.optional(z.enum([
|
|
18
18
|
'512x512',
|
|
19
19
|
'768x768',
|
|
20
20
|
'1024x1024',
|
|
@@ -26,9 +26,8 @@ const inputSchema = z.object({
|
|
|
26
26
|
'1120x640',
|
|
27
27
|
'1344x768',
|
|
28
28
|
'1792x1024',
|
|
29
|
-
])
|
|
30
|
-
.
|
|
31
|
-
.describe('The size of the image (default is 1024x1024).'),
|
|
29
|
+
]))
|
|
30
|
+
.check(z.describe('The size of the image (default is 1024x1024).')),
|
|
32
31
|
});
|
|
33
32
|
const generateImageHandler = (req) => ({ model, uploadUrl = `${process.env.ROOT_URL}/temp-upload`, }) => {
|
|
34
33
|
return tool({
|
package/lib/context.d.ts
CHANGED
package/lib/context.js
CHANGED
|
@@ -6,7 +6,7 @@ export const getCurrentContextResolver = () => context;
|
|
|
6
6
|
export const setCurrentContextResolver = (newContext) => {
|
|
7
7
|
context = newContext;
|
|
8
8
|
};
|
|
9
|
-
export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({ getHeader, setHeader, remoteAddress, remotePort, userId, impersonatorId, accessToken,
|
|
9
|
+
export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({ getHeader, setHeader, remoteAddress, remotePort, userId, impersonatorId, accessToken, login, logout, }) => {
|
|
10
10
|
const abstractHttpServerContext = { remoteAddress, remotePort, getHeader, setHeader };
|
|
11
11
|
const loaders = instantiateLoaders(unchainedAPI);
|
|
12
12
|
const localeContext = await getLocaleContext(abstractHttpServerContext, unchainedAPI);
|
|
@@ -21,13 +21,8 @@ export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({
|
|
|
21
21
|
if (userId && !userContext.userId) {
|
|
22
22
|
const user = await unchainedAPI.modules.users.findUserById(userId);
|
|
23
23
|
if (user) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
userContext.user = user;
|
|
29
|
-
userContext.userId = user._id;
|
|
30
|
-
}
|
|
24
|
+
userContext.user = user;
|
|
25
|
+
userContext.userId = user._id;
|
|
31
26
|
}
|
|
32
27
|
}
|
|
33
28
|
return {
|
package/lib/events.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export declare const API_EVENTS: {
|
|
2
2
|
readonly API_LOGIN_TOKEN_CREATED: "API_LOGIN_TOKEN_CREATED";
|
|
3
3
|
readonly API_LOGOUT: "API_LOGOUT";
|
|
4
|
-
readonly ACL_DENIED: "ACL_DENIED";
|
|
5
|
-
readonly ACL_GRANTED_SENSITIVE: "ACL_GRANTED_SENSITIVE";
|
|
6
4
|
};
|
|
7
5
|
export type API_EVENTS = (typeof API_EVENTS)[keyof typeof API_EVENTS];
|
package/lib/events.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import initMCPServer from "../mcp/index.js";
|
|
2
1
|
import { createLogger } from '@unchainedshop/logger';
|
|
3
2
|
const logger = createLogger('unchained:api:mcp');
|
|
4
3
|
let StreamableHTTPServerTransport;
|
|
@@ -35,6 +34,7 @@ const handlePostRequest = async (req, res) => {
|
|
|
35
34
|
}
|
|
36
35
|
};
|
|
37
36
|
const roles = req.unchainedContext.user?.roles || [];
|
|
37
|
+
const { default: initMCPServer } = await import("../mcp/index.js");
|
|
38
38
|
const server = initMCPServer(new McpServer({
|
|
39
39
|
name: 'Unchained MCP Server',
|
|
40
40
|
version: '1.0.0',
|
package/lib/express/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import e from 'express';
|
|
2
2
|
import type { YogaServerInstance } from 'graphql-yoga';
|
|
3
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
3
4
|
import type { UnchainedCore } from '@unchainedshop/core';
|
|
4
|
-
import type { AuthConfig } from '../auth.ts';
|
|
5
5
|
import type { ChatConfiguration } from '../chat/utils.ts';
|
|
6
6
|
import { connectChat } from './chatHandler.ts';
|
|
7
7
|
export interface AdminUIRouterOptions {
|
|
@@ -9,14 +9,17 @@ export interface AdminUIRouterOptions {
|
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
}
|
|
11
11
|
export declare const adminUIRouter: (enabled?: boolean) => import("express-serve-static-core").Router;
|
|
12
|
-
export declare const connect: (expressApp: e.Express, { graphqlHandler, unchainedAPI, }: {
|
|
12
|
+
export declare const connect: (expressApp: e.Express, { graphqlHandler, db, unchainedAPI, }: {
|
|
13
13
|
graphqlHandler: YogaServerInstance<any, any>;
|
|
14
|
+
db: mongodb.Db;
|
|
14
15
|
unchainedAPI: UnchainedCore;
|
|
15
|
-
}, { allowRemoteToLocalhostSecureCookies, adminUI, chat,
|
|
16
|
+
}, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
|
|
16
17
|
allowRemoteToLocalhostSecureCookies?: boolean;
|
|
17
18
|
adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
|
|
18
19
|
chat?: ChatConfiguration;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}) =>
|
|
20
|
+
initPluginMiddlewares?: (app: e.Express, { unchainedAPI }: {
|
|
21
|
+
unchainedAPI: UnchainedCore;
|
|
22
|
+
}) => void;
|
|
23
|
+
}) => void;
|
|
24
|
+
export declare const expressRouter: (enabled?: boolean) => import("express-serve-static-core").Router;
|
|
22
25
|
export { connectChat };
|
package/lib/express/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import e from 'express';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import session from 'express-session';
|
|
3
|
+
import multer from 'multer';
|
|
4
|
+
import MongoStore from "../mongo-store.js";
|
|
5
|
+
import { Passport } from 'passport';
|
|
6
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
7
|
+
import { emit } from '@unchainedshop/events';
|
|
4
8
|
import { getCurrentContextResolver } from "../context.js";
|
|
5
|
-
import
|
|
9
|
+
import createBulkImportMiddleware from "./createBulkImportMiddleware.js";
|
|
10
|
+
import createERCMetadataMiddleware from "./createERCMetadataMiddleware.js";
|
|
11
|
+
import createTempUploadMiddleware from "./createTempUploadMiddleware.js";
|
|
6
12
|
import createMCPMiddleware from "./createMCPMiddleware.js";
|
|
13
|
+
import { API_EVENTS } from "../events.js";
|
|
7
14
|
import { connectChat } from "./chatHandler.js";
|
|
8
|
-
import { mountRoutes } from "./mountRoutes.js";
|
|
9
|
-
import { createBackchannelLogoutRoute } from "../handlers/createBackchannelLogoutHandler.js";
|
|
10
15
|
export const adminUIRouter = (enabled = true) => {
|
|
11
16
|
const router = e.Router();
|
|
12
17
|
const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
|
|
@@ -19,53 +24,87 @@ export const adminUIRouter = (enabled = true) => {
|
|
|
19
24
|
}
|
|
20
25
|
return router;
|
|
21
26
|
};
|
|
22
|
-
const resolveUserRemoteAddress = (req
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const forwardedIps = forwardedFor?.split(',').map((ip) => ip.trim());
|
|
27
|
-
remoteAddress =
|
|
28
|
-
req.headers['x-real-ip'] ||
|
|
29
|
-
forwardedIps?.[forwardedIps.length - 1] ||
|
|
30
|
-
req.socket?.remoteAddress;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
remoteAddress = req.socket?.remoteAddress;
|
|
34
|
-
}
|
|
27
|
+
const resolveUserRemoteAddress = (req) => {
|
|
28
|
+
const remoteAddress = req.headers['x-real-ip'] ||
|
|
29
|
+
req.headers['x-forwarded-for'] ||
|
|
30
|
+
req.socket?.remoteAddress;
|
|
35
31
|
const remotePort = req.socket?.remotePort;
|
|
36
32
|
return { remoteAddress, remotePort };
|
|
37
33
|
};
|
|
38
|
-
const
|
|
39
|
-
const
|
|
34
|
+
const storage = multer.memoryStorage();
|
|
35
|
+
const upload = multer({ storage: storage });
|
|
36
|
+
const { BULK_IMPORT_API_PATH = '/bulk-import', ERC_METADATA_API_PATH = '/erc-metadata', TEMP_UPLOAD_API_PATH = '/temp-upload', MCP_API_PATH = '/mcp', GRAPHQL_API_PATH = '/graphql', UNCHAINED_COOKIE_NAME = 'unchained_token', UNCHAINED_COOKIE_PATH = '/', UNCHAINED_COOKIE_DOMAIN, UNCHAINED_COOKIE_SAMESITE = 'none', UNCHAINED_COOKIE_INSECURE, } = process.env;
|
|
37
|
+
const addContext = async function middlewareWithContext(req, res, next) {
|
|
40
38
|
try {
|
|
41
39
|
const setHeader = (key, value) => res.setHeader(key, value);
|
|
42
40
|
const getHeader = (key) => req.headers[key];
|
|
43
|
-
const
|
|
44
|
-
const setCookie = (name, value, options) => res.cookie(name, value, options);
|
|
45
|
-
const clearCookie = (name, options) => res.clearCookie(name, { ...options, maxAge: 0 });
|
|
46
|
-
const { remoteAddress, remotePort } = resolveUserRemoteAddress(req, trustProxy);
|
|
47
|
-
const authContextParams = {
|
|
48
|
-
setHeader,
|
|
49
|
-
getHeader,
|
|
50
|
-
getCookie,
|
|
51
|
-
setCookie,
|
|
52
|
-
clearCookie,
|
|
53
|
-
remoteAddress,
|
|
54
|
-
remotePort,
|
|
55
|
-
};
|
|
56
|
-
const authContext = await createAuthContext(authContextParams, authConfig);
|
|
41
|
+
const { remoteAddress, remotePort } = resolveUserRemoteAddress(req);
|
|
57
42
|
const context = getCurrentContextResolver();
|
|
43
|
+
const login = async (user, options = {}) => {
|
|
44
|
+
const { impersonator } = options;
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
req.login(user, (error, result) => {
|
|
47
|
+
if (error) {
|
|
48
|
+
return reject(error);
|
|
49
|
+
}
|
|
50
|
+
req.session.impersonatorId = impersonator?._id;
|
|
51
|
+
return resolve(result);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
const tokenObject = {
|
|
55
|
+
_id: req.sessionID,
|
|
56
|
+
userId: user._id,
|
|
57
|
+
tokenExpires: new Date(req.session?.cookie._expires),
|
|
58
|
+
};
|
|
59
|
+
await emit(API_EVENTS.API_LOGIN_TOKEN_CREATED, tokenObject);
|
|
60
|
+
user._inLoginMethodResponse = true;
|
|
61
|
+
return { user, ...tokenObject };
|
|
62
|
+
};
|
|
63
|
+
const logout = async (sessionId) => {
|
|
64
|
+
const { user } = req;
|
|
65
|
+
if (!user)
|
|
66
|
+
return false;
|
|
67
|
+
const currentSessionId = req.sessionID;
|
|
68
|
+
const targetSessionId = sessionId || currentSessionId;
|
|
69
|
+
const tokenObject = {
|
|
70
|
+
_id: targetSessionId,
|
|
71
|
+
userId: user._id,
|
|
72
|
+
};
|
|
73
|
+
if (sessionId && sessionId !== currentSessionId) {
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
req.sessionStore.destroy(sessionId, (error) => {
|
|
76
|
+
if (error) {
|
|
77
|
+
return reject(error);
|
|
78
|
+
}
|
|
79
|
+
return resolve();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await new Promise((resolve, reject) => {
|
|
85
|
+
req.logout((error, result) => {
|
|
86
|
+
if (error) {
|
|
87
|
+
return reject(error);
|
|
88
|
+
}
|
|
89
|
+
req.session.impersonatorId = null;
|
|
90
|
+
return resolve(result);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
await emit(API_EVENTS.API_LOGOUT, tokenObject);
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
const [, accessToken] = req.headers.authorization?.split(' ') || [];
|
|
58
98
|
req.unchainedContext = await context({
|
|
59
99
|
setHeader,
|
|
60
100
|
getHeader,
|
|
61
101
|
remoteAddress,
|
|
62
102
|
remotePort,
|
|
63
|
-
login
|
|
64
|
-
logout
|
|
65
|
-
accessToken
|
|
66
|
-
userId:
|
|
67
|
-
impersonatorId:
|
|
68
|
-
tokenVersion: authContext.tokenVersion,
|
|
103
|
+
login,
|
|
104
|
+
logout,
|
|
105
|
+
accessToken,
|
|
106
|
+
userId: req.user?._id,
|
|
107
|
+
impersonatorId: req.session.impersonatorId,
|
|
69
108
|
}, req, res);
|
|
70
109
|
next();
|
|
71
110
|
}
|
|
@@ -73,12 +112,8 @@ const createAddContextMiddleware = (authConfig, trustProxy = false) => async fun
|
|
|
73
112
|
next(error);
|
|
74
113
|
}
|
|
75
114
|
};
|
|
76
|
-
export const connect =
|
|
115
|
+
export const connect = (expressApp, { graphqlHandler, db, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, initPluginMiddlewares, } = {}) => {
|
|
77
116
|
if (allowRemoteToLocalhostSecureCookies) {
|
|
78
|
-
if (process.env.NODE_ENV === 'production') {
|
|
79
|
-
throw new Error('allowRemoteToLocalhostSecureCookies is not allowed in production. ' +
|
|
80
|
-
'Configure a proper CORS policy with specific allowed origins instead.');
|
|
81
|
-
}
|
|
82
117
|
expressApp.set('trust proxy', 1);
|
|
83
118
|
expressApp.use((req, res, next) => {
|
|
84
119
|
req.headers['x-forwarded-proto'] = 'https';
|
|
@@ -90,21 +125,59 @@ export const connect = async (expressApp, { graphqlHandler, unchainedAPI, }, { a
|
|
|
90
125
|
next();
|
|
91
126
|
});
|
|
92
127
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
const passport = new Passport();
|
|
129
|
+
passport.serializeUser(function serialize(user, done) {
|
|
130
|
+
done(null, user._id);
|
|
131
|
+
});
|
|
132
|
+
passport.deserializeUser(function deserialize(_id, done) {
|
|
133
|
+
done(null, { _id });
|
|
134
|
+
});
|
|
135
|
+
const name = UNCHAINED_COOKIE_NAME;
|
|
136
|
+
const domain = UNCHAINED_COOKIE_DOMAIN;
|
|
137
|
+
const path = UNCHAINED_COOKIE_PATH;
|
|
138
|
+
const secure = UNCHAINED_COOKIE_INSECURE ? false : true;
|
|
139
|
+
const sameSite = ({
|
|
140
|
+
none: 'none',
|
|
141
|
+
lax: 'lax',
|
|
142
|
+
strict: 'strict',
|
|
143
|
+
'1': true,
|
|
144
|
+
'0': false,
|
|
145
|
+
}[UNCHAINED_COOKIE_SAMESITE?.trim()?.toLowerCase()] || false);
|
|
146
|
+
expressApp.use(session({
|
|
147
|
+
secret: process.env.UNCHAINED_TOKEN_SECRET,
|
|
148
|
+
store: MongoStore.create({
|
|
149
|
+
client: db.client,
|
|
150
|
+
dbName: db.databaseName,
|
|
151
|
+
collectionName: 'sessions',
|
|
152
|
+
touchAfter: 24 * 3600,
|
|
153
|
+
}),
|
|
154
|
+
name,
|
|
155
|
+
saveUninitialized: false,
|
|
156
|
+
resave: false,
|
|
157
|
+
cookie: {
|
|
158
|
+
domain,
|
|
159
|
+
path,
|
|
160
|
+
sameSite,
|
|
161
|
+
secure,
|
|
162
|
+
httpOnly: true,
|
|
163
|
+
maxAge: 1000 * 60 * 60 * 24 * 7,
|
|
164
|
+
},
|
|
165
|
+
}), passport.initialize(), passport.session(), addContext);
|
|
166
|
+
expressApp.use(GRAPHQL_API_PATH, graphqlHandler.handle);
|
|
167
|
+
expressApp.use(ERC_METADATA_API_PATH, createERCMetadataMiddleware);
|
|
168
|
+
expressApp.use(BULK_IMPORT_API_PATH, createBulkImportMiddleware);
|
|
169
|
+
expressApp.use(TEMP_UPLOAD_API_PATH, upload.any(), createTempUploadMiddleware);
|
|
96
170
|
expressApp.use(MCP_API_PATH, e.json({ limit: '10mb' }));
|
|
97
171
|
expressApp.use(MCP_API_PATH, createMCPMiddleware);
|
|
98
172
|
if (chat) {
|
|
99
173
|
connectChat(expressApp, chat);
|
|
100
174
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
routes.push(createBackchannelLogoutRoute(authConfig.oidcProviders));
|
|
175
|
+
if (initPluginMiddlewares) {
|
|
176
|
+
initPluginMiddlewares(expressApp, { unchainedAPI });
|
|
104
177
|
}
|
|
105
|
-
mountRoutes(expressApp, unchainedAPI, routes);
|
|
106
178
|
if (adminUI) {
|
|
107
179
|
expressApp.use(typeof adminUI === 'object' ? adminUI.prefix : '/', adminUIRouter(true));
|
|
108
180
|
}
|
|
109
181
|
};
|
|
182
|
+
export const expressRouter = adminUIRouter;
|
|
110
183
|
export { connectChat };
|
package/lib/fastify/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AuthConfig } from '../auth.ts';
|
|
2
1
|
import type { YogaServerInstance } from 'graphql-yoga';
|
|
2
|
+
import type { mongodb } from '@unchainedshop/mongodb';
|
|
3
3
|
import type { UnchainedCore } from '@unchainedshop/core';
|
|
4
4
|
import type { FastifyBaseLogger, FastifyInstance, FastifyPluginAsync } from 'fastify';
|
|
5
5
|
import { connectChat } from './chatHandler.ts';
|
|
@@ -9,15 +9,18 @@ export interface AdminUIRouterOptions {
|
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
}
|
|
11
11
|
export declare const unchainedLogger: (prefix: string) => FastifyBaseLogger;
|
|
12
|
-
export declare const connect: (fastify: FastifyInstance, { graphqlHandler, unchainedAPI, }: {
|
|
12
|
+
export declare const connect: (fastify: FastifyInstance, { graphqlHandler, db, unchainedAPI, }: {
|
|
13
13
|
graphqlHandler: YogaServerInstance<any, any>;
|
|
14
|
+
db: mongodb.Db;
|
|
14
15
|
unchainedAPI: UnchainedCore;
|
|
15
|
-
}, { allowRemoteToLocalhostSecureCookies, adminUI, chat,
|
|
16
|
+
}, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
|
|
16
17
|
allowRemoteToLocalhostSecureCookies?: boolean;
|
|
17
18
|
adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
|
|
18
19
|
chat?: ChatConfiguration;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}) =>
|
|
20
|
+
initPluginMiddlewares?: (app: FastifyInstance, { unchainedAPI }: {
|
|
21
|
+
unchainedAPI: UnchainedCore;
|
|
22
|
+
}) => void;
|
|
23
|
+
}) => void;
|
|
22
24
|
export declare const adminUIRouter: FastifyPluginAsync<AdminUIRouterOptions>;
|
|
25
|
+
export declare const fastifyRouter: FastifyPluginAsync<AdminUIRouterOptions>;
|
|
23
26
|
export { connectChat };
|
package/lib/fastify/index.js
CHANGED
|
@@ -1,59 +1,64 @@
|
|
|
1
1
|
import { getCurrentContextResolver } from "../context.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import bulkImportHandler from "./bulkImportHandler.js";
|
|
3
|
+
import ercMetadataHandler from "./ercMetadataHandler.js";
|
|
4
|
+
import MongoStore from "../mongo-store.js";
|
|
5
|
+
import { emit } from '@unchainedshop/events';
|
|
6
|
+
import { API_EVENTS } from "../events.js";
|
|
7
|
+
import fastifySession from '@fastify/session';
|
|
4
8
|
import fastifyCookie from '@fastify/cookie';
|
|
9
|
+
import fastifyMultipart from '@fastify/multipart';
|
|
5
10
|
import { createLogger } from '@unchainedshop/logger';
|
|
6
11
|
import mcpHandler from "./mcpHandler.js";
|
|
12
|
+
import tempUploadHandler from "./tempUploadHandler.js";
|
|
7
13
|
import { connectChat } from "./chatHandler.js";
|
|
8
|
-
import { mountRoutes } from "./mountRoutes.js";
|
|
9
14
|
import { readFileSync } from 'node:fs';
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const forwardedFor = req.headers['x-forwarded-for'];
|
|
15
|
-
const forwardedIps = forwardedFor?.split(',').map((ip) => ip.trim());
|
|
16
|
-
remoteAddress =
|
|
17
|
-
req.headers['x-real-ip'] ||
|
|
18
|
-
forwardedIps?.[forwardedIps.length - 1] ||
|
|
19
|
-
req.socket?.remoteAddress;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
remoteAddress = req.socket?.remoteAddress;
|
|
23
|
-
}
|
|
15
|
+
const resolveUserRemoteAddress = (req) => {
|
|
16
|
+
const remoteAddress = req.headers['x-real-ip'] ||
|
|
17
|
+
req.headers['x-forwarded-for'] ||
|
|
18
|
+
req.socket?.remoteAddress;
|
|
24
19
|
const remotePort = req.socket?.remotePort;
|
|
25
20
|
return { remoteAddress, remotePort };
|
|
26
21
|
};
|
|
27
|
-
const { MCP_API_PATH = '/mcp' } = process.env;
|
|
28
|
-
const
|
|
22
|
+
const { MCP_API_PATH = '/mcp', GRAPHQL_API_PATH = '/graphql', BULK_IMPORT_API_PATH = '/bulk-import', TEMP_UPLOAD_API_PATH = '/temp-upload', ERC_METADATA_API_PATH = '/erc-metadata/:productId/:localeOrTokenFilename/:tokenFileName?', UNCHAINED_COOKIE_NAME = 'unchained_token', UNCHAINED_COOKIE_PATH = '/', UNCHAINED_COOKIE_DOMAIN, UNCHAINED_COOKIE_SAMESITE = 'none', UNCHAINED_COOKIE_INSECURE, } = process.env;
|
|
23
|
+
const middlewareHook = async function middlewareHook(req, reply) {
|
|
29
24
|
const setHeader = (key, value) => reply.header(key, value);
|
|
30
25
|
const getHeader = (key) => req.headers[key];
|
|
31
|
-
const
|
|
32
|
-
const setCookie = (name, value, options) => reply.setCookie(name, value, options);
|
|
33
|
-
const clearCookie = (name, options) => reply.clearCookie(name, { ...options, maxAge: 0 });
|
|
34
|
-
const { remoteAddress, remotePort } = resolveUserRemoteAddress(req, trustProxy);
|
|
35
|
-
const authContextParams = {
|
|
36
|
-
setHeader,
|
|
37
|
-
getHeader,
|
|
38
|
-
getCookie,
|
|
39
|
-
setCookie,
|
|
40
|
-
clearCookie,
|
|
41
|
-
remoteAddress,
|
|
42
|
-
remotePort,
|
|
43
|
-
};
|
|
44
|
-
const authContext = await createAuthContext(authContextParams, authConfig);
|
|
26
|
+
const { remoteAddress, remotePort } = resolveUserRemoteAddress(req);
|
|
45
27
|
const context = getCurrentContextResolver();
|
|
28
|
+
const login = async function (user, options = {}) {
|
|
29
|
+
const { impersonator } = options;
|
|
30
|
+
req.session.userId = user._id;
|
|
31
|
+
req.session.impersonatorId = impersonator?._id;
|
|
32
|
+
const tokenObject = {
|
|
33
|
+
_id: req.session.sessionId,
|
|
34
|
+
userId: user._id,
|
|
35
|
+
tokenExpires: new Date(req.session.cookie._expires),
|
|
36
|
+
};
|
|
37
|
+
await emit(API_EVENTS.API_LOGIN_TOKEN_CREATED, tokenObject);
|
|
38
|
+
user._inLoginMethodResponse = true;
|
|
39
|
+
return { user, ...tokenObject };
|
|
40
|
+
};
|
|
41
|
+
const logout = async function logout() {
|
|
42
|
+
const tokenObject = {
|
|
43
|
+
_id: req.session.sessionId,
|
|
44
|
+
userId: req.session?.userId,
|
|
45
|
+
};
|
|
46
|
+
req.session.userId = null;
|
|
47
|
+
req.session.impersonatorId = null;
|
|
48
|
+
await emit(API_EVENTS.API_LOGOUT, tokenObject);
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const [, accessToken] = req.headers.authorization?.split(' ') || [];
|
|
46
52
|
req.unchainedContext = await context({
|
|
47
53
|
setHeader,
|
|
48
54
|
getHeader,
|
|
49
55
|
remoteAddress,
|
|
50
56
|
remotePort,
|
|
51
|
-
login
|
|
52
|
-
logout
|
|
53
|
-
accessToken
|
|
54
|
-
userId:
|
|
55
|
-
impersonatorId:
|
|
56
|
-
tokenVersion: authContext.tokenVersion,
|
|
57
|
+
login,
|
|
58
|
+
logout,
|
|
59
|
+
accessToken,
|
|
60
|
+
userId: req.session.userId,
|
|
61
|
+
impersonatorId: req.session.impersonatorId,
|
|
57
62
|
}, req, reply);
|
|
58
63
|
};
|
|
59
64
|
export const unchainedLogger = (prefix) => {
|
|
@@ -72,12 +77,8 @@ export const unchainedLogger = (prefix) => {
|
|
|
72
77
|
};
|
|
73
78
|
return new Logger();
|
|
74
79
|
};
|
|
75
|
-
export const connect =
|
|
80
|
+
export const connect = (fastify, { graphqlHandler, db, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, initPluginMiddlewares, } = {}) => {
|
|
76
81
|
if (allowRemoteToLocalhostSecureCookies) {
|
|
77
|
-
if (process.env.NODE_ENV === 'production') {
|
|
78
|
-
throw new Error('allowRemoteToLocalhostSecureCookies is not allowed in production. ' +
|
|
79
|
-
'Configure a proper CORS policy with specific allowed origins instead.');
|
|
80
|
-
}
|
|
81
82
|
fastify.addHook('preHandler', async function (request) {
|
|
82
83
|
request.headers['x-forwarded-proto'] = 'https';
|
|
83
84
|
});
|
|
@@ -91,34 +92,94 @@ export const connect = async (fastify, { graphqlHandler, unchainedAPI, }, { allo
|
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
const cookieName = UNCHAINED_COOKIE_NAME;
|
|
96
|
+
const domain = UNCHAINED_COOKIE_DOMAIN;
|
|
97
|
+
const path = UNCHAINED_COOKIE_PATH;
|
|
98
|
+
const secure = UNCHAINED_COOKIE_INSECURE ? false : true;
|
|
99
|
+
const sameSite = ({
|
|
100
|
+
none: 'none',
|
|
101
|
+
lax: 'lax',
|
|
102
|
+
strict: 'strict',
|
|
103
|
+
'1': true,
|
|
104
|
+
'0': false,
|
|
105
|
+
}[UNCHAINED_COOKIE_SAMESITE?.trim()?.toLowerCase()] || false);
|
|
94
106
|
if (!fastify.hasPlugin('@fastify/cookie')) {
|
|
95
107
|
fastify.register(fastifyCookie);
|
|
96
108
|
}
|
|
109
|
+
fastify.register(fastifySession, {
|
|
110
|
+
secret: process.env.UNCHAINED_TOKEN_SECRET,
|
|
111
|
+
cookieName,
|
|
112
|
+
store: MongoStore.create({
|
|
113
|
+
client: db.client,
|
|
114
|
+
dbName: db.databaseName,
|
|
115
|
+
collectionName: 'sessions',
|
|
116
|
+
}),
|
|
117
|
+
cookie: {
|
|
118
|
+
domain,
|
|
119
|
+
httpOnly: true,
|
|
120
|
+
path,
|
|
121
|
+
secure,
|
|
122
|
+
sameSite,
|
|
123
|
+
maxAge: 1000 * 60 * 60 * 24 * 7,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
97
126
|
fastify.decorateRequest('unchainedContext');
|
|
98
|
-
fastify.addHook('onRequest',
|
|
127
|
+
fastify.addHook('onRequest', middlewareHook);
|
|
99
128
|
fastify.route({
|
|
100
|
-
url:
|
|
129
|
+
url: GRAPHQL_API_PATH,
|
|
101
130
|
method: ['GET', 'POST', 'OPTIONS'],
|
|
102
131
|
handler: async (req, reply) => {
|
|
103
|
-
|
|
132
|
+
const response = await graphqlHandler.handleNodeRequestAndResponse(req, reply, {
|
|
104
133
|
req,
|
|
105
134
|
reply,
|
|
106
135
|
});
|
|
136
|
+
response.headers.forEach((value, key) => {
|
|
137
|
+
reply.header(key, value);
|
|
138
|
+
});
|
|
139
|
+
reply.status(response.status);
|
|
140
|
+
reply.send(response.body);
|
|
141
|
+
return reply;
|
|
107
142
|
},
|
|
108
143
|
});
|
|
144
|
+
fastify.route({
|
|
145
|
+
url: ERC_METADATA_API_PATH,
|
|
146
|
+
method: ['GET'],
|
|
147
|
+
handler: ercMetadataHandler,
|
|
148
|
+
});
|
|
109
149
|
fastify.route({
|
|
110
150
|
url: MCP_API_PATH,
|
|
111
151
|
method: ['GET', 'POST', 'DELETE'],
|
|
112
152
|
handler: mcpHandler,
|
|
113
153
|
});
|
|
154
|
+
fastify.register((s, opts, registered) => {
|
|
155
|
+
s.register(fastifyMultipart, { throwFileSizeLimit: true, limits: { fileSize: 1024 * 1024 * 35 } });
|
|
156
|
+
s.route({
|
|
157
|
+
url: TEMP_UPLOAD_API_PATH,
|
|
158
|
+
method: ['POST'],
|
|
159
|
+
bodyLimit: 1024 * 1024 * 35,
|
|
160
|
+
handler: tempUploadHandler,
|
|
161
|
+
});
|
|
162
|
+
registered();
|
|
163
|
+
});
|
|
164
|
+
fastify.register((s, opts, registered) => {
|
|
165
|
+
s.removeAllContentTypeParsers();
|
|
166
|
+
s.addContentTypeParser('*', function (req, payload, done) {
|
|
167
|
+
done(null);
|
|
168
|
+
});
|
|
169
|
+
s.route({
|
|
170
|
+
url: BULK_IMPORT_API_PATH,
|
|
171
|
+
method: ['POST'],
|
|
172
|
+
bodyLimit: 1024 * 1024 * 1024 * 5,
|
|
173
|
+
handler: bulkImportHandler,
|
|
174
|
+
});
|
|
175
|
+
registered();
|
|
176
|
+
});
|
|
114
177
|
if (chat) {
|
|
115
178
|
connectChat(fastify, chat);
|
|
116
179
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
routes.push(createBackchannelLogoutRoute(authConfig.oidcProviders));
|
|
180
|
+
if (initPluginMiddlewares) {
|
|
181
|
+
initPluginMiddlewares(fastify, { unchainedAPI });
|
|
120
182
|
}
|
|
121
|
-
mountRoutes(fastify, unchainedAPI, routes);
|
|
122
183
|
if (adminUI) {
|
|
123
184
|
fastify.register(adminUIRouter, {
|
|
124
185
|
enabled: true,
|
|
@@ -182,4 +243,5 @@ export const adminUIRouter = async (fastify, opts) => {
|
|
|
182
243
|
}
|
|
183
244
|
}
|
|
184
245
|
};
|
|
246
|
+
export const fastifyRouter = adminUIRouter;
|
|
185
247
|
export { connectChat };
|