6658 workflows add a first twenty piece email sender (#6965)

This commit is contained in:
martmull
2024-09-12 11:00:25 +02:00
committed by GitHub
parent f8e5b333d9
commit 3190f4a87b
397 changed files with 1143 additions and 1037 deletions

View File

@ -0,0 +1 @@
export const CAPTCHA_DRIVER = Symbol('CAPTCHA_DRIVER');

View File

@ -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',
);
}
}

View File

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

View File

@ -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],
};
}
}

View File

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

View File

@ -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',
}),
};
}
}

View File

@ -0,0 +1,5 @@
import { CaptchaValidateResult } from 'src/engine/core-modules/captcha/interfaces';
export interface CaptchaDriver {
validate(token: string): Promise<CaptchaValidateResult>;
}

View File

@ -0,0 +1,6 @@
export type CaptchaServerResponse = {
success: boolean;
challenge_ts: string;
hostname: string;
'error-codes': string[];
};

View File

@ -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',
}),
};
}
}

View File

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

View File

@ -0,0 +1 @@
export * from 'src/engine/core-modules/captcha/interfaces/captcha.interface';