6658 workflows add a first twenty piece email sender (#6965)
This commit is contained in:
@ -0,0 +1 @@
|
||||
export const CAPTCHA_DRIVER = Symbol('CAPTCHA_DRIVER');
|
||||
@ -0,0 +1,28 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
|
||||
import { CaptchaService } from 'src/engine/core-modules/captcha/captcha.service';
|
||||
|
||||
@Injectable()
|
||||
export class CaptchaGuard implements CanActivate {
|
||||
constructor(private captchaService: CaptchaService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
|
||||
const { captchaToken: token } = ctx.getArgs();
|
||||
|
||||
const result = await this.captchaService.validate(token || '');
|
||||
|
||||
if (result.success) return true;
|
||||
else
|
||||
throw new BadRequestException(
|
||||
'Invalid Captcha, please try another device',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
import {
|
||||
CaptchaDriverOptions,
|
||||
CaptchaModuleOptions,
|
||||
} from 'src/engine/core-modules/captcha/interfaces';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
export const captchaModuleFactory = (
|
||||
environmentService: EnvironmentService,
|
||||
): CaptchaModuleOptions | undefined => {
|
||||
const driver = environmentService.get('CAPTCHA_DRIVER');
|
||||
const siteKey = environmentService.get('CAPTCHA_SITE_KEY');
|
||||
const secretKey = environmentService.get('CAPTCHA_SECRET_KEY');
|
||||
|
||||
if (!driver) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!siteKey || !secretKey) {
|
||||
throw new Error('Captcha driver requires site key and secret key');
|
||||
}
|
||||
|
||||
const captchaOptions: CaptchaDriverOptions = {
|
||||
siteKey,
|
||||
secretKey,
|
||||
};
|
||||
|
||||
return {
|
||||
type: driver,
|
||||
options: captchaOptions,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,42 @@
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/captcha.constants';
|
||||
import { CaptchaService } from 'src/engine/core-modules/captcha/captcha.service';
|
||||
import { GoogleRecaptchaDriver } from 'src/engine/core-modules/captcha/drivers/google-recaptcha.driver';
|
||||
import { TurnstileDriver } from 'src/engine/core-modules/captcha/drivers/turnstile.driver';
|
||||
import {
|
||||
CaptchaDriverType,
|
||||
CaptchaModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/captcha/interfaces';
|
||||
|
||||
@Global()
|
||||
export class CaptchaModule {
|
||||
static forRoot(options: CaptchaModuleAsyncOptions): DynamicModule {
|
||||
const provider = {
|
||||
provide: CAPTCHA_DRIVER,
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (config.type) {
|
||||
case CaptchaDriverType.GoogleRecaptcha:
|
||||
return new GoogleRecaptchaDriver(config.options);
|
||||
case CaptchaDriverType.Turnstile:
|
||||
return new TurnstileDriver(config.options);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
inject: options.inject || [],
|
||||
};
|
||||
|
||||
return {
|
||||
module: CaptchaModule,
|
||||
providers: [CaptchaService, provider],
|
||||
exports: [CaptchaService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import { CaptchaDriver } from 'src/engine/core-modules/captcha/drivers/interfaces/captcha-driver.interface';
|
||||
|
||||
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/captcha.constants';
|
||||
import { CaptchaValidateResult } from 'src/engine/core-modules/captcha/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class CaptchaService implements CaptchaDriver {
|
||||
constructor(@Inject(CAPTCHA_DRIVER) private driver: CaptchaDriver) {}
|
||||
|
||||
async validate(token: string): Promise<CaptchaValidateResult> {
|
||||
if (this.driver) {
|
||||
return await this.driver.validate(token);
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import { CaptchaDriver } from 'src/engine/core-modules/captcha/drivers/interfaces/captcha-driver.interface';
|
||||
import { CaptchaServerResponse } from 'src/engine/core-modules/captcha/drivers/interfaces/captcha-server-response';
|
||||
|
||||
import {
|
||||
CaptchaDriverOptions,
|
||||
CaptchaValidateResult,
|
||||
} from 'src/engine/core-modules/captcha/interfaces';
|
||||
|
||||
export class GoogleRecaptchaDriver implements CaptchaDriver {
|
||||
private readonly siteKey: string;
|
||||
private readonly secretKey: string;
|
||||
private readonly httpService: AxiosInstance;
|
||||
constructor(private options: CaptchaDriverOptions) {
|
||||
this.siteKey = options.siteKey;
|
||||
this.secretKey = options.secretKey;
|
||||
this.httpService = axios.create({
|
||||
baseURL: 'https://www.google.com/recaptcha/api/siteverify',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(token: string): Promise<CaptchaValidateResult> {
|
||||
const formData = new URLSearchParams({
|
||||
secret: this.secretKey,
|
||||
response: token,
|
||||
});
|
||||
|
||||
const response = await this.httpService.post('', formData);
|
||||
const responseData = response.data as CaptchaServerResponse;
|
||||
|
||||
return {
|
||||
success: responseData.success,
|
||||
...(!responseData.success && {
|
||||
error: responseData['error-codes']?.[0] ?? 'Captcha Error',
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
import { CaptchaValidateResult } from 'src/engine/core-modules/captcha/interfaces';
|
||||
|
||||
export interface CaptchaDriver {
|
||||
validate(token: string): Promise<CaptchaValidateResult>;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export type CaptchaServerResponse = {
|
||||
success: boolean;
|
||||
challenge_ts: string;
|
||||
hostname: string;
|
||||
'error-codes': string[];
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import { CaptchaDriver } from 'src/engine/core-modules/captcha/drivers/interfaces/captcha-driver.interface';
|
||||
import { CaptchaServerResponse } from 'src/engine/core-modules/captcha/drivers/interfaces/captcha-server-response';
|
||||
|
||||
import {
|
||||
CaptchaDriverOptions,
|
||||
CaptchaValidateResult,
|
||||
} from 'src/engine/core-modules/captcha/interfaces';
|
||||
|
||||
export class TurnstileDriver implements CaptchaDriver {
|
||||
private readonly siteKey: string;
|
||||
private readonly secretKey: string;
|
||||
private readonly httpService: AxiosInstance;
|
||||
constructor(private options: CaptchaDriverOptions) {
|
||||
this.siteKey = options.siteKey;
|
||||
this.secretKey = options.secretKey;
|
||||
this.httpService = axios.create({
|
||||
baseURL: 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(token: string): Promise<CaptchaValidateResult> {
|
||||
const formData = new URLSearchParams({
|
||||
secret: this.secretKey,
|
||||
response: token,
|
||||
});
|
||||
const response = await this.httpService.post('', formData);
|
||||
|
||||
const responseData = response.data as CaptchaServerResponse;
|
||||
|
||||
return {
|
||||
success: responseData.success,
|
||||
...(!responseData.success && {
|
||||
error: responseData['error-codes']?.[0] ?? 'Captcha Error',
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum CaptchaDriverType {
|
||||
GoogleRecaptcha = 'google-recaptcha',
|
||||
Turnstile = 'turnstile',
|
||||
}
|
||||
|
||||
registerEnumType(CaptchaDriverType, {
|
||||
name: 'CaptchaDriverType',
|
||||
});
|
||||
|
||||
export type CaptchaDriverOptions = {
|
||||
siteKey: string;
|
||||
secretKey: string;
|
||||
};
|
||||
|
||||
export interface GoogleRecaptchaDriverFactoryOptions {
|
||||
type: CaptchaDriverType.GoogleRecaptcha;
|
||||
options: CaptchaDriverOptions;
|
||||
}
|
||||
|
||||
export interface TurnstileDriverFactoryOptions {
|
||||
type: CaptchaDriverType.Turnstile;
|
||||
options: CaptchaDriverOptions;
|
||||
}
|
||||
|
||||
export type CaptchaModuleOptions =
|
||||
| GoogleRecaptchaDriverFactoryOptions
|
||||
| TurnstileDriverFactoryOptions;
|
||||
|
||||
export type CaptchaModuleAsyncOptions = {
|
||||
useFactory: (
|
||||
...args: any[]
|
||||
) => CaptchaModuleOptions | Promise<CaptchaModuleOptions> | undefined;
|
||||
} & Pick<ModuleMetadata, 'imports'> &
|
||||
Pick<FactoryProvider, 'inject'>;
|
||||
|
||||
export type CaptchaValidateResult = { success: boolean; error?: string };
|
||||
@ -0,0 +1 @@
|
||||
export * from 'src/engine/core-modules/captcha/interfaces/captcha.interface';
|
||||
Reference in New Issue
Block a user