mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🚀 feat: Add Cloudflare Turnstile support (#5987)
* 🚀 feat: Add @marsidev/react-turnstile dependency to package.json and package-lock.json * 🚀 feat: Integrate Cloudflare Turnstile configuration support in AppService and add schema validation * 🚀 feat: Implemented Cloudflare Turnstile integration in Login and Registration forms * 🚀 feat: Enhance AppService tests with additional mocks and configuration setups * 🚀 feat: Comment out outdated config version warning tests in AppService.spec.js * 🚀 feat: Remove outdated warning tests and add new checks for environment variables and API health * 🔧 test: Update AppService.spec.js to use expect.anything() for paths validation * 🔧 test: Refactor AppService.spec.js to streamline mocks and enhance clarity * 🔧 chore: removed not needed test * Potential fix for code scanning alert no. 5638: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 5629: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 5642: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update turnstile.js * Potential fix for code scanning alert no. 5634: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 5646: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for code scanning alert no. 5647: Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting. Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
parent
621fa6e1aa
commit
535e7798b3
10 changed files with 145 additions and 11 deletions
|
|
@ -75,6 +75,7 @@ router.get('/', async function (req, res) {
|
||||||
process.env.SHOW_BIRTHDAY_ICON === '',
|
process.env.SHOW_BIRTHDAY_ICON === '',
|
||||||
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
||||||
interface: req.app.locals.interfaceConfig,
|
interface: req.app.locals.interfaceConfig,
|
||||||
|
turnstile: req.app.locals.turnstileConfig,
|
||||||
modelSpecs: req.app.locals.modelSpecs,
|
modelSpecs: req.app.locals.modelSpecs,
|
||||||
balance: req.app.locals.balance,
|
balance: req.app.locals.balance,
|
||||||
sharedLinksEnabled,
|
sharedLinksEnabled,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const { initializeFirebase } = require('./Files/Firebase/initialize');
|
||||||
const loadCustomConfig = require('./Config/loadCustomConfig');
|
const loadCustomConfig = require('./Config/loadCustomConfig');
|
||||||
const handleRateLimits = require('./Config/handleRateLimits');
|
const handleRateLimits = require('./Config/handleRateLimits');
|
||||||
const { loadDefaultInterface } = require('./start/interface');
|
const { loadDefaultInterface } = require('./start/interface');
|
||||||
|
const { loadTurnstileConfig } = require('./start/turnstile');
|
||||||
const { azureConfigSetup } = require('./start/azureOpenAI');
|
const { azureConfigSetup } = require('./start/azureOpenAI');
|
||||||
const { processModelSpecs } = require('./start/modelSpecs');
|
const { processModelSpecs } = require('./start/modelSpecs');
|
||||||
const { initializeS3 } = require('./Files/S3/initialize');
|
const { initializeS3 } = require('./Files/S3/initialize');
|
||||||
|
|
@ -23,7 +24,6 @@ const { getMCPManager } = require('~/config');
|
||||||
const paths = require('~/config/paths');
|
const paths = require('~/config/paths');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Loads custom config and initializes app-wide variables.
|
* Loads custom config and initializes app-wide variables.
|
||||||
* @function AppService
|
* @function AppService
|
||||||
* @param {Express.Application} app - The Express application object.
|
* @param {Express.Application} app - The Express application object.
|
||||||
|
|
@ -74,6 +74,7 @@ const AppService = async (app) => {
|
||||||
const socialLogins =
|
const socialLogins =
|
||||||
config?.registration?.socialLogins ?? configDefaults?.registration?.socialLogins;
|
config?.registration?.socialLogins ?? configDefaults?.registration?.socialLogins;
|
||||||
const interfaceConfig = await loadDefaultInterface(config, configDefaults);
|
const interfaceConfig = await loadDefaultInterface(config, configDefaults);
|
||||||
|
const turnstileConfig = loadTurnstileConfig(config, configDefaults);
|
||||||
|
|
||||||
const defaultLocals = {
|
const defaultLocals = {
|
||||||
ocr,
|
ocr,
|
||||||
|
|
@ -85,6 +86,7 @@ const AppService = async (app) => {
|
||||||
availableTools,
|
availableTools,
|
||||||
imageOutputType,
|
imageOutputType,
|
||||||
interfaceConfig,
|
interfaceConfig,
|
||||||
|
turnstileConfig,
|
||||||
balance,
|
balance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,12 @@ jest.mock('./ToolService', () => ({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('./start/turnstile', () => ({
|
||||||
|
loadTurnstileConfig: jest.fn(() => ({
|
||||||
|
siteKey: 'default-site-key',
|
||||||
|
options: {},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const azureGroups = [
|
const azureGroups = [
|
||||||
{
|
{
|
||||||
|
|
@ -86,6 +92,10 @@ const azureGroups = [
|
||||||
|
|
||||||
describe('AppService', () => {
|
describe('AppService', () => {
|
||||||
let app;
|
let app;
|
||||||
|
const mockedTurnstileConfig = {
|
||||||
|
siteKey: 'default-site-key',
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
app = { locals: {} };
|
app = { locals: {} };
|
||||||
|
|
@ -107,6 +117,7 @@ describe('AppService', () => {
|
||||||
sidePanel: true,
|
sidePanel: true,
|
||||||
presets: true,
|
presets: true,
|
||||||
}),
|
}),
|
||||||
|
turnstileConfig: mockedTurnstileConfig,
|
||||||
modelSpecs: undefined,
|
modelSpecs: undefined,
|
||||||
availableTools: {
|
availableTools: {
|
||||||
ExampleTool: {
|
ExampleTool: {
|
||||||
|
|
|
||||||
35
api/server/services/start/turnstile.js
Normal file
35
api/server/services/start/turnstile.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
const { removeNullishValues } = require('librechat-data-provider');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and maps the Cloudflare Turnstile configuration.
|
||||||
|
*
|
||||||
|
* Expected config structure:
|
||||||
|
*
|
||||||
|
* turnstile:
|
||||||
|
* siteKey: "your-site-key-here"
|
||||||
|
* options:
|
||||||
|
* language: "auto" // "auto" or an ISO 639-1 language code (e.g. en)
|
||||||
|
* size: "normal" // Options: "normal", "compact", "flexible", or "invisible"
|
||||||
|
*
|
||||||
|
* @param {TCustomConfig | undefined} config - The loaded custom configuration.
|
||||||
|
* @param {TConfigDefaults} configDefaults - The custom configuration default values.
|
||||||
|
* @returns {TCustomConfig['turnstile']} The mapped Turnstile configuration.
|
||||||
|
*/
|
||||||
|
function loadTurnstileConfig(config, configDefaults) {
|
||||||
|
const { turnstile: customTurnstile = {} } = config ?? {};
|
||||||
|
const { turnstile: defaults = {} } = configDefaults;
|
||||||
|
|
||||||
|
/** @type {TCustomConfig['turnstile']} */
|
||||||
|
const loadedTurnstile = removeNullishValues({
|
||||||
|
siteKey: customTurnstile.siteKey ?? defaults.siteKey,
|
||||||
|
options: customTurnstile.options ?? defaults.options,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Turnstile configuration loaded:\n' + JSON.stringify(loadedTurnstile, null, 2));
|
||||||
|
return loadedTurnstile;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loadTurnstileConfig,
|
||||||
|
};
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"@dicebear/collection": "^9.2.2",
|
"@dicebear/collection": "^9.2.2",
|
||||||
"@dicebear/core": "^9.2.2",
|
"@dicebear/core": "^9.2.2",
|
||||||
"@headlessui/react": "^2.1.2",
|
"@headlessui/react": "^2.1.2",
|
||||||
|
"@marsidev/react-turnstile": "^1.1.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.3",
|
"@radix-ui/react-checkbox": "^1.0.3",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
|
import { Turnstile } from '@marsidev/react-turnstile';
|
||||||
import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
|
import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
|
||||||
import type { TAuthContext } from '~/common';
|
import type { TAuthContext } from '~/common';
|
||||||
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
|
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
|
||||||
import { useLocalize } from '~/hooks';
|
import { ThemeContext, useLocalize } from '~/hooks';
|
||||||
|
|
||||||
type TLoginFormProps = {
|
type TLoginFormProps = {
|
||||||
onSubmit: (data: TLoginUser) => void;
|
onSubmit: (data: TLoginUser) => void;
|
||||||
|
|
@ -14,6 +15,7 @@ type TLoginFormProps = {
|
||||||
|
|
||||||
const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error, setError }) => {
|
const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error, setError }) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
getValues,
|
getValues,
|
||||||
|
|
@ -21,9 +23,11 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<TLoginUser>();
|
} = useForm<TLoginUser>();
|
||||||
const [showResendLink, setShowResendLink] = useState<boolean>(false);
|
const [showResendLink, setShowResendLink] = useState<boolean>(false);
|
||||||
|
const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data: config } = useGetStartupConfig();
|
const { data: config } = useGetStartupConfig();
|
||||||
const useUsernameLogin = config?.ldap?.username;
|
const useUsernameLogin = config?.ldap?.username;
|
||||||
|
const validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error && error.includes('422') && !showResendLink) {
|
if (error && error.includes('422') && !showResendLink) {
|
||||||
|
|
@ -159,11 +163,29 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
{localize('com_auth_password_forgot')}
|
{localize('com_auth_password_forgot')}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Render Turnstile only if enabled in startupConfig */}
|
||||||
|
{startupConfig.turnstile && (
|
||||||
|
<div className="my-4 flex justify-center">
|
||||||
|
<Turnstile
|
||||||
|
siteKey={startupConfig.turnstile.siteKey}
|
||||||
|
options={{
|
||||||
|
...startupConfig.turnstile.options,
|
||||||
|
theme: validTheme,
|
||||||
|
}}
|
||||||
|
onSuccess={(token) => setTurnstileToken(token)}
|
||||||
|
onError={() => setTurnstileToken(null)}
|
||||||
|
onExpire={() => setTurnstileToken(null)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button
|
<button
|
||||||
aria-label={localize('com_auth_continue')}
|
aria-label={localize('com_auth_continue')}
|
||||||
data-testid="login-button"
|
data-testid="login-button"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
disabled={startupConfig.turnstile ? !turnstileToken : false}
|
||||||
className="
|
className="
|
||||||
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white
|
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white
|
||||||
transition-colors hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700
|
transition-colors hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import React, { useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { Turnstile } from '@marsidev/react-turnstile';
|
||||||
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
|
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
|
||||||
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
|
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
|
||||||
import type { TRegisterUser, TError } from 'librechat-data-provider';
|
import type { TRegisterUser, TError } from 'librechat-data-provider';
|
||||||
import type { TLoginLayoutContext } from '~/common';
|
import type { TLoginLayoutContext } from '~/common';
|
||||||
import { ErrorMessage } from './ErrorMessage';
|
import { ErrorMessage } from './ErrorMessage';
|
||||||
import { Spinner } from '~/components/svg';
|
import { Spinner } from '~/components/svg';
|
||||||
import { useLocalize, TranslationKeys } from '~/hooks';
|
import { useLocalize, TranslationKeys, ThemeContext } from '~/hooks';
|
||||||
|
|
||||||
const Registration: React.FC = () => {
|
const Registration: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
const { startupConfig, startupConfigError, isFetching } = useOutletContext<TLoginLayoutContext>();
|
const { startupConfig, startupConfigError, isFetching } = useOutletContext<TLoginLayoutContext>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -24,10 +26,12 @@ const Registration: React.FC = () => {
|
||||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [countdown, setCountdown] = useState<number>(3);
|
const [countdown, setCountdown] = useState<number>(3);
|
||||||
|
const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const token = queryParams.get('token');
|
const token = queryParams.get('token');
|
||||||
|
const validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||||
|
|
||||||
const registerUser = useRegisterUserMutation({
|
const registerUser = useRegisterUserMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
|
|
@ -178,17 +182,38 @@ const Registration: React.FC = () => {
|
||||||
validate: (value: string) =>
|
validate: (value: string) =>
|
||||||
value === password || localize('com_auth_password_not_match'),
|
value === password || localize('com_auth_password_not_match'),
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Render Turnstile only if enabled in startupConfig */}
|
||||||
|
{startupConfig?.turnstile && (
|
||||||
|
<div className="my-4 flex justify-center">
|
||||||
|
<Turnstile
|
||||||
|
siteKey={startupConfig.turnstile.siteKey}
|
||||||
|
options={{
|
||||||
|
...startupConfig.turnstile.options,
|
||||||
|
theme: validTheme,
|
||||||
|
}}
|
||||||
|
onSuccess={(token) => setTurnstileToken(token)}
|
||||||
|
onError={() => setTurnstileToken(null)}
|
||||||
|
onExpire={() => setTurnstileToken(null)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button
|
<button
|
||||||
disabled={Object.keys(errors).length > 0}
|
disabled={
|
||||||
|
Object.keys(errors).length > 0 ||
|
||||||
|
isSubmitting ||
|
||||||
|
(startupConfig?.turnstile ? !turnstileToken : false)
|
||||||
|
}
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="Submit registration"
|
aria-label="Submit registration"
|
||||||
className="
|
className="
|
||||||
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white
|
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white
|
||||||
transition-colors hover:bg-green-700 focus:outline-none focus:ring-2
|
transition-colors hover:bg-green-700 focus:outline-none focus:ring-2
|
||||||
focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50
|
focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50
|
||||||
disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700
|
disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,13 @@ interface:
|
||||||
multiConvo: true
|
multiConvo: true
|
||||||
agents: true
|
agents: true
|
||||||
|
|
||||||
|
# Example Cloudflare turnstile (optional)
|
||||||
|
#turnstile:
|
||||||
|
# siteKey: "your-site-key-here"
|
||||||
|
# options:
|
||||||
|
# language: "auto" # "auto" or an ISO 639-1 language code (e.g. en)
|
||||||
|
# size: "normal" # Options: "normal", "compact", "flexible", or "invisible"
|
||||||
|
|
||||||
# Example Registration Object Structure (optional)
|
# Example Registration Object Structure (optional)
|
||||||
registration:
|
registration:
|
||||||
socialLogins: ['github', 'google', 'discord', 'openid', 'facebook', 'apple']
|
socialLogins: ['github', 'google', 'discord', 'openid', 'facebook', 'apple']
|
||||||
|
|
|
||||||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -1073,6 +1073,7 @@
|
||||||
"@dicebear/collection": "^9.2.2",
|
"@dicebear/collection": "^9.2.2",
|
||||||
"@dicebear/core": "^9.2.2",
|
"@dicebear/core": "^9.2.2",
|
||||||
"@headlessui/react": "^2.1.2",
|
"@headlessui/react": "^2.1.2",
|
||||||
|
"@marsidev/react-turnstile": "^1.1.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.3",
|
"@radix-ui/react-checkbox": "^1.0.3",
|
||||||
|
|
@ -19729,6 +19730,16 @@
|
||||||
"resolved": "client",
|
"resolved": "client",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@marsidev/react-turnstile": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-X7bP9ZYutDd+E+klPYF+/BJHqEyyVkN4KKmZcNRr84zs3DcMoftlMAuoKqNSnqg0HE7NQ1844+TLFSJoztCdSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.2 || ^18.0.0 || ^19.0",
|
||||||
|
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@microsoft/eslint-formatter-sarif": {
|
"node_modules/@microsoft/eslint-formatter-sarif": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/eslint-formatter-sarif/-/eslint-formatter-sarif-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/eslint-formatter-sarif/-/eslint-formatter-sarif-3.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -505,10 +505,28 @@ export const intefaceSchema = z
|
||||||
export type TInterfaceConfig = z.infer<typeof intefaceSchema>;
|
export type TInterfaceConfig = z.infer<typeof intefaceSchema>;
|
||||||
export type TBalanceConfig = z.infer<typeof balanceSchema>;
|
export type TBalanceConfig = z.infer<typeof balanceSchema>;
|
||||||
|
|
||||||
|
export const turnstileOptionsSchema = z
|
||||||
|
.object({
|
||||||
|
language: z.string().default('auto'),
|
||||||
|
size: z.enum(['normal', 'compact', 'flexible', 'invisible']).default('normal'),
|
||||||
|
})
|
||||||
|
.default({
|
||||||
|
language: 'auto',
|
||||||
|
size: 'normal',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const turnstileSchema = z.object({
|
||||||
|
siteKey: z.string(),
|
||||||
|
options: turnstileOptionsSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TTurnstileConfig = z.infer<typeof turnstileSchema>;
|
||||||
|
|
||||||
export type TStartupConfig = {
|
export type TStartupConfig = {
|
||||||
appTitle: string;
|
appTitle: string;
|
||||||
socialLogins?: string[];
|
socialLogins?: string[];
|
||||||
interface?: TInterfaceConfig;
|
interface?: TInterfaceConfig;
|
||||||
|
turnstile?: TTurnstileConfig;
|
||||||
balance?: TBalanceConfig;
|
balance?: TBalanceConfig;
|
||||||
discordLoginEnabled: boolean;
|
discordLoginEnabled: boolean;
|
||||||
facebookLoginEnabled: boolean;
|
facebookLoginEnabled: boolean;
|
||||||
|
|
@ -578,6 +596,7 @@ export const configSchema = z.object({
|
||||||
filteredTools: z.array(z.string()).optional(),
|
filteredTools: z.array(z.string()).optional(),
|
||||||
mcpServers: MCPServersSchema.optional(),
|
mcpServers: MCPServersSchema.optional(),
|
||||||
interface: intefaceSchema,
|
interface: intefaceSchema,
|
||||||
|
turnstile: turnstileSchema.optional(),
|
||||||
fileStrategy: fileSourceSchema.default(FileSources.local),
|
fileStrategy: fileSourceSchema.default(FileSources.local),
|
||||||
actions: z
|
actions: z
|
||||||
.object({
|
.object({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue