@unchainedshop/api 4.8.4 → 5.0.0-alpha.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.
Files changed (110) hide show
  1. package/lib/acl.d.ts +2 -2
  2. package/lib/acl.js +36 -8
  3. package/lib/api-index.d.ts +3 -1
  4. package/lib/chat/generateImageHandler.js +6 -5
  5. package/lib/context.d.ts +4 -1
  6. package/lib/context.js +9 -3
  7. package/lib/events.d.ts +2 -0
  8. package/lib/events.js +2 -0
  9. package/lib/express/createMCPMiddleware.js +1 -1
  10. package/lib/express/index.d.ts +6 -9
  11. package/lib/express/index.js +52 -125
  12. package/lib/fastify/index.d.ts +6 -9
  13. package/lib/fastify/index.js +51 -117
  14. package/lib/fastify/mcpHandler.js +1 -1
  15. package/lib/loaders/buildTextMap.js +6 -3
  16. package/lib/locale-context.d.ts +4 -2
  17. package/lib/locale-context.js +2 -6
  18. package/lib/mcp/tools/assortment/assortmentManagement.d.ts +2 -2
  19. package/lib/mcp/tools/assortment/assortmentManagement.js +2 -2
  20. package/lib/mcp/tools/assortment/handlers/getAssortment.js +1 -1
  21. package/lib/mcp/tools/assortment/schemas.d.ts +322 -141
  22. package/lib/mcp/tools/assortment/schemas.js +179 -96
  23. package/lib/mcp/tools/filter/filterManagement.d.ts +2 -2
  24. package/lib/mcp/tools/filter/filterManagement.js +2 -2
  25. package/lib/mcp/tools/filter/schemas.d.ts +184 -75
  26. package/lib/mcp/tools/filter/schemas.js +87 -51
  27. package/lib/mcp/tools/localization/getLocalizationsConfig.d.ts +1 -1
  28. package/lib/mcp/tools/localization/localizationManagement.d.ts +2 -2
  29. package/lib/mcp/tools/localization/localizationManagement.js +2 -2
  30. package/lib/mcp/tools/localization/schemas.d.ts +144 -47
  31. package/lib/mcp/tools/localization/schemas.js +51 -36
  32. package/lib/mcp/tools/order/handlers/getTopCustomers.d.ts +2 -0
  33. package/lib/mcp/tools/order/orderManagement.d.ts +2 -2
  34. package/lib/mcp/tools/order/orderManagement.js +2 -2
  35. package/lib/mcp/tools/order/schemas.d.ts +184 -75
  36. package/lib/mcp/tools/order/schemas.js +113 -51
  37. package/lib/mcp/tools/product/handlers/getProduct.js +1 -1
  38. package/lib/mcp/tools/product/productManagement.d.ts +2 -2
  39. package/lib/mcp/tools/product/productManagement.js +2 -2
  40. package/lib/mcp/tools/product/schemas.d.ts +642 -317
  41. package/lib/mcp/tools/product/schemas.js +287 -191
  42. package/lib/mcp/tools/provider/providerManagement.d.ts +2 -2
  43. package/lib/mcp/tools/provider/providerManagement.js +2 -2
  44. package/lib/mcp/tools/provider/schemas.d.ts +142 -47
  45. package/lib/mcp/tools/provider/schemas.js +67 -32
  46. package/lib/mcp/tools/quotation/handlers/getQuotation.d.ts +2 -0
  47. package/lib/mcp/tools/quotation/handlers/listQuotations.d.ts +2 -0
  48. package/lib/mcp/tools/quotation/handlers/makeQuotationProposal.d.ts +2 -0
  49. package/lib/mcp/tools/quotation/handlers/rejectQuotation.d.ts +2 -0
  50. package/lib/mcp/tools/quotation/handlers/verifyQuotation.d.ts +2 -0
  51. package/lib/mcp/tools/quotation/quotationManagement.d.ts +2 -2
  52. package/lib/mcp/tools/quotation/quotationManagement.js +2 -2
  53. package/lib/mcp/tools/quotation/schemas.d.ts +111 -40
  54. package/lib/mcp/tools/quotation/schemas.js +54 -29
  55. package/lib/mcp/tools/system/handlers/countEvents.d.ts +8 -1
  56. package/lib/mcp/tools/system/handlers/countWork.d.ts +10 -1
  57. package/lib/mcp/tools/system/handlers/countWork.js +1 -0
  58. package/lib/mcp/tools/system/handlers/index.d.ts +4 -4
  59. package/lib/mcp/tools/system/handlers/listEvents.d.ts +12 -1
  60. package/lib/mcp/tools/system/handlers/listWork.d.ts +14 -1
  61. package/lib/mcp/tools/system/handlers/listWork.js +1 -0
  62. package/lib/mcp/tools/system/schemas.d.ts +179 -84
  63. package/lib/mcp/tools/system/schemas.js +188 -67
  64. package/lib/mcp/tools/system/systemManagement.d.ts +1 -1
  65. package/lib/mcp/tools/system/systemManagement.js +1 -1
  66. package/lib/mcp/tools/users/handlers/addUserEmail.d.ts +2 -0
  67. package/lib/mcp/tools/users/handlers/createUser.d.ts +2 -0
  68. package/lib/mcp/tools/users/handlers/enrollUser.d.ts +2 -0
  69. package/lib/mcp/tools/users/handlers/getCurrentUser.d.ts +2 -0
  70. package/lib/mcp/tools/users/handlers/getUser.d.ts +2 -0
  71. package/lib/mcp/tools/users/handlers/listUsers.d.ts +2 -0
  72. package/lib/mcp/tools/users/handlers/setUserTags.d.ts +2 -0
  73. package/lib/mcp/tools/users/handlers/setUserUsername.d.ts +2 -0
  74. package/lib/mcp/tools/users/handlers/updateUser.d.ts +2 -0
  75. package/lib/mcp/tools/users/schemas.d.ts +296 -163
  76. package/lib/mcp/tools/users/schemas.js +152 -71
  77. package/lib/mcp/tools/users/usersManagement.d.ts +2 -2
  78. package/lib/mcp/tools/users/usersManagement.js +2 -2
  79. package/lib/mcp/utils/getNormalizedQuotationDetails.d.ts +2 -0
  80. package/lib/mcp/utils/getNormalizedUserDetails.d.ts +2 -0
  81. package/lib/mcp/utils/normalizeMediaUrl.js +1 -1
  82. package/lib/mcp/utils/sanitizeLocalizationEntityData.d.ts +1 -1
  83. package/lib/mcp/utils/sharedSchemas.d.ts +28 -39
  84. package/lib/mcp/utils/sharedSchemas.js +28 -47
  85. package/lib/mcp/utils/validateIsoCode.d.ts +1 -1
  86. package/lib/resolvers/mutations/accounts/changePassword.js +1 -2
  87. package/lib/resolvers/mutations/index.d.ts +150 -155
  88. package/lib/resolvers/mutations/index.js +2 -12
  89. package/lib/resolvers/mutations/orders/updateCart.js +7 -1
  90. package/lib/resolvers/mutations/users/setRoles.d.ts +1 -1
  91. package/lib/resolvers/mutations/users/setRoles.js +8 -1
  92. package/lib/resolvers/queries/events/registeredEventTypes.d.ts +1 -1
  93. package/lib/resolvers/queries/events/registeredEventTypes.js +1 -1
  94. package/lib/resolvers/queries/index.d.ts +71 -71
  95. package/lib/resolvers/queries/index.js +1 -1
  96. package/lib/resolvers/type/index.d.ts +0 -1
  97. package/lib/resolvers/type/login-method-response-types.js +2 -1
  98. package/lib/resolvers/type/order/order-delivery-pickup-types.d.ts +0 -1
  99. package/lib/resolvers/type/order/order-delivery-pickup-types.js +0 -7
  100. package/lib/resolvers/type/shop-types.js +1 -1
  101. package/lib/resolvers/type/user-types.js +3 -3
  102. package/lib/roles/all.js +5 -3
  103. package/lib/roles/index.d.ts +4 -11
  104. package/lib/roles/index.js +16 -11
  105. package/lib/roles/loggedIn.js +2 -0
  106. package/lib/schema/mutation.js +6 -44
  107. package/lib/schema/query.js +5 -5
  108. package/lib/schema/types/index.js +2 -2
  109. package/lib/schema/types/order/delivery.js +0 -2
  110. package/package.json +13 -23
