From fc91e47f579f003adb0a6eb8263c203555b9887e Mon Sep 17 00:00:00 2001 From: Daniel K Date: Mon, 11 Dec 2023 12:08:07 -0800 Subject: [PATCH] (fix) token permissions resolution order and ensure backward compatibility with single token management --- apps/api/karrio/server/VERSION | 2 +- modules/admin/setup.py | 2 +- modules/core/karrio/server/core/dataunits.py | 2 +- modules/core/karrio/server/user/models.py | 42 +- modules/core/setup.py | 2 +- .../server/graph/schemas/base/inputs.py | 1 + .../server/graph/schemas/base/mutations.py | 16 +- .../karrio/server/graph/schemas/base/types.py | 16 - .../server/graph/tests/test_user_info.py | 6 +- modules/graph/setup.py | 2 +- modules/pricing/setup.py | 2 +- packages/types/graphql/types.ts | 1 + packages/ui/modals/generate-api-dialog.tsx | 5 +- requirements.build.txt | 96 ++-- requirements.txt | 1 - schemas/graphql.json | 409 ++++++++++++++++++ source.requirements.build.txt | 54 --- 17 files changed, 519 insertions(+), 140 deletions(-) delete mode 100644 source.requirements.build.txt diff --git a/apps/api/karrio/server/VERSION b/apps/api/karrio/server/VERSION index bb764935a6..6895b410d8 100644 --- a/apps/api/karrio/server/VERSION +++ b/apps/api/karrio/server/VERSION @@ -1 +1 @@ -2023.9.3 \ No newline at end of file +2023.9.4 \ No newline at end of file diff --git a/modules/admin/setup.py b/modules/admin/setup.py index 7bf04c24fd..3a266c1641 100644 --- a/modules/admin/setup.py +++ b/modules/admin/setup.py @@ -5,7 +5,7 @@ setup( name="karrio.server.admin", - version="2023.9", + version="2023.9.4", description="Multi-carrier shipping API admin module", long_description=long_description, long_description_content_type="text/markdown", diff --git a/modules/core/karrio/server/core/dataunits.py b/modules/core/karrio/server/core/dataunits.py index 5f6f8e8001..852c947576 100644 --- a/modules/core/karrio/server/core/dataunits.py +++ b/modules/core/karrio/server/core/dataunits.py @@ -53,8 +53,8 @@ def contextual_metadata(request: Request): def contextual_reference(request: Request = None, reduced: bool = True): - import karrio.server.core.validators as validators import karrio.server.core.gateway as gateway + import karrio.server.core.validators as validators import karrio.server.core.middleware as middleware request = request or middleware.SessionContext.get_current_request() diff --git a/modules/core/karrio/server/user/models.py b/modules/core/karrio/server/user/models.py index b18467aa21..7c066eb1f5 100644 --- a/modules/core/karrio/server/user/models.py +++ b/modules/core/karrio/server/user/models.py @@ -71,7 +71,28 @@ def object_type(self): @property def permissions(self): - _permissions = self.groups.all().values_list("name", flat=True) + import karrio.server.conf as conf + import karrio.server.iam.models as iam + import karrio.server.core.middleware as middleware + + ctx = middleware.SessionContext.get_current_request() + _permissions = [] + + if conf.settings.MULTI_ORGANIZATIONS and ctx.org is not None: + org_user = ctx.org.organization_users.filter(user_id=self.pk) + _permissions = ( + iam.ContextPermission.objects.get( + object_pk=org_user.first().pk, + content_type=ContentType.objects.get_for_model(org_user.first()), + ) + .groups.all() + .values_list("name", flat=True) + if org_user.exists() + else [] + ) + + if not any(_permissions): + _permissions = self.groups.all().values_list("name", flat=True) if not any(_permissions) and self.is_superuser: return Group.objects.all().values_list("name", flat=True) @@ -119,30 +140,31 @@ def permissions(self): _permissions = [] - if conf.settings.MULTI_ORGANIZATIONS and self.org.exists(): - org_user = self.org.first().organization_users.filter(user_id=self.user_id) + if iam.ContextPermission.objects.filter(object_pk=self.pk).exists(): _permissions = ( iam.ContextPermission.objects.get( - object_pk=org_user.first().pk, - content_type=ContentType.objects.get_for_model(org_user.first()), + object_pk=self.pk, + content_type=ContentType.objects.get_for_model(Token), ) .groups.all() .values_list("name", flat=True) - if org_user.exists() - else [] ) if ( not any(_permissions) - and iam.ContextPermission.objects.filter(object_pk=self.pk).exists() + and conf.settings.MULTI_ORGANIZATIONS + and self.org.exists() ): + org_user = self.org.first().organization_users.filter(user_id=self.user_id) _permissions = ( iam.ContextPermission.objects.get( - object_pk=self.pk, - content_type=ContentType.objects.get_for_model(Token), + object_pk=org_user.first().pk, + content_type=ContentType.objects.get_for_model(org_user.first()), ) .groups.all() .values_list("name", flat=True) + if org_user.exists() + else [] ) return _permissions if any(_permissions) else self.user.permissions diff --git a/modules/core/setup.py b/modules/core/setup.py index 9095f5c381..1d69a3d5ce 100644 --- a/modules/core/setup.py +++ b/modules/core/setup.py @@ -5,7 +5,7 @@ setup( name="karrio.server.core", - version="2023.9.3", + version="2023.9.4", description="Multi-carrier shipping API Core module", long_description=long_description, long_description_content_type="text/markdown", diff --git a/modules/graph/karrio/server/graph/schemas/base/inputs.py b/modules/graph/karrio/server/graph/schemas/base/inputs.py index fdccf6a9ea..d4753114a7 100644 --- a/modules/graph/karrio/server/graph/schemas/base/inputs.py +++ b/modules/graph/karrio/server/graph/schemas/base/inputs.py @@ -73,6 +73,7 @@ class UpdateUserInput(utils.BaseInput): @strawberry.input class TokenMutationInput(utils.BaseInput): + key: str password: typing.Optional[str] = strawberry.UNSET refresh: typing.Optional[bool] = strawberry.UNSET diff --git a/modules/graph/karrio/server/graph/schemas/base/mutations.py b/modules/graph/karrio/server/graph/schemas/base/mutations.py index 84e55c816a..d7a34c7dd5 100644 --- a/modules/graph/karrio/server/graph/schemas/base/mutations.py +++ b/modules/graph/karrio/server/graph/schemas/base/mutations.py @@ -64,9 +64,12 @@ class TokenMutation(utils.BaseMutation): @utils.authentication_required @utils.authorization_required() def mutate( - info: Info, refresh: bool = None, password: str = None + info: Info, + key: str = None, + refresh: bool = None, + password: str = None, ) -> "UserUpdateMutation": - tokens = user_models.Token.access_by(info.context.request) + tokens = user_models.Token.access_by(info.context.request).filter(key=key) if refresh: if len(password or "") == 0: @@ -80,6 +83,9 @@ def mutate( if any(tokens): tokens.delete() + else: + return TokenMutation(token=tokens.first()) # type:ignore + token = ( TokenSerializer.map(data={}, context=info.context.request).save().instance ) @@ -105,7 +111,11 @@ def mutate( api_key = TokenSerializer.map(data=data, context=context).save().instance if any(permissions): - _auth_ctx = getattr(context, "token", context.user) + _auth_ctx = ( + context.token + if hasattr(getattr(info.context.request, "token", None), "permissions") + else context.user + ) _ctx_permissions = getattr(_auth_ctx, "permissions", []) _invalid_permissions = [_ for _ in permissions if _ not in _ctx_permissions] diff --git a/modules/graph/karrio/server/graph/schemas/base/types.py b/modules/graph/karrio/server/graph/schemas/base/types.py index eda82624ce..87e4d5ecfe 100644 --- a/modules/graph/karrio/server/graph/schemas/base/types.py +++ b/modules/graph/karrio/server/graph/schemas/base/types.py @@ -41,22 +41,6 @@ def permissions(self: User, info) -> typing.Optional[typing.List[str]]: if hasattr(getattr(info.context.request, "token", None), "permissions"): return info.context.request.token.permissions - # Return permissions from org user if multiple orgs context - if hasattr(info.context.request, "org"): - org_user = info.context.request.org.organization_users.filter( - user_id=self.id - ) - return ( - iam.ContextPermission.objects.get( - object_pk=org_user.first().pk, - content_type=ContentType.objects.get_for_model(org_user.first()), - ) - .groups.all() - .values_list("name", flat=True) - if org_user.exists() - else [] - ) - # Return permissions from user return info.context.request.user.permissions diff --git a/modules/graph/karrio/server/graph/tests/test_user_info.py b/modules/graph/karrio/server/graph/tests/test_user_info.py index 6ba8a3a57f..59a2e1f9a1 100644 --- a/modules/graph/karrio/server/graph/tests/test_user_info.py +++ b/modules/graph/karrio/server/graph/tests/test_user_info.py @@ -42,7 +42,9 @@ def test_update_token(self): } """, operation_name="mutate_token", - variables=TOKEN_MUTATION_DATA, + variables={ + "data": {"refresh": True, "password": "test", "key": current_token} + }, ) response_data = response.data @@ -67,5 +69,3 @@ def test_update_token(self): } } } - -TOKEN_MUTATION_DATA = {"data": {"refresh": True, "password": "test"}} diff --git a/modules/graph/setup.py b/modules/graph/setup.py index 337affca1c..8d89afda69 100644 --- a/modules/graph/setup.py +++ b/modules/graph/setup.py @@ -5,7 +5,7 @@ setup( name="karrio.server.graph", - version="2023.9.2", + version="2023.9.4", description="Multi-carrier shipping API Graph module", long_description=long_description, long_description_content_type="text/markdown", diff --git a/modules/pricing/setup.py b/modules/pricing/setup.py index d6f1315701..729c553520 100644 --- a/modules/pricing/setup.py +++ b/modules/pricing/setup.py @@ -5,7 +5,7 @@ setup( name="karrio.server.pricing", - version="2023.4", + version="2023.9.4", description="Multi-carrier shipping API Pricing panel", long_description=long_description, long_description_content_type="text/markdown", diff --git a/packages/types/graphql/types.ts b/packages/types/graphql/types.ts index e71437e928..49d0f193a4 100644 --- a/packages/types/graphql/types.ts +++ b/packages/types/graphql/types.ts @@ -6517,6 +6517,7 @@ export interface UpdateAddressTemplateInput { // null export interface TokenMutationInput { + key: string; password?: string | null; refresh?: boolean | null; } diff --git a/packages/ui/modals/generate-api-dialog.tsx b/packages/ui/modals/generate-api-dialog.tsx index b8b0668a2e..cf30b5abef 100644 --- a/packages/ui/modals/generate-api-dialog.tsx +++ b/packages/ui/modals/generate-api-dialog.tsx @@ -1,4 +1,4 @@ -import { useAPITokenMutation } from '@karrio/hooks/api-token'; +import { useAPIToken, useAPITokenMutation } from '@karrio/hooks/api-token'; import React, { useContext, useRef, useState } from 'react'; import { Notifier, Notify } from '../components/notifier'; import { NotificationType } from '@karrio/types'; @@ -12,13 +12,14 @@ export const GenerateAPIModal: React.FC<{ children?: React.ReactNode }> = ({ chi const { loading, setLoading } = useLoader(); const password = useRef(null); const { query: { data: { user } = {} } } = useUser(); + const { query: { data: { token } = {} } } = useAPIToken(); const [isActive, setIsActive] = useState(false); const handleSubmit = async (evt: React.FormEvent) => { evt.preventDefault(); try { setLoading(true); - await mutation.updateToken.mutateAsync({ refresh: true, password: password.current?.value }); + await mutation.updateToken.mutateAsync({ refresh: true, key: token!.key, password: password.current?.value }); setLoading(false); setIsActive(false); notify({ type: NotificationType.success, message: "New token generated successfully!" }); diff --git a/requirements.build.txt b/requirements.build.txt index 4798162b1b..43bb1f41a3 100644 --- a/requirements.build.txt +++ b/requirements.build.txt @@ -1,48 +1,54 @@ ---extra-index-url https://karrio.gateway.scarf.sh/simple/ +--extra-index-url https://karrio.gateway.scarf.sh/simple/?sourceBuild +Django==4.2.8 -./packages/karrio -./packages/connections/generic -./packages/connections/amazon_shipping -./packages/connections/aramex -./packages/connections/australiapost -./packages/connections/boxknight -./packages/connections/canadapost -./packages/connections/canpar -./packages/connections/chronopost -./packages/connections/dhl_express -./packages/connections/dhl_poland -./packages/connections/dhl_universal -./packages/connections/dicom -./packages/connections/dpd -./packages/connections/dpdhl -./packages/connections/fedex -./packages/connections/geodis -./packages/connections/laposte -./packages/connections/nationex -./packages/connections/purolator -./packages/connections/roadie -./packages/connections/royalmail -./packages/connections/sendle -./packages/connections/tnt -./packages/connections/ups -./packages/connections/usps -./packages/connections/usps_international +# Carrier Extentions modules +-e ./modules/sdk +-e ./modules/connectors/amazon_shipping +-e ./modules/connectors/aramex +-e ./modules/connectors/asendia_us +-e ./modules/connectors/australiapost +-e ./modules/connectors/boxknight +-e ./modules/connectors/bpost +-e ./modules/connectors/canadapost +-e ./modules/connectors/canpar +-e ./modules/connectors/chronopost +-e ./modules/connectors/colissimo +-e ./modules/connectors/dhl_express +-e ./modules/connectors/dhl_poland +-e ./modules/connectors/dhl_universal +-e ./modules/connectors/dicom +-e ./modules/connectors/dpd +-e ./modules/connectors/dpdhl +-e ./modules/connectors/fedex +-e ./modules/connectors/generic +-e ./modules/connectors/geodis +-e ./modules/connectors/laposte +-e ./modules/connectors/nationex +-e ./modules/connectors/purolator +-e ./modules/connectors/roadie +-e ./modules/connectors/royalmail +-e ./modules/connectors/sendle +-e ./modules/connectors/tnt +-e ./modules/connectors/ups +-e ./modules/connectors/usps +-e ./modules/connectors/usps_international -./packages/connections/easypost -./packages/connections/eshipper -./packages/connections/freightcom -./packages/connections/locate2u -./packages/connections/zoom2u +-e ./modules/connectors/easypost +-e ./modules/connectors/eshipper +-e ./modules/connectors/freightcom +-e ./modules/connectors/locate2u +-e ./modules/connectors/zoom2u -./packages/iam -./packages/core -./apps/api -./packages/graph -./packages/apps -./packages/data -./packages/events -./packages/manager -./packages/orders -./packages/proxy -./packages/pricing -./packages/documents + +# karrio server modules +-e ./modules/core +-e ./apps/api +-e ./modules/graph +-e ./modules/apps +-e ./modules/data +-e ./modules/events +-e ./modules/manager +-e ./modules/orders +-e ./modules/proxy +-e ./modules/pricing +-e ./modules/documents \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d4f4591894..b8a2feca73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -86,7 +86,6 @@ jwcrypto==1.5.0 -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.documents&subdirectory=modules/documents -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.events&subdirectory=modules/events -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.graph&subdirectory=modules/graph --e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.iam&subdirectory=modules/iam -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.manager&subdirectory=modules/manager -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.orders&subdirectory=modules/orders -e git+ssh://git@github.com/karrioapi/karrio.git@c012a19dda60427c24a34c2996bc0122ab0695ed#egg=karrio.server.pricing&subdirectory=modules/pricing diff --git a/schemas/graphql.json b/schemas/graphql.json index 3b9dcc6eab..e38b5c1ee0 100644 --- a/schemas/graphql.json +++ b/schemas/graphql.json @@ -56,6 +56,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "api_keys", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "APIKeyType", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "user_connections", "description": null, @@ -1238,6 +1262,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "key", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "label", "description": null, @@ -1254,6 +1294,85 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_mode", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "created", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "permissions", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "APIKeyType", + "description": null, + "fields": [ + { + "name": "object_type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "key", "description": null, @@ -1270,6 +1389,38 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "test_mode", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "created", "description": null, @@ -22135,6 +22286,68 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "create_api_key", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateAPIKeyMutationInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CreateAPIKeyMutation", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "delete_api_key", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "DeleteAPIKeyMutationInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DeleteAPIKeyMutation", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "request_email_change", "description": null, @@ -23942,6 +24155,20 @@ "description": null, "fields": null, "inputFields": [ + { + "name": "key", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, { "name": "password", "description": null, @@ -24010,6 +24237,188 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "CreateAPIKeyMutationInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "password", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "label", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "permissions", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CreateAPIKeyMutation", + "description": null, + "fields": [ + { + "name": "errors", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ErrorType", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "api_key", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "APIKeyType", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "DeleteAPIKeyMutationInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "password", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "key", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DeleteAPIKeyMutation", + "description": null, + "fields": [ + { + "name": "errors", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ErrorType", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "RequestEmailChangeMutationInput", diff --git a/source.requirements.build.txt b/source.requirements.build.txt deleted file mode 100644 index 43bb1f41a3..0000000000 --- a/source.requirements.build.txt +++ /dev/null @@ -1,54 +0,0 @@ ---extra-index-url https://karrio.gateway.scarf.sh/simple/?sourceBuild -Django==4.2.8 - -# Carrier Extentions modules --e ./modules/sdk --e ./modules/connectors/amazon_shipping --e ./modules/connectors/aramex --e ./modules/connectors/asendia_us --e ./modules/connectors/australiapost --e ./modules/connectors/boxknight --e ./modules/connectors/bpost --e ./modules/connectors/canadapost --e ./modules/connectors/canpar --e ./modules/connectors/chronopost --e ./modules/connectors/colissimo --e ./modules/connectors/dhl_express --e ./modules/connectors/dhl_poland --e ./modules/connectors/dhl_universal --e ./modules/connectors/dicom --e ./modules/connectors/dpd --e ./modules/connectors/dpdhl --e ./modules/connectors/fedex --e ./modules/connectors/generic --e ./modules/connectors/geodis --e ./modules/connectors/laposte --e ./modules/connectors/nationex --e ./modules/connectors/purolator --e ./modules/connectors/roadie --e ./modules/connectors/royalmail --e ./modules/connectors/sendle --e ./modules/connectors/tnt --e ./modules/connectors/ups --e ./modules/connectors/usps --e ./modules/connectors/usps_international - --e ./modules/connectors/easypost --e ./modules/connectors/eshipper --e ./modules/connectors/freightcom --e ./modules/connectors/locate2u --e ./modules/connectors/zoom2u - - -# karrio server modules --e ./modules/core --e ./apps/api --e ./modules/graph --e ./modules/apps --e ./modules/data --e ./modules/events --e ./modules/manager --e ./modules/orders --e ./modules/proxy --e ./modules/pricing --e ./modules/documents \ No newline at end of file