Przeglądaj źródła

feat(core): Add custom error result on AuthenticationStrategy

Closes #499
Bruno Macabeus 5 lat temu
rodzic
commit
d3ddb96a5e

+ 1 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -2130,6 +2130,7 @@ export type InvalidCredentialsError = ErrorResult & {
   __typename?: 'InvalidCredentialsError';
   errorCode: ErrorCode;
   message: Scalars['String'];
+  authenticationError: Scalars['String'];
 };
 
 /** Returned if there is an error in transitioning the Order state */

+ 209 - 152
packages/admin-ui/src/lib/core/src/common/introspection-result.ts

@@ -1,155 +1,212 @@
 // tslint:disable
 
-export interface PossibleTypesResultData {
-    possibleTypes: {
-        [key: string]: string[];
-    };
-}
-const result: PossibleTypesResultData = {
-    possibleTypes: {
-        CreateAssetResult: ['Asset', 'MimeTypeError'],
-        NativeAuthenticationResult: ['CurrentUser', 'InvalidCredentialsError', 'NativeAuthStrategyError'],
-        AuthenticationResult: ['CurrentUser', 'InvalidCredentialsError'],
-        CreateChannelResult: ['Channel', 'LanguageNotAvailableError'],
-        UpdateChannelResult: ['Channel', 'LanguageNotAvailableError'],
-        CreateCustomerResult: ['Customer', 'EmailAddressConflictError'],
-        UpdateCustomerResult: ['Customer', 'EmailAddressConflictError'],
-        UpdateGlobalSettingsResult: ['GlobalSettings', 'ChannelDefaultLanguageError'],
-        TransitionOrderToStateResult: ['Order', 'OrderStateTransitionError'],
-        SettlePaymentResult: [
-            'Payment',
-            'SettlePaymentError',
-            'PaymentStateTransitionError',
-            'OrderStateTransitionError',
-        ],
-        AddFulfillmentToOrderResult: [
-            'Fulfillment',
-            'EmptyOrderLineSelectionError',
-            'ItemsAlreadyFulfilledError',
-        ],
-        CancelOrderResult: [
-            'Order',
-            'EmptyOrderLineSelectionError',
-            'QuantityTooGreatError',
-            'MultipleOrderError',
-            'CancelActiveOrderError',
-            'OrderStateTransitionError',
-        ],
-        RefundOrderResult: [
-            'Refund',
-            'QuantityTooGreatError',
-            'NothingToRefundError',
-            'OrderStateTransitionError',
-            'MultipleOrderError',
-            'PaymentOrderMismatchError',
-            'RefundOrderStateError',
-            'AlreadyRefundedError',
-            'RefundStateTransitionError',
-        ],
-        SettleRefundResult: ['Refund', 'RefundStateTransitionError'],
-        TransitionFulfillmentToStateResult: ['Fulfillment', 'FulfillmentStateTransitionError'],
-        RemoveOptionGroupFromProductResult: ['Product', 'ProductOptionInUseError'],
-        CreatePromotionResult: ['Promotion', 'MissingConditionsError'],
-        UpdatePromotionResult: ['Promotion', 'MissingConditionsError'],
-        PaginatedList: [
-            'CustomerGroupList',
-            'JobList',
-            'PaymentMethodList',
-            'AdministratorList',
-            'AssetList',
-            'CollectionList',
-            'ProductVariantList',
-            'CountryList',
-            'CustomerList',
-            'FacetList',
-            'HistoryEntryList',
-            'OrderList',
-            'ProductList',
-            'PromotionList',
-            'RoleList',
-            'ShippingMethodList',
-            'TaxRateList',
-        ],
-        Node: [
-            'Collection',
-            'Customer',
-            'Facet',
-            'Fulfillment',
-            'Job',
-            'Order',
-            'Product',
-            'ProductVariant',
-            'Address',
-            'Administrator',
-            'Asset',
-            'Channel',
-            'Country',
-            'CustomerGroup',
-            'FacetValue',
-            'HistoryEntry',
-            'OrderItem',
-            'OrderLine',
-            'Payment',
-            'Refund',
-            'PaymentMethod',
-            'ProductOptionGroup',
-            'ProductOption',
-            'Promotion',
-            'Role',
-            'ShippingMethod',
-            'StockAdjustment',
-            'Sale',
-            'Cancellation',
-            'Return',
-            'TaxCategory',
-            'TaxRate',
-            'User',
-            'AuthenticationMethod',
-            'Zone',
-        ],
-        ErrorResult: [
-            'MimeTypeError',
-            'LanguageNotAvailableError',
-            'ChannelDefaultLanguageError',
-            'SettlePaymentError',
-            'EmptyOrderLineSelectionError',
-            'ItemsAlreadyFulfilledError',
-            'MultipleOrderError',
-            'CancelActiveOrderError',
-            'PaymentOrderMismatchError',
-            'RefundOrderStateError',
-            'NothingToRefundError',
-            'AlreadyRefundedError',
-            'QuantityTooGreatError',
-            'RefundStateTransitionError',
-            'PaymentStateTransitionError',
-            'FulfillmentStateTransitionError',
-            'ProductOptionInUseError',
-            'MissingConditionsError',
-            'NativeAuthStrategyError',
-            'InvalidCredentialsError',
-            'OrderStateTransitionError',
-            'EmailAddressConflictError',
-        ],
-        CustomField: [
-            'StringCustomFieldConfig',
-            'LocaleStringCustomFieldConfig',
-            'IntCustomFieldConfig',
-            'FloatCustomFieldConfig',
-            'BooleanCustomFieldConfig',
-            'DateTimeCustomFieldConfig',
-        ],
-        CustomFieldConfig: [
-            'StringCustomFieldConfig',
-            'LocaleStringCustomFieldConfig',
-            'IntCustomFieldConfig',
-            'FloatCustomFieldConfig',
-            'BooleanCustomFieldConfig',
-            'DateTimeCustomFieldConfig',
-        ],
-        SearchResultPrice: ['PriceRange', 'SinglePrice'],
-        StockMovement: ['StockAdjustment', 'Sale', 'Cancellation', 'Return'],
-        StockMovementItem: ['StockAdjustment', 'Sale', 'Cancellation', 'Return'],
-    },
+      export interface PossibleTypesResultData {
+        possibleTypes: {
+          [key: string]: string[]
+        }
+      }
+      const result: PossibleTypesResultData = {
+  "possibleTypes": {
+    "CreateAssetResult": [
+      "Asset",
+      "MimeTypeError"
+    ],
+    "NativeAuthenticationResult": [
+      "CurrentUser",
+      "InvalidCredentialsError",
+      "NativeAuthStrategyError"
+    ],
+    "AuthenticationResult": [
+      "CurrentUser",
+      "InvalidCredentialsError"
+    ],
+    "CreateChannelResult": [
+      "Channel",
+      "LanguageNotAvailableError"
+    ],
+    "UpdateChannelResult": [
+      "Channel",
+      "LanguageNotAvailableError"
+    ],
+    "CreateCustomerResult": [
+      "Customer",
+      "EmailAddressConflictError"
+    ],
+    "UpdateCustomerResult": [
+      "Customer",
+      "EmailAddressConflictError"
+    ],
+    "UpdateGlobalSettingsResult": [
+      "GlobalSettings",
+      "ChannelDefaultLanguageError"
+    ],
+    "TransitionOrderToStateResult": [
+      "Order",
+      "OrderStateTransitionError"
+    ],
+    "SettlePaymentResult": [
+      "Payment",
+      "SettlePaymentError",
+      "PaymentStateTransitionError",
+      "OrderStateTransitionError"
+    ],
+    "AddFulfillmentToOrderResult": [
+      "Fulfillment",
+      "EmptyOrderLineSelectionError",
+      "ItemsAlreadyFulfilledError"
+    ],
+    "CancelOrderResult": [
+      "Order",
+      "EmptyOrderLineSelectionError",
+      "QuantityTooGreatError",
+      "MultipleOrderError",
+      "CancelActiveOrderError",
+      "OrderStateTransitionError"
+    ],
+    "RefundOrderResult": [
+      "Refund",
+      "QuantityTooGreatError",
+      "NothingToRefundError",
+      "OrderStateTransitionError",
+      "MultipleOrderError",
+      "PaymentOrderMismatchError",
+      "RefundOrderStateError",
+      "AlreadyRefundedError",
+      "RefundStateTransitionError"
+    ],
+    "SettleRefundResult": [
+      "Refund",
+      "RefundStateTransitionError"
+    ],
+    "TransitionFulfillmentToStateResult": [
+      "Fulfillment",
+      "FulfillmentStateTransitionError"
+    ],
+    "RemoveOptionGroupFromProductResult": [
+      "Product",
+      "ProductOptionInUseError"
+    ],
+    "CreatePromotionResult": [
+      "Promotion",
+      "MissingConditionsError"
+    ],
+    "UpdatePromotionResult": [
+      "Promotion",
+      "MissingConditionsError"
+    ],
+    "PaginatedList": [
+      "CustomerGroupList",
+      "JobList",
+      "PaymentMethodList",
+      "AdministratorList",
+      "AssetList",
+      "CollectionList",
+      "ProductVariantList",
+      "CountryList",
+      "CustomerList",
+      "FacetList",
+      "HistoryEntryList",
+      "OrderList",
+      "ProductList",
+      "PromotionList",
+      "RoleList",
+      "ShippingMethodList",
+      "TaxRateList"
+    ],
+    "Node": [
+      "Collection",
+      "Customer",
+      "Facet",
+      "Fulfillment",
+      "Job",
+      "Order",
+      "Product",
+      "ProductVariant",
+      "Address",
+      "Administrator",
+      "Asset",
+      "Channel",
+      "Country",
+      "CustomerGroup",
+      "FacetValue",
+      "HistoryEntry",
+      "OrderItem",
+      "OrderLine",
+      "Payment",
+      "Refund",
+      "PaymentMethod",
+      "ProductOptionGroup",
+      "ProductOption",
+      "Promotion",
+      "Role",
+      "ShippingMethod",
+      "StockAdjustment",
+      "Sale",
+      "Cancellation",
+      "Return",
+      "TaxCategory",
+      "TaxRate",
+      "User",
+      "AuthenticationMethod",
+      "Zone"
+    ],
+    "ErrorResult": [
+      "MimeTypeError",
+      "LanguageNotAvailableError",
+      "ChannelDefaultLanguageError",
+      "SettlePaymentError",
+      "EmptyOrderLineSelectionError",
+      "ItemsAlreadyFulfilledError",
+      "MultipleOrderError",
+      "CancelActiveOrderError",
+      "PaymentOrderMismatchError",
+      "RefundOrderStateError",
+      "NothingToRefundError",
+      "AlreadyRefundedError",
+      "QuantityTooGreatError",
+      "RefundStateTransitionError",
+      "PaymentStateTransitionError",
+      "FulfillmentStateTransitionError",
+      "ProductOptionInUseError",
+      "MissingConditionsError",
+      "NativeAuthStrategyError",
+      "InvalidCredentialsError",
+      "OrderStateTransitionError",
+      "EmailAddressConflictError"
+    ],
+    "CustomField": [
+      "StringCustomFieldConfig",
+      "LocaleStringCustomFieldConfig",
+      "IntCustomFieldConfig",
+      "FloatCustomFieldConfig",
+      "BooleanCustomFieldConfig",
+      "DateTimeCustomFieldConfig"
+    ],
+    "CustomFieldConfig": [
+      "StringCustomFieldConfig",
+      "LocaleStringCustomFieldConfig",
+      "IntCustomFieldConfig",
+      "FloatCustomFieldConfig",
+      "BooleanCustomFieldConfig",
+      "DateTimeCustomFieldConfig"
+    ],
+    "SearchResultPrice": [
+      "PriceRange",
+      "SinglePrice"
+    ],
+    "StockMovement": [
+      "StockAdjustment",
+      "Sale",
+      "Cancellation",
+      "Return"
+    ],
+    "StockMovementItem": [
+      "StockAdjustment",
+      "Sale",
+      "Cancellation",
+      "Return"
+    ]
+  }
 };
-export default result;
+      export default result;
+    

Plik diff jest za duży
+ 529 - 400
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts


Plik diff jest za duży
+ 743 - 706
packages/common/src/generated-shop-types.ts


+ 1 - 0
packages/common/src/generated-types.ts

@@ -2099,6 +2099,7 @@ export type InvalidCredentialsError = ErrorResult & {
   __typename?: 'InvalidCredentialsError';
   errorCode: ErrorCode;
   message: Scalars['String'];
+  authenticationError: Scalars['String'];
 };
 
 /** Returned if there is an error in transitioning the Order state */

+ 17 - 1
packages/core/e2e/authentication-strategy.e2e-spec.ts

@@ -81,6 +81,21 @@ describe('AuthenticationStrategy', () => {
 
             expect(authenticate.message).toBe('The provided credentials are invalid');
             expect(authenticate.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
+            expect(authenticate.authenticationError).toBe('');
+        });
+
+        it('fails with an expried token', async () => {
+            const { authenticate } = await shopClient.query(AUTHENTICATE, {
+                input: {
+                    test_strategy: {
+                        token: 'expired-token',
+                    },
+                },
+            });
+
+            expect(authenticate.message).toBe('The provided credentials are invalid');
+            expect(authenticate.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
+            expect(authenticate.authenticationError).toBe('Expired token');
         });
 
         it('creates a new Customer with valid token', async () => {
@@ -281,7 +296,8 @@ const AUTHENTICATE = gql`
     mutation Authenticate($input: AuthenticationInput!) {
         authenticate(input: $input) {
             ...CurrentUser
-            ... on ErrorResult {
+            ... on InvalidCredentialsError {
+                authenticationError
                 errorCode
                 message
             }

+ 4 - 1
packages/core/e2e/fixtures/test-authentication-strategies.ts

@@ -42,8 +42,11 @@ export class TestAuthenticationStrategy implements AuthenticationStrategy<TestAu
         `;
     }
 
-    async authenticate(ctx: RequestContext, data: TestAuthPayload): Promise<User | false> {
+    async authenticate(ctx: RequestContext, data: TestAuthPayload): Promise<User | false | string> {
         const { token, userData } = data;
+        if (token === 'expired-token') {
+            return 'Expired token';
+        }
         if (data.token !== VALID_AUTH_TOKEN) {
             return false;
         }

Plik diff jest za duży
+ 529 - 400
packages/core/e2e/graphql/generated-e2e-admin-types.ts


Plik diff jest za duży
+ 724 - 687
packages/core/e2e/graphql/generated-e2e-shop-types.ts


+ 4 - 3
packages/core/src/api/resolvers/base/base-auth.resolver.ts

@@ -15,6 +15,7 @@ import { NativeAuthStrategyError as AdminNativeAuthStrategyError } from '../../.
 import {
     InvalidCredentialsError,
     NativeAuthStrategyError as ShopNativeAuthStrategyError,
+    NotVerifiedError,
 } from '../../../common/error/generated-graphql-shop-errors';
 import { NATIVE_AUTH_STRATEGY_NAME } from '../../../config/auth/native-authentication-strategy';
 import { ConfigService } from '../../../config/config.service';
@@ -52,7 +53,7 @@ export class BaseAuthResolver {
         ctx: RequestContext,
         req: Request,
         res: Response,
-    ): Promise<AdminAuthenticationResult | ShopAuthenticationResult> {
+    ): Promise<AdminAuthenticationResult | ShopAuthenticationResult | NotVerifiedError> {
         return await this.authenticateAndCreateSession(
             ctx,
             {
@@ -105,7 +106,7 @@ export class BaseAuthResolver {
         args: MutationAuthenticateArgs,
         req: Request,
         res: Response,
-    ): Promise<AdminAuthenticationResult | ShopAuthenticationResult> {
+    ): Promise<AdminAuthenticationResult | ShopAuthenticationResult | NotVerifiedError> {
         const [method, data] = Object.entries(args.input)[0];
         const { apiType } = ctx;
         const session = await this.authService.authenticate(ctx, apiType, method, data);
@@ -115,7 +116,7 @@ export class BaseAuthResolver {
         if (apiType && apiType === 'admin') {
             const administrator = await this.administratorService.findOneByUserId(ctx, session.user.id);
             if (!administrator) {
-                return new InvalidCredentialsError();
+                return new InvalidCredentialsError('');
             }
         }
         setSessionToken({

+ 2 - 1
packages/core/src/api/resolvers/shop/shop-auth.resolver.ts

@@ -79,7 +79,8 @@ export class ShopAuthResolver extends BaseAuthResolver {
     @Mutation()
     @Allow(Permission.Public)
     async authenticate(
-        @Args() args: MutationAuthenticateArgs,
+        // TODO: correct typings
+        @Args() args: any,
         @Ctx() ctx: RequestContext,
         @Context('req') req: Request,
         @Context('res') res: Response,

+ 1 - 0
packages/core/src/api/schema/common/common-types.graphql

@@ -201,6 +201,7 @@ type NativeAuthStrategyError implements ErrorResult {
 type InvalidCredentialsError implements ErrorResult {
     errorCode: ErrorCode!
     message: String!
+    authenticationError: String!
 }
 
 "Returned if there is an error in transitioning the Order state"

+ 1 - 0
packages/core/src/common/error/generated-graphql-admin-errors.ts

@@ -233,6 +233,7 @@ export class InvalidCredentialsError extends ErrorResult {
   readonly errorCode = 'INVALID_CREDENTIALS_ERROR' as any;
   readonly message = 'INVALID_CREDENTIALS_ERROR';
   constructor(
+    public   authenticationError: Scalars['String'],
   ) {
     super();
   }

+ 1 - 0
packages/core/src/common/error/generated-graphql-shop-errors.ts

@@ -33,6 +33,7 @@ export class InvalidCredentialsError extends ErrorResult {
   readonly errorCode = 'INVALID_CREDENTIALS_ERROR' as any;
   readonly message = 'INVALID_CREDENTIALS_ERROR';
   constructor(
+    public   authenticationError: Scalars['String'],
   ) {
     super();
   }

+ 3 - 2
packages/core/src/config/auth/authentication-strategy.ts

@@ -62,9 +62,10 @@ export interface AuthenticationStrategy<Data = unknown> extends InjectableStrate
      * @description
      * Used to authenticate a user with the authentication provider. This method
      * will implement the provider-specific authentication logic, and should resolve to either a
-     * {@link User} object on success, or `false` on failure.
+     * {@link User} object on success, or `false | string` on failure.
+     * A `string` return could be used to describe what error happened, otherwise `false` to an unknown error.
      */
-    authenticate(ctx: RequestContext, data: Data): Promise<User | false>;
+    authenticate(ctx: RequestContext, data: Data): Promise<User | false | string>;
 
     /**
      * @description

+ 8 - 5
packages/core/src/service/services/auth.service.ts

@@ -57,11 +57,14 @@ export class AuthService {
             ),
         );
         const authenticationStrategy = this.getAuthenticationStrategy(apiType, authenticationMethod);
-        const user = await authenticationStrategy.authenticate(ctx, authenticationData);
-        if (!user) {
-            return new InvalidCredentialsError();
+        const authenticateResult = await authenticationStrategy.authenticate(ctx, authenticationData);
+        if (typeof authenticateResult === 'string') {
+            return new InvalidCredentialsError(authenticateResult);
         }
-        return this.createAuthenticatedSessionForUser(ctx, user, authenticationStrategy.name);
+        if (!authenticateResult) {
+            return new InvalidCredentialsError('');
+        }
+        return this.createAuthenticatedSessionForUser(ctx, authenticateResult, authenticationStrategy.name);
     }
 
     async createAuthenticatedSessionForUser(
@@ -111,7 +114,7 @@ export class AuthService {
         );
         const passwordMatches = await nativeAuthenticationStrategy.verifyUserPassword(ctx, userId, password);
         if (!passwordMatches) {
-            return new InvalidCredentialsError();
+            return new InvalidCredentialsError('');
         }
         return true;
     }

+ 1 - 1
packages/core/src/service/services/user.service.ts

@@ -246,7 +246,7 @@ export class UserService {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         const matches = await this.passwordCipher.check(currentPassword, nativeAuthMethod.passwordHash);
         if (!matches) {
-            return new InvalidCredentialsError();
+            return new InvalidCredentialsError('');
         }
         nativeAuthMethod.passwordHash = await this.passwordCipher.hash(newPassword);
         await this.connection

Plik diff jest za duży
+ 529 - 400
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts


Plik diff jest za duży
+ 0 - 0
schema-admin.json


Plik diff jest za duży
+ 0 - 0
schema-shop.json


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików