feat(server): make captcha modular (#5961)

This commit is contained in:
darkskygit
2024-09-03 09:03:51 +00:00
parent 52c9da67f0
commit 935771c8a8
28 changed files with 432 additions and 58 deletions
@@ -526,4 +526,10 @@ export const USER_FRIENDLY_ERRORS = {
type: 'action_forbidden',
message: 'Cannot delete own account.',
},
// captcha errors
captcha_verification_failed: {
type: 'bad_request',
message: 'Captcha verification failed.',
},
} satisfies Record<string, UserFriendlyErrorOptions>;
@@ -533,6 +533,12 @@ export class CannotDeleteOwnAccount extends UserFriendlyError {
super('action_forbidden', 'cannot_delete_own_account', message);
}
}
export class CaptchaVerificationFailed extends UserFriendlyError {
constructor(message?: string) {
super('bad_request', 'captcha_verification_failed', message);
}
}
export enum ErrorNames {
INTERNAL_SERVER_ERROR,
TOO_MANY_REQUEST,
@@ -604,7 +610,8 @@ export enum ErrorNames {
INVALID_RUNTIME_CONFIG_TYPE,
MAILER_SERVICE_IS_NOT_CONFIGURED,
CANNOT_DELETE_ALL_ADMIN_ACCOUNT,
CANNOT_DELETE_OWN_ACCOUNT
CANNOT_DELETE_OWN_ACCOUNT,
CAPTCHA_VERIFICATION_FAILED
}
registerEnumType(ErrorNames, {
name: 'ErrorNames'
@@ -0,0 +1,50 @@
import {
applyDecorators,
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
UseGuards,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GUARD_PROVIDER, NamedGuards } from './provider';
const BasicGuardSymbol = Symbol('BasicGuard');
@Injectable()
export class BasicGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
async canActivate(context: ExecutionContext) {
// get registered guard name
const providerName = this.reflector.get<string>(
BasicGuardSymbol,
context.getHandler()
);
const provider = GUARD_PROVIDER[providerName as NamedGuards];
if (provider) {
return await provider.canActivate(context);
}
return true;
}
}
/**
* This guard is used to protect routes/queries/mutations that use a registered guard
*
* @example
*
* ```typescript
* \@UseNamedGuard('captcha') // use captcha guard
* \@Auth()
* \@Query(() => UserType)
* user(@CurrentUser() user: CurrentUser) {
* return user;
* }
* ```
*/
export const UseNamedGuard = (name: NamedGuards) =>
applyDecorators(UseGuards(BasicGuard), SetMetadata(BasicGuardSymbol, name));
@@ -0,0 +1,2 @@
export { UseNamedGuard } from './guard';
export { GuardProvider, type RegisterGuardName } from './provider';
@@ -0,0 +1,26 @@
import {
CanActivate,
ExecutionContext,
Injectable,
Logger,
OnModuleInit,
} from '@nestjs/common';
export interface RegisterGuardName {}
export type NamedGuards = keyof RegisterGuardName;
export const GUARD_PROVIDER: Partial<Record<NamedGuards, GuardProvider>> = {};
@Injectable()
export abstract class GuardProvider implements OnModuleInit, CanActivate {
private readonly logger = new Logger(GuardProvider.name);
abstract name: NamedGuards;
onModuleInit() {
GUARD_PROVIDER[this.name] = this;
this.logger.log(`Guard provider [${this.name}] registered`);
}
abstract canActivate(context: ExecutionContext): boolean | Promise<boolean>;
}
@@ -16,6 +16,7 @@ export {
export * from './error';
export { EventEmitter, type EventPayload, OnEvent } from './event';
export type { GraphqlContext } from './graphql';
export * from './guard';
export { CryptoHelper, URLHelper } from './helpers';
export { MailService } from './mailer';
export { CallCounter, CallTimer, metrics } from './metrics';