@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 +1 -0
- package/lib/context.js +1 -0
- package/lib/express/index.js +2 -2
- package/lib/fastify/index.js +2 -2
- package/lib/loaders/index.d.ts +5 -0
- package/lib/loaders/index.js +2 -0
- package/lib/loaders/tokenExportStatusLoader.d.ts +9 -0
- package/lib/loaders/tokenExportStatusLoader.js +31 -0
- package/lib/resolvers/mutations/users/setRoles.js +5 -0
- package/lib/resolvers/type/index.d.ts +3 -3
- package/lib/resolvers/type/login-method-response-types.js +2 -1
- package/lib/resolvers/type/token-types.d.ts +3 -3
- package/lib/resolvers/type/token-types.js +6 -5
- package/lib/roles/all.js +6 -4
- package/lib/roles/loggedIn.js +1 -0
- package/package.json +1 -1
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) => {
|
package/lib/express/index.js
CHANGED
|
@@ -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
|
|
60
|
+
user[IN_LOGIN_RESPONSE] = true;
|
|
61
61
|
return { user, ...tokenObject };
|
|
62
62
|
};
|
|
63
63
|
const logout = async (sessionId) => {
|
package/lib/fastify/index.js
CHANGED
|
@@ -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
|
|
39
|
+
user[IN_LOGIN_RESPONSE] = true;
|
|
40
40
|
return { user, ...tokenObject };
|
|
41
41
|
};
|
|
42
42
|
const logout = async function logout() {
|
package/lib/loaders/index.d.ts
CHANGED
|
@@ -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;
|
package/lib/loaders/index.js
CHANGED
|
@@ -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, {
|
|
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
|
|
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: {
|
|
@@ -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, {
|
|
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
|
|
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, {
|
|
13
|
-
return
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
22
|
-
if (accessKeyHeader && accessKeyHeader
|
|
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, () =>
|
|
150
|
+
role.allow(actions.changePassword, () => false);
|
|
149
151
|
role.allow(actions.heartbeat, () => true);
|
|
150
152
|
};
|
package/lib/roles/loggedIn.js
CHANGED
|
@@ -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
|
+
"version": "4.8.9",
|
|
5
5
|
"main": "lib/api-index.js",
|
|
6
6
|
"types": "lib/api-index.d.ts",
|
|
7
7
|
"type": "module",
|