ui: mask API Key field in settings and error splash to stop browser a… (#26562)

* ui: mask API Key field in settings and error splash to stop browser autofill

* ui: set autocomplete=new-password on private fields

The password input type makes browsers offer to save the API key
in the password manager and autofill saved site credentials into
the field. The new-password autocomplete value disables both.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
This commit is contained in:
Cristiano Pinto
2026-08-15 22:52:55 +02:00
committed by GitHub
co-authored by Pascal
parent 0d9ceae1e3
commit ece963f41b
5 changed files with 20 additions and 1 deletions
@@ -158,6 +158,8 @@
<div class="relative"> <div class="relative">
<Input <Input
id="api-key-input" id="api-key-input"
type="password"
autocomplete="new-password"
placeholder="Enter your API key..." placeholder="Enter your API key..."
bind:value={apiKeyInput} bind:value={apiKeyInput}
onkeydown={handleApiKeyKeydown} onkeydown={handleApiKeyKeydown}
@@ -81,7 +81,8 @@
<div class="relative w-full"> <div class="relative w-full">
<Input <Input
id={field.key} id={field.key}
type={field.isPositiveInteger ? 'number' : 'text'} type={field.isPrivate ? 'password' : field.isPositiveInteger ? 'number' : 'text'}
autocomplete={field.isPrivate ? 'new-password' : undefined}
{...field.isPositiveInteger {...field.isPositiveInteger
? { ? {
min: String(field.min ?? 1), min: String(field.min ?? 1),
@@ -324,6 +324,7 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
{ {
defaultValue: '', defaultValue: '',
help: `Set the API Key if you are using <code> ${CLI_FLAGS.API_KEY} </code> option for the server.`, help: `Set the API Key if you are using <code> ${CLI_FLAGS.API_KEY} </code> option for the server.`,
isPrivate: true,
key: SETTINGS_KEYS.API_KEY, key: SETTINGS_KEYS.API_KEY,
label: 'API Key', label: 'API Key',
section: SETTINGS_SECTION_SLUGS.GENERAL, section: SETTINGS_SECTION_SLUGS.GENERAL,
@@ -713,6 +714,7 @@ export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [
help: s.help, help: s.help,
isExperimental: s.isExperimental, isExperimental: s.isExperimental,
isPositiveInteger: s.isPositiveInteger, isPositiveInteger: s.isPositiveInteger,
isPrivate: s.isPrivate,
key: s.key, key: s.key,
label: s.label, label: s.label,
max: s.max, max: s.max,
+2
View File
@@ -31,6 +31,7 @@ export interface SettingsEntry {
radioOptions?: Array<{ value: string; label: string; key: string; isExperimental?: boolean }>; radioOptions?: Array<{ value: string; label: string; key: string; isExperimental?: boolean }>;
isExperimental?: boolean; isExperimental?: boolean;
isPositiveInteger?: boolean; isPositiveInteger?: boolean;
isPrivate?: boolean;
placeholder?: string; placeholder?: string;
min?: number; min?: number;
max?: number; max?: number;
@@ -55,6 +56,7 @@ export interface SettingsFieldConfig {
type: SettingsFieldType; type: SettingsFieldType;
isExperimental?: boolean; isExperimental?: boolean;
isPositiveInteger?: boolean; isPositiveInteger?: boolean;
isPrivate?: boolean;
placeholder?: string; placeholder?: string;
min?: number; min?: number;
max?: number; max?: number;
@@ -0,0 +1,12 @@
import { SETTINGS_CHAT_SECTIONS, SETTINGS_KEYS } from '$lib/constants';
import { describe, expect, it } from 'vitest';
describe('checkApiKeyField', () => {
it('should have isPrivate set to true', () => {
const fields = SETTINGS_CHAT_SECTIONS.flatMap((section) => section.fields);
const apiKeyField = fields.find((field) => field?.key === SETTINGS_KEYS.API_KEY);
expect(apiKeyField).toBeDefined();
expect(apiKeyField?.isPrivate).toBe(true);
});
});