Merge pull request #221 from cogwheel0/jwt-token-authentication-support
jwt-token-authentication-support
This commit is contained in:
@@ -187,6 +187,23 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
|
|
||||||
if (token != null && token.isNotEmpty) {
|
if (token != null && token.isNotEmpty) {
|
||||||
DebugLogger.auth('Found stored token during initialization');
|
DebugLogger.auth('Found stored token during initialization');
|
||||||
|
|
||||||
|
// Check if stored token is an API key - force logout if so
|
||||||
|
if (TokenValidator.isApiKey(token)) {
|
||||||
|
DebugLogger.auth('Detected API key token, forcing logout');
|
||||||
|
await storage.deleteAuthToken();
|
||||||
|
await storage.deleteSavedCredentials();
|
||||||
|
_update(
|
||||||
|
(current) => current.copyWith(
|
||||||
|
status: AuthStatus.credentialError,
|
||||||
|
error: 'apiKeyNoLongerSupported',
|
||||||
|
isLoading: false,
|
||||||
|
clearToken: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Fast path: trust token format to avoid blocking startup on network
|
// Fast path: trust token format to avoid blocking startup on network
|
||||||
final formatOk = _isValidTokenFormat(token);
|
final formatOk = _isValidTokenFormat(token);
|
||||||
if (formatOk) {
|
if (formatOk) {
|
||||||
@@ -270,7 +287,8 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform login with API key
|
/// Perform login with JWT token
|
||||||
|
/// Note: API keys (sk-...) are not supported for streaming.
|
||||||
Future<bool> loginWithApiKey(
|
Future<bool> loginWithApiKey(
|
||||||
String apiKey, {
|
String apiKey, {
|
||||||
bool rememberCredentials = false,
|
bool rememberCredentials = false,
|
||||||
@@ -284,9 +302,16 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate API key format
|
// Validate token is not empty
|
||||||
if (apiKey.trim().isEmpty) {
|
if (apiKey.trim().isEmpty) {
|
||||||
throw Exception('API key cannot be empty');
|
throw Exception('Token cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
final tokenStr = apiKey.trim();
|
||||||
|
|
||||||
|
// Reject API keys - they don't support streaming
|
||||||
|
if (TokenValidator.isApiKey(tokenStr)) {
|
||||||
|
throw Exception('apiKeyNotSupported');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure API service is available
|
// Ensure API service is available
|
||||||
@@ -296,12 +321,9 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
throw Exception('No server connection available');
|
throw Exception('No server connection available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use API key directly as Bearer token
|
// Validate token format
|
||||||
final tokenStr = apiKey.trim();
|
|
||||||
|
|
||||||
// Validate token format (consistent with credentials method)
|
|
||||||
if (!_isValidTokenFormat(tokenStr)) {
|
if (!_isValidTokenFormat(tokenStr)) {
|
||||||
throw Exception('Invalid API key format');
|
throw Exception('Invalid token format');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update API service with the API key
|
// Update API service with the API key
|
||||||
@@ -315,16 +337,15 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
final storage = ref.read(optimizedStorageServiceProvider);
|
||||||
await storage.saveAuthToken(tokenStr);
|
await storage.saveAuthToken(tokenStr);
|
||||||
|
|
||||||
// Save API key if requested (for convenience, though less secure than credentials)
|
// Save JWT token if requested
|
||||||
if (rememberCredentials) {
|
if (rememberCredentials) {
|
||||||
final activeServer = await ref.read(activeServerProvider.future);
|
final activeServer = await ref.read(activeServerProvider.future);
|
||||||
if (activeServer != null) {
|
if (activeServer != null) {
|
||||||
// Store API key as a special credential type
|
// Store JWT as a special credential type
|
||||||
await storage.saveCredentials(
|
await storage.saveCredentials(
|
||||||
serverId: activeServer.id,
|
serverId: activeServer.id,
|
||||||
username:
|
username: 'jwt_user', // Special username to indicate JWT auth
|
||||||
'api_key_user', // Special username to indicate API key auth
|
password: tokenStr, // Store JWT in password field
|
||||||
password: tokenStr, // Store API key in password field
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,11 +369,11 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
_loadUserData();
|
_loadUserData();
|
||||||
_prefetchConversations();
|
_prefetchConversations();
|
||||||
|
|
||||||
DebugLogger.auth('API key login successful');
|
DebugLogger.auth('JWT token login successful');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If user fetch fails, the API key might be invalid
|
// If user fetch fails, the token might be invalid
|
||||||
throw Exception('Invalid API key or insufficient permissions');
|
throw Exception('Invalid token or insufficient permissions');
|
||||||
}
|
}
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
DebugLogger.error(
|
DebugLogger.error(
|
||||||
@@ -561,8 +582,8 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt login (detect API key vs normal credentials)
|
// Attempt login (detect API key vs normal credentials)
|
||||||
if (username == 'api_key_user') {
|
if (username == 'api_key_user' || username == 'jwt_user') {
|
||||||
// This is a saved API key
|
// This is a saved JWT token (or legacy API key)
|
||||||
return await loginWithApiKey(password, rememberCredentials: false);
|
return await loginWithApiKey(password, rememberCredentials: false);
|
||||||
} else {
|
} else {
|
||||||
// Normal username/password credentials
|
// Normal username/password credentials
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ import '../utils/debug_logger.dart';
|
|||||||
class TokenValidator {
|
class TokenValidator {
|
||||||
static const Duration _validationTimeout = Duration(seconds: 5);
|
static const Duration _validationTimeout = Duration(seconds: 5);
|
||||||
|
|
||||||
/// Validate token format (supports both JWT and API key formats)
|
/// Check if token is an API key format (sk-, api-, key-)
|
||||||
|
/// API keys are not supported for streaming.
|
||||||
|
static bool isApiKey(String token) {
|
||||||
|
return token.startsWith('sk-') ||
|
||||||
|
token.startsWith('api-') ||
|
||||||
|
token.startsWith('key-');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate token format (JWT tokens only - API keys not supported)
|
||||||
static TokenValidationResult validateTokenFormat(String token) {
|
static TokenValidationResult validateTokenFormat(String token) {
|
||||||
try {
|
try {
|
||||||
// Basic format check
|
// Basic format check
|
||||||
@@ -14,15 +22,11 @@ class TokenValidator {
|
|||||||
return TokenValidationResult.invalid('Token too short');
|
return TokenValidationResult.invalid('Token too short');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's an API key format (starts with sk- or similar)
|
// Reject API keys - they don't support streaming
|
||||||
if (token.startsWith('sk-') ||
|
if (isApiKey(token)) {
|
||||||
token.startsWith('api-') ||
|
return TokenValidationResult.apiKeyNotSupported(
|
||||||
token.startsWith('key-')) {
|
'API keys are not supported. Please use a JWT token.',
|
||||||
// API key format - validate differently
|
);
|
||||||
if (token.length < 20) {
|
|
||||||
return TokenValidationResult.invalid('API key too short');
|
|
||||||
}
|
|
||||||
return TokenValidationResult.valid('API key format valid');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it looks like a JWT (has at least 2 dots)
|
// Check if it looks like a JWT (has at least 2 dots)
|
||||||
@@ -209,6 +213,9 @@ class TokenValidationResult {
|
|||||||
const TokenValidationResult.networkError(String message)
|
const TokenValidationResult.networkError(String message)
|
||||||
: this._(false, TokenValidationStatus.networkError, message);
|
: this._(false, TokenValidationStatus.networkError, message);
|
||||||
|
|
||||||
|
const TokenValidationResult.apiKeyNotSupported(String message)
|
||||||
|
: this._(false, TokenValidationStatus.apiKeyNotSupported, message);
|
||||||
|
|
||||||
final bool isValid;
|
final bool isValid;
|
||||||
final TokenValidationStatus status;
|
final TokenValidationStatus status;
|
||||||
final String message;
|
final String message;
|
||||||
@@ -218,6 +225,8 @@ class TokenValidationResult {
|
|||||||
bool get isExpired => status == TokenValidationStatus.expired;
|
bool get isExpired => status == TokenValidationStatus.expired;
|
||||||
bool get isExpiringSoon => status == TokenValidationStatus.expiringSoon;
|
bool get isExpiringSoon => status == TokenValidationStatus.expiringSoon;
|
||||||
bool get hasNetworkError => status == TokenValidationStatus.networkError;
|
bool get hasNetworkError => status == TokenValidationStatus.networkError;
|
||||||
|
bool get isApiKeyNotSupported =>
|
||||||
|
status == TokenValidationStatus.apiKeyNotSupported;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
@@ -230,6 +239,7 @@ enum TokenValidationStatus {
|
|||||||
expired,
|
expired,
|
||||||
expiringSoon,
|
expiringSoon,
|
||||||
networkError,
|
networkError,
|
||||||
|
apiKeyNotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache for token validation results
|
/// Cache for token validation results
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ class RouterNotifier extends ChangeNotifier {
|
|||||||
final reviewerMode = ref.read(reviewerModeProvider);
|
final reviewerMode = ref.read(reviewerModeProvider);
|
||||||
final activeServerAsync = ref.read(activeServerProvider);
|
final activeServerAsync = ref.read(activeServerProvider);
|
||||||
|
|
||||||
|
// Check for API key forced logout first - redirect to authentication
|
||||||
|
final authSnapshot = ref
|
||||||
|
.read(authStateManagerProvider)
|
||||||
|
.maybeWhen(data: (s) => s, orElse: () => null);
|
||||||
|
if (authSnapshot?.error?.contains('apiKey') == true) {
|
||||||
|
return location == Routes.authentication ? null : Routes.authentication;
|
||||||
|
}
|
||||||
|
|
||||||
if (reviewerMode) {
|
if (reviewerMode) {
|
||||||
// Stay on whatever route if already in chat; otherwise go to chat
|
// Stay on whatever route if already in chat; otherwise go to chat
|
||||||
if (location == Routes.chat) return null;
|
if (location == Routes.chat) return null;
|
||||||
|
|||||||
@@ -43,6 +43,23 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadSavedCredentials();
|
_loadSavedCredentials();
|
||||||
|
// Check for auth errors (e.g., forced logout due to API key)
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_checkAuthStateError();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkAuthStateError() {
|
||||||
|
final authState = ref.read(authStateManagerProvider).asData?.value;
|
||||||
|
if (authState?.error != null && authState!.error!.isNotEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_loginError = _formatLoginError(authState.error!);
|
||||||
|
// Switch to token tab if the error is about API keys
|
||||||
|
if (authState.error!.contains('apiKey')) {
|
||||||
|
_useApiKey = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadSavedCredentials() async {
|
Future<void> _loadSavedCredentials() async {
|
||||||
@@ -127,16 +144,21 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _formatLoginError(String error) {
|
String _formatLoginError(String error) {
|
||||||
if (error.contains('401') || error.contains('Unauthorized')) {
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return AppLocalizations.of(context)!.invalidCredentials;
|
if (error.contains('apiKeyNotSupported')) {
|
||||||
|
return l10n.apiKeyNotSupported;
|
||||||
|
} else if (error.contains('apiKeyNoLongerSupported')) {
|
||||||
|
return l10n.apiKeyNoLongerSupported;
|
||||||
|
} else if (error.contains('401') || error.contains('Unauthorized')) {
|
||||||
|
return l10n.invalidCredentials;
|
||||||
} else if (error.contains('redirect')) {
|
} else if (error.contains('redirect')) {
|
||||||
return AppLocalizations.of(context)!.serverRedirectingHttps;
|
return l10n.serverRedirectingHttps;
|
||||||
} else if (error.contains('SocketException')) {
|
} else if (error.contains('SocketException')) {
|
||||||
return AppLocalizations.of(context)!.unableToConnectServer;
|
return l10n.unableToConnectServer;
|
||||||
} else if (error.contains('timeout')) {
|
} else if (error.contains('timeout')) {
|
||||||
return AppLocalizations.of(context)!.requestTimedOut;
|
return l10n.requestTimedOut;
|
||||||
}
|
}
|
||||||
return AppLocalizations.of(context)!.genericSignInFailed;
|
return l10n.genericSignInFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -399,7 +421,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
icon: Platform.isIOS
|
icon: Platform.isIOS
|
||||||
? CupertinoIcons.lock_shield
|
? CupertinoIcons.lock_shield
|
||||||
: Icons.vpn_key_outlined,
|
: Icons.vpn_key_outlined,
|
||||||
label: AppLocalizations.of(context)!.apiKey,
|
label: AppLocalizations.of(context)!.token,
|
||||||
isSelected: _useApiKey,
|
isSelected: _useApiKey,
|
||||||
onTap: () => setState(() => _useApiKey = true),
|
onTap: () => setState(() => _useApiKey = true),
|
||||||
),
|
),
|
||||||
@@ -480,24 +502,43 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates that a token is a JWT and not an API key.
|
||||||
|
/// API keys (sk-, api-, key-) don't work with WebSocket authentication.
|
||||||
|
String? _validateJwtToken(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return AppLocalizations.of(context)!.validationMissingRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
final trimmed = value.trim();
|
||||||
|
final lowerTrimmed = trimmed.toLowerCase();
|
||||||
|
|
||||||
|
// Reject API keys - they don't work with socket authentication
|
||||||
|
// Case-insensitive check to catch SK-, API-, KEY- variants
|
||||||
|
if (lowerTrimmed.startsWith('sk-') ||
|
||||||
|
lowerTrimmed.startsWith('api-') ||
|
||||||
|
lowerTrimmed.startsWith('key-')) {
|
||||||
|
return AppLocalizations.of(context)!.apiKeyNotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check minimum length
|
||||||
|
if (trimmed.length < 10) {
|
||||||
|
return AppLocalizations.of(context)!.tokenTooShort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildApiKeyForm() {
|
Widget _buildApiKeyForm() {
|
||||||
return Column(
|
return Column(
|
||||||
key: const ValueKey('api_key_form'),
|
key: const ValueKey('api_key_form'),
|
||||||
children: [
|
children: [
|
||||||
AccessibleFormField(
|
AccessibleFormField(
|
||||||
label: AppLocalizations.of(context)!.apiKey,
|
label: AppLocalizations.of(context)!.token,
|
||||||
hint: 'sk-...',
|
hint: 'eyJ...',
|
||||||
controller: _apiKeyController,
|
controller: _apiKeyController,
|
||||||
validator: InputValidationService.combine([
|
validator: _validateJwtToken,
|
||||||
InputValidationService.validateRequired,
|
|
||||||
(value) => InputValidationService.validateMinLength(
|
|
||||||
value,
|
|
||||||
10,
|
|
||||||
fieldName: AppLocalizations.of(context)!.apiKey,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
semanticLabel: AppLocalizations.of(context)!.enterApiKey,
|
semanticLabel: AppLocalizations.of(context)!.enterToken,
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
Platform.isIOS
|
Platform.isIOS
|
||||||
? CupertinoIcons.lock_shield
|
? CupertinoIcons.lock_shield
|
||||||
@@ -520,6 +561,13 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
isRequired: true,
|
isRequired: true,
|
||||||
autofillHints: const [AutofillHints.password],
|
autofillHints: const [AutofillHints.password],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: Spacing.sm),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.tokenHint,
|
||||||
|
style: context.conduitTheme.bodySmall?.copyWith(
|
||||||
|
color: context.conduitTheme.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -591,7 +639,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
|||||||
text: _isSigningIn
|
text: _isSigningIn
|
||||||
? AppLocalizations.of(context)!.signingIn
|
? AppLocalizations.of(context)!.signingIn
|
||||||
: _useApiKey
|
: _useApiKey
|
||||||
? AppLocalizations.of(context)!.signInWithApiKey
|
? AppLocalizations.of(context)!.signInWithToken
|
||||||
: AppLocalizations.of(context)!.signIn,
|
: AppLocalizations.of(context)!.signIn,
|
||||||
icon: _isSigningIn
|
icon: _isSigningIn
|
||||||
? null
|
? null
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen",
|
"enterCredentials": "Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen",
|
||||||
"credentials": "Zugangsdaten",
|
"credentials": "Zugangsdaten",
|
||||||
"apiKey": "API-Schlüssel",
|
"apiKey": "API-Schlüssel",
|
||||||
|
"token": "Token",
|
||||||
"usernameOrEmail": "Benutzername oder E‑Mail",
|
"usernameOrEmail": "Benutzername oder E‑Mail",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"signInWithApiKey": "Mit API-Schlüssel anmelden",
|
"signInWithApiKey": "Mit API-Schlüssel anmelden",
|
||||||
|
"signInWithToken": "Mit Token anmelden",
|
||||||
"connectToServer": "Mit Server verbinden",
|
"connectToServer": "Mit Server verbinden",
|
||||||
"enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen",
|
"enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen",
|
||||||
"serverUrl": "Server-URL",
|
"serverUrl": "Server-URL",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Gib deinen Benutzernamen oder deine E‑Mail ein",
|
"usernameOrEmailHint": "Gib deinen Benutzernamen oder deine E‑Mail ein",
|
||||||
"passwordHint": "Gib dein Passwort ein",
|
"passwordHint": "Gib dein Passwort ein",
|
||||||
"enterApiKey": "Gib deinen API-Schlüssel ein",
|
"enterApiKey": "Gib deinen API-Schlüssel ein",
|
||||||
|
"enterToken": "Gib dein JWT-Token ein",
|
||||||
|
"tokenHint": "Hole das JWT-Token aus den OpenWebUI-Einstellungen. API-Schlüssel (sk-...) werden für Streaming nicht unterstützt.",
|
||||||
|
"apiKeyNotSupported": "API-Schlüssel (sk-...) werden nicht unterstützt. Bitte verwende stattdessen ein JWT-Token.",
|
||||||
|
"apiKeyNoLongerSupported": "Du wurdest abgemeldet, da API-Schlüssel nicht mehr unterstützt werden. Bitte melde dich mit einem JWT-Token aus den OpenWebUI-Einstellungen an.",
|
||||||
|
"tokenTooShort": "Token ist zu kurz",
|
||||||
"signingIn": "Anmeldung läuft...",
|
"signingIn": "Anmeldung läuft...",
|
||||||
"advancedSettings": "Erweiterte Einstellungen",
|
"advancedSettings": "Erweiterte Einstellungen",
|
||||||
"customHeaders": "Benutzerdefinierte Header",
|
"customHeaders": "Benutzerdefinierte Header",
|
||||||
|
|||||||
@@ -216,6 +216,10 @@
|
|||||||
"@apiKey": {
|
"@apiKey": {
|
||||||
"description": "Label for API key input field."
|
"description": "Label for API key input field."
|
||||||
},
|
},
|
||||||
|
"token": "Token",
|
||||||
|
"@token": {
|
||||||
|
"description": "Label for JWT token input field."
|
||||||
|
},
|
||||||
"usernameOrEmail": "Username or Email",
|
"usernameOrEmail": "Username or Email",
|
||||||
"@usernameOrEmail": {
|
"@usernameOrEmail": {
|
||||||
"description": "Label for username/email input field."
|
"description": "Label for username/email input field."
|
||||||
@@ -228,6 +232,10 @@
|
|||||||
"@signInWithApiKey": {
|
"@signInWithApiKey": {
|
||||||
"description": "Alternative sign-in method using an API key."
|
"description": "Alternative sign-in method using an API key."
|
||||||
},
|
},
|
||||||
|
"signInWithToken": "Sign in with Token",
|
||||||
|
"@signInWithToken": {
|
||||||
|
"description": "Alternative sign-in method using a JWT token."
|
||||||
|
},
|
||||||
"connectToServer": "Connect to Server",
|
"connectToServer": "Connect to Server",
|
||||||
"@connectToServer": {
|
"@connectToServer": {
|
||||||
"description": "Call-to-action button for server connection."
|
"description": "Call-to-action button for server connection."
|
||||||
@@ -1004,6 +1012,26 @@
|
|||||||
"@enterApiKey": {
|
"@enterApiKey": {
|
||||||
"description": "Hint text for API key input."
|
"description": "Hint text for API key input."
|
||||||
},
|
},
|
||||||
|
"enterToken": "Enter your JWT token",
|
||||||
|
"@enterToken": {
|
||||||
|
"description": "Hint text for JWT token input."
|
||||||
|
},
|
||||||
|
"tokenHint": "Get the JWT token from OpenWebUI settings. API keys (sk-...) are not supported for streaming.",
|
||||||
|
"@tokenHint": {
|
||||||
|
"description": "Help text explaining what type of token to use."
|
||||||
|
},
|
||||||
|
"apiKeyNotSupported": "API keys (sk-...) are not supported. Please use a JWT token instead.",
|
||||||
|
"@apiKeyNotSupported": {
|
||||||
|
"description": "Error message when user tries to use an API key instead of JWT token."
|
||||||
|
},
|
||||||
|
"apiKeyNoLongerSupported": "You were logged out because API keys are no longer supported. Please sign in with a JWT token from OpenWebUI settings.",
|
||||||
|
"@apiKeyNoLongerSupported": {
|
||||||
|
"description": "Error message shown when user is forced logged out due to using an API key."
|
||||||
|
},
|
||||||
|
"tokenTooShort": "Token is too short",
|
||||||
|
"@tokenTooShort": {
|
||||||
|
"description": "Error message when token is too short."
|
||||||
|
},
|
||||||
"signingIn": "Signing in...",
|
"signingIn": "Signing in...",
|
||||||
"@signingIn": {
|
"@signingIn": {
|
||||||
"description": "Status message shown while signing in."
|
"description": "Status message shown while signing in."
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Ingresa tus credenciales para acceder a tus conversaciones de IA",
|
"enterCredentials": "Ingresa tus credenciales para acceder a tus conversaciones de IA",
|
||||||
"credentials": "Credenciales",
|
"credentials": "Credenciales",
|
||||||
"apiKey": "Clave API",
|
"apiKey": "Clave API",
|
||||||
|
"token": "Token",
|
||||||
"usernameOrEmail": "Usuario o correo electrónico",
|
"usernameOrEmail": "Usuario o correo electrónico",
|
||||||
"password": "Contraseña",
|
"password": "Contraseña",
|
||||||
"signInWithApiKey": "Iniciar sesión con clave API",
|
"signInWithApiKey": "Iniciar sesión con clave API",
|
||||||
|
"signInWithToken": "Iniciar sesión con token",
|
||||||
"connectToServer": "Conectar al servidor",
|
"connectToServer": "Conectar al servidor",
|
||||||
"enterServerAddress": "Ingresa la dirección de tu servidor Open-WebUI para comenzar",
|
"enterServerAddress": "Ingresa la dirección de tu servidor Open-WebUI para comenzar",
|
||||||
"serverUrl": "URL del servidor",
|
"serverUrl": "URL del servidor",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Ingresa tu usuario o correo electrónico",
|
"usernameOrEmailHint": "Ingresa tu usuario o correo electrónico",
|
||||||
"passwordHint": "Ingresa tu contraseña",
|
"passwordHint": "Ingresa tu contraseña",
|
||||||
"enterApiKey": "Ingresa tu clave API",
|
"enterApiKey": "Ingresa tu clave API",
|
||||||
|
"enterToken": "Ingresa tu token JWT",
|
||||||
|
"tokenHint": "Obtén el token JWT desde la configuración de OpenWebUI. Las claves API (sk-...) no son compatibles con streaming.",
|
||||||
|
"apiKeyNotSupported": "Las claves API (sk-...) no son compatibles. Por favor usa un token JWT en su lugar.",
|
||||||
|
"apiKeyNoLongerSupported": "Se cerró tu sesión porque las claves API ya no son compatibles. Por favor inicia sesión con un token JWT desde la configuración de OpenWebUI.",
|
||||||
|
"tokenTooShort": "El token es demasiado corto",
|
||||||
"signingIn": "Iniciando sesión...",
|
"signingIn": "Iniciando sesión...",
|
||||||
"advancedSettings": "Configuración avanzada",
|
"advancedSettings": "Configuración avanzada",
|
||||||
"customHeaders": "Encabezados personalizados",
|
"customHeaders": "Encabezados personalizados",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Entrez vos identifiants pour accéder à vos conversations IA",
|
"enterCredentials": "Entrez vos identifiants pour accéder à vos conversations IA",
|
||||||
"credentials": "Identifiants",
|
"credentials": "Identifiants",
|
||||||
"apiKey": "Clé API",
|
"apiKey": "Clé API",
|
||||||
|
"token": "Jeton",
|
||||||
"usernameOrEmail": "Nom d'utilisateur ou e‑mail",
|
"usernameOrEmail": "Nom d'utilisateur ou e‑mail",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"signInWithApiKey": "Se connecter avec une clé API",
|
"signInWithApiKey": "Se connecter avec une clé API",
|
||||||
|
"signInWithToken": "Se connecter avec un jeton",
|
||||||
"connectToServer": "Se connecter au serveur",
|
"connectToServer": "Se connecter au serveur",
|
||||||
"enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer",
|
"enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer",
|
||||||
"serverUrl": "URL du serveur",
|
"serverUrl": "URL du serveur",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Entrez votre nom d'utilisateur ou e‑mail",
|
"usernameOrEmailHint": "Entrez votre nom d'utilisateur ou e‑mail",
|
||||||
"passwordHint": "Entrez votre mot de passe",
|
"passwordHint": "Entrez votre mot de passe",
|
||||||
"enterApiKey": "Entrez votre clé API",
|
"enterApiKey": "Entrez votre clé API",
|
||||||
|
"enterToken": "Entrez votre jeton JWT",
|
||||||
|
"tokenHint": "Obtenez le jeton JWT dans les paramètres d'OpenWebUI. Les clés API (sk-...) ne sont pas prises en charge pour le streaming.",
|
||||||
|
"apiKeyNotSupported": "Les clés API (sk-...) ne sont pas prises en charge. Veuillez utiliser un jeton JWT à la place.",
|
||||||
|
"apiKeyNoLongerSupported": "Vous avez été déconnecté car les clés API ne sont plus prises en charge. Veuillez vous connecter avec un jeton JWT depuis les paramètres d'OpenWebUI.",
|
||||||
|
"tokenTooShort": "Le jeton est trop court",
|
||||||
"signingIn": "Connexion en cours...",
|
"signingIn": "Connexion en cours...",
|
||||||
"advancedSettings": "Paramètres avancés",
|
"advancedSettings": "Paramètres avancés",
|
||||||
"customHeaders": "En-têtes personnalisés",
|
"customHeaders": "En-têtes personnalisés",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Inserisci le credenziali per accedere alle conversazioni IA",
|
"enterCredentials": "Inserisci le credenziali per accedere alle conversazioni IA",
|
||||||
"credentials": "Credenziali",
|
"credentials": "Credenziali",
|
||||||
"apiKey": "Chiave API",
|
"apiKey": "Chiave API",
|
||||||
|
"token": "Token",
|
||||||
"usernameOrEmail": "Username o e‑mail",
|
"usernameOrEmail": "Username o e‑mail",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"signInWithApiKey": "Accedi con chiave API",
|
"signInWithApiKey": "Accedi con chiave API",
|
||||||
|
"signInWithToken": "Accedi con token",
|
||||||
"connectToServer": "Connetti al server",
|
"connectToServer": "Connetti al server",
|
||||||
"enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare",
|
"enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare",
|
||||||
"serverUrl": "URL del server",
|
"serverUrl": "URL del server",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Inserisci il tuo username o e‑mail",
|
"usernameOrEmailHint": "Inserisci il tuo username o e‑mail",
|
||||||
"passwordHint": "Inserisci la password",
|
"passwordHint": "Inserisci la password",
|
||||||
"enterApiKey": "Inserisci la tua chiave API",
|
"enterApiKey": "Inserisci la tua chiave API",
|
||||||
|
"enterToken": "Inserisci il tuo token JWT",
|
||||||
|
"tokenHint": "Ottieni il token JWT dalle impostazioni di OpenWebUI. Le chiavi API (sk-...) non sono supportate per lo streaming.",
|
||||||
|
"apiKeyNotSupported": "Le chiavi API (sk-...) non sono supportate. Per favore usa un token JWT.",
|
||||||
|
"apiKeyNoLongerSupported": "Sei stato disconnesso perché le chiavi API non sono più supportate. Per favore accedi con un token JWT dalle impostazioni di OpenWebUI.",
|
||||||
|
"tokenTooShort": "Il token è troppo corto",
|
||||||
"signingIn": "Accesso in corso...",
|
"signingIn": "Accesso in corso...",
|
||||||
"advancedSettings": "Impostazioni avanzate",
|
"advancedSettings": "Impostazioni avanzate",
|
||||||
"customHeaders": "Header personalizzati",
|
"customHeaders": "Header personalizzati",
|
||||||
|
|||||||
@@ -87,9 +87,11 @@
|
|||||||
"enterCredentials": "AI 대화에 액세스하려면 자격 증명을 입력하세요",
|
"enterCredentials": "AI 대화에 액세스하려면 자격 증명을 입력하세요",
|
||||||
"credentials": "자격 증명",
|
"credentials": "자격 증명",
|
||||||
"apiKey": "API 키",
|
"apiKey": "API 키",
|
||||||
|
"token": "토큰",
|
||||||
"usernameOrEmail": "사용자 이름 또는 이메일",
|
"usernameOrEmail": "사용자 이름 또는 이메일",
|
||||||
"password": "비밀번호",
|
"password": "비밀번호",
|
||||||
"signInWithApiKey": "API 키로 로그인",
|
"signInWithApiKey": "API 키로 로그인",
|
||||||
|
"signInWithToken": "토큰으로 로그인",
|
||||||
"connectToServer": "서버 연결",
|
"connectToServer": "서버 연결",
|
||||||
"enterServerAddress": "시작하려면 Open-WebUI 서버 주소를 입력하세요",
|
"enterServerAddress": "시작하려면 Open-WebUI 서버 주소를 입력하세요",
|
||||||
"serverUrl": "서버 URL",
|
"serverUrl": "서버 URL",
|
||||||
@@ -335,6 +337,11 @@
|
|||||||
"usernameOrEmailHint": "사용자 이름 또는 이메일을 입력하세요",
|
"usernameOrEmailHint": "사용자 이름 또는 이메일을 입력하세요",
|
||||||
"passwordHint": "비밀번호를 입력하세요",
|
"passwordHint": "비밀번호를 입력하세요",
|
||||||
"enterApiKey": "API 키를 입력하세요",
|
"enterApiKey": "API 키를 입력하세요",
|
||||||
|
"enterToken": "JWT 토큰을 입력하세요",
|
||||||
|
"tokenHint": "OpenWebUI 설정에서 JWT 토큰을 가져오세요. API 키(sk-...)는 스트리밍에 지원되지 않습니다.",
|
||||||
|
"apiKeyNotSupported": "API 키(sk-...)는 지원되지 않습니다. JWT 토큰을 사용하세요.",
|
||||||
|
"apiKeyNoLongerSupported": "API 키가 더 이상 지원되지 않아 로그아웃되었습니다. OpenWebUI 설정에서 JWT 토큰으로 로그인하세요.",
|
||||||
|
"tokenTooShort": "토큰이 너무 짧습니다",
|
||||||
"signingIn": "로그인 중...",
|
"signingIn": "로그인 중...",
|
||||||
"advancedSettings": "고급 설정",
|
"advancedSettings": "고급 설정",
|
||||||
"customHeaders": "사용자 정의 헤더",
|
"customHeaders": "사용자 정의 헤더",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Voer je inloggegevens in om toegang te krijgen tot je AI-gesprekken",
|
"enterCredentials": "Voer je inloggegevens in om toegang te krijgen tot je AI-gesprekken",
|
||||||
"credentials": "Inloggegevens",
|
"credentials": "Inloggegevens",
|
||||||
"apiKey": "API-sleutel",
|
"apiKey": "API-sleutel",
|
||||||
|
"token": "Token",
|
||||||
"usernameOrEmail": "Gebruikersnaam of e-mail",
|
"usernameOrEmail": "Gebruikersnaam of e-mail",
|
||||||
"password": "Wachtwoord",
|
"password": "Wachtwoord",
|
||||||
"signInWithApiKey": "Inloggen met API-sleutel",
|
"signInWithApiKey": "Inloggen met API-sleutel",
|
||||||
|
"signInWithToken": "Inloggen met token",
|
||||||
"connectToServer": "Verbinden met server",
|
"connectToServer": "Verbinden met server",
|
||||||
"enterServerAddress": "Voer je Open-WebUI serveradres in om te beginnen",
|
"enterServerAddress": "Voer je Open-WebUI serveradres in om te beginnen",
|
||||||
"serverUrl": "Server-URL",
|
"serverUrl": "Server-URL",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Voer je gebruikersnaam of e-mail in",
|
"usernameOrEmailHint": "Voer je gebruikersnaam of e-mail in",
|
||||||
"passwordHint": "Voer je wachtwoord in",
|
"passwordHint": "Voer je wachtwoord in",
|
||||||
"enterApiKey": "Voer je API-sleutel in",
|
"enterApiKey": "Voer je API-sleutel in",
|
||||||
|
"enterToken": "Voer je JWT-token in",
|
||||||
|
"tokenHint": "Haal het JWT-token uit de OpenWebUI-instellingen. API-sleutels (sk-...) worden niet ondersteund voor streaming.",
|
||||||
|
"apiKeyNotSupported": "API-sleutels (sk-...) worden niet ondersteund. Gebruik in plaats daarvan een JWT-token.",
|
||||||
|
"apiKeyNoLongerSupported": "Je bent uitgelogd omdat API-sleutels niet meer worden ondersteund. Log in met een JWT-token uit de OpenWebUI-instellingen.",
|
||||||
|
"tokenTooShort": "Token is te kort",
|
||||||
"signingIn": "Inloggen...",
|
"signingIn": "Inloggen...",
|
||||||
"advancedSettings": "Geavanceerde instellingen",
|
"advancedSettings": "Geavanceerde instellingen",
|
||||||
"customHeaders": "Aangepaste headers",
|
"customHeaders": "Aangepaste headers",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "Введите свои учетные данные для доступа к вашим разговорам с ИИ",
|
"enterCredentials": "Введите свои учетные данные для доступа к вашим разговорам с ИИ",
|
||||||
"credentials": "Учетные данные",
|
"credentials": "Учетные данные",
|
||||||
"apiKey": "API-ключ",
|
"apiKey": "API-ключ",
|
||||||
|
"token": "Токен",
|
||||||
"usernameOrEmail": "Имя пользователя или email",
|
"usernameOrEmail": "Имя пользователя или email",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
"signInWithApiKey": "Войти с помощью API-ключа",
|
"signInWithApiKey": "Войти с помощью API-ключа",
|
||||||
|
"signInWithToken": "Войти с помощью токена",
|
||||||
"connectToServer": "Подключиться к серверу",
|
"connectToServer": "Подключиться к серверу",
|
||||||
"enterServerAddress": "Введите адрес вашего сервера Open-WebUI для начала",
|
"enterServerAddress": "Введите адрес вашего сервера Open-WebUI для начала",
|
||||||
"serverUrl": "URL сервера",
|
"serverUrl": "URL сервера",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "Введите ваше имя пользователя или email",
|
"usernameOrEmailHint": "Введите ваше имя пользователя или email",
|
||||||
"passwordHint": "Введите ваш пароль",
|
"passwordHint": "Введите ваш пароль",
|
||||||
"enterApiKey": "Введите ваш API-ключ",
|
"enterApiKey": "Введите ваш API-ключ",
|
||||||
|
"enterToken": "Введите ваш JWT-токен",
|
||||||
|
"tokenHint": "Получите JWT-токен в настройках OpenWebUI. API-ключи (sk-...) не поддерживаются для потоковой передачи.",
|
||||||
|
"apiKeyNotSupported": "API-ключи (sk-...) не поддерживаются. Пожалуйста, используйте JWT-токен.",
|
||||||
|
"apiKeyNoLongerSupported": "Вы были выведены из системы, так как API-ключи больше не поддерживаются. Пожалуйста, войдите с JWT-токеном из настроек OpenWebUI.",
|
||||||
|
"tokenTooShort": "Токен слишком короткий",
|
||||||
"signingIn": "Вход...",
|
"signingIn": "Вход...",
|
||||||
"advancedSettings": "Расширенные настройки",
|
"advancedSettings": "Расширенные настройки",
|
||||||
"customHeaders": "Пользовательские заголовки",
|
"customHeaders": "Пользовательские заголовки",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "输入您的凭据以访问您的 AI 对话",
|
"enterCredentials": "输入您的凭据以访问您的 AI 对话",
|
||||||
"credentials": "凭据",
|
"credentials": "凭据",
|
||||||
"apiKey": "API 密钥",
|
"apiKey": "API 密钥",
|
||||||
|
"token": "令牌",
|
||||||
"usernameOrEmail": "用户名或电子邮件",
|
"usernameOrEmail": "用户名或电子邮件",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"signInWithApiKey": "使用 API 密钥登录",
|
"signInWithApiKey": "使用 API 密钥登录",
|
||||||
|
"signInWithToken": "使用令牌登录",
|
||||||
"connectToServer": "连接到服务器",
|
"connectToServer": "连接到服务器",
|
||||||
"enterServerAddress": "输入您的 Open-WebUI 服务器地址以开始",
|
"enterServerAddress": "输入您的 Open-WebUI 服务器地址以开始",
|
||||||
"serverUrl": "服务器 URL",
|
"serverUrl": "服务器 URL",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "输入您的用户名或电子邮件",
|
"usernameOrEmailHint": "输入您的用户名或电子邮件",
|
||||||
"passwordHint": "输入您的密码",
|
"passwordHint": "输入您的密码",
|
||||||
"enterApiKey": "输入您的 API 密钥",
|
"enterApiKey": "输入您的 API 密钥",
|
||||||
|
"enterToken": "输入您的 JWT 令牌",
|
||||||
|
"tokenHint": "从 OpenWebUI 设置获取 JWT 令牌。API 密钥 (sk-...) 不支持流式传输。",
|
||||||
|
"apiKeyNotSupported": "不支持 API 密钥 (sk-...)。请改用 JWT 令牌。",
|
||||||
|
"apiKeyNoLongerSupported": "由于不再支持 API 密钥,您已被登出。请使用 OpenWebUI 设置中的 JWT 令牌登录。",
|
||||||
|
"tokenTooShort": "令牌太短",
|
||||||
"signingIn": "正在登录...",
|
"signingIn": "正在登录...",
|
||||||
"advancedSettings": "高级设置",
|
"advancedSettings": "高级设置",
|
||||||
"customHeaders": "自定义标头",
|
"customHeaders": "自定义标头",
|
||||||
|
|||||||
@@ -46,9 +46,11 @@
|
|||||||
"enterCredentials": "輸入您的憑據以訪問您的 AI 對話",
|
"enterCredentials": "輸入您的憑據以訪問您的 AI 對話",
|
||||||
"credentials": "憑據",
|
"credentials": "憑據",
|
||||||
"apiKey": "API 密鑰",
|
"apiKey": "API 密鑰",
|
||||||
|
"token": "令牌",
|
||||||
"usernameOrEmail": "用戶名或電子郵件",
|
"usernameOrEmail": "用戶名或電子郵件",
|
||||||
"password": "密碼",
|
"password": "密碼",
|
||||||
"signInWithApiKey": "使用 API 密鑰登錄",
|
"signInWithApiKey": "使用 API 密鑰登錄",
|
||||||
|
"signInWithToken": "使用令牌登錄",
|
||||||
"connectToServer": "連接到服務器",
|
"connectToServer": "連接到服務器",
|
||||||
"enterServerAddress": "輸入您的 Open-WebUI 服務器地址以開始",
|
"enterServerAddress": "輸入您的 Open-WebUI 服務器地址以開始",
|
||||||
"serverUrl": "服務器 URL",
|
"serverUrl": "服務器 URL",
|
||||||
@@ -225,6 +227,11 @@
|
|||||||
"usernameOrEmailHint": "輸入您的用戶名或電子郵件",
|
"usernameOrEmailHint": "輸入您的用戶名或電子郵件",
|
||||||
"passwordHint": "輸入您的密碼",
|
"passwordHint": "輸入您的密碼",
|
||||||
"enterApiKey": "輸入您的 API 密鑰",
|
"enterApiKey": "輸入您的 API 密鑰",
|
||||||
|
"enterToken": "輸入您的 JWT 令牌",
|
||||||
|
"tokenHint": "從 OpenWebUI 設定取得 JWT 令牌。API 密鑰 (sk-...) 不支援串流。",
|
||||||
|
"apiKeyNotSupported": "不支援 API 密鑰 (sk-...)。請改用 JWT 令牌。",
|
||||||
|
"apiKeyNoLongerSupported": "由於不再支援 API 密鑰,您已被登出。請使用 OpenWebUI 設定中的 JWT 令牌登入。",
|
||||||
|
"tokenTooShort": "令牌太短",
|
||||||
"signingIn": "正在登錄...",
|
"signingIn": "正在登錄...",
|
||||||
"advancedSettings": "高級設置",
|
"advancedSettings": "高級設置",
|
||||||
"customHeaders": "自定義標頭",
|
"customHeaders": "自定義標頭",
|
||||||
|
|||||||
Reference in New Issue
Block a user