@lobehub/chat 1.61.4 → 1.61.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/auth.json +10 -1
- package/locales/bg-BG/auth.json +10 -1
- package/locales/de-DE/auth.json +10 -1
- package/locales/en-US/auth.json +10 -1
- package/locales/es-ES/auth.json +10 -1
- package/locales/fa-IR/auth.json +10 -1
- package/locales/fr-FR/auth.json +10 -1
- package/locales/it-IT/auth.json +10 -1
- package/locales/ja-JP/auth.json +10 -1
- package/locales/ko-KR/auth.json +10 -1
- package/locales/nl-NL/auth.json +10 -1
- package/locales/pl-PL/auth.json +10 -1
- package/locales/pt-BR/auth.json +10 -1
- package/locales/ru-RU/auth.json +10 -1
- package/locales/tr-TR/auth.json +10 -1
- package/locales/vi-VN/auth.json +10 -1
- package/locales/zh-CN/auth.json +9 -0
- package/locales/zh-TW/auth.json +10 -1
- package/package.json +1 -1
- package/src/app/[variants]/(main)/profile/(home)/Client.tsx +9 -0
- package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/AuthIcons.tsx +37 -0
- package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx +93 -0
- package/src/database/server/models/user.ts +24 -1
- package/src/locales/default/auth.ts +10 -0
- package/src/server/routers/lambda/user.ts +32 -2
- package/src/services/user/_deprecated.ts +9 -0
- package/src/services/user/client.ts +9 -0
- package/src/services/user/server.ts +11 -0
- package/src/services/user/type.ts +3 -0
- package/src/types/user/index.ts +5 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.61.5](https://github.com/lobehub/lobe-chat/compare/v1.61.4...v1.61.5)
|
6
|
+
|
7
|
+
<sup>Released on **2025-02-19**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **misc**: Show sso providers for next-auth in profile page.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Styles
|
19
|
+
|
20
|
+
- **misc**: Show sso providers for next-auth in profile page, closes [#5303](https://github.com/lobehub/lobe-chat/issues/5303) ([dd61bce](https://github.com/lobehub/lobe-chat/commit/dd61bce))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.61.4](https://github.com/lobehub/lobe-chat/compare/v1.61.3...v1.61.4)
|
6
31
|
|
7
32
|
<sup>Released on **2025-02-18**</sup>
|
package/changelog/v1.json
CHANGED
package/locales/ar/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "الصورة الشخصية",
|
36
36
|
"email": "عنوان البريد الإلكتروني",
|
37
|
+
"sso": {
|
38
|
+
"loading": "جارٍ تحميل الحسابات المرتبطة من طرف ثالث",
|
39
|
+
"providers": "الحسابات المتصلة",
|
40
|
+
"unlink": {
|
41
|
+
"description": "بعد unlink ، لن تتمكن من تسجيل الدخول باستخدام حساب {{provider}} \"{{providerAccountId}}\". إذا كنت بحاجة إلى إعادة ربط حساب {{provider}} بالحساب الحالي، يرجى التأكد من أن عنوان البريد الإلكتروني لحساب {{provider}} هو {{email}}، وسنقوم بربطه تلقائيًا بالحساب المسجل الدخول عند تسجيل الدخول.",
|
42
|
+
"forbidden": "يجب أن تحتفظ بحساب طرف ثالث واحد على الأقل مرتبطًا.",
|
43
|
+
"title": "هل تريد فصل حساب الطرف الثالث {{provider}}؟"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "اسم المستخدم"
|
38
47
|
},
|
39
48
|
"signout": "تسجيل الخروج",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "الأمان",
|
85
94
|
"stats": "الإحصائيات"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/bg-BG/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Аватар",
|
36
36
|
"email": "Имейл адрес",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Зареждане на свързаните трети страни акаунти",
|
39
|
+
"providers": "Свързани акаунти",
|
40
|
+
"unlink": {
|
41
|
+
"description": "След като свържете, няма да можете да използвате акаунта на {{provider}} „{{providerAccountId}}“ за вход. Ако искате отново да свържете акаунта на {{provider}} с текущия акаунт, уверете се, че имейл адресът на акаунта на {{provider}} е {{email}}, а ние автоматично ще ви свържем с текущия влезлия акаунт при вход.",
|
42
|
+
"forbidden": "Трябва да имате поне един свързан акаунт на трета страна.",
|
43
|
+
"title": "Наистина ли искате да свържете акаунта на трета страна {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Потребителско име"
|
38
47
|
},
|
39
48
|
"signout": "Изход",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Сигурност",
|
85
94
|
"stats": "Статистика"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/de-DE/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "E-Mail-Adresse",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Laden der verknüpften Drittanbieter-Konten",
|
39
|
+
"providers": "Verbundene Konten",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Wenn Sie die Verknüpfung aufheben, können Sie sich nicht mehr mit dem {{provider}}-Konto „{{providerAccountId}}“ anmelden. Wenn Sie das {{provider}}-Konto erneut mit dem aktuellen Konto verknüpfen möchten, stellen Sie bitte sicher, dass die E-Mail-Adresse des {{provider}}-Kontos {{email}} ist, und wir werden es Ihnen automatisch bei der Anmeldung mit dem aktuellen Konto zuordnen.",
|
42
|
+
"forbidden": "Sie müssen mindestens ein Drittanbieter-Konto verbunden behalten.",
|
43
|
+
"title": "Möchten Sie das Drittanbieter-Konto {{provider}} wirklich trennen?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Benutzername"
|
38
47
|
},
|
39
48
|
"signout": "Ausloggen",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Sicherheit",
|
85
94
|
"stats": "Statistiken"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/en-US/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "Email Address",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Loading linked third-party accounts",
|
39
|
+
"providers": "Connected Accounts",
|
40
|
+
"unlink": {
|
41
|
+
"description": "After unlinking, you will not be able to log in using the {{provider}} account \"{{providerAccountId}}\". If you need to re-link your {{provider}} account to the current account, please ensure that the email address for your {{provider}} account is {{email}}. We will automatically link it to the current logged-in account upon login.",
|
42
|
+
"forbidden": "You must retain at least one linked third-party account.",
|
43
|
+
"title": "Are you sure you want to unlink the third-party account {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Username"
|
38
47
|
},
|
39
48
|
"signout": "Log Out",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Security",
|
85
94
|
"stats": "Statistics"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/es-ES/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "Dirección de correo electrónico",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Cargando cuentas de terceros vinculadas",
|
39
|
+
"providers": "Cuentas conectadas",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Al desvincular, no podrá iniciar sesión con la cuenta de {{provider}} \"{{providerAccountId}}\". Si necesita volver a vincular la cuenta de {{provider}} a la cuenta actual, asegúrese de que la dirección de correo electrónico de la cuenta {{provider}} sea {{email}} y la vincularemos automáticamente a la cuenta actual al iniciar sesión.",
|
42
|
+
"forbidden": "Debe conservar al menos una cuenta de terceros vinculada.",
|
43
|
+
"title": "¿Desea desvincular la cuenta de terceros {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Nombre de usuario"
|
38
47
|
},
|
39
48
|
"signout": "Cerrar sesión",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Seguridad",
|
85
94
|
"stats": "Estadísticas"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/fa-IR/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "آواتار",
|
36
36
|
"email": "آدرس ایمیل",
|
37
|
+
"sso": {
|
38
|
+
"loading": "در حال بارگذاری حسابهای شخص ثالث متصل شده",
|
39
|
+
"providers": "حسابهای متصل",
|
40
|
+
"unlink": {
|
41
|
+
"description": "با لغو اتصال، شما نمیتوانید با حساب {{provider}} «{{providerAccountId}}» وارد شوید. اگر نیاز دارید حساب {{provider}} را دوباره به حساب جاری متصل کنید، لطفا اطمینان حاصل کنید که آدرس ایمیل حساب {{provider}} {{email}} است، ما در هنگام ورود به طور خودکار آن را به حساب جاری متصل خواهیم کرد.",
|
42
|
+
"forbidden": "شما حداقل باید یک حساب شخص ثالث متصل را حفظ کنید.",
|
43
|
+
"title": "آیا میخواهید این حساب شخص ثالث {{provider}} را لغو اتصال کنید؟"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "نام کاربری"
|
38
47
|
},
|
39
48
|
"signout": "خروج",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "امنیت",
|
85
94
|
"stats": "آمار"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/fr-FR/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "Adresse e-mail",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Chargement des comptes tiers liés",
|
39
|
+
"providers": "Comptes connectés",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Après la déconnexion, vous ne pourrez plus vous connecter avec le compte {{provider}} « {{providerAccountId}} ». Si vous avez besoin de rattacher le compte {{provider}} à votre compte actuel, assurez-vous que l'adresse e-mail du compte {{provider}} soit {{email}}. Nous procéderons automatiquement au rattachement lors de votre prochaine connexion.",
|
42
|
+
"forbidden": "Vous devez conserver au moins un compte tiers lié.",
|
43
|
+
"title": "Voulez-vous vraiment déconnecter ce compte tiers {{provider}} ?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Nom d'utilisateur"
|
38
47
|
},
|
39
48
|
"signout": "Se déconnecter",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Sécurité",
|
85
94
|
"stats": "Statistiques"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/it-IT/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "Indirizzo Email",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Caricamento degli account di terze parti collegati",
|
39
|
+
"providers": "Account collegati",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Dopo la disconnessione, non potrai più accedere con l'account {{provider}} “{{providerAccountId}}”. Se desideri ricollegare l'account {{provider}} a questo account, assicurati che l'indirizzo email dell'account {{provider}} sia {{email}}, e lo collegheremo automaticamente all'account attualmente in uso al momento dell'accesso.",
|
42
|
+
"forbidden": "Devi mantenere almeno un account di terze parti collegato.",
|
43
|
+
"title": "Vuoi disconnettere questo account di terze parti {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Nome Utente"
|
38
47
|
},
|
39
48
|
"signout": "Disconnetti",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Sicurezza",
|
85
94
|
"stats": "Statistiche"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/ja-JP/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "アバター",
|
36
36
|
"email": "メールアドレス",
|
37
|
+
"sso": {
|
38
|
+
"loading": "リンクされたサードパーティアカウントを読み込み中",
|
39
|
+
"providers": "接続されたアカウント",
|
40
|
+
"unlink": {
|
41
|
+
"description": "解除すると、{{provider}} アカウント「{{providerAccountId}}」を使用してログインできなくなります。現在のアカウントに{{provider}} アカウントを再度リンクする必要がある場合は、{{provider}} アカウントのメールアドレスが {{email}} であることを確認してください。ログイン時に自動的に現在のログインアカウントにリンクされます。",
|
42
|
+
"forbidden": "少なくとも1つのサードパーティアカウントをリンクしておく必要があります。",
|
43
|
+
"title": "サードパーティアカウント {{provider}} を解除しますか?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "ユーザー名"
|
38
47
|
},
|
39
48
|
"signout": "ログアウト",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "セキュリティ",
|
85
94
|
"stats": "統計"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/ko-KR/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "아바타",
|
36
36
|
"email": "이메일 주소",
|
37
|
+
"sso": {
|
38
|
+
"loading": "연결된 제3자 계정을 로드 중입니다",
|
39
|
+
"providers": "연결된 계정",
|
40
|
+
"unlink": {
|
41
|
+
"description": "연결을 해제하면 {{provider}} 계정“{{providerAccountId}}”으로 로그인할 수 없습니다. 현재 계정에 {{provider}} 계정을 다시 연결해야 하는 경우, {{provider}} 계정의 이메일 주소가 {{email}}인지 확인하십시오. 로그인 시 자동으로 현재 로그인 계정에 연결됩니다.",
|
42
|
+
"forbidden": "최소한 하나의 제3자 계정 연결을 유지해야 합니다.",
|
43
|
+
"title": "이 제3자 계정 {{provider}} 를 연결 해제하시겠습니까?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "사용자 이름"
|
38
47
|
},
|
39
48
|
"signout": "로그아웃",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "보안",
|
85
94
|
"stats": "통계"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/nl-NL/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "E-mailadres",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Bezig met laden van gekoppelde externe accounts",
|
39
|
+
"providers": "Verbindingse accounts",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Als u ontkoppelt, kunt u niet meer inloggen met het {{provider}} account “{{providerAccountId}}”. Als u het {{provider}} account opnieuw aan deze account wilt koppelen, zorg er dan voor dat het e-mailadres van het {{provider}} account {{email}} is, dan zullen we het automatisch koppelen aan de huidige ingelogde account.",
|
42
|
+
"forbidden": "U moet minstens één extern account gekoppeld houden.",
|
43
|
+
"title": "Wilt u dit externe account {{provider}} ontkoppelen?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Gebruikersnaam"
|
38
47
|
},
|
39
48
|
"signout": "Uitloggen",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Beveiliging",
|
85
94
|
"stats": "Statistieken"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/pl-PL/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Awatar",
|
36
36
|
"email": "Adres e-mail",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Ładowanie powiązanych kont zewnętrznych",
|
39
|
+
"providers": "Podłączone konta",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Po odłączeniu nie będziesz mógł korzystać z konta {{provider}} „{{providerAccountId}}” do logowania. Jeśli potrzebujesz ponownie powiązać konto {{provider}} z bieżącym kontem, upewnij się, że adres e-mail konta {{provider}} to {{email}}, a my automatycznie je powiążemy podczas logowania.",
|
42
|
+
"forbidden": "Musisz zachować co najmniej jedno powiązane konto zewnętrzne.",
|
43
|
+
"title": "Czy odłączyć to konto zewnętrzne {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Nazwa użytkownika"
|
38
47
|
},
|
39
48
|
"signout": "Wyloguj się",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Bezpieczeństwo",
|
85
94
|
"stats": "Statystyki"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/pt-BR/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "Endereço de E-mail",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Carregando contas de terceiros vinculadas",
|
39
|
+
"providers": "Contas conectadas",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Após desvincular, você não poderá usar a conta {{provider}} “{{providerAccountId}}” para fazer login. Se precisar re-vincular a conta {{provider}} à conta atual, certifique-se de que o endereço de e-mail da conta {{provider}} seja {{email}}; nós a vincularemos automaticamente à conta de login atual.",
|
42
|
+
"forbidden": "Você deve manter pelo menos uma conta de terceiros vinculada.",
|
43
|
+
"title": "Deseja desvincular a conta de terceiros {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Nome de Usuário"
|
38
47
|
},
|
39
48
|
"signout": "Sair",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Segurança",
|
85
94
|
"stats": "Estatísticas"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/ru-RU/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Аватар",
|
36
36
|
"email": "Электронная почта",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Загрузка связанных сторонних аккаунтов",
|
39
|
+
"providers": "Подключенные аккаунты",
|
40
|
+
"unlink": {
|
41
|
+
"description": "После отключения вы не сможете использовать аккаунт {{provider}} \"{{providerAccountId}}\" для входа. Если вам нужно повторно связать аккаунт {{provider}} с текущим аккаунтом, убедитесь, что адрес электронной почты вашего аккаунта {{provider}} - {{email}}. Мы автоматически свяжем его с текущим вошедшим в систему аккаунтом при входе.",
|
42
|
+
"forbidden": "Вы должны оставить хотя бы одну привязку стороннего аккаунта.",
|
43
|
+
"title": "Вы уверены, что хотите отменить связь с сторонним аккаунтом {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Имя пользователя"
|
38
47
|
},
|
39
48
|
"signout": "Выйти",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Безопасность",
|
85
94
|
"stats": "Статистика"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/tr-TR/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Avatar",
|
36
36
|
"email": "E-posta Adresi",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Bağlı üçüncü taraf hesapları yükleniyor",
|
39
|
+
"providers": "Bağlı Hesaplar",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Bu hesap ile {{provider}} hesap “{{providerAccountId}}” ilişkisi kesildiğinde giriş yapamayacaksınız. Eğer {{provider}} hesabınızı mevcut hesaba yeniden bağlamak isterseniz, lütfen {{provider}} hesabının e-posta adresinin {{email}} olduğundan emin olun, giriş yaptığınızda otomatik olarak mevcut hesaba bağlanacaktır.",
|
42
|
+
"forbidden": "En az bir üçüncü taraf hesap bağlamaya devam etmelisiniz.",
|
43
|
+
"title": "{{provider}} adlı üçüncü taraf hesabını kaldırmak istiyor musunuz?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Kullanıcı Adı"
|
38
47
|
},
|
39
48
|
"signout": "Çıkış Yap",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Güvenlik",
|
85
94
|
"stats": "İstatistikler"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/vi-VN/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "Ảnh đại diện",
|
36
36
|
"email": "Địa chỉ email",
|
37
|
+
"sso": {
|
38
|
+
"loading": "Đang tải tài khoản bên thứ ba đã liên kết",
|
39
|
+
"providers": "Tài khoản liên kết",
|
40
|
+
"unlink": {
|
41
|
+
"description": "Sau khi hủy liên kết, bạn sẽ không thể sử dụng tài khoản {{provider}} “{{providerAccountId}}” để đăng nhập. Nếu bạn cần liên kết lại tài khoản {{provider}} với tài khoản hiện tại, hãy đảm bảo rằng địa chỉ email của tài khoản {{provider}} là {{email}} , chúng tôi sẽ tự động liên kết nó với tài khoản đăng nhập hiện tại của bạn khi bạn đăng nhập.",
|
42
|
+
"forbidden": "Bạn cần phải giữ lại ít nhất một tài khoản bên thứ ba được liên kết.",
|
43
|
+
"title": "Có chắc chắn muốn hủy liên kết tài khoản bên thứ ba {{provider}}?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "Tên người dùng"
|
38
47
|
},
|
39
48
|
"signout": "Đăng xuất",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "Bảo mật",
|
85
94
|
"stats": "Thống kê"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/locales/zh-CN/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "头像",
|
36
36
|
"email": "电子邮件地址",
|
37
|
+
"sso": {
|
38
|
+
"loading": "正在加载已绑定的第三方账户",
|
39
|
+
"providers": "连接的帐户",
|
40
|
+
"unlink": {
|
41
|
+
"description": "解绑后,您将无法使用 {{provider}} 账户“{{providerAccountId}}”登录。如果您需要重新绑定 {{provider}} 账户到当前账户,请确保 {{provider}} 账户的邮件地址为 {{email}} ,我们会在登陆时为你自动绑定到当前登录账户。",
|
42
|
+
"forbidden": "您至少需要保留一个第三方账户绑定。",
|
43
|
+
"title": "是否解绑该第三方账户 {{provider}} ?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "用户名"
|
38
47
|
},
|
39
48
|
"signout": "退出登录",
|
package/locales/zh-TW/auth.json
CHANGED
@@ -34,6 +34,15 @@
|
|
34
34
|
"profile": {
|
35
35
|
"avatar": "頭像",
|
36
36
|
"email": "電子郵件地址",
|
37
|
+
"sso": {
|
38
|
+
"loading": "正在載入已綁定的第三方帳戶",
|
39
|
+
"providers": "連結的帳戶",
|
40
|
+
"unlink": {
|
41
|
+
"description": "解除綁定後,您將無法使用 {{provider}} 帳戶「{{providerAccountId}}」登入。如果您需要重新綁定 {{provider}} 帳戶到當前帳戶,請確保 {{provider}} 帳戶的電子郵件地址為 {{email}},我們會在登入時為您自動綁定到當前登入帳戶。",
|
42
|
+
"forbidden": "您至少需要保留一個第三方帳戶綁定。",
|
43
|
+
"title": "是否解除綁定該第三方帳戶 {{provider}} ?"
|
44
|
+
}
|
45
|
+
},
|
37
46
|
"username": "用戶名"
|
38
47
|
},
|
39
48
|
"signout": "登出",
|
@@ -84,4 +93,4 @@
|
|
84
93
|
"security": "安全",
|
85
94
|
"stats": "數據統計"
|
86
95
|
}
|
87
|
-
}
|
96
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.61.
|
3
|
+
"version": "1.61.5",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -11,6 +11,8 @@ import UserAvatar from '@/features/User/UserAvatar';
|
|
11
11
|
import { useUserStore } from '@/store/user';
|
12
12
|
import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
|
13
13
|
|
14
|
+
import SSOProvidersList from './features/SSOProvidersList';
|
15
|
+
|
14
16
|
type SettingItemGroup = ItemGroup;
|
15
17
|
|
16
18
|
const Client = memo<{ mobile?: boolean }>(() => {
|
@@ -42,6 +44,13 @@ const Client = memo<{ mobile?: boolean }>(() => {
|
|
42
44
|
label: t('profile.email'),
|
43
45
|
minWidth: undefined,
|
44
46
|
},
|
47
|
+
{
|
48
|
+
children: <SSOProvidersList />,
|
49
|
+
hidden: !isLoginWithNextAuth,
|
50
|
+
label: t('profile.sso.providers'),
|
51
|
+
layout: 'vertical',
|
52
|
+
minWidth: undefined,
|
53
|
+
},
|
45
54
|
],
|
46
55
|
title: t('tab.profile'),
|
47
56
|
};
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import {
|
2
|
+
Auth0,
|
3
|
+
Authelia,
|
4
|
+
Authentik,
|
5
|
+
Casdoor,
|
6
|
+
Cloudflare,
|
7
|
+
Github,
|
8
|
+
Logto,
|
9
|
+
MicrosoftEntra,
|
10
|
+
NextAuth,
|
11
|
+
Zitadel,
|
12
|
+
} from '@lobehub/ui/icons';
|
13
|
+
import React from 'react';
|
14
|
+
|
15
|
+
const iconProps = {
|
16
|
+
size: 32,
|
17
|
+
};
|
18
|
+
|
19
|
+
const iconComponents: { [key: string]: React.ElementType } = {
|
20
|
+
'auth0': Auth0,
|
21
|
+
'authelia': Authelia.Color,
|
22
|
+
'authentik': Authentik.Color,
|
23
|
+
'casdoor': Casdoor.Color,
|
24
|
+
'cloudflare': Cloudflare.Color,
|
25
|
+
'default': NextAuth.Color,
|
26
|
+
'github': Github,
|
27
|
+
'logto': Logto.Color,
|
28
|
+
'microsoft-entra-id': MicrosoftEntra.Color,
|
29
|
+
'zitadel': Zitadel.Color,
|
30
|
+
};
|
31
|
+
|
32
|
+
const AuthIcons = (id: string) => {
|
33
|
+
const IconComponent = iconComponents[id] || iconComponents.default;
|
34
|
+
return <IconComponent {...iconProps} />;
|
35
|
+
};
|
36
|
+
|
37
|
+
export default AuthIcons;
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import { ActionIcon, CopyButton, List } from '@lobehub/ui';
|
2
|
+
import { RotateCw, Unlink } from 'lucide-react';
|
3
|
+
import { CSSProperties, memo, useState } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
6
|
+
|
7
|
+
import { modal, notification } from '@/components/AntdStaticMethods';
|
8
|
+
import { useOnlyFetchOnceSWR } from '@/libs/swr';
|
9
|
+
import { userService } from '@/services/user';
|
10
|
+
import { useUserStore } from '@/store/user';
|
11
|
+
import { userProfileSelectors } from '@/store/user/selectors';
|
12
|
+
|
13
|
+
import AuthIcons from './AuthIcons';
|
14
|
+
|
15
|
+
const { Item } = List;
|
16
|
+
|
17
|
+
const providerNameStyle: CSSProperties = {
|
18
|
+
textTransform: 'capitalize',
|
19
|
+
};
|
20
|
+
|
21
|
+
export const SSOProvidersList = memo(() => {
|
22
|
+
const [userProfile] = useUserStore((s) => [userProfileSelectors.userProfile(s)]);
|
23
|
+
const { t } = useTranslation('auth');
|
24
|
+
|
25
|
+
const [allowUnlink, setAllowUnlink] = useState<boolean>(false);
|
26
|
+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
27
|
+
|
28
|
+
const { data, isLoading, mutate } = useOnlyFetchOnceSWR('profile-sso-providers', async () => {
|
29
|
+
const list = await userService.getUserSSOProviders();
|
30
|
+
setAllowUnlink(list?.length > 1);
|
31
|
+
return list;
|
32
|
+
});
|
33
|
+
|
34
|
+
const handleUnlinkSSO = async (provider: string, providerAccountId: string) => {
|
35
|
+
if (data?.length === 1 || !data) {
|
36
|
+
// At least one SSO provider should be linked
|
37
|
+
notification.error({
|
38
|
+
message: t('profile.sso.unlink.forbidden'),
|
39
|
+
});
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
modal.confirm({
|
43
|
+
content: t('profile.sso.unlink.description', {
|
44
|
+
email: userProfile?.email || 'None',
|
45
|
+
provider,
|
46
|
+
providerAccountId,
|
47
|
+
}),
|
48
|
+
okButtonProps: {
|
49
|
+
danger: true,
|
50
|
+
},
|
51
|
+
onOk: async () => {
|
52
|
+
await userService.unlinkSSOProvider(provider, providerAccountId);
|
53
|
+
mutate();
|
54
|
+
},
|
55
|
+
title: <span style={providerNameStyle}>{t('profile.sso.unlink.title', { provider })}</span>,
|
56
|
+
});
|
57
|
+
};
|
58
|
+
|
59
|
+
return isLoading ? (
|
60
|
+
<Flexbox align={'center'} gap={4} horizontal>
|
61
|
+
<ActionIcon icon={RotateCw} spin />
|
62
|
+
{t('profile.sso.loading')}
|
63
|
+
</Flexbox>
|
64
|
+
) : (
|
65
|
+
<Flexbox>
|
66
|
+
{data?.map((item, index) => (
|
67
|
+
<Item
|
68
|
+
actions={
|
69
|
+
<Flexbox gap={4} horizontal>
|
70
|
+
<CopyButton content={item.providerAccountId} size={'small'} />
|
71
|
+
<ActionIcon
|
72
|
+
disable={!allowUnlink}
|
73
|
+
icon={Unlink}
|
74
|
+
onClick={() => handleUnlinkSSO(item.provider, item.providerAccountId)}
|
75
|
+
size={'small'}
|
76
|
+
/>
|
77
|
+
</Flexbox>
|
78
|
+
}
|
79
|
+
avatar={AuthIcons(item.provider)}
|
80
|
+
date={item.expires_at}
|
81
|
+
description={item.providerAccountId}
|
82
|
+
key={index}
|
83
|
+
onMouseEnter={() => setHoveredIndex(index)}
|
84
|
+
onMouseLeave={() => setHoveredIndex(null)}
|
85
|
+
showAction={hoveredIndex === index}
|
86
|
+
title={<span style={providerNameStyle}>{item.provider}</span>}
|
87
|
+
/>
|
88
|
+
))}
|
89
|
+
</Flexbox>
|
90
|
+
);
|
91
|
+
});
|
92
|
+
|
93
|
+
export default SSOProvidersList;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
import dayjs from 'dayjs';
|
3
3
|
import { eq } from 'drizzle-orm/expressions';
|
4
|
+
import type { AdapterAccount } from 'next-auth/adapters';
|
4
5
|
import { DeepPartial } from 'utility-types';
|
5
6
|
|
6
7
|
import { LobeChatDatabase } from '@/database/type';
|
@@ -9,7 +10,14 @@ import { UserKeyVaults, UserSettings } from '@/types/user/settings';
|
|
9
10
|
import { merge } from '@/utils/merge';
|
10
11
|
import { today } from '@/utils/time';
|
11
12
|
|
12
|
-
import {
|
13
|
+
import {
|
14
|
+
NewUser,
|
15
|
+
UserItem,
|
16
|
+
UserSettingsItem,
|
17
|
+
nextauthAccounts,
|
18
|
+
userSettings,
|
19
|
+
users,
|
20
|
+
} from '../../schemas';
|
13
21
|
|
14
22
|
type DecryptUserKeyVaults = (
|
15
23
|
encryptKeyVaultsStr: string | null,
|
@@ -96,6 +104,21 @@ export class UserModel {
|
|
96
104
|
};
|
97
105
|
};
|
98
106
|
|
107
|
+
getUserSSOProviders = async () => {
|
108
|
+
const result = await this.db
|
109
|
+
.select({
|
110
|
+
expiresAt: nextauthAccounts.expires_at,
|
111
|
+
provider: nextauthAccounts.provider,
|
112
|
+
providerAccountId: nextauthAccounts.providerAccountId,
|
113
|
+
scope: nextauthAccounts.scope,
|
114
|
+
type: nextauthAccounts.type,
|
115
|
+
userId: nextauthAccounts.userId,
|
116
|
+
})
|
117
|
+
.from(nextauthAccounts)
|
118
|
+
.where(eq(nextauthAccounts.userId, this.userId));
|
119
|
+
return result as unknown as AdapterAccount[];
|
120
|
+
};
|
121
|
+
|
99
122
|
getUserSettings = async () => {
|
100
123
|
return this.db.query.userSettings.findFirst({ where: eq(userSettings.id, this.userId) });
|
101
124
|
};
|
@@ -34,6 +34,16 @@ export default {
|
|
34
34
|
profile: {
|
35
35
|
avatar: '头像',
|
36
36
|
email: '电子邮件地址',
|
37
|
+
sso: {
|
38
|
+
loading: '正在加载已绑定的第三方账户',
|
39
|
+
providers: '连接的帐户',
|
40
|
+
unlink: {
|
41
|
+
description:
|
42
|
+
'解绑后,您将无法使用 {{provider}} 账户“{{providerAccountId}}”登录。如果您需要重新绑定 {{provider}} 账户到当前账户,请确保 {{provider}} 账户的邮件地址为 {{email}} ,我们会在登陆时为你自动绑定到当前登录账户。',
|
43
|
+
forbidden: '您至少需要保留一个第三方账户绑定。',
|
44
|
+
title: '是否解绑该第三方账户 {{provider}} ?',
|
45
|
+
},
|
46
|
+
},
|
37
47
|
username: '用户名',
|
38
48
|
},
|
39
49
|
signout: '退出登录',
|
@@ -7,15 +7,24 @@ import { serverDB } from '@/database/server';
|
|
7
7
|
import { MessageModel } from '@/database/server/models/message';
|
8
8
|
import { SessionModel } from '@/database/server/models/session';
|
9
9
|
import { UserModel, UserNotFoundError } from '@/database/server/models/user';
|
10
|
+
import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
|
10
11
|
import { authedProcedure, router } from '@/libs/trpc';
|
11
12
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
12
13
|
import { UserService } from '@/server/services/user';
|
13
|
-
import {
|
14
|
+
import {
|
15
|
+
NextAuthAccountSchame,
|
16
|
+
UserGuideSchema,
|
17
|
+
UserInitializationState,
|
18
|
+
UserPreference,
|
19
|
+
} from '@/types/user';
|
14
20
|
import { UserSettings } from '@/types/user/settings';
|
15
21
|
|
16
22
|
const userProcedure = authedProcedure.use(async (opts) => {
|
17
23
|
return opts.next({
|
18
|
-
ctx: {
|
24
|
+
ctx: {
|
25
|
+
nextAuthDbAdapter: LobeNextAuthDbAdapter(serverDB),
|
26
|
+
userModel: new UserModel(serverDB, opts.ctx.userId),
|
27
|
+
},
|
19
28
|
});
|
20
29
|
});
|
21
30
|
|
@@ -24,6 +33,10 @@ export const userRouter = router({
|
|
24
33
|
return ctx.userModel.getUserRegistrationDuration();
|
25
34
|
}),
|
26
35
|
|
36
|
+
getUserSSOProviders: userProcedure.query(async ({ ctx }) => {
|
37
|
+
return ctx.userModel.getUserSSOProviders();
|
38
|
+
}),
|
39
|
+
|
27
40
|
getUserState: userProcedure.query(async ({ ctx }): Promise<UserInitializationState> => {
|
28
41
|
let state: Awaited<ReturnType<UserModel['getUserState']>> | undefined;
|
29
42
|
|
@@ -92,6 +105,23 @@ export const userRouter = router({
|
|
92
105
|
return ctx.userModel.deleteSetting();
|
93
106
|
}),
|
94
107
|
|
108
|
+
unlinkSSOProvider: userProcedure.input(NextAuthAccountSchame).mutation(async ({ ctx, input }) => {
|
109
|
+
const { provider, providerAccountId } = input;
|
110
|
+
if (
|
111
|
+
ctx.nextAuthDbAdapter?.unlinkAccount &&
|
112
|
+
typeof ctx.nextAuthDbAdapter.unlinkAccount === 'function' &&
|
113
|
+
ctx.nextAuthDbAdapter?.getAccount &&
|
114
|
+
typeof ctx.nextAuthDbAdapter.getAccount === 'function'
|
115
|
+
) {
|
116
|
+
const account = await ctx.nextAuthDbAdapter.getAccount(providerAccountId, provider);
|
117
|
+
// The userId can either get from ctx.nextAuth?.id or ctx.userId
|
118
|
+
if (!account || account.userId !== ctx.userId) throw new Error('The account does not exist');
|
119
|
+
await ctx.nextAuthDbAdapter.unlinkAccount({ provider, providerAccountId });
|
120
|
+
} else {
|
121
|
+
throw new Error('The method in LobeNextAuthDbAdapter `unlinkAccount` is not implemented');
|
122
|
+
}
|
123
|
+
}),
|
124
|
+
|
95
125
|
updateGuide: userProcedure.input(UserGuideSchema).mutation(async ({ ctx, input }) => {
|
96
126
|
return ctx.userModel.updateGuide(input);
|
97
127
|
}),
|
@@ -37,6 +37,15 @@ export class ClientService implements IUserService {
|
|
37
37
|
};
|
38
38
|
}
|
39
39
|
|
40
|
+
getUserSSOProviders = async () => {
|
41
|
+
// Account not exist on next-auth in client mode, no need to implement this method
|
42
|
+
return [];
|
43
|
+
};
|
44
|
+
|
45
|
+
unlinkSSOProvider = async () => {
|
46
|
+
// Account not exist on next-auth in client mode, no need to implement this method
|
47
|
+
};
|
48
|
+
|
40
49
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
41
50
|
updateUserSettings = async (patch: DeepPartial<UserSettings>, _?: any) => {
|
42
51
|
return UserModel.updateSettings(patch);
|
@@ -54,6 +54,15 @@ export class ClientService extends BaseClientService implements IUserService {
|
|
54
54
|
};
|
55
55
|
};
|
56
56
|
|
57
|
+
getUserSSOProviders: IUserService['getUserSSOProviders'] = async () => {
|
58
|
+
// Account not exist on next-auth in client mode, no need to implement this method
|
59
|
+
return [];
|
60
|
+
};
|
61
|
+
|
62
|
+
unlinkSSOProvider: IUserService['unlinkSSOProvider'] = async () => {
|
63
|
+
// Account not exist on next-auth in client mode, no need to implement this method
|
64
|
+
};
|
65
|
+
|
57
66
|
updateUserSettings: IUserService['updateUserSettings'] = async (value) => {
|
58
67
|
const { keyVaults, ...res } = value;
|
59
68
|
|
@@ -10,6 +10,17 @@ export class ServerService implements IUserService {
|
|
10
10
|
return lambdaClient.user.getUserState.query();
|
11
11
|
};
|
12
12
|
|
13
|
+
getUserSSOProviders: IUserService['getUserSSOProviders'] = async () => {
|
14
|
+
return lambdaClient.user.getUserSSOProviders.query();
|
15
|
+
};
|
16
|
+
|
17
|
+
unlinkSSOProvider: IUserService['unlinkSSOProvider'] = async (
|
18
|
+
provider: string,
|
19
|
+
providerAccountId: string,
|
20
|
+
) => {
|
21
|
+
return lambdaClient.user.unlinkSSOProvider.mutate({ provider, providerAccountId });
|
22
|
+
};
|
23
|
+
|
13
24
|
makeUserOnboarded = async () => {
|
14
25
|
return lambdaClient.user.makeUserOnboarded.mutate();
|
15
26
|
};
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import type { AdapterAccount } from 'next-auth/adapters';
|
1
2
|
import { DeepPartial } from 'utility-types';
|
2
3
|
|
3
4
|
import { UserGuide, UserInitializationState, UserPreference } from '@/types/user';
|
@@ -9,8 +10,10 @@ export interface IUserService {
|
|
9
10
|
duration: number;
|
10
11
|
updatedAt: string;
|
11
12
|
}>;
|
13
|
+
getUserSSOProviders: () => Promise<AdapterAccount[]>;
|
12
14
|
getUserState: () => Promise<UserInitializationState>;
|
13
15
|
resetUserSettings: () => Promise<any>;
|
16
|
+
unlinkSSOProvider: (provider: string, providerAccountId: string) => Promise<any>;
|
14
17
|
updateGuide: (guide: Partial<UserGuide>) => Promise<any>;
|
15
18
|
updatePreference: (preference: Partial<UserPreference>) => Promise<any>;
|
16
19
|
updateUserSettings: (value: DeepPartial<UserSettings>, signal?: AbortSignal) => Promise<any>;
|
package/src/types/user/index.ts
CHANGED