diff --git a/apps/backend/.gitignore b/apps/backend/.gitignore index f45ea2d91..de36198c5 100644 --- a/apps/backend/.gitignore +++ b/apps/backend/.gitignore @@ -66,5 +66,4 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json !/uploads/temp/index.html # Configuration -/src/plugins/*/admin/database/migrations /src/plugins/core/ \ No newline at end of file diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 26920ec19..fca7573e1 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -17,6 +17,11 @@ import { PluginsModule } from './plugins/plugins.module'; config: DATABASE_ENVS, schemaDatabase, }, + // captcha: { + // type: 'cloudflare_turnstile', + // secret_key: '', + // site_key: '', + // }, // email: emailResend({ // api_key: process.env.EMAIL_RESEND_API_KEY, // from: process.env.EMAIL_RESEND_FROM, diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index 024bd5f63..484edfdba 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -141,28 +141,6 @@ "send_testing_email": "Send Testing Email" } }, - "security": { - "captcha": { - "title": "Captcha", - "desc": "Captcha is a security feature that requires users to complete a challenge to prove they are human. This helps prevent spam and abuse.", - "type": { - "title": "Type", - "none": { - "title": "None" - }, - "recaptcha_desc": "Read more on Google reCAPTCHA.", - "recaptcha_v2_invisible": "reCAPTCHA v2 Invisible", - "recaptcha_v2_checkbox": "reCAPTCHA v2 Checkbox", - "recaptcha_v3": "reCAPTCHA v3", - "cloudflare_turnstile": { - "title": "Cloudflare Turnstile", - "desc": "Read more on Cloudflare Turnstile." - } - }, - "site_key": "Site Key", - "secret_key": "Secret Key" - } - }, "authorization": { "title": "Authorization", "desc": "Manage authorization settings for your website.", @@ -569,30 +547,6 @@ "submit": "Yes, delete file" } } - }, - "security": { - "spam": { - "title": "Spam Protection", - "captcha": { - "title": "Captcha", - "type": { - "title": "Type", - "none": { - "title": "None" - }, - "recaptcha_desc": "Read more on Google reCAPTCHA.", - "recaptcha_v2_invisible": "reCAPTCHA v2 Invisible", - "recaptcha_v2_checkbox": "reCAPTCHA v2 Checkbox", - "recaptcha_v3": "reCAPTCHA v3", - "cloudflare_turnstile": { - "title": "Cloudflare Turnstile", - "desc": "Read more on Cloudflare Turnstile." - } - }, - "site_key": "Site Key", - "secret_key": "Secret Key" - } - } } }, "members": { diff --git a/apps/frontend/src/plugins/core/langs/en.json b/apps/frontend/src/plugins/core/langs/en.json index d2ed2d3a3..72dc11b6c 100644 --- a/apps/frontend/src/plugins/core/langs/en.json +++ b/apps/frontend/src/plugins/core/langs/en.json @@ -358,8 +358,6 @@ "settings_email": "Email", "settings_authorization": "Authorization", "settings_legal": "Legal & Policies", - "security": "Security", - "security_spam": "Spam Protection", "plugins": "Plugins", "styles": "Styles", "styles_theme-editor": "Theme Editor", @@ -379,8 +377,6 @@ "can_manage_settings_authorization": "Can manage authorization settings?", "can_manage_settings_legal": "Can manage legal settings?", "can_manage_plugins": "Can manage plugins?", - "security": "Security", - "can_manage_security_spam": "Can manage spam protection?", "styles": "Styles", "can_manage_styles_theme-editor": "Can manage theme editor?", "can_manage_styles_nav": "Can manage navigation?", diff --git a/packages/backend/scripts/generate-config.ts b/packages/backend/scripts/generate-config.ts deleted file mode 100644 index 7f5791469..000000000 --- a/packages/backend/scripts/generate-config.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { existsSync } from 'fs'; -import { mkdir, writeFile } from 'fs/promises'; -import { join } from 'path'; - -import { - ConfigType, - DEFAULT_CONFIG_DATA, - getConfigFile, -} from '../src/helpers/config'; -import { updateObject } from './helpers/update-object'; - -export const generateConfig = async ({ - pluginsPath, -}: { - pluginsPath: string; -}) => { - const folderPath = join(pluginsPath, 'core', 'utils'); - if (!existsSync(folderPath)) { - await mkdir(folderPath, { recursive: true }); - } - - const configPath = join(folderPath, 'config.json'); - if (!existsSync(configPath)) { - await writeFile( - configPath, - JSON.stringify(DEFAULT_CONFIG_DATA, null, 2), - 'utf8', - ); - - return; - } - - const config = getConfigFile(); - const updatedConfig: ConfigType = updateObject(config, DEFAULT_CONFIG_DATA); - - await writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf8'); -}; diff --git a/packages/backend/scripts/generate-manifest.ts b/packages/backend/scripts/generate-manifest.ts index d847911d2..89dee322f 100644 --- a/packages/backend/scripts/generate-manifest.ts +++ b/packages/backend/scripts/generate-manifest.ts @@ -88,22 +88,6 @@ export const generateManifest = async ({ process.exit(1); } - const configPath = join( - process.cwd(), - 'src', - 'plugins', - 'core', - 'utils', - 'config.json', - ); - - if (!existsSync(configPath)) { - console.log( - `⛔️ Config file not found in 'backend/utils/config.json' directory. "${configPath}"`, - ); - process.exit(1); - } - const languages = await db.query.core_languages.findMany({ columns: { code: true, diff --git a/packages/backend/scripts/setup.ts b/packages/backend/scripts/setup.ts index b904f6ba0..ef41b7628 100644 --- a/packages/backend/scripts/setup.ts +++ b/packages/backend/scripts/setup.ts @@ -9,7 +9,6 @@ import coreSchemaDatabase from '../src/database'; import { checkUpdateSchemaDatabase } from './check-update-schema-database'; import { copyFiles } from './copy-files'; import { generateDatabaseMigrations, runMigrations } from './database'; -import { generateConfig } from './generate-config'; import { generateManifest } from './generate-manifest'; import { updatePlugins } from './update-plugins'; @@ -37,12 +36,7 @@ const init = async () => { const pluginsPath = getPluginsPath(); console.log( - `${initConsole} [1/${skipDatabase ? 2 : 7}] Setup the project. Generating the config file...`, - ); - await generateConfig({ pluginsPath }); - - console.log( - `${initConsole} [2/${skipDatabase ? 2 : 7}] Copying files into backend...`, + `${initConsole} [1/${skipDatabase ? 1 : 6}] Copying files into backend...`, ); await copyFiles({ pluginsPath }); @@ -51,7 +45,7 @@ const init = async () => { process.exit(0); } - console.log(`${initConsole} [3/7] Generating database migrations...`); + console.log(`${initConsole} [2/6] Generating database migrations...`); await generateDatabaseMigrations(); const database = createClientDatabase({ @@ -60,17 +54,17 @@ const init = async () => { }); console.log( - `${initConsole} [4/7] Create tables in database using migrations...`, + `${initConsole} [3/6] Create tables in database using migrations...`, ); await runMigrations(); - console.log(`${initConsole} [5/7] Updating plugins...`); + console.log(`${initConsole} [4/6] Updating plugins...`); await updatePlugins({ pluginsPath, db: database.db }); - console.log(`${initConsole} [6/7] Checking and updating schema database...`); + console.log(`${initConsole} [5/6] Checking and updating schema database...`); await checkUpdateSchemaDatabase({ db: database.db }); - console.log(`${initConsole} [7/7] Generating the manifest files...`); + console.log(`${initConsole} [6/6] Generating the manifest files...`); await generateManifest({ db: database.db }); console.log(`${initConsole} ✅ Project setup complete.`); diff --git a/packages/backend/src/app.module.ts b/packages/backend/src/app.module.ts index 06f34f82e..3058ac195 100644 --- a/packages/backend/src/app.module.ts +++ b/packages/backend/src/app.module.ts @@ -9,6 +9,7 @@ import { join } from 'path'; import { CoreModule } from './core/core.module'; import { SSOAuthItem } from './helpers/auth/sso/sso.service'; +import { CaptchaConfig } from './helpers/captcha/captcha.service'; import { EmailSenderFunction } from './helpers/email/email-helpers.type'; import { GlobalHelpersModule } from './helpers/helpers.module'; import { @@ -171,7 +172,9 @@ export class VitNodeCoreModule { database, email, ssoLoginMethod, + captcha, }: { + captcha?: CaptchaConfig; database: DatabaseModuleArgs; email?: EmailSenderFunction; ssoLoginMethod?: SSOAuthItem[]; @@ -207,7 +210,7 @@ export class VitNodeCoreModule { maxAge: 31536000, }, }), - GlobalHelpersModule.register({ email, ssoLoginMethod }), + GlobalHelpersModule.register({ email, ssoLoginMethod, captcha }), ], }; } diff --git a/packages/backend/src/core/admin/admin.module.ts b/packages/backend/src/core/admin/admin.module.ts index e8ac64157..62d555f3a 100644 --- a/packages/backend/src/core/admin/admin.module.ts +++ b/packages/backend/src/core/admin/admin.module.ts @@ -6,7 +6,6 @@ import { DashboardAdminModule } from './dashboard/dashboard.module'; import { LanguagesAdminModule } from './languages/languages.module'; import { MembersAdminModule } from './members/members.module'; import { PluginsAdminModule } from './plugins/plugins.module'; -import { SecurityAdminModule } from './security/security.module'; import { SettingsAdminModule } from './settings/settings.module'; import { StylesAdminModule } from './styles/styles.module'; @@ -19,7 +18,6 @@ import { StylesAdminModule } from './styles/styles.module'; PluginsAdminModule, StylesAdminModule, AdvancedAdminModule, - SecurityAdminModule, DashboardAdminModule, ], }) diff --git a/packages/backend/src/core/admin/auth/services/nav/core.nav.ts b/packages/backend/src/core/admin/auth/services/nav/core.nav.ts index fc2e1bb80..fde071acb 100644 --- a/packages/backend/src/core/admin/auth/services/nav/core.nav.ts +++ b/packages/backend/src/core/admin/auth/services/nav/core.nav.ts @@ -53,17 +53,6 @@ export const coreNav: ShowAuthAdminObj['nav'] = [ }, ], }, - { - code: 'security', - icon: 'shield', - keywords: [], - children: [ - { - code: 'spam', - keywords: ['spam', 'report', 'reporting', 'recaptcha'], - }, - ], - }, { code: 'styles', icon: 'paintbrush', diff --git a/packages/backend/src/core/admin/members/staff/admin/helpers/core-admin-permissions.ts b/packages/backend/src/core/admin/members/staff/admin/helpers/core-admin-permissions.ts index 12bf7ac24..e986f4349 100644 --- a/packages/backend/src/core/admin/members/staff/admin/helpers/core-admin-permissions.ts +++ b/packages/backend/src/core/admin/members/staff/admin/helpers/core-admin-permissions.ts @@ -19,10 +19,6 @@ export const coreAdminPermissions: PermissionsStaffObj[] = [ 'can_manage_settings_legal', ], }, - { - id: 'security', - permissions: ['can_manage_security_spam'], - }, { id: 'can_manage_plugins', permissions: [], diff --git a/packages/backend/src/core/admin/security/captcha/captcha.controller.ts b/packages/backend/src/core/admin/security/captcha/captcha.controller.ts deleted file mode 100644 index 31021ef69..000000000 --- a/packages/backend/src/core/admin/security/captcha/captcha.controller.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Controllers } from '@/helpers/controller.decorator'; -import { Body, Get, Put } from '@nestjs/common'; -import { ApiOkResponse } from '@nestjs/swagger'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -import { EditCaptchaSecurityAdminService } from './service/edit.service'; -import { ShowCaptchaSecurityAdminService } from './service/show.service'; - -@Controllers({ - plugin_name: 'Core', - plugin_code: 'security', - isAdmin: true, - route: 'captcha', -}) -export class CaptchaSecurityAdminController { - constructor( - private readonly showService: ShowCaptchaSecurityAdminService, - private readonly editService: EditCaptchaSecurityAdminService, - ) {} - - @ApiOkResponse({ - description: 'Edit captcha settings', - type: ShowCaptchaSecurityAdminObj, - }) - @Put() - async edit( - @Body() body: ShowCaptchaSecurityAdminObj, - ): Promise { - return await this.editService.edit(body); - } - - @ApiOkResponse({ - description: 'Show captcha settings', - type: ShowCaptchaSecurityAdminObj, - }) - @Get() - async show(): Promise { - return await this.showService.show(); - } -} diff --git a/packages/backend/src/core/admin/security/captcha/captcha.module.ts b/packages/backend/src/core/admin/security/captcha/captcha.module.ts deleted file mode 100644 index dac69dbea..000000000 --- a/packages/backend/src/core/admin/security/captcha/captcha.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { CaptchaSecurityAdminController } from './captcha.controller'; -import { EditCaptchaSecurityAdminService } from './service/edit.service'; -import { ShowCaptchaSecurityAdminService } from './service/show.service'; - -@Module({ - providers: [ShowCaptchaSecurityAdminService, EditCaptchaSecurityAdminService], - controllers: [CaptchaSecurityAdminController], -}) -export class CaptchaSecurityAdminModule {} diff --git a/packages/backend/src/core/admin/security/captcha/helpers.service.ts b/packages/backend/src/core/admin/security/captcha/helpers.service.ts deleted file mode 100644 index a2ebb884d..000000000 --- a/packages/backend/src/core/admin/security/captcha/helpers.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ABSOLUTE_PATHS } from '@/app.module'; -import { Injectable } from '@nestjs/common'; -import { join } from 'path'; - -export interface CaptchaSecurityConfig { - secret_key: string; -} - -@Injectable() -export class HelpersCaptchaSecurityAdminService { - protected readonly path: string = join( - ABSOLUTE_PATHS.plugin({ code: 'core' }).root, - 'utils', - 'captcha.config.json', - ); -} diff --git a/packages/backend/src/core/admin/security/captcha/service/edit.service.ts b/packages/backend/src/core/admin/security/captcha/service/edit.service.ts deleted file mode 100644 index dfa8a6871..000000000 --- a/packages/backend/src/core/admin/security/captcha/service/edit.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { configPath, getConfigFile } from '@/helpers/config'; -import { Injectable } from '@nestjs/common'; -import { writeFile } from 'fs/promises'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -import { - CaptchaSecurityConfig, - HelpersCaptchaSecurityAdminService, -} from '../helpers.service'; - -@Injectable() -export class EditCaptchaSecurityAdminService extends HelpersCaptchaSecurityAdminService { - async edit({ - secret_key, - ...rest - }: ShowCaptchaSecurityAdminObj): Promise { - const config = getConfigFile(); - const captchaSecurityConfig: CaptchaSecurityConfig = { - secret_key, - }; - config.security.captcha = { - ...rest, - }; - - // Write public config to file - await writeFile(configPath, JSON.stringify(config, null, 2)); - // Write default config to file - await writeFile(this.path, JSON.stringify(captchaSecurityConfig, null, 2)); - - return { - ...config.security.captcha, - ...captchaSecurityConfig, - }; - } -} diff --git a/packages/backend/src/core/admin/security/captcha/service/show.service.ts b/packages/backend/src/core/admin/security/captcha/service/show.service.ts deleted file mode 100644 index 9a2fdde07..000000000 --- a/packages/backend/src/core/admin/security/captcha/service/show.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getConfigFile } from '@/helpers/config'; -import { Injectable } from '@nestjs/common'; -import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -import { - CaptchaSecurityConfig, - HelpersCaptchaSecurityAdminService, -} from '../helpers.service'; - -@Injectable() -export class ShowCaptchaSecurityAdminService extends HelpersCaptchaSecurityAdminService { - async show(): Promise { - const config = getConfigFile(); - - if (!existsSync(this.path)) { - return { - ...config.security.captcha, - secret_key: '', - }; - } - - const captchaSecurityConfig: CaptchaSecurityConfig = JSON.parse( - await readFile(this.path, 'utf8'), - ); - - return { - ...config.security.captcha, - ...captchaSecurityConfig, - }; - } -} diff --git a/packages/backend/src/core/admin/security/security.module.ts b/packages/backend/src/core/admin/security/security.module.ts deleted file mode 100644 index c0ea204c7..000000000 --- a/packages/backend/src/core/admin/security/security.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { CaptchaSecurityAdminModule } from './captcha/captcha.module'; - -@Module({ - imports: [CaptchaSecurityAdminModule], -}) -export class SecurityAdminModule {} diff --git a/packages/backend/src/core/admin/settings/auth/methods/services/create.service.ts b/packages/backend/src/core/admin/settings/auth/methods/services/create.service.ts index 77749cd9c..43a1825ce 100644 --- a/packages/backend/src/core/admin/settings/auth/methods/services/create.service.ts +++ b/packages/backend/src/core/admin/settings/auth/methods/services/create.service.ts @@ -1,11 +1,11 @@ +import { core_users_sso } from '@/database/schema/users'; import { SSOAuthConfig, SSOAuthHelper } from '@/helpers/auth/sso/sso.service'; +import { InternalDatabaseService } from '@/utils/database/internal_database.service'; import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; -import { existsSync } from 'fs'; -import { writeFile } from 'fs/promises'; import { CreateMethodAuthSettingsAdminBody, ShowMethodAuthSettingsAdmin, @@ -13,7 +13,10 @@ import { @Injectable() export class CreateMethodsAuthSettingsAdminService { - constructor(private readonly ssoAuthHelper: SSOAuthHelper) {} + constructor( + private readonly ssoAuthHelper: SSOAuthHelper, + private readonly databaseService: InternalDatabaseService, + ) {} async create({ code, @@ -32,35 +35,21 @@ export class CreateMethodsAuthSettingsAdminService { enabled: true, }; - if (!existsSync(this.ssoAuthHelper.path)) { - const dataToSave: SSOAuthConfig = { - sso: [dataSSO], - }; - - await writeFile( - this.ssoAuthHelper.path, - JSON.stringify(dataToSave, null, 2), - ); - - return { - ...dataSSO, - name: sso.name, - }; - } - - const ssoConfig = await this.ssoAuthHelper.getSSOConfig(); - const checkIfSSOExists = ssoConfig.sso.find(item => item.code === code); + const ssoConfig = + await this.databaseService.db.query.core_users_sso.findMany(); + const checkIfSSOExists = ssoConfig.find(item => item.code === code); if (checkIfSSOExists) { throw new ConflictException( `SSO method with ${code} code already exists`, ); } - ssoConfig.sso.push(dataSSO); - await writeFile( - this.ssoAuthHelper.path, - JSON.stringify(ssoConfig, null, 2), - ); + await this.databaseService.db.insert(core_users_sso).values({ + code, + client_id, + client_secret, + enabled: true, + }); return { ...dataSSO, diff --git a/packages/backend/src/core/admin/settings/auth/methods/services/delete.service.ts b/packages/backend/src/core/admin/settings/auth/methods/services/delete.service.ts index f91ba67c8..68e83451b 100644 --- a/packages/backend/src/core/admin/settings/auth/methods/services/delete.service.ts +++ b/packages/backend/src/core/admin/settings/auth/methods/services/delete.service.ts @@ -1,10 +1,13 @@ import { SSOAuthHelper } from '@/helpers/auth/sso/sso.service'; +import { InternalDatabaseService } from '@/utils/database/internal_database.service'; import { Injectable, NotFoundException } from '@nestjs/common'; -import { rm, writeFile } from 'fs/promises'; @Injectable() export class DeleteMethodsAuthSettingsAdminService { - constructor(private readonly ssoAuthHelper: SSOAuthHelper) {} + constructor( + private readonly ssoAuthHelper: SSOAuthHelper, + private readonly databaseService: InternalDatabaseService, + ) {} async delete(code: string): Promise { const sso = await this.ssoAuthHelper.getActiveSSO(code); @@ -12,17 +15,12 @@ export class DeleteMethodsAuthSettingsAdminService { throw new NotFoundException(`SSO method with ${code} code not found`); } - const ssoConfigFile = await this.ssoAuthHelper.getSSOConfig(); - ssoConfigFile.sso = ssoConfigFile.sso.filter(item => item.code !== code); - if (ssoConfigFile.sso.length === 0) { - await rm(this.ssoAuthHelper.path); - - return; + const ssoConfig = + await this.databaseService.db.query.core_users_sso.findFirst({ + where: (table, { eq }) => eq(table.code, code), + }); + if (!ssoConfig) { + throw new NotFoundException(`SSO method with ${code} code not found`); } - - await writeFile( - this.ssoAuthHelper.path, - JSON.stringify(ssoConfigFile, null, 2), - ); } } diff --git a/packages/backend/src/core/admin/settings/auth/methods/services/edit.service.ts b/packages/backend/src/core/admin/settings/auth/methods/services/edit.service.ts index eeee5a992..125b462f8 100644 --- a/packages/backend/src/core/admin/settings/auth/methods/services/edit.service.ts +++ b/packages/backend/src/core/admin/settings/auth/methods/services/edit.service.ts @@ -1,6 +1,8 @@ +import { core_users_sso } from '@/database/schema/users'; import { SSOAuthHelper } from '@/helpers/auth/sso/sso.service'; +import { InternalDatabaseService } from '@/utils/database/internal_database.service'; import { Injectable, NotFoundException } from '@nestjs/common'; -import { writeFile } from 'fs/promises'; +import { eq } from 'drizzle-orm'; import { EditMethodAuthSettingsAdminBody, ShowMethodAuthSettingsAdmin, @@ -8,7 +10,10 @@ import { @Injectable() export class EditMethodsAuthSettingsAdminService { - constructor(private readonly ssoAuthHelper: SSOAuthHelper) {} + constructor( + private readonly ssoAuthHelper: SSOAuthHelper, + private readonly databaseService: InternalDatabaseService, + ) {} async edit({ code, @@ -21,26 +26,26 @@ export class EditMethodsAuthSettingsAdminService { if (!sso) { throw new NotFoundException(`SSO method with ${code} code not found`); } - const ssoConfigFile = await this.ssoAuthHelper.getSSOConfig(); - const ssoConfig = ssoConfigFile.sso; - const ssoIndex = ssoConfig.findIndex(item => item.code === code); - if (ssoIndex === -1) { + const ssoConfig = + await this.databaseService.db.query.core_users_sso.findFirst({ + where: (table, { eq }) => eq(table.code, code), + }); + if (!ssoConfig) { throw new NotFoundException(`SSO method with ${code} code not found`); } - ssoConfig[ssoIndex] = { - ...ssoConfig[ssoIndex], - client_id, - client_secret, - enabled, - }; - await writeFile( - this.ssoAuthHelper.path, - JSON.stringify(ssoConfigFile, null, 2), - ); + const [data] = await this.databaseService.db + .update(core_users_sso) + .set({ + client_id, + client_secret, + enabled, + }) + .where(eq(core_users_sso.code, code)) + .returning(); return { - ...ssoConfig[ssoIndex], + ...data, name: sso.name, }; } diff --git a/packages/backend/src/core/middleware/services/show.service.ts b/packages/backend/src/core/middleware/services/show.service.ts index 42badd608..3b2dd821f 100644 --- a/packages/backend/src/core/middleware/services/show.service.ts +++ b/packages/backend/src/core/middleware/services/show.service.ts @@ -1,10 +1,15 @@ +import type { CaptchaConfig } from '@/helpers/captcha/captcha.service'; + import { ABSOLUTE_PATHS } from '@/app.module'; import { SSOAuthHelper } from '@/helpers/auth/sso/sso.service'; -import { getConfigFile } from '@/helpers/config'; import { ConfigHelperService } from '@/helpers/config.service'; import { EmailHelperService } from '@/helpers/email/email.service'; import { InternalDatabaseService } from '@/utils/database/internal_database.service'; -import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { + Inject, + Injectable, + InternalServerErrorException, +} from '@nestjs/common'; import { readFile } from 'fs/promises'; import { join } from 'path'; import { AppTypeMainSettingsAdmin } from 'vitnode-shared/admin/settings/main.enum'; @@ -21,6 +26,8 @@ export class ShowMiddlewareService { private readonly navService: NavMiddlewareService, private readonly ssoHelper: SSOAuthHelper, private readonly configService: ConfigHelperService, + @Inject('VITNODE_CAPTCHA_CONFIG') + private readonly captchaConfig?: CaptchaConfig, ) {} protected async getManifests({ @@ -47,7 +54,6 @@ export class ShowMiddlewareService { async show(): Promise { // TODO: Add cache - const config = getConfigFile(); const [plugins, langs] = await Promise.all([ this.databaseService.db.query.core_plugins.findMany({ columns: { @@ -109,8 +115,8 @@ export class ShowMiddlewareService { site_short_name: configFromDb.site_short_name, security: { captcha: { - site_key: config.security.captcha.site_key, - type: config.security.captcha.type, + site_key: this.captchaConfig?.site_key ?? '', + type: this.captchaConfig?.type ?? '', }, }, editor: { diff --git a/packages/backend/src/database/schema/users.ts b/packages/backend/src/database/schema/users.ts index 5dd949968..63a12af72 100644 --- a/packages/backend/src/database/schema/users.ts +++ b/packages/backend/src/database/schema/users.ts @@ -61,6 +61,13 @@ export const core_users_relations = relations(core_users, ({ one, many }) => ({ }), })); +export const core_users_sso = pgTable('core_users_sso', t => ({ + code: t.varchar({ length: 100 }).notNull().unique(), + client_id: t.varchar({ length: 255 }).notNull(), + client_secret: t.varchar({ length: 255 }).notNull(), + enabled: t.boolean().notNull().default(false), +})); + export const core_users_sso_tokens = pgTable( 'core_users_sso_tokens', t => ({ @@ -71,7 +78,12 @@ export const core_users_sso_tokens = pgTable( onDelete: 'cascade', }) .notNull(), - provider: t.varchar({ length: 100 }).notNull(), + provider: t + .varchar({ length: 100 }) + .references(() => core_users_sso.code, { + onDelete: 'no action', + }) + .notNull(), provider_id: t.varchar({ length: 255 }).notNull(), created_at: t.timestamp().notNull().defaultNow(), updated_at: t.timestamp().notNull().defaultNow(), diff --git a/packages/backend/src/helpers/auth/sso/sso.service.ts b/packages/backend/src/helpers/auth/sso/sso.service.ts index ec2362bba..ce7538afc 100644 --- a/packages/backend/src/helpers/auth/sso/sso.service.ts +++ b/packages/backend/src/helpers/auth/sso/sso.service.ts @@ -1,8 +1,5 @@ -import { ABSOLUTE_PATHS } from '@/app.module'; +import { InternalDatabaseService } from '@/utils/database/internal_database.service'; import { Inject, Injectable, NotFoundException } from '@nestjs/common'; -import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { join } from 'path'; import { ShowMethodAuthSettingsAdmin } from 'vitnode-shared/admin/settings/auth.dto'; import { SSOUrlAuthObj } from 'vitnode-shared/auth/sso.dto'; @@ -50,19 +47,14 @@ export class SSOAuthHelper { constructor( @Inject('VITNODE_SSO_LOGIN_METHODS') private readonly loginMethods: SSOAuthItem[], + private readonly databaseService: InternalDatabaseService, ) {} - path = join( - ABSOLUTE_PATHS.plugin({ code: 'core' }).root, - 'utils', - 'sso.config.json', - ); - async getActiveSSO( code: string, ): Promise { const item = this.getSSO(code); - if (!item || !existsSync(this.path)) { + if (!item) { throw new NotFoundException(`SSO provider with ${code} code not found`); } @@ -77,14 +69,11 @@ export class SSOAuthHelper { } async getActiveSSOs(): Promise { - if (!existsSync(this.path)) { - return []; - } - - const ssoConfig = await this.getSSOConfig(); + const ssoConfig = + await this.databaseService.db.query.core_users_sso.findMany(); const SSOs = this.getSSOs(); const activeSSOs: ShowMethodAuthSettingsAdmin[] = []; - ssoConfig.sso.forEach(sso => { + ssoConfig.forEach(sso => { const ssoItem = SSOs.find(item => item.code === sso.code); if (!ssoItem) return; @@ -101,14 +90,6 @@ export class SSOAuthHelper { return this.getSSOs().find(sso => sso.code === code); } - async getSSOConfig(): Promise { - if (!existsSync(this.path)) { - return { sso: [] }; - } - - return JSON.parse(await readFile(this.path, 'utf8')); - } - getSSOs(): SSOAuthItem[] { return [googleSSO, facebookSSO, ...this.loginMethods]; } diff --git a/packages/backend/src/helpers/captcha/captcha.service.ts b/packages/backend/src/helpers/captcha/captcha.service.ts index fc4fcc567..9e9eff2ae 100644 --- a/packages/backend/src/helpers/captcha/captcha.service.ts +++ b/packages/backend/src/helpers/captcha/captcha.service.ts @@ -1,19 +1,23 @@ -import { ABSOLUTE_PATHS } from '@/app.module'; import { getUserIp } from '@/functions'; -import { getConfigFile } from '@/helpers/config'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { Request } from 'express'; -import * as fs from 'fs'; -import { join } from 'path'; -import { CaptchaTypeEnum } from 'vitnode-shared/utils/global'; + +export interface CaptchaConfig { + secret_key: string; + site_key: string; + type: + | 'cloudflare_turnstile' + | 'recaptcha_v2_checkbox' + | 'recaptcha_v2_invisible' + | 'recaptcha_v3'; +} @Injectable() export class CaptchaHelper { - protected readonly path: string = join( - ABSOLUTE_PATHS.plugin({ code: 'core' }).root, - 'utils', - 'captcha.config.json', - ); + constructor( + @Inject('VITNODE_CAPTCHA_CONFIG') + private readonly captchaConfig?: CaptchaConfig, + ) {} private async getResFromReCaptcha({ captchaKey, @@ -22,35 +26,20 @@ export class CaptchaHelper { captchaKey: string | string[]; userIp: string; }): Promise<{ 'error-codes'?: string[]; score: number; success: boolean }> { - const { - security: { captcha: config }, - } = getConfigFile(); - // If captcha is disabled, return success - if (config.type === CaptchaTypeEnum.none) { + if (!this.captchaConfig) { return { success: true, score: 1, }; } - // Get secret key from file - if (!fs.existsSync(this.path)) { - return { - success: false, - score: 0, - }; - } - const captchaSecurityConfig: { secret_key: string } = JSON.parse( - fs.readFileSync(this.path, 'utf8'), - ); - - if (config.type === CaptchaTypeEnum.cloudflare_turnstile) { + if (this.captchaConfig.type === 'cloudflare_turnstile') { const res = await fetch( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', body: JSON.stringify({ - secret: captchaSecurityConfig.secret_key, + secret: this.captchaConfig.secret_key, response: captchaKey, remoteip: userIp, }), @@ -65,13 +54,13 @@ export class CaptchaHelper { return data; } else if ( [ - CaptchaTypeEnum.recaptcha_v2_checkbox, - CaptchaTypeEnum.recaptcha_v2_invisible, - CaptchaTypeEnum.recaptcha_v3, - ].includes(config.type) + 'recaptcha_v2_checkbox', + 'recaptcha_v2_invisible', + 'recaptcha_v3', + ].includes(this.captchaConfig.type) ) { const res = await fetch( - `https://www.google.com/recaptcha/api/siteverify?secret=${captchaSecurityConfig.secret_key}&response=${captchaKey}&remoteip=${userIp}`, + `https://www.google.com/recaptcha/api/siteverify?secret=${this.captchaConfig.secret_key}&response=${captchaKey}&remoteip=${userIp}`, { method: 'POST', }, @@ -90,11 +79,7 @@ export class CaptchaHelper { async validateCaptcha({ req }: { req: Request }) { const captchaKey = req.headers['x-vitnode-captcha-token']; - - const { - security: { captcha: config }, - } = getConfigFile(); - if (!captchaKey && config.type !== CaptchaTypeEnum.none) { + if (!captchaKey && this.captchaConfig) { throw new HttpException( 'Captcha token not provided', HttpStatus.BAD_REQUEST, diff --git a/packages/backend/src/helpers/config.ts b/packages/backend/src/helpers/config.ts deleted file mode 100644 index 36f000067..000000000 --- a/packages/backend/src/helpers/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as fs from 'fs'; -import { join } from 'path'; -import { CaptchaTypeEnum } from 'vitnode-shared/utils/global'; - -export interface ConfigType { - security: { - captcha: { - site_key: string; - type: CaptchaTypeEnum; - }; - }; -} - -export const DEFAULT_CONFIG_DATA: ConfigType = { - security: { - captcha: { - type: CaptchaTypeEnum.none, - site_key: '', - }, - }, -}; - -export const configPath = join( - process.cwd(), - 'src', - 'plugins', - 'core', - 'utils', - 'config.json', -); - -export const getConfigFile = () => { - const file = fs.readFileSync(configPath, 'utf-8'); - - return JSON.parse(file) as ConfigType; -}; - -export const updateConfigFile = (config: ConfigType) => { - fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); -}; diff --git a/packages/backend/src/helpers/helpers.module.ts b/packages/backend/src/helpers/helpers.module.ts index db59c70fe..c22da472d 100644 --- a/packages/backend/src/helpers/helpers.module.ts +++ b/packages/backend/src/helpers/helpers.module.ts @@ -10,7 +10,7 @@ import { import { DeviceAuthService } from './auth/device.service'; import { InternalAuthAdminService } from './auth/internal_auth_admin.service'; import { SSOAuthHelper, SSOAuthItem } from './auth/sso/sso.service'; -import { CaptchaHelper } from './captcha/captcha.service'; +import { CaptchaConfig, CaptchaHelper } from './captcha/captcha.service'; import { ConfigHelperService } from './config.service'; import { EmailHelpersService } from './email/email-helpers.service'; import { @@ -26,6 +26,7 @@ import { UserHelper } from './user.service'; @Module({}) export class GlobalHelpersModule { static register(options: { + captcha?: CaptchaConfig; email?: EmailSenderFunction; ssoLoginMethod?: SSOAuthItem[]; }): DynamicModule { @@ -45,6 +46,10 @@ export class GlobalHelpersModule { await options.email(params); }, }, + { + provide: 'VITNODE_CAPTCHA_CONFIG', + useValue: options.captcha, + }, { provide: 'VITNODE_SSO_LOGIN_METHODS', useValue: options.ssoLoginMethod ?? [], @@ -81,6 +86,7 @@ export class GlobalHelpersModule { 'EmailHelpersService', 'IOAuthGuards', 'IOAdminAuthGuards', + 'VITNODE_CAPTCHA_CONFIG', DeviceAuthService, UserHelper, FilesHelperService, diff --git a/packages/frontend/src/hooks/use-captcha.ts b/packages/frontend/src/hooks/use-captcha.ts index 68a29ef98..f322f3422 100644 --- a/packages/frontend/src/hooks/use-captcha.ts +++ b/packages/frontend/src/hooks/use-captcha.ts @@ -1,8 +1,7 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { useLocale } from 'next-intl'; import { useTheme } from 'next-themes'; -/* eslint-disable @typescript-eslint/ban-ts-comment */ import React from 'react'; -import { CaptchaTypeEnum } from 'vitnode-shared/utils/global'; import { useMiddlewareData } from './use-middleware-data'; @@ -13,15 +12,13 @@ export const useCaptcha = () => { const { security: { captcha: config }, } = useMiddlewareData(); - const [token, setToken] = React.useState( - config.type === CaptchaTypeEnum.none ? 'none' : '', - ); + const [token, setToken] = React.useState(!config.type ? 'none' : ''); const handleLoaded = () => { const elementId = 'vitnode_captcha'; if ( - config.type === CaptchaTypeEnum.recaptcha_v2_checkbox || - config.type === CaptchaTypeEnum.recaptcha_v2_invisible + config.type === 'recaptcha_v2_checkbox' || + config.type === 'recaptcha_v2_invisible' ) { // @ts-expect-error window.grecaptcha.ready(() => { @@ -30,16 +27,13 @@ export const useCaptcha = () => { sitekey: config.site_key, theme: resolvedTheme, locale, - size: - config.type === CaptchaTypeEnum.recaptcha_v2_invisible - ? 'invisible' - : null, + size: config.type === 'recaptcha_v2_invisible' ? 'invisible' : null, callback: (token: string) => { setToken(token); }, }); }); - } else if (config.type === CaptchaTypeEnum.cloudflare_turnstile) { + } else if (config.type === 'cloudflare_turnstile') { // @ts-expect-error window.turnstile.render(`#${elementId}`, { sitekey: config.site_key, @@ -55,7 +49,7 @@ export const useCaptcha = () => { }; React.useEffect(() => { - if (config.type === CaptchaTypeEnum.none) { + if (!config.type) { setIsReady(true); return; @@ -69,11 +63,11 @@ export const useCaptcha = () => { const script = document.createElement('script'); if ( - config.type === CaptchaTypeEnum.recaptcha_v2_checkbox || - config.type === CaptchaTypeEnum.recaptcha_v2_invisible + config.type === 'recaptcha_v2_checkbox' || + config.type === 'recaptcha_v2_invisible' ) { script.src = `${googleCaptchaDomain}&render=explicit`; - } else if (config.type === CaptchaTypeEnum.recaptcha_v3) { + } else if (config.type === 'recaptcha_v3') { script.src = `${googleCaptchaDomain}&render=${config.site_key}`; } else { // window[functionCF] = handleLoaded; @@ -93,7 +87,7 @@ export const useCaptcha = () => { }, []); const getTokenFromCaptcha = async (): Promise => { - if (config.type === CaptchaTypeEnum.recaptcha_v3) { + if (config.type === 'recaptcha_v3') { // Captcha return new Promise(resolve => { // @ts-expect-error diff --git a/packages/frontend/src/views/admin/views/core/security/spam/captcha/captcha-spam-security-admin-view.tsx b/packages/frontend/src/views/admin/views/core/security/spam/captcha/captcha-spam-security-admin-view.tsx deleted file mode 100644 index 594833540..000000000 --- a/packages/frontend/src/views/admin/views/core/security/spam/captcha/captcha-spam-security-admin-view.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { fetcher } from '@/api/fetcher'; -import { TranslationsProvider } from '@/components/translations-provider'; -import { Metadata } from 'next'; -import { getTranslations } from 'next-intl/server'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -import { ContentCaptchaSpamSecurityAdmin } from './content'; - -const getData = async () => { - const { data } = await fetcher({ - url: '/admin/security/captcha', - cache: 'force-cache', - }); - - return data; -}; - -export const generateMetadataCaptchaSpamSecurityAdmin = - async (): Promise => { - const t = await getTranslations('admin.core.security.spam.captcha'); - - return { - title: t('title'), - }; - }; - -export const CaptchaSpamSecurityAdminView = async () => { - const data = await getData(); - - return ( - - - - ); -}; diff --git a/packages/frontend/src/views/admin/views/core/security/spam/captcha/content.tsx b/packages/frontend/src/views/admin/views/core/security/spam/captcha/content.tsx deleted file mode 100644 index fa98ac5dc..000000000 --- a/packages/frontend/src/views/admin/views/core/security/spam/captcha/content.tsx +++ /dev/null @@ -1,142 +0,0 @@ -'use client'; - -import { AutoForm, DependencyType } from '@/components/form/auto-form'; -import { AutoFormInput } from '@/components/form/fields/input'; -import { AutoFormRadioGroup } from '@/components/form/fields/radio-group'; -import { Link } from '@/navigation'; -import { SquareArrowOutUpRightIcon } from 'lucide-react'; -import { useTranslations } from 'next-intl'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -import { useCaptchaSecurityAdmin } from './hooks/use-captcha-security-admin'; - -export const ContentCaptchaSpamSecurityAdmin = ( - data: ShowCaptchaSecurityAdminObj, -) => { - const { formSchema, onSubmit } = useCaptchaSecurityAdmin(data); - const t = useTranslations('admin.core.security.spam.captcha'); - - return ( - type === 'none', - }, - { - sourceField: 'type', - type: DependencyType.HIDES, - targetField: 'secret_key', - when: (type: string) => type === 'none', - }, - { - sourceField: 'type', - type: DependencyType.REQUIRES, - targetField: 'site_key', - when: (type: string) => type !== 'none', - }, - { - sourceField: 'type', - type: DependencyType.REQUIRES, - targetField: 'secret_key', - when: (type: string) => type !== 'none', - }, - ]} - fields={[ - { - id: 'type', - label: t('type.title'), - component: props => ( - ( - - {text} - - - ), - }), - }, - recaptcha_v3: { - title: t('type.recaptcha_v3'), - description: t.rich(`type.recaptcha_desc`, { - link: text => ( - - {text} - - - ), - }), - }, - recaptcha_v2_invisible: { - title: t('type.recaptcha_v2_invisible'), - description: t.rich(`type.recaptcha_desc`, { - link: text => ( - - {text} - - - ), - }), - }, - recaptcha_v2_checkbox: { - title: t('type.recaptcha_v2_checkbox'), - description: t.rich(`type.recaptcha_desc`, { - link: text => ( - - {text} - - - ), - }), - }, - }} - /> - ), - }, - { - id: 'site_key', - label: t('site_key'), - component: AutoFormInput, - }, - { - id: 'secret_key', - label: t('secret_key'), - component: AutoFormInput, - }, - ]} - formSchema={formSchema} - onSubmit={onSubmit} - theme="horizontal" - /> - ); -}; diff --git a/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/mutation-api.ts b/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/mutation-api.ts deleted file mode 100644 index d3775e5d3..000000000 --- a/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/mutation-api.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use server'; - -import { fetcher } from '@/api/fetcher'; -import { revalidatePath } from 'next/cache'; -import { ShowCaptchaSecurityAdminObj } from 'vitnode-shared/admin/security/captcha.dto'; - -export const mutationApi = async (body: ShowCaptchaSecurityAdminObj) => { - await fetcher({ - url: '/admin/security/captcha', - method: 'PUT', - body, - }); - - revalidatePath('/', 'layout'); -}; diff --git a/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/use-captcha-security-admin.ts b/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/use-captcha-security-admin.ts deleted file mode 100644 index 539194e97..000000000 --- a/packages/frontend/src/views/admin/views/core/security/spam/captcha/hooks/use-captcha-security-admin.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useTranslations } from 'next-intl'; -import { UseFormReturn } from 'react-hook-form'; -import { toast } from 'sonner'; -import { CaptchaTypeEnum } from 'vitnode-shared/utils/global'; -import { z } from 'zod'; - -import { ContentCaptchaSpamSecurityAdmin } from '../content'; -import { mutationApi } from './mutation-api'; - -export const useCaptchaSecurityAdmin = ({ - type, - secret_key, - site_key, -}: React.ComponentProps) => { - const t = useTranslations('core.global'); - const formSchema = z - .object({ - type: z - .enum([ - CaptchaTypeEnum.none, - CaptchaTypeEnum.cloudflare_turnstile, - CaptchaTypeEnum.recaptcha_v3, - CaptchaTypeEnum.recaptcha_v2_invisible, - CaptchaTypeEnum.recaptcha_v2_checkbox, - ]) - .default(type), - secret_key: z.string().default(secret_key), - site_key: z.string().default(site_key), - }) - .refine(input => { - if (input.type === CaptchaTypeEnum.none) { - return true; - } - - return input.secret_key !== '' && input.site_key !== ''; - }); - - const onSubmit = async ( - values: z.infer, - form: UseFormReturn>, - ) => { - try { - await mutationApi(values); - toast.success(t('saved_success')); - form.reset(values); - } catch (_) { - toast.error(t('errors.title'), { - description: t('errors.internal_server_error'), - }); - } - }; - - return { onSubmit, formSchema }; -}; diff --git a/packages/frontend/src/views/admin/views/core/security/spam/layout.tsx b/packages/frontend/src/views/admin/views/core/security/spam/layout.tsx deleted file mode 100644 index 9eaf04843..000000000 --- a/packages/frontend/src/views/admin/views/core/security/spam/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Card } from '@/components/ui/card'; -import { HeaderContent } from '@/components/ui/header-content'; -import { getTranslations } from 'next-intl/server'; - -export const SpamSecurityAdminLayout = async ({ - children, -}: { - children: React.ReactNode; -}) => { - const t = await getTranslations('admin.core.security.spam'); - - return ( - <> - - {children} - - ); -}; diff --git a/packages/frontend/src/views/admin/views/dynamic-admin-view.tsx b/packages/frontend/src/views/admin/views/dynamic-admin-view.tsx index 558bf653e..3779e26e8 100644 --- a/packages/frontend/src/views/admin/views/dynamic-admin-view.tsx +++ b/packages/frontend/src/views/admin/views/dynamic-admin-view.tsx @@ -28,11 +28,6 @@ import { generateMetadataPluginsAdmin, PluginsAdminView, } from './core/plugins/plugins-admin-view'; -import { - CaptchaSpamSecurityAdminView, - generateMetadataCaptchaSpamSecurityAdmin, -} from './core/security/spam/captcha/captcha-spam-security-admin-view'; -import { SpamSecurityAdminLayout } from './core/security/spam/layout'; import { AuthorizationSettingsAdminView } from './core/settings/authorization/authorization-settings-admin-view'; import { LayoutAuthorizationSettingsAdmin } from './core/settings/authorization/layout/layout'; import { MethodsAuthSettingsAdminView } from './core/settings/authorization/methods/methods-auth-settings-admin-view'; @@ -149,23 +144,6 @@ export const generateMetadataDynamic = async (props: { } } - if (slug[1] === 'security' && !slug[4]) { - const t = await getTranslations('admin.core.security.spam'); - const primary: Metadata = { - title: t('title'), - }; - - if (slug[2] === 'spam') { - const current = await generateMetadataCaptchaSpamSecurityAdmin(); - - return { - ...primary, - ...current, - title: `${current.title} - ${primary.title}`, - }; - } - } - if (slug[1] === 'plugins' && !slug[5]) { if (!slug[2]) { return generateMetadataPluginsAdmin(); @@ -297,24 +275,6 @@ export const DynamicAdminView = async (props: { } } - if (slug[1] === 'security' && !slug[4]) { - if (slug[2] === 'spam') { - return ( - - {(() => { - if (slug[3] === 'captcha' || !slug[3]) { - return ; - } - - notFound(); - })()} - - ); - } - - notFound(); - } - if (slug[1] === 'plugins' && !slug[5]) { return ( diff --git a/packages/shared/src/middleware.dto.ts b/packages/shared/src/middleware.dto.ts index 9ff1b0e37..7cc0b86e8 100644 --- a/packages/shared/src/middleware.dto.ts +++ b/packages/shared/src/middleware.dto.ts @@ -1,7 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, - IsEnum, IsNumber, IsObject, IsOptional, @@ -11,7 +10,6 @@ import { import { MainSettingsAdminBody } from './admin/settings/main.dto'; import { ShowNavStyles } from './nav.dto'; import { FileObj } from './utils/files.dto'; -import { CaptchaTypeEnum } from './utils/global'; class EditorMiddleware { @ApiProperty() @@ -50,9 +48,9 @@ export class CaptchaSecurityMiddleware { @IsString() site_key: string; - @ApiProperty({ enum: CaptchaTypeEnum }) - @IsEnum(CaptchaTypeEnum) - type: CaptchaTypeEnum; + @ApiProperty() + @IsString() + type: string; } export class LanguagesMiddleware { diff --git a/packages/shared/src/utils/global.ts b/packages/shared/src/utils/global.ts deleted file mode 100644 index d0bb83e4d..000000000 --- a/packages/shared/src/utils/global.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum CaptchaTypeEnum { - cloudflare_turnstile = 'cloudflare_turnstile', - none = 'none', - recaptcha_v2_checkbox = 'recaptcha_v2_checkbox', - recaptcha_v2_invisible = 'recaptcha_v2_invisible', - recaptcha_v3 = 'recaptcha_v3', -}