package/lib/acl.d.ts CHANGED
@@ -2,11 +2,11 @@ import type { CheckPermissionArgs } from '@unchainedshop/roles';
2
2
  export declare const ensureActionExists: (action: any, userOptions: any) => void;
3
3
  export declare const ensureIsFunction: (fn: any, action: any, options: any, key: any) => void;
4
4
  declare const checkAction: (context: any, action: any, args?: CheckPermissionArgs, options?: any) => Promise<void>;
5
- declare const checkResolver: (action: any, userOptions?: any) => (fn: any, name?: string) => (root: any, params: any, context: any, info: any) => Promise<any>;
5
+ declare const checkResolver: (action: any, userOptions?: any) => (fn: any, name?: string) => (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
6
6
  declare const checkTypeResolver: (action: any, key: any) => (obj: any, params: any, context: any) => Promise<any>;
7
7
  declare const resolverDecorator: (action: any, userOptions?: any) => (target: any, key: any, descriptor: any) => {
8
8
  configurable: boolean;
9
- get(): (root: any, params: any, context: any, info: any) => Promise<any>;
9
+ get(): (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
10
10
  };
11
11
  export default resolverDecorator;
12
12
  export { resolverDecorator, checkResolver, checkTypeResolver, checkAction };
package/lib/acl.js CHANGED
@@ -1,5 +1,16 @@
1
- import { Roles } from '@unchainedshop/roles';
1
+ import { emit } from '@unchainedshop/events';
2
2
  import { NoPermissionError, PermissionSystemError } from "./errors.js";
3
+ import { API_EVENTS } from "./events.js";
4
+ const SENSITIVE_ACTION_PREFIXES = [
5
+ 'manageUser',
6
+ 'login',
7
+ 'logout',
8
+ 'reset',
9
+ 'forgot',
10
+ 'impersonate',
11
+ 'updateUser',
12
+ 'createUser',
13
+ ];
3
14
  const defaultOptions = {
4
15
  showKey: true,
5
16
  mapArgs: (...args) => args,
@@ -19,11 +30,27 @@ export const ensureIsFunction = (fn, action, options, key) => {
19
30
  });
20
31
  }
21
32
  };
