@unchainedshop/api 4.8.4 → 4.8.9

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/context.d.ts CHANGED
@@ -3,6 +3,7 @@ import { type UnchainedLoaders } from './loaders/index.ts';
3
3
  import { type UnchainedLocaleContext } from './locale-context.ts';
4
4
  import type { UnchainedServerOptions } from './api-index.ts';
5
5
  import type { User } from '@unchainedshop/core-users';
6
+ export declare const IN_LOGIN_RESPONSE: unique symbol;
6
7
  export type LoginFn = (user: User, options?: {
7
8
  impersonator?: User;
8
9
  }) => Promise<{
package/lib/context.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import instantiateLoaders, {} from "./loaders/index.js";
2
2
  import { getLocaleContext } from "./locale-context.js";
3
3
  const { npm_package_version, UNCHAINED_API_VERSION } = process.env;
4
+ export const IN_LOGIN_RESPONSE = Symbol('inLoginMethodResponse');
4
5
  let context;
5
6
  export const getCurrentContextResolver = () => context;
6
7
  export const setCurrentContextResolver = (newContext) => {
@@ -5,7 +5,7 @@ import MongoStore from "../mongo-store.js";
5
5
  import { Passport } from 'passport';
6
6
  import { mongodb } from '@unchainedshop/mongodb';
7
7
  import { emit } from '@unchainedshop/events';
8
- import { getCurrentContextResolver } from "../context.js";
8
+ import { getCurrentContextResolver, IN_LOGIN_RESPONSE, } from "../context.js";
9
9
  import createBulkImportMiddleware from "./createBulkImportMiddleware.js";
10
10
  import createERCMetadataMiddleware from "./createERCMetadataMiddleware.js";
11
11
  import createTempUploadMiddleware from "./createTempUploadMiddleware.js";
@@ -57,7 +57,7 @@ const addContext = async function middlewareWithContext(req, res, next) {
57
57
  tokenExpires: new Date(req.session?.cookie._expires),
58
58
  };
59
59
  await emit(API_EVENTS.API_LOGIN_TOKEN_CREATED, tokenObject);
60
- user._inLoginMethodResponse = true;
60
+ user[IN_LOGIN_RESPONSE] = true;
61
61
  return { user, ...tokenObject };
62
62
  };
63
63
  const logout = async (sessionId) => {
@@ -1,4 +1,4 @@
1
- import { getCurrentContextResolver } from "../context.js";
1
+ import { getCurrentContextResolver, IN_LOGIN_RESPONSE, } from "../context.js";
2
2
  import bulkImportHandler from "./bulkImportHandler.js";
3
3
  import ercMetadataHandler from "./ercMetadataHandler.js";
4
4
  import MongoStore from "../mongo-store.js";
@@ -36,7 +36,7 @@ const middlewareHook = async function middlewareHook(req, reply) {
36
36
  tokenExpires: new Date(req.session.cookie._expires),
37
37
  };
38
38
  await emit(API_EVENTS.API_LOGIN_TOKEN_CREATED, tokenObject);
39
- user._inLoginMethodResponse = true;
39
+ user[IN_LOGIN_RESPONSE] = true;
40
40
  return { user, ...tokenObject };
41
41
  };
42
42
  const logout = async function logout() {
@@ -171,6 +171,11 @@ declare const loaders: (unchainedAPI: UnchainedCore) => {
171
171
  }, import("@unchainedshop/core-quotations").Quotation | null, {
172
172
  quotationId: string;
173
173
  }>;
174
+ tokenExportStatusLoader: import("dataloader")<{
175
+ token: import("@unchainedshop/core-warehousing").TokenSurrogate;
176
+ }, import("@unchainedshop/core-warehousing").TokenStatus, {
177
+ token: import("@unchainedshop/core-warehousing").TokenSurrogate;
178
+ }>;
174
179
  };
175
180
  export type UnchainedLoaders = Awaited<ReturnType<typeof loaders>>;
176
181
  export default loaders;
@@ -27,6 +27,7 @@ import paymentProviderLoader from "./paymentProviderLoader.js";
27
27
  import warehousingProviderLoader from "./warehousingProviderLoader.js";
28
28
  import orderLoader from "./orderLoader.js";
29
29
  import quotationLoader from "./quotationLoader.js";
30
+ import tokenExportStatusLoader from "./tokenExportStatusLoader.js";
30
31
  const loaders = (unchainedAPI) => {
31
32
  return {
32
33
  assortmentLoader: assortmentLoader(unchainedAPI),
@@ -58,6 +59,7 @@ const loaders = (unchainedAPI) => {
58
59
  warehousingProviderLoader: warehousingProviderLoader(unchainedAPI),
59
60
  orderLoader: orderLoader(unchainedAPI),
60
61
  quotationLoader: quotationLoader(unchainedAPI),
62
+ tokenExportStatusLoader: tokenExportStatusLoader(unchainedAPI),
61
63
  };
62
64
  };
63
65
  export default loaders;
@@ -0,0 +1,9 @@
1
+ import type { UnchainedCore } from '@unchainedshop/core';
2
+ import type { TokenSurrogate, TokenStatus as TokenStatusType } from '@unchainedshop/core-warehousing';
3
+ import DataLoader from 'dataloader';
4
+ declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
5
+ token: TokenSurrogate;
6
+ }, TokenStatusType, {
7
+ token: TokenSurrogate;
8
+ }>;
9
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import { TokenStatus } from '@unchainedshop/core-warehousing';
2
+ import { WorkStatus } from '@unchainedshop/core-worker';
3
+ import DataLoader from 'dataloader';
4
+ export default (unchainedAPI) => new DataLoader(async (queries) => {
5
+ const tokensNeedingWorkCheck = [];
6
+ for (const { token } of queries) {
7
+ if (!(token.walletAddress && !token.userId)) {
8
+ tokensNeedingWorkCheck.push(token);
9
+ }
10
+ }
11
+ let exportingTokenIds;
12
+ if (tokensNeedingWorkCheck.length > 0) {
13
+ const workItems = await unchainedAPI.modules.worker.findWorkQueue({
14
+ types: ['EXPORT_TOKEN'],
15
+ status: [WorkStatus.NEW, WorkStatus.ALLOCATED],
16
+ });
17
+ exportingTokenIds = new Set(workItems.map((item) => item.input?.token?._id).filter(Boolean));
18
+ }
19
+ else {
20
+ exportingTokenIds = new Set();
21
+ }
22
+ return queries.map(({ token }) => {
23
+ if (token.walletAddress && !token.userId) {
24
+ return TokenStatus.DECENTRALIZED;
25
+ }
26
+ if (exportingTokenIds.has(token._id)) {
27
+ return TokenStatus.EXPORTING;
28
+ }
29
+ return TokenStatus.CENTRALIZED;
30
+ });
31
+ });
@@ -1,4 +1,5 @@
1
1
  import { log } from '@unchainedshop/logger';
2
+ import { Roles } from '@unchainedshop/roles';
2
3
  import { InvalidIdError, UserNotFoundError } from "../../../errors.js";
3
4
  export default async function setRoles(root, params, { modules, userId }) {
4
5
  const foreignUserId = params.userId;
@@ -7,5 +8,9 @@ export default async function setRoles(root, params, { modules, userId }) {
7
8
  throw new InvalidIdError({ foreignUserId });
8
9
  if (!(await modules.users.userExists({ userId: foreignUserId })))
9
10
  throw new UserNotFoundError({ userId: foreignUserId });
11
+ const invalidRoles = params.roles.filter((role) => !Roles.roles[role]);
12
+ if (invalidRoles.length > 0) {
13
+ throw new Error(`Invalid role names: ${invalidRoles.join(', ')}`);
14
+ }
10
15
  return modules.users.updateRoles(foreignUserId, params.roles);
11
16
  }
@@ -776,12 +776,12 @@ declare const types: {
776
776
  Token: {
777
777
  product: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-products").Product>;
778
778
  user: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-users").User | null>;
779
- status: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { services }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
779
+ status: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
780
780
  ercMetadata: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, { forceLocale }: {
781
781
  forceLocale: string;
782
782
  }, context: import("../../context.ts").Context) => Promise<any>;
783
- isInvalidateable: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, _params: never, { services }: import("../../context.ts").Context) => Promise<boolean>;
784
- accessKey: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, requestContext: import("../../context.ts").Context) => Promise<string | null>;
783
+ isInvalidateable: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, _params: never, { loaders, services }: import("../../context.ts").Context) => Promise<boolean>;
784
+ accessKey: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, requestContext: import("../../context.ts").Context) => Promise<string>;
785
785
  };
