+
+ {/* Welcome back header and login buttons below logo */}
+ {!hasStartupConfigError && !isFetching && header && (
+
+ )}
+
+ {/* ——— WELCOME SECTIONS ——— */}
+ {logoText && (
+
@@ -75,19 +118,7 @@ function AuthLayout({
- {!hasStartupConfigError && !isFetching && header && (
-
- {header}
-
- )}
{children}
- {!pathname.includes('2fa') &&
- (pathname.includes('login') || pathname.includes('register')) && (
-
- )}
diff --git a/librechat.example.yaml b/librechat.example.yaml
index 92206c4b6e..e87760ecd5 100644
--- a/librechat.example.yaml
+++ b/librechat.example.yaml
@@ -131,7 +131,10 @@ interface:
# Temporary chat retention period in hours (default: 720, min: 1, max: 8760)
# temporaryChatRetention: 1
-
+ loginImageUrl: 'https://librechat.ai/assets/logo.png'
+ loginText: |
+ ## Welcome to LibreChat! Your AI companion for seamless conversations.
+
# Example Cloudflare turnstile (optional)
#turnstile:
# siteKey: "your-site-key-here"
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index ca40ec2c8c..c66eaf7d77 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -8,6 +8,7 @@ import { fileConfigSchema } from './file-config';
import { apiBaseUrl } from './api-endpoints';
import { FileSources } from './types/files';
import { MCPServersSchema } from './mcp';
+import { isSafeImageUrl } from './utils';
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord', 'saml'];
@@ -689,6 +690,14 @@ export const interfaceSchema = z
.optional(),
termsOfService: termsOfServiceSchema.optional(),
customWelcome: z.string().optional(),
+ loginImageUrl: z
+ .string()
+ .url()
+ .refine(isSafeImageUrl, {
+ message: 'loginImageUrl must be a valid HTTP, HTTPS, or data:image URL',
+ })
+ .optional(),
+ loginText: z.string().optional(),
mcpServers: mcpServersSchema.optional(),
modelSelect: z.boolean().optional(),
parameters: z.boolean().optional(),
diff --git a/packages/data-provider/src/utils.ts b/packages/data-provider/src/utils.ts
index 1eefcff8c4..578dc28207 100644
--- a/packages/data-provider/src/utils.ts
+++ b/packages/data-provider/src/utils.ts
@@ -83,3 +83,26 @@ export function extractEnvVariable(value: string) {
export function normalizeEndpointName(name = ''): string {
return name.toLowerCase() === 'ollama' ? 'ollama' : name;
}
+
+/**
+ * Validates that a URL uses only safe protocols (http, https, or data:image/*)
+ * @param url - The URL string to validate
+ * @returns true if the URL is safe, false otherwise
+ */
+export const isSafeImageUrl = (url: string): boolean => {
+ try {
+ const parsedUrl = new URL(url);
+ if (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:') {
+ return true;
+ }
+
+ if (parsedUrl.protocol !== 'data:') {
+ return false;
+ }
+
+ // Restrict data URLs to image payloads only.
+ return /^data:image\/[a-z0-9.+-]+(?:;[a-z0-9=._+-]+)*(?:;base64)?,/i.test(url);
+ } catch {
+ return false;
+ }
+};
diff --git a/packages/data-schemas/src/app/interface.ts b/packages/data-schemas/src/app/interface.ts
index 3cd71cfb20..863fa26f6f 100644
--- a/packages/data-schemas/src/app/interface.ts
+++ b/packages/data-schemas/src/app/interface.ts
@@ -38,6 +38,8 @@ export async function loadDefaultInterface({
termsOfService: interfaceConfig?.termsOfService ?? defaults.termsOfService,
mcpServers: interfaceConfig?.mcpServers ?? defaults.mcpServers,
customWelcome: interfaceConfig?.customWelcome ?? defaults.customWelcome,
+ loginImageUrl: interfaceConfig?.loginImageUrl ?? defaults.loginImageUrl,
+ loginText: interfaceConfig?.loginText ?? defaults.loginText,
// Permissions - only include if explicitly configured
bookmarks: interfaceConfig?.bookmarks,