fix(auth): clarify LAN auth and mobile guidance (#2035)

* fix(auth): clarify LAN auth and mobile guidance

* chore: retrigger PR checks

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
Leonid
2026-07-11 15:23:34 +03:00
committed by GitHub
co-authored by bashrusakh
parent d5745aaac9
commit 0d4118e87a
12 changed files with 35 additions and 10 deletions
+1 -1
View File
@@ -2330,7 +2330,7 @@ export const dict = {
'sessionAuth.error.networkTitle': 'Unable to reach server',
'sessionAuth.error.rateLimitDescriptionSingle': 'Please wait {minutes} minute before trying again.',
'sessionAuth.error.rateLimitDescriptionPlural': 'Please wait {minutes} minutes before trying again.',
'sessionAuth.error.networkDescription': 'We could not verify the UI session. Check that the service is running and try again.',
'sessionAuth.error.networkDescription': 'We could not verify the UI session. If you\'re opening OpenChamber from another device on your local network, make sure Desktop Network Access is enabled on the desktop app and use the LAN address shown in Settings.',
'sessionAuth.error.retry': 'Retry',
'sessionAuth.error.passkeySetupFailed': 'Passkey setup failed.',
'sessionAuth.error.incorrectPassword': 'Incorrect password. Try again.',
+1 -1
View File
@@ -2296,7 +2296,7 @@ export const dict: Record<I18nKey, string> = {
"sessionAuth.error.networkTitle": "No se puede alcanzar el servidor",
"sessionAuth.error.rateLimitDescriptionSingle": "Por favor, espera {minutes} minuto antes de intentarlo de nuevo.",
"sessionAuth.error.rateLimitDescriptionPlural": "Por favor, espera {minutes} minutos antes de intentarlo de nuevo.",
"sessionAuth.error.networkDescription": "No pudimos verificar la sesión de la UI. Comprueba que el servicio esté en funcionamiento y vuelve a intentarlo.",
"sessionAuth.error.networkDescription": "No pudimos verificar la sesión de la UI. Si abres OpenChamber desde otro dispositivo en tu red local, activa Desktop Network Access en la app de escritorio y usa la dirección LAN que aparece en Ajustes.",
"sessionAuth.error.retry": "Volver a intentar",
"sessionAuth.error.passkeySetupFailed": "No se pudo configurar la clave de paso.",
"sessionAuth.error.incorrectPassword": "Contraseña incorrecta. Inténtalo de nuevo.",
+1 -1
View File
@@ -2104,7 +2104,7 @@ export const dict = {
'sessionAuth.error.networkTitle': 'Impossible d\'atteindre le serveur',
'sessionAuth.error.rateLimitDescriptionSingle': 'Veuillez attendre {minutes} minute avant de réessayer.',
'sessionAuth.error.rateLimitDescriptionPlural': 'Veuillez attendre {minutes} minutes avant de réessayer.',
'sessionAuth.error.networkDescription': 'Nous n\'avons pas pu vérifier la session d\'interface utilisateur. Vérifiez que le service est en cours d\'exécution et réessayez.',
'sessionAuth.error.networkDescription': 'Nous n\'avons pas pu vérifier la session d\'interface utilisateur. Si vous ouvrez OpenChamber depuis un autre appareil sur votre réseau local, activez l\'accès au réseau du bureau dans l\'application de bureau et utilisez l\'adresse LAN indiquée dans les paramètres.',
'sessionAuth.error.retry': 'Réessayer',
'sessionAuth.error.passkeySetupFailed': 'La configuration du mot de passe a échoué.',
'sessionAuth.error.incorrectPassword': 'Mot de passe incorrect. Essayer à nouveau.',
+1 -1
View File
@@ -2329,7 +2329,7 @@ export const dict: Record<I18nKey, string> = {
'sessionAuth.error.networkTitle': 'サーバーに到達できません',
'sessionAuth.error.rateLimitDescriptionSingle': '再試行まで{minutes}分お待ちください。',
'sessionAuth.error.rateLimitDescriptionPlural': '再試行まで{minutes}分お待ちください。',
'sessionAuth.error.networkDescription': 'UIセッションを確認できませんでした。サービスが実行中であることを確認して再試行してください。',
'sessionAuth.error.networkDescription': 'UI セッションを確認できませんでした。ローカルネットワーク上の別のデバイスから OpenChamber を開く場合は、デスクトップアプリで Desktop Network Access を有効にし、設定に表示される LAN アドレスを使用してください。',
'sessionAuth.error.retry': '再試行',
'sessionAuth.error.passkeySetupFailed': 'パスキーの設定に失敗しました。',
'sessionAuth.error.incorrectPassword': 'パスワードが間違っています。もう一度お試しください。',
+1 -1
View File
@@ -2330,7 +2330,7 @@ export const dict: Record<I18nKey, string> = {
'sessionAuth.error.networkTitle': '서버에 연결할 수 없습니다',
'sessionAuth.error.rateLimitDescriptionSingle': '{minutes}분 후 다시 시도하세요.',
'sessionAuth.error.rateLimitDescriptionPlural': '{minutes}분 후 다시 시도하세요.',
'sessionAuth.error.networkDescription': 'UI 세션을 확인할 수 없습니다. 서비스가 실행 중인지 확인하고 다시 시도하세요.',
'sessionAuth.error.networkDescription': 'UI 세션을 확인할 수 없습니다. 로컬 네트워크의 다른 기기에서 OpenChamber를 열고 있다면 데스크톱 앱에서 Desktop Network Access를 활성화하고 설정에 표시되는 LAN 주소를 사용하세요.',
'sessionAuth.error.retry': '다시 시도',
'sessionAuth.error.passkeySetupFailed': '패스키 설정에 실패했습니다.',
'sessionAuth.error.incorrectPassword': '비밀번호가 올바르지 않습니다. 다시 시도하세요.',
+1 -1
View File
@@ -2488,7 +2488,7 @@ export const dict: Record<I18nKey, string> = {
'sessionAuth.actions.usePasskey': 'Użyj klucza dostępu',
'sessionAuth.error.enterPasswordForPasskey': 'Wpisz hasło, aby dodać klucz dostępu.',
'sessionAuth.error.incorrectPassword': 'Nieprawidłowe hasło. Spróbuj ponownie.',
'sessionAuth.error.networkDescription': 'Nie udało się zweryfikować sesji UI. Sprawdź, czy usługa działa, i spróbuj ponownie.',
'sessionAuth.error.networkDescription': 'Nie udało się zweryfikować sesji UI. Jeśli otwierasz OpenChamber z innego urządzenia w sieci lokalnej, włącz Desktop Network Access w aplikacji desktopowej i użyj adresu LAN widocznego w Ustawieniach.',
'sessionAuth.error.networkRetry': 'Błąd sieci. Sprawdź połączenie i spróbuj ponownie.',
'sessionAuth.error.networkTitle': 'Nie można połączyć się z serwerem',
'sessionAuth.error.passkeySetupFailed': 'Nie udało się skonfigurować klucza dostępu.',
+1 -1
View File
@@ -2296,7 +2296,7 @@ export const dict: Record<I18nKey, string> = {
"sessionAuth.error.networkTitle": "Não é possível acessar o servidor",
"sessionAuth.error.rateLimitDescriptionSingle": "Aguarde {minutes} minuto antes de tentar novamente.",
"sessionAuth.error.rateLimitDescriptionPlural": "Aguarde {minutes} minutos antes de tentar novamente.",
"sessionAuth.error.networkDescription": "Não foi possível verificar a sessão da interface. Confira se o serviço está em execução e tente novamente.",
"sessionAuth.error.networkDescription": "Não foi possível verificar a sessão da interface. Se você estiver abrindo o OpenChamber de outro dispositivo na sua rede local, ative o Desktop Network Access no app para desktop e use o endereço LAN mostrado em Configurações.",
"sessionAuth.error.retry": "Tentar novamente",
"sessionAuth.error.passkeySetupFailed": "Não foi possível configurar a chave de acesso.",
"sessionAuth.error.incorrectPassword": "Senha incorreta. Tente novamente.",
+1 -1
View File
@@ -2296,7 +2296,7 @@ export const dict: Record<I18nKey, string> = {
"sessionAuth.error.networkTitle": "Неможливо отримати доступ до сервера",
"sessionAuth.error.rateLimitDescriptionSingle": "Зачекайте {minutes} хвилину перед повторною спробою.",
"sessionAuth.error.rateLimitDescriptionPlural": "Зачекайте {minutes} хвилин, перш ніж повторити спробу.",
"sessionAuth.error.networkDescription": "Нам не вдалося перевірити сесію інтерфейсу. Перевірте, чи працює сервіс, і повторіть спробу.",
"sessionAuth.error.networkDescription": "Нам не вдалося перевірити сесію інтерфейсу. Якщо ви відкриваєте OpenChamber з іншого пристрою у локальній мережі, увімкніть Desktop Network Access у настільному застосунку та використайте LAN-адресу, показану в налаштуваннях.",
"sessionAuth.error.retry": "Повторити спробу",
"sessionAuth.error.passkeySetupFailed": "Помилка налаштування ключа доступу.",
"sessionAuth.error.incorrectPassword": "Невірний пароль. Спробуйте знову.",
+1 -1
View File
@@ -2296,7 +2296,7 @@ export const dict: Record<I18nKey, string> = {
'sessionAuth.error.networkTitle': '无法连接服务器',
'sessionAuth.error.rateLimitDescriptionSingle': '请等待 {minutes} 分钟后再试。',
'sessionAuth.error.rateLimitDescriptionPlural': '请等待 {minutes} 分钟后再试。',
'sessionAuth.error.networkDescription': '无法验证 UI 会话。请检查服务是否运行后重试。',
'sessionAuth.error.networkDescription': '无法验证 UI 会话。如果你是从本地网络中的另一台设备打开 OpenChamber,请在桌面端应用中启用 Desktop Network Access,并使用设置中显示的 LAN 地址。',
'sessionAuth.error.retry': '重试',
'sessionAuth.error.passkeySetupFailed': 'Passkey 设置失败。',
'sessionAuth.error.incorrectPassword': '密码错误,请重试。',
+1 -1
View File
@@ -2300,7 +2300,7 @@ export const dict: Record<I18nKey, string> = {
'sessionAuth.error.networkTitle': '無法連線伺服器',
'sessionAuth.error.rateLimitDescriptionSingle': '請等待 {minutes} 分鐘後再試。',
'sessionAuth.error.rateLimitDescriptionPlural': '請等待 {minutes} 分鐘後再試。',
'sessionAuth.error.networkDescription': '無法驗證 UI 會話。請檢查服務是否執行後重試。',
'sessionAuth.error.networkDescription': '無法驗證 UI 會話。如果你是從本機網路中的其他裝置開啟 OpenChamber,請在桌面應用程式中啟用 Desktop Network Access,並使用設定中顯示的 LAN 位址。',
'sessionAuth.error.retry': '重試',
'sessionAuth.error.passkeySetupFailed': 'Passkey 設定失敗。',
'sessionAuth.error.incorrectPassword': '密碼錯誤,請重試。',
@@ -1,6 +1,7 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import express from 'express';
import request from 'supertest';
import { createTunnelAuth } from './tunnel-auth.js';
import { registerAuthAndAccessRoutes, registerCommonRequestMiddleware, registerServerStatusRoutes } from './core-routes.js';
describe('core-routes', () => {
@@ -679,4 +680,24 @@ describe('client auth routes', () => {
expect(dependencies.testHooks.requireSessionAuth).toHaveBeenCalledTimes(2);
expect(dependencies.testHooks.requireAuth).not.toHaveBeenCalled();
});
it('treats private LAN hosts as local even when a tunnel is active', async () => {
const app = express();
const dependencies = createDependencies();
const tunnelAuthController = createTunnelAuth();
tunnelAuthController.setActiveTunnel({ tunnelId: 'tunnel-1', publicUrl: 'https://tunnel.example.com' });
dependencies.tunnelAuthController = tunnelAuthController;
dependencies.uiAuthController.handlePasskeyStatus = vi.fn((_req, res) => {
res.json({ enabled: true, hasPasskeys: true, passkeyCount: 1, rpID: 'example.com' });
});
registerAuthAndAccessRoutes(app, dependencies);
await request(app)
.get('/auth/passkey/status')
.set('Host', '192.168.1.5:57123')
.expect(200, { enabled: true, hasPasskeys: true, passkeyCount: 1, rpID: 'example.com' });
expect(dependencies.uiAuthController.handlePasskeyStatus).toHaveBeenCalledTimes(1);
});
});
@@ -176,6 +176,10 @@ const isLocalHost = (host, req) => {
return true;
}
if (isPrivateOrLoopbackIp(host)) {
return true;
}
if (host === 'host.docker.internal') {
return isPrivateOrLoopbackIp(getSocketRemoteIp(req));
}