@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
TypeScript
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;
}