UNPKG

@danielbiegler/vendure-plugin-user-registration-guard

Version:

Let's you flexibly customize if and how you prevent users from registering with your Vendure instance.

185 lines (184 loc) 7.83 kB
import { CallHandler, ExecutionContext, NestInterceptor, OnApplicationBootstrap } from "@nestjs/common"; import { ModuleRef } from "@nestjs/core"; import { EventBus, RequestContextService } from "@vendure/core"; import { Observable } from "rxjs"; import { PluginUserRegistrationGuardOptions } from "./types"; /** * Since this interceptor will be registered globally it will run between every single request. * This means we must make sure to exit as early as possible in order to not slow down the Vendure instance. */ export declare class UserRegistrationInterceptor implements NestInterceptor, OnApplicationBootstrap { private eventBus; private moduleRef; private requestContextService; private options; private injector; constructor(eventBus: EventBus, moduleRef: ModuleRef, requestContextService: RequestContextService, options: PluginUserRegistrationGuardOptions); /** @internal */ onApplicationBootstrap(): Promise<void>; /** @internal */ private _isAllowed; /** @internal */ intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>; } /** * The UserRegistrationGuardPlugin let's you flexibly customize if and how you prevent users from registering with your Vendure instance. * * ```ts * import { UserRegistrationGuardPlugin } from "@danielbiegler/vendure-plugin-user-registration-guard"; * export const config: VendureConfig = { * // ... * plugins: [ * UserRegistrationGuardPlugin.init({ * shop: { * assert: { * // AND means every single assertion must be true to allow user registration * logicalOperator: "AND", * functions: [ ], // Insert your assertions here * }, * }, * admin: { * assert: { * // OR means user registration is allowed if a single assertion is true * logicalOperator: "OR", * functions: [ ], // You may leave this empty too * }, * }, * }), * ], * } * ``` * * Please refer to the specific {@link PluginUserRegistrationGuardOptions} for how and what you can customize. * * ### 2. Create an assertion * * Here's an example assertion where we block customer registrations if the email ends with `example.com`: * * ```ts * const blockExampleDotCom: AssertFunctionShopApi = async (ctx, args) => { * const isAllowed = !args.input.emailAddress.endsWith("example.com"); * return { * isAllowed, * reason: !isAllowed ? 'Failed because email ends with "example.com"' : undefined, * }; * }; * ``` * * The `reason` field is helpful for when you're subscribing to the published {@link BlockedCustomerRegistrationEvent} or {@link BlockedCreateAdministratorEvent} and want to log or understand why somebody got blocked. * * In your assertions you'll receive the [`RequestContext`](https://docs.vendure.io/reference/typescript-api/request/request-context) and the GraphQL arguments of the mutation, which by default are either [`RegisterCustomerInput`](https://docs.vendure.io/reference/graphql-api/shop/input-types#registercustomerinput) or [`CreateAdministratorInput`](https://docs.vendure.io/reference/graphql-api/admin/input-types#createadministratorinput) depending on the API type. For example, if you'd like to block IP ranges you can access the underlying [Express Request](https://docs.vendure.io/reference/typescript-api/request/request-context#req) object through the `RequestContext` . * * If you've extended your GraphQL API types you may override the TypeScript Generic to get completions in your assertion functions like so: * * ```ts * const example: AssertFunctionShopApi<{ * example: boolean; * // ... * }> = async (ctx, args) => { * return { isAllowed: args.example }; * }; * ``` * * ### 3. Add it to the plugin * * ```ts * import { UserRegistrationGuardPlugin } from "@danielbiegler/vendure-plugin-user-registration-guard"; * export const config: VendureConfig = { * // ... * plugins: [ * UserRegistrationGuardPlugin.init({ * shop: { * assert: { * logicalOperator: "AND", * functions: [ blockExampleDotCom ], * }, * }, * admin: { * assert: { * logicalOperator: "AND", * functions: [], * }, * }, * }), * ], * } * ``` * * ### 4. Try registering new customers * * ```graphql * mutation { * registerCustomerAccount(input: { * emailAddress: "example@example.com", * # ... * }) { * __typename * } * } * ``` * * This user will now be blocked from registering according to our `blockExampleDotCom` assertion. * * #### Handling errors * * The plugin is non-intrusive and does not extend the API itself to avoid introducing unhandled errors in your code. * * It respects [`RegisterCustomerAccountResult`](https://docs.vendure.io/reference/graphql-api/shop/object-types#registercustomeraccountresult) being a Union, so we don't throw an error, but return a [`NativeAuthStrategyError`](https://docs.vendure.io/reference/graphql-api/shop/object-types#nativeauthstrategyerror). You may handle the error just like the other `RegisterCustomerAccountResult` types like [`PasswordValidationError`](https://docs.vendure.io/reference/graphql-api/shop/object-types#passwordvalidationerror) for example. * * In contrast, for admins we do throw the error! This is a little different because by default the [`createAdministrator`](https://docs.vendure.io/reference/graphql-api/admin/mutations#createadministrator) mutation does not return a Union with error types. * * Granted, the `NativeAuthStrategyError` is technically not correct for blocking registrations and doesn't communicate the blocking properly, but it's the only reasonable error type in the Union for a default non-api-extended Vendure instance. You might want to add some comments in your registration logic that the error means blockage. * * ### 5. Subscribe to events * * You may want to [subscribe](https://docs.vendure.io/guides/developer-guide/events/#subscribing-to-events) to the [EventBus](https://docs.vendure.io/reference/typescript-api/events/event-bus) to monitor blocked registration attempts. * * ```ts * this.eventBus * .ofType(BlockedCustomerRegistrationEvent<MutationRegisterCustomerAccountArgs>) * .subscribe(async (event) => { * const rejecteds = event.assertions.filter((a) => !a.isAllowed); * console.log(`Blocked customer registration! ${rejecteds.length}/${event.assertions.length} assertions failed, see reasons:`); * rejecteds.forEach(r => console.log(" -", r.reason)); * * // Example output: * // Blocked customer registration! 1/1 assertions failed, see reasons: * // - Failed because email ends with "example.com" * }); * * this.eventBus * // You can even override the passed in args if you've extended your Graphql API * .ofType(BlockedCreateAdministratorEvent<{ example: boolean }>).subscribe(async (event) => { * event.args.example // is typed now! :) * }); * ``` * * @category Plugin */ export declare class UserRegistrationGuardPlugin { /** @internal */ static options: PluginUserRegistrationGuardOptions; /** * The static `init()` method is called with the options to configure the plugin. * * @example * ```ts * UserRegistrationGuardPlugin.init({ * shop: { * assert: { * logicalOperator: "AND", * functions: [firstFunc, secondFunc], * }, * }, * admin: { * assert: { * logicalOperator: "OR", * functions: [], * }, * }, * }), * ``` */ static init(options: PluginUserRegistrationGuardOptions): typeof UserRegistrationGuardPlugin; }