@unchainedshop/api 4.7.2 → 4.8.0

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.
Files changed (52) hide show
  1. package/lib/acl.js +1 -29
  2. package/lib/api-index.d.ts +0 -1
  3. package/lib/context.d.ts +0 -1
  4. package/lib/context.js +3 -8
  5. package/lib/events.d.ts +0 -2
  6. package/lib/events.js +0 -2
  7. package/lib/express/index.d.ts +9 -6
  8. package/lib/express/index.js +125 -52
  9. package/lib/fastify/index.d.ts +9 -6
  10. package/lib/fastify/index.js +113 -51
  11. package/lib/mcp/tools/localization/getLocalizationsConfig.d.ts +1 -1
  12. package/lib/mcp/tools/localization/schemas.d.ts +0 -34
  13. package/lib/mcp/tools/order/handlers/getTopCustomers.d.ts +0 -2
  14. package/lib/mcp/tools/quotation/handlers/getQuotation.d.ts +0 -2
  15. package/lib/mcp/tools/quotation/handlers/listQuotations.d.ts +0 -2
  16. package/lib/mcp/tools/quotation/handlers/makeQuotationProposal.d.ts +0 -2
  17. package/lib/mcp/tools/quotation/handlers/rejectQuotation.d.ts +0 -2
  18. package/lib/mcp/tools/quotation/handlers/verifyQuotation.d.ts +0 -2
  19. package/lib/mcp/tools/system/handlers/countEvents.d.ts +1 -8
  20. package/lib/mcp/tools/system/handlers/countWork.d.ts +1 -10
  21. package/lib/mcp/tools/system/handlers/countWork.js +0 -1
  22. package/lib/mcp/tools/system/handlers/index.d.ts +4 -4
  23. package/lib/mcp/tools/system/handlers/listEvents.d.ts +1 -12
  24. package/lib/mcp/tools/system/handlers/listWork.d.ts +1 -14
  25. package/lib/mcp/tools/system/handlers/listWork.js +0 -1
  26. package/lib/mcp/tools/users/handlers/addUserEmail.d.ts +0 -2
  27. package/lib/mcp/tools/users/handlers/createUser.d.ts +0 -2
  28. package/lib/mcp/tools/users/handlers/enrollUser.d.ts +0 -2
  29. package/lib/mcp/tools/users/handlers/getCurrentUser.d.ts +0 -2
  30. package/lib/mcp/tools/users/handlers/getUser.d.ts +0 -2
  31. package/lib/mcp/tools/users/handlers/listUsers.d.ts +0 -2
  32. package/lib/mcp/tools/users/handlers/setUserTags.d.ts +0 -2
  33. package/lib/mcp/tools/users/handlers/setUserUsername.d.ts +0 -2
  34. package/lib/mcp/tools/users/handlers/updateUser.d.ts +0 -2
  35. package/lib/mcp/utils/getNormalizedQuotationDetails.d.ts +0 -2
  36. package/lib/mcp/utils/getNormalizedUserDetails.d.ts +0 -2
  37. package/lib/mcp/utils/normalizeMediaUrl.js +1 -1
  38. package/lib/mcp/utils/sanitizeLocalizationEntityData.d.ts +1 -1
  39. package/lib/mcp/utils/validateIsoCode.d.ts +1 -1
  40. package/lib/resolvers/mutations/index.d.ts +6 -1
  41. package/lib/resolvers/mutations/index.js +12 -2
  42. package/lib/resolvers/mutations/orders/updateCart.js +1 -7
  43. package/lib/resolvers/queries/orders/orders.js +2 -2
  44. package/lib/resolvers/type/index.d.ts +1 -0
  45. package/lib/resolvers/type/order/order-delivery-pickup-types.d.ts +1 -0
  46. package/lib/resolvers/type/order/order-delivery-pickup-types.js +7 -0
  47. package/lib/roles/index.js +0 -1
  48. package/lib/roles/loggedIn.js +0 -1
  49. package/lib/schema/mutation.js +44 -6
  50. package/lib/schema/types/index.js +2 -2
  51. package/lib/schema/types/order/delivery.js +2 -0
  52. 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,
@@ -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;
package/lib/context.d.ts CHANGED
@@ -16,7 +16,6 @@ export interface UnchainedUserContext {
16
16
  userId?: string;
17
17
  impersonatorId?: string;
18
18
  accessToken?: string;
19
- tokenVersion?: number;
20
19
  user?: User;
21
20
  }
22
21
  export interface CustomAdminUiProperties {
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, tokenVersion, login, logout, }) => {
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
- const userTokenVersion = user.tokenVersion ?? 1;
25
- if (tokenVersion !== undefined && tokenVersion !== userTokenVersion) {
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,6 +1,4 @@
1
1
  export const API_EVENTS = {
2
2
  API_LOGIN_TOKEN_CREATED: 'API_LOGIN_TOKEN_CREATED',
3
3
  API_LOGOUT: 'API_LOGOUT',
4
- ACL_DENIED: 'ACL_DENIED',
5
- ACL_GRANTED_SENSITIVE: 'ACL_GRANTED_SENSITIVE',
6
4
  };
@@ -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, authConfig, trustProxy, }?: {
16
+ }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
16
17
  allowRemoteToLocalhostSecureCookies?: boolean;
17
18
  adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
18
19
  chat?: ChatConfiguration;
19
- authConfig?: AuthConfig;
20
- trustProxy?: boolean;
21
- }) => Promise<void>;
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 };
@@ -1,12 +1,17 @@
1
1
  import e from 'express';