33
+ const isSensitiveAction = (action) => {
34
+ return SENSITIVE_ACTION_PREFIXES.some((prefix) => action.startsWith(prefix));
35
+ };
22
36
  const checkAction = async (context, action, args = emptyArray, options = emptyObject) => {
23
37
  const { key } = options || emptyObject;
24
- const hasPermission = await Roles.userHasPermission(context, action, args);
25
- if (hasPermission)
38
+ const hasPermission = await context.roles.userHasPermission(context, action, args);
39
+ if (hasPermission) {
40
+ if (isSensitiveAction(action)) {
41
+ await emit(API_EVENTS.ACL_GRANTED_SENSITIVE, {
42
+ userId: context.userId,
43
+ action,
44
+ key,
45
+ });
46
+ }
26
47
  return;
48
+ }
49
+ await emit(API_EVENTS.ACL_DENIED, {
50
+ userId: context.userId,
51
+ action,
52
+ key,
53
+ });
27
54
  const keyText = key && key !== '' ? ` in "${key}"` : '';
28
55
  throw new NoPermissionError({
29
56
  userId: context.userId,
@@ -36,11 +63,12 @@ const wrapFunction = (fn, name, action, userOptions) => {
36
63
  const key = name || fn.name;
37
64
  const options = { ...defaultOptions, ...userOptions };
38
65
  ensureIsFunction(fn, action, options, key);
39
- const actionOptions = { key: options.showKey ? key : '' };
40
- return async (root, params, context, info) => {
41
- const args = options.mapArgs(root, params, info);
42
- await checkAction(context, action, args, actionOptions);
43
- return fn(root, params, context, info);
66
+ return async (root, params, context, ...other) => {
67
+ const args = options.mapArgs(root, params, ...other);
68
+ await checkAction(context, action, args, {
69
+ key: options.showKey ? key : '',
70
+ });
71
+ return fn(root, params, context, ...other);
44
72
  };
45
73
  };
46
74
  const checkResolver = (action, userOptions) => {
@@ -8,9 +8,11 @@ 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';
11
12
  export { createContextResolver, getCurrentContextResolver, setCurrentContextResolver };
13
+ import type { RolesInterface } from '@unchainedshop/roles';
12
14
  export type UnchainedServerOptions = {
13
- roles?: any;
15
+ roles?: RolesInterface;
14
16
  adminUiConfig?: AdminUiConfig;
15
17
  unchainedAPI: UnchainedCore;
16
18
  context?: (defaultResolver: UnchainedContextResolver) => UnchainedContextResolver;
@@ -1,4 +1,4 @@
1
- import { z } from 'zod/v4-mini';
1
+ import { z } from 'zod';
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().check(z.describe('The prompt to generate the image from')),
15
+ prompt: z.string().describe('The prompt to generate the image from'),
16
16
  size: z
17
- .optional(z.enum([
17
+ .enum([
18
18
  '512x512',
19
19
  '768x768',
20
20
  '1024x1024',
@@ -26,8 +26,9 @@ const inputSchema = z.object({
26
26
  '1120x640',
27
27
  '1344x768',
28
28
  '1792x1024',
29
- ]))
30
- .check(z.describe('The size of the image (default is 1024x1024).')),
29
+ ])
30
+ .optional()
31
+ .describe('The size of the image (default is 1024x1024).'),
31
32
  });
32
33
  const generateImageHandler = (req) => ({ model, uploadUrl = `${process.env.ROOT_URL}/temp-upload`, }) => {
33
34
  return tool({
package/lib/context.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { UnchainedCore } from '@unchainedshop/core';
2
+ import type { RolesInterface } from '@unchainedshop/roles';
2
3
  import { type UnchainedLoaders } from './loaders/index.ts';
3
4
  import { type UnchainedLocaleContext } from './locale-context.ts';
4
5
  import type { UnchainedServerOptions } from './api-index.ts';
5
6
  import type { User } from '@unchainedshop/core-users';
7
+ export declare const IN_LOGIN_RESPONSE: unique symbol;
6
8
  export type LoginFn = (user: User, options?: {
7
9
  impersonator?: User;
8
10
  }) => Promise<{
@@ -16,6 +18,7 @@ export interface UnchainedUserContext {
16
18
  userId?: string;
17
19
  impersonatorId?: string;
18
20
  accessToken?: string;
21
+ tokenVersion?: number;
19
22
  user?: User;
20
23
  }
21
24
  export interface CustomAdminUiProperties {
@@ -37,7 +40,7 @@ export interface UnchainedHTTPServerContext {
37
40
  }
38
41
  export type Context = UnchainedCore & {
39
42
  version?: string;
40
- roles?: any;
43
+ roles?: RolesInterface;
41
44
  adminUiConfig?: AdminUiConfig;
42
45
  loaders: UnchainedLoaders;
43
46
  } & UnchainedUserContext & UnchainedLocaleContext & UnchainedHTTPServerContext;
package/lib/context.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import instantiateLoaders, {} from "./loaders/index.js";
2
2
  import { getLocaleContext } from "./locale-context.js";
3
+ export const IN_LOGIN_RESPONSE = Symbol('inLoginMethodResponse');
3
4
  const { npm_package_version, UNCHAINED_API_VERSION } = process.env;
4
5
  let context;
5
6
  export const getCurrentContextResolver = () => context;
6
7
  export const setCurrentContextResolver = (newContext) => {
7
8
  context = newContext;
8
9
  };
9
- export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({ getHeader, setHeader, remoteAddress, remotePort, userId, impersonatorId, accessToken, login, logout, }) => {
10
+ export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({ getHeader, setHeader, remoteAddress, remotePort, userId, impersonatorId, accessToken, tokenVersion, login, logout, }) => {
10
11
  const abstractHttpServerContext = { remoteAddress, remotePort, getHeader, setHeader };
11
12
  const loaders = instantiateLoaders(unchainedAPI);
12
13
  const localeContext = await getLocaleContext(abstractHttpServerContext, unchainedAPI);
@@ -21,8 +22,13 @@ export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({
21
22
  if (userId && !userContext.userId) {
22
23
  const user = await unchainedAPI.modules.users.findUserById(userId);
23
24
  if (user) {
24
- userContext.user = user;
25
- userContext.userId = user._id;
25
+ const userTokenVersion = user.tokenVersion ?? 1;
26
+ if (tokenVersion !== undefined && tokenVersion !== userTokenVersion) {
27
+ }
28
+ else {
29
+ userContext.user = user;
30
+ userContext.userId = user._id;
31
+ }
26
32
  }
27
33
  }
28
34
  return {
package/lib/events.d.ts CHANGED
@@ -1,5 +1,7 @@
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";
4
6
  };
5
7
  export type API_EVENTS = (typeof API_EVENTS)[keyof typeof API_EVENTS];
package/lib/events.js CHANGED
@@ -1,4 +1,6 @@
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',
4
6
  };
@@ -1,3 +1,4 @@
1
+ import initMCPServer from "../mcp/index.js";
1
2
  import { createLogger } from '@unchainedshop/logger';
2
3
  const logger = createLogger('unchained:api:mcp');
3
4
  let StreamableHTTPServerTransport;
@@ -34,7 +35,6 @@ const handlePostRequest = async (req, res) => {
34
35
  }
35
36
  };
36
37
  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',
@@ -1,7 +1,7 @@
1
1
  import e from 'express';
2
2
  import type { YogaServerInstance } from 'graphql-yoga';
3
- import { mongodb } from '@unchainedshop/mongodb';
4
3
  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,17 +9,14 @@ 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, db, unchainedAPI, }: {
12
+ export declare const connect: (expressApp: e.Express, { graphqlHandler, unchainedAPI, }: {
13
13
  graphqlHandler: YogaServerInstance<any, any>;
14
- db: mongodb.Db;
15
14
  unchainedAPI: UnchainedCore;
16
- }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
15
+ }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, authConfig, trustProxy, }?: {
17
16
  allowRemoteToLocalhostSecureCookies?: boolean;
18
17
  adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
19
18
  chat?: ChatConfiguration;
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;
19
+ authConfig?: AuthConfig;
20
+ trustProxy?: boolean;
21
+ }) => Promise<void>;
25
22
  export { connectChat };
@@ -1,17 +1,12 @@
1
1
  import e from 'express';
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';
2
+ import cookieParser from 'cookie-parser';
3
+ import { pluginRegistry } from '@unchainedshop/core';
8
4
  import { getCurrentContextResolver } from "../context.js";
9
- import createBulkImportMiddleware from "./createBulkImportMiddleware.js";
10
- import createERCMetadataMiddleware from "./createERCMetadataMiddleware.js";
11
- import createTempUploadMiddleware from "./createTempUploadMiddleware.js";
5
+ import { createAuthContext } from "../middleware/createAuthMiddleware.js";
12
6
  import createMCPMiddleware from "./createMCPMiddleware.js";
13
- import { API_EVENTS } from "../events.js";
14
7
  import { connectChat } from "./chatHandler.js";
8
+ import { mountRoutes } from "./mountRoutes.js";
9
+ import { createBackchannelLogoutRoute } from "../handlers/createBackchannelLogoutHandler.js";
15
10
  export const adminUIRouter = (enabled = true) => {
16
11
  const router = e.Router();
17
12
  const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
@@ -24,87 +19,53 @@ export const adminUIRouter = (enabled = true) => {
24
19
  }
25
20
  return router;
26
21
  };
27
- const resolveUserRemoteAddress = (req) => {
28
- const remoteAddress = req.headers['x-real-ip'] ||
29
- req.headers['x-forwarded-for'] ||
30
- req.socket?.remoteAddress;
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
+ }
31
35
  const remotePort = req.socket?.remotePort;
32
36
  return { remoteAddress, remotePort };
33
37
  };
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) {
38
+ const { MCP_API_PATH = '/mcp' } = process.env;
39
+ const createAddContextMiddleware = (authConfig, trustProxy = false) => async function middlewareWithContext(req, res, next) {
38
40
  try {
39
41
  const setHeader = (key, value) => res.setHeader(key, value);
40
42
  const getHeader = (key) => req.headers[key];
41
- const { remoteAddress, remotePort } = resolveUserRemoteAddress(req);
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;
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,
96
55
  };
97
- const [, accessToken] = req.headers.authorization?.split(' ') || [];
56
+ const authContext = await createAuthContext(authContextParams, authConfig);
57
+ const context = getCurrentContextResolver();
98
58
  req.unchainedContext = await context({
99
59
  setHeader,
100
60
  getHeader,
101
61
  remoteAddress,
102
62
  remotePort,
103
- login,
104
- logout,
105
- accessToken,
106
- userId: req.user?._id,
107
- impersonatorId: req.session.impersonatorId,
63
+ login: authContext.login,
64
+ logout: authContext.logout,
65
+ accessToken: authContext.accessToken,
66
+ userId: authContext.userId,
67
+ impersonatorId: authContext.impersonatorId,
68
+ tokenVersion: authContext.tokenVersion,
108
69
  }, req, res);
109
70
  next();
110
71
  }
@@ -112,8 +73,12 @@ const addContext = async function middlewareWithContext(req, res, next) {
112
73
  next(error);
113
74
  }
114
75
  };
115
- export const connect = (expressApp, { graphqlHandler, db, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, initPluginMiddlewares, } = {}) => {
76
+ export const connect = async (expressApp, { graphqlHandler, unchainedAPI, }, { allowRemoteToLocalhostSecureCookies = false, adminUI = false, chat, authConfig, trustProxy = false, } = {}) => {
116
77
  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
+ }
117
82
  expressApp.set('trust proxy', 1);
118
83
  expressApp.use((req, res, next) => {
119
84
  req.headers['x-forwarded-proto'] = 'https';
@@ -125,59 +90,21 @@ export const connect = (expressApp, { graphqlHandler, db, unchainedAPI, }, { all
125
90
  next();
126
91
  });
127
92
  }
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);
93
+ expressApp.use(cookieParser());
94
+ expressApp.use(createAddContextMiddleware(authConfig, trustProxy || allowRemoteToLocalhostSecureCookies));
95
+ expressApp.use(graphqlHandler.graphqlEndpoint, graphqlHandler.handle);
170
96
  expressApp.use(MCP_API_PATH, e.json({ limit: '10mb' }));
171
97
  expressApp.use(MCP_API_PATH, createMCPMiddleware);
172
98
  if (chat) {
173
99
  connectChat(expressApp, chat);
174
100
  }
175
- if (initPluginMiddlewares) {
176
- initPluginMiddlewares(expressApp, { unchainedAPI });
101
+ const routes = pluginRegistry.getRoutes();
102
+ if (authConfig?.oidcProviders?.length) {
103
+ routes.push(createBackchannelLogoutRoute(authConfig.oidcProviders));
177
104
  }
105
+ mountRoutes(expressApp, unchainedAPI, routes);
178
106
  if (adminUI) {
179
107
  expressApp.use(typeof adminUI === 'object' ? adminUI.prefix : '/', adminUIRouter(true));
180
108
  }
181
109
  };
182
- export const expressRouter = adminUIRouter;
183
110
  export { connectChat };
@@ -1,5 +1,5 @@
1
+ import type { AuthConfig } from '../auth.ts';
1
2
  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,18 +9,15 @@ 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, db, unchainedAPI, }: {
12
+ export declare const connect: (fastify: FastifyInstance, { graphqlHandler, unchainedAPI, }: {
13
13
  graphqlHandler: YogaServerInstance<any, any>;
14
- db: mongodb.Db;
15
14
  unchainedAPI: UnchainedCore;
16
- }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, initPluginMiddlewares, }?: {
15
+ }, { allowRemoteToLocalhostSecureCookies, adminUI, chat, authConfig, trustProxy, }?: {
17
16
  allowRemoteToLocalhostSecureCookies?: boolean;
18
17
  adminUI?: boolean | Omit<AdminUIRouterOptions, "enabled">;
19
18
  chat?: ChatConfiguration;
20
- initPluginMiddlewares?: (app: FastifyInstance, { unchainedAPI }: {
21
- unchainedAPI: UnchainedCore;
22
- }) => void;
23
- }) => void;
19
+ authConfig?: AuthConfig;
20
+ trustProxy?: boolean;
21
+ }) => Promise<void>;
24
22
  export declare const adminUIRouter: FastifyPluginAsync<AdminUIRouterOptions>;
25
- export declare const fastifyRouter: FastifyPluginAsync<AdminUIRouterOptions>;
26
23
  export { connectChat };