786
786
  User: import("./user-types.ts").UserHelperTypes;
787
787
  WebAuthnCredentials: {
@@ -1,6 +1,7 @@
1
+ import { IN_LOGIN_RESPONSE } from "../../context.js";
1
2
  export const LoginMethodResponse = {
2
3
  user({ user }) {
3
- user._inLoginMethodResponse = true;
4
+ user[IN_LOGIN_RESPONSE] = true;
4
5
  return user;
5
6
  },
6
7
  };
@@ -3,10 +3,10 @@ import type { TokenSurrogate } from '@unchainedshop/core-warehousing';
3
3
  export declare const Token: {
4
4
  product: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-products").Product>;
5
5
  user: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-users").User | null>;
6
- status: (token: TokenSurrogate, params: never, { services }: Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
6
+ status: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
7
7
  ercMetadata: (token: TokenSurrogate, { forceLocale }: {
8
8
  forceLocale: string;
9
9
  }, context: Context) => Promise<any>;
10
- isInvalidateable: (token: TokenSurrogate, _params: never, { services }: Context) => Promise<boolean>;
11
- accessKey: (token: TokenSurrogate, params: never, requestContext: Context) => Promise<string | null>;
10
+ isInvalidateable: (token: TokenSurrogate, _params: never, { loaders, services }: Context) => Promise<boolean>;
11
+ accessKey: (token: TokenSurrogate, params: never, requestContext: Context) => Promise<string>;
12
12
  };
@@ -9,8 +9,8 @@ export const Token = {
9
9
  return null;
10
10
  return loaders.userLoader.load({ userId: token.userId });
11
11
  },
12
- status: async (token, params, { services }) => {
13
- return services.warehousing.resolveTokenStatus({ token });
12
+ status: async (token, params, { loaders }) => {
13
+ return loaders.tokenExportStatusLoader.load({ token });
14
14
  },
15
15
  ercMetadata: async (token, { forceLocale }, context) => {
16
16
  const { loaders, services } = context;
@@ -21,12 +21,13 @@ export const Token = {
21
21
  locale: forceLocale ? new Intl.Locale(forceLocale) : context.locale,
22
22
  });
23
23
  },
24
- isInvalidateable: async (token, _params, { services }) => {
25
- return services.warehousing.isTokenInvalidateable({ token });
24
+ isInvalidateable: async (token, _params, { loaders, services }) => {
25
+ const product = await loaders.productLoader.load({ productId: token.productId });
26
+ return services.warehousing.isTokenInvalidateable({ token, product });
26
27
  },
27
28
  accessKey: async (token, params, requestContext) => {
28
29
  const { modules } = requestContext;
29
30
  await checkAction(requestContext, actions.updateToken, [undefined, { tokenId: token._id }]);
30
- return modules.warehousing.buildAccessKeyForToken(token._id);
31
+ return modules.warehousing.buildAccessKeyFromToken(token);
31
32
  },
32
33
  };
package/lib/roles/all.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
2
+ import { IN_LOGIN_RESPONSE } from "../context.js";
1
3
  export const all = (role, actions) => {
2
4
  const isInLoginMutationResponse = (root) => {
3
- if (root && root._inLoginMethodResponse) {
5
+ if (root && root[IN_LOGIN_RESPONSE]) {
4
6
  return true;
5
7
  }
6
8
  return false;
@@ -18,8 +20,8 @@ export const all = (role, actions) => {
18
20
  if (isOwnedByUser)
19
21
  return true;
20
22
  const accessKeyHeader = context.getHeader('x-token-accesskey');
21
- const accessKey = await context.modules.warehousing.buildAccessKeyForToken(tokenId);
22
- if (accessKeyHeader && accessKeyHeader === accessKey)
23
+ const accessKey = await context.modules.warehousing.buildAccessKeyFromToken(token);
24
+ if (accessKeyHeader && accessKey && (await timingSafeStringEqual(accessKeyHeader, accessKey)))
23
25
  return true;
24
26
  return false;
25
27
  };
@@ -145,6 +147,6 @@ export const all = (role, actions) => {
145
147
  role.allow(actions.createUser, () => true);
146
148
  role.allow(actions.forgotPassword, () => true);
147
149
  role.allow(actions.resetPassword, () => true);
148
- role.allow(actions.changePassword, () => true);
150
+ role.allow(actions.changePassword, () => false);
149
151
  role.allow(actions.heartbeat, () => true);
150
152
  };
@@ -166,6 +166,7 @@ export const loggedIn = (role, actions) => {
166
166
  role.allow(actions.manageBookmarks, isOwnedBookmark);
167
167
  role.allow(actions.bookmarkProduct, () => true);
168
168
  role.allow(actions.voteProductReview, () => true);
169
+ role.allow(actions.changePassword, () => true);
169
170
  role.allow(actions.registerPaymentCredentials, () => true);
170
171
  role.allow(actions.managePaymentCredentials, isOwnedPaymentCredential);
171
172
  role.allow(actions.confirmMediaUpload, () => true);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unchainedshop/api",
3
3
  "description": "GraphQL API layer for the Unchained Engine with Express/Fastify adapters and MCP server",
4
- "version": "4.8.4",
4
+ "version": "4.8.9",
5
5
  "main": "lib/api-index.js",
6
6
  "types": "lib/api-index.d.ts",
7
7
  "type": "module",