2
- import cookieParser from 'cookie-parser';
3
- import { pluginRegistry } from '@unchainedshop/core';
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 { createAuthContext } from "../middleware/createAuthMiddleware.js";
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, trustProxy = false) => {
23
- let remoteAddress;
24
- if (trustProxy) {
25
- const forwardedFor = req.headers['x-forwarded-for'];
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 { MCP_API_PATH = '/mcp' } = process.env;
39
- const createAddContextMiddleware = (authConfig, trustProxy = false) => async function middlewareWithContext(req, res, next) {
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 getCookie = (name) => req.cookies?.[name];
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: authContext.login,
64
- logout: authContext.logout,
65
- accessToken: authContext.accessToken,
66
- userId: authContext.userId,
67
- impersonatorId: authContext.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 = async (expressApp, { graphqlHandler, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, authConfig, trustProxy = false, } = {}) => {
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
- expressApp.use(cookieParser());
94
- expressApp.use(createAddContextMiddleware(authConfig, trustProxy || allowRemoteToLocalhostSecureCookies));
95
- expressApp.use(graphqlHandler.graphqlEndpoint, graphqlHandler.handle);
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
- const routes = pluginRegistry.getRoutes();
102
- if (authConfig?.oidcProviders?.length) {
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 };
@@ -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, authConfig, trustProxy, }?: {
16
+ }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
16
17
  allowRemoteToLocalhostSecureCookies?: boolean;
17
18
  adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
18
19
  chat?: ChatConfiguration;
19
- authConfig?: AuthConfig;
20
- trustProxy?: boolean;
21
- }) => Promise<void>;
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 };
@@ -1,59 +1,64 @@
1
1
  import { getCurrentContextResolver } from "../context.js";
2
- import { createAuthContext } from "../middleware/createAuthMiddleware.js";
3
- import { pluginRegistry } from '@unchainedshop/core';
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
- import { createBackchannelLogoutRoute } from "../handlers/createBackchannelLogoutHandler.js";
11
- const resolveUserRemoteAddress = (req, trustProxy = false) => {
12
- let remoteAddress;
13
- if (trustProxy) {
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 createMiddlewareHook = (authConfig, trustProxy = false) => async function middlewareHook(req, reply) {
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 getCookie = (name) => req.cookies?.[name];
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: authContext.login,
52
- logout: authContext.logout,
53
- accessToken: authContext.accessToken,
54
- userId: authContext.userId,
55
- impersonatorId: authContext.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 = async (fastify, { graphqlHandler, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, authConfig, trustProxy = false, } = {}) => {
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', createMiddlewareHook(authConfig, trustProxy || allowRemoteToLocalhostSecureCookies));
127
+ fastify.addHook('onRequest', middlewareHook);
99
128
  fastify.route({
100
- url: graphqlHandler.graphqlEndpoint,
129
+ url: GRAPHQL_API_PATH,
101
130
  method: ['GET', 'POST', 'OPTIONS'],
102
131
  handler: async (req, reply) => {
103
- return graphqlHandler.handleNodeRequestAndResponse(req, reply, {
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
- const routes = pluginRegistry.getRoutes();
118
- if (authConfig?.oidcProviders?.length) {
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 };
@@ -1,3 +1,3 @@
1
1
  import type { Context } from 'vm';
2
- import type { LocalizationType, LocalizationModuleConfig } from './schemas.ts';
2
+ import type { LocalizationType, LocalizationModuleConfig } from './types.ts';
3
3
  export declare const getLocalizationsConfig: (context: Context, localizationType: LocalizationType) => LocalizationModuleConfig;
@@ -1,39 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { SortDirection } from '@unchainedshop/utils';
3
- export type LocalizationType = 'COUNTRY' | 'CURRENCY' | 'LANGUAGE';
4
- export interface LocalizationModuleConfig {
5
- module: any;
6
- NotFoundError: any;
7
- entityName: string;
8
- idField: string;
9
- existsMethod: any;
10
- findMethod: any;
11
- findMultipleMethod: any;
12
- }
13
- export interface LocalizationEntity {
14
- isoCode: string;
15
- contractAddress?: string;
16
- decimals?: number;
17
- }
18
- export interface LocalizationUpdateEntity {
19
- isoCode?: string;
20
- contractAddress?: string;
21
- decimals?: number;
22
- }
23
- export interface LocalizationListOptions {
24
- limit?: number;
25
- offset?: number;
26
- includeInactive?: boolean;
27
- queryString?: string;
28
- sort?: {
29
- key: string;
30
- value: 'ASC' | 'DESC';
31
- }[];
32
- }
33
- export interface LocalizationCountOptions {
34
- includeInactive?: boolean;
35
- queryString?: string;
36
- }
37
3
  export declare const sortDirectionKeys: [keyof typeof SortDirection, ...(keyof typeof SortDirection)[]];
38
4
  export declare const LocalizationTypeEnum: z.ZodEnum<{
39
5
  COUNTRY: "COUNTRY";