diff --git a/.env.example b/.env.example
index ee50e58d6..64c73b769 100644
--- a/.env.example
+++ b/.env.example
@@ -229,6 +229,13 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=/oauth/google/callback
+# Facebook:
+# Add your Facebook Client ID and Secret here, you must register an app with Facebook to get these values
+# https://developers.facebook.com/
+FACEBOOK_CLIENT_ID=
+FACEBOOK_CLIENT_SECRET=
+FACEBOOK_CALLBACK_URL=/oauth/facebook/callback
+
# OpenID:
# See OpenID provider to get the below values
# Create random string for OPENID_SESSION_SECRET
diff --git a/api/models/User.js b/api/models/User.js
index bcbc141f1..5e1d035de 100644
--- a/api/models/User.js
+++ b/api/models/User.js
@@ -63,6 +63,11 @@ const userSchema = mongoose.Schema(
unique: true,
sparse: true,
},
+ facebookId: {
+ type: String,
+ unique: true,
+ sparse: true,
+ },
openidId: {
type: String,
unique: true,
diff --git a/api/server/routes/__tests__/config.spec.js b/api/server/routes/__tests__/config.spec.js
index 87ce05af0..9194d458f 100644
--- a/api/server/routes/__tests__/config.spec.js
+++ b/api/server/routes/__tests__/config.spec.js
@@ -8,6 +8,8 @@ afterEach(() => {
delete process.env.APP_TITLE;
delete process.env.GOOGLE_CLIENT_ID;
delete process.env.GOOGLE_CLIENT_SECRET;
+ delete process.env.FACEBOOK_CLIENT_ID;
+ delete process.env.FACEBOOK_CLIENT_SECRET;
delete process.env.OPENID_CLIENT_ID;
delete process.env.OPENID_CLIENT_SECRET;
delete process.env.OPENID_ISSUER;
@@ -31,6 +33,8 @@ describe.skip('GET /', () => {
process.env.APP_TITLE = 'Test Title';
process.env.GOOGLE_CLIENT_ID = 'Test Google Client Id';
process.env.GOOGLE_CLIENT_SECRET = 'Test Google Client Secret';
+ process.env.FACEBOOK_CLIENT_ID = 'Test Facebook Client Id';
+ process.env.FACEBOOK_CLIENT_SECRET = 'Test Facebook Client Secret';
process.env.OPENID_CLIENT_ID = 'Test OpenID Id';
process.env.OPENID_CLIENT_SECRET = 'Test OpenID Secret';
process.env.OPENID_ISSUER = 'Test OpenID Issuer';
@@ -51,6 +55,7 @@ describe.skip('GET /', () => {
expect(response.body).toEqual({
appTitle: 'Test Title',
googleLoginEnabled: true,
+ facebookLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidImageUrl: 'http://test-server.com',
diff --git a/api/server/routes/config.js b/api/server/routes/config.js
index f86abb177..2d3433af7 100644
--- a/api/server/routes/config.js
+++ b/api/server/routes/config.js
@@ -5,6 +5,8 @@ router.get('/', async function (req, res) {
try {
const appTitle = process.env.APP_TITLE || 'LibreChat';
const googleLoginEnabled = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
+ const facebookLoginEnabled =
+ !!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET;
const openidLoginEnabled =
!!process.env.OPENID_CLIENT_ID &&
!!process.env.OPENID_CLIENT_SECRET &&
@@ -27,6 +29,7 @@ router.get('/', async function (req, res) {
return res.status(200).send({
appTitle,
googleLoginEnabled,
+ facebookLoginEnabled,
openidLoginEnabled,
openidLabel,
openidImageUrl,
diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js
index bd82f4cb4..ea5837427 100644
--- a/api/server/routes/oauth.js
+++ b/api/server/routes/oauth.js
@@ -38,7 +38,8 @@ router.get(
router.get(
'/facebook',
passport.authenticate('facebook', {
- scope: ['public_profile', 'email'],
+ scope: ['public_profile'],
+ profileFields: ['id', 'email', 'name'],
session: false,
}),
);
@@ -49,7 +50,8 @@ router.get(
failureRedirect: `${domains.client}/login`,
failureMessage: true,
session: false,
- scope: ['public_profile', 'email'],
+ scope: ['public_profile'],
+ profileFields: ['id', 'email', 'name'],
}),
(req, res) => {
const token = req.user.generateToken();
diff --git a/api/strategies/facebookStrategy.js b/api/strategies/facebookStrategy.js
index 92ad85364..41d30754b 100644
--- a/api/strategies/facebookStrategy.js
+++ b/api/strategies/facebookStrategy.js
@@ -5,8 +5,7 @@ const domains = config.domains;
const facebookLogin = async (accessToken, refreshToken, profile, cb) => {
try {
- console.log('facebookLogin => profile', profile);
- const email = profile.emails[0].value;
+ const email = profile.emails[0]?.value;
const facebookId = profile.id;
const oldUser = await User.findOne({
email,
@@ -15,17 +14,17 @@ const facebookLogin = async (accessToken, refreshToken, profile, cb) => {
process.env.ALLOW_SOCIAL_REGISTRATION?.toLowerCase() === 'true';
if (oldUser) {
- oldUser.avatar = profile.photos[0].value;
+ oldUser.avatar = profile.photo;
await oldUser.save();
return cb(null, oldUser);
} else if (ALLOW_SOCIAL_REGISTRATION) {
const newUser = await new User({
provider: 'facebook',
facebookId,
- username: profile.name.givenName + profile.name.familyName,
+ username: profile.displayName,
email,
- name: profile.displayName,
- avatar: profile.photos[0].value,
+ name: profile.name?.givenName + ' ' + profile.name?.familyName,
+ avatar: profile.photos[0]?.value,
}).save();
return cb(null, newUser);
@@ -43,23 +42,12 @@ const facebookLogin = async (accessToken, refreshToken, profile, cb) => {
module.exports = () =>
new FacebookStrategy(
{
- clientID: process.env.FACEBOOK_APP_ID,
- clientSecret: process.env.FACEBOOK_SECRET,
+ clientID: process.env.FACEBOOK_CLIENT_ID,
+ clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
callbackURL: `${domains.server}${process.env.FACEBOOK_CALLBACK_URL}`,
proxy: true,
- // profileFields: [
- // 'id',
- // 'email',
- // 'gender',
- // 'profileUrl',
- // 'displayName',
- // 'locale',
- // 'name',
- // 'timezone',
- // 'updated_time',
- // 'verified',
- // 'picture.type(large)'
- // ]
+ scope: ['public_profile'],
+ profileFields: ['id', 'email', 'name'],
},
facebookLogin,
);
diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx
index 65a119de4..025051da5 100644
--- a/client/src/components/Auth/Login.tsx
+++ b/client/src/components/Auth/Login.tsx
@@ -4,7 +4,7 @@ import { useAuthContext } from '~/hooks/AuthContext';
import { useNavigate } from 'react-router-dom';
import { useLocalize } from '~/hooks';
import { useGetStartupConfig } from 'librechat-data-provider';
-import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
+import { GoogleIcon, FacebookIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
function Login() {
const { login, error, isAuthenticated } = useAuthContext();
@@ -65,6 +65,20 @@ function Login() {
>
)}
+ {startupConfig?.facebookLoginEnabled && startupConfig?.socialLoginEnabled && (
+ <>
+
+ >
+ )}
{startupConfig?.openidLoginEnabled && startupConfig?.socialLoginEnabled && (
<>
diff --git a/client/src/components/Auth/Registration.tsx b/client/src/components/Auth/Registration.tsx
index cd4a4399e..397e769f4 100644
--- a/client/src/components/Auth/Registration.tsx
+++ b/client/src/components/Auth/Registration.tsx
@@ -7,7 +7,7 @@ import {
TRegisterUser,
useGetStartupConfig,
} from 'librechat-data-provider';
-import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
+import { GoogleIcon, FacebookIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
function Registration() {
const navigate = useNavigate();
@@ -308,6 +308,20 @@ function Registration() {
>
)}
+ {startupConfig?.facebookLoginEnabled && startupConfig?.socialLoginEnabled && (
+ <>
+
+ >
+ )}
{startupConfig?.openidLoginEnabled && startupConfig?.socialLoginEnabled && (
<>
diff --git a/client/src/components/Auth/__tests__/Login.spec.tsx b/client/src/components/Auth/__tests__/Login.spec.tsx
index 0be0f47a0..6b8b66f1e 100644
--- a/client/src/components/Auth/__tests__/Login.spec.tsx
+++ b/client/src/components/Auth/__tests__/Login.spec.tsx
@@ -23,6 +23,7 @@ const setup = ({
isError: false,
data: {
googleLoginEnabled: true,
+ facebookLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidImageUrl: 'http://test-server.com',
@@ -67,6 +68,21 @@ test('renders login form', () => {
'href',
'mock-server/oauth/google',
);
+ expect(getByRole('link', { name: /Login with Facebook/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Facebook/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/facebook',
+ );
+ expect(getByRole('link', { name: /Login with Github/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Github/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/github',
+ );
+ expect(getByRole('link', { name: /Login with Discord/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Discord/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/discord',
+ );
});
test('calls loginUser.mutate on login', async () => {
diff --git a/client/src/components/Auth/__tests__/Registration.spec.tsx b/client/src/components/Auth/__tests__/Registration.spec.tsx
index 7c7bbd230..0c40b29c0 100644
--- a/client/src/components/Auth/__tests__/Registration.spec.tsx
+++ b/client/src/components/Auth/__tests__/Registration.spec.tsx
@@ -24,6 +24,7 @@ const setup = ({
isError: false,
data: {
googleLoginEnabled: true,
+ facebookLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidImageUrl: 'http://test-server.com',
@@ -75,6 +76,21 @@ test('renders registration form', () => {
'href',
'mock-server/oauth/google',
);
+ expect(getByRole('link', { name: /Login with Facebook/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Facebook/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/facebook',
+ );
+ expect(getByRole('link', { name: /Login with Github/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Github/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/github',
+ );
+ expect(getByRole('link', { name: /Login with Discord/i })).toBeInTheDocument();
+ expect(getByRole('link', { name: /Login with Discord/i })).toHaveAttribute(
+ 'href',
+ 'mock-server/oauth/discord',
+ );
});
// eslint-disable-next-line jest/no-commented-out-tests
diff --git a/client/src/components/svg/FacebookIcon.tsx b/client/src/components/svg/FacebookIcon.tsx
new file mode 100644
index 000000000..131c751ea
--- /dev/null
+++ b/client/src/components/svg/FacebookIcon.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+export default function FacebookIcon() {
+ return (
+
+ );
+}
diff --git a/client/src/components/svg/index.ts b/client/src/components/svg/index.ts
index bcf9e0438..2f4c2289c 100644
--- a/client/src/components/svg/index.ts
+++ b/client/src/components/svg/index.ts
@@ -13,6 +13,7 @@ export { default as StopGeneratingIcon } from './StopGeneratingIcon';
export { default as RegenerateIcon } from './RegenerateIcon';
export { default as ContinueIcon } from './ContinueIcon';
export { default as GoogleIcon } from './GoogleIcon';
+export { default as FacebookIcon } from './FacebookIcon';
export { default as OpenIDIcon } from './OpenIDIcon';
export { default as GithubIcon } from './GithubIcon';
export { default as DiscordIcon } from './DiscordIcon';
diff --git a/client/src/localization/languages/Br.tsx b/client/src/localization/languages/Br.tsx
index 386672afc..63131461e 100644
--- a/client/src/localization/languages/Br.tsx
+++ b/client/src/localization/languages/Br.tsx
@@ -34,6 +34,7 @@ export default {
com_auth_sign_up: 'Cadastre-se',
com_auth_sign_in: 'Entrar',
com_auth_google_login: 'Entrar com o Google',
+ com_auth_facebook_login: 'Entrar com o Facebook',
com_auth_github_login: 'Entrar com o Github',
com_auth_discord_login: 'Entrar com o Discord',
com_auth_email: 'Email',
diff --git a/client/src/localization/languages/De.tsx b/client/src/localization/languages/De.tsx
index 96f563d6e..6f13beeb7 100644
--- a/client/src/localization/languages/De.tsx
+++ b/client/src/localization/languages/De.tsx
@@ -34,6 +34,7 @@ export default {
com_auth_sign_up: 'Registrieren',
com_auth_sign_in: 'Anmelden',
com_auth_google_login: 'Anmelden mit Google',
+ com_auth_facebook_login: 'Anmelden mit Facebook',
com_auth_github_login: 'Anmelden mit Github',
com_auth_discord_login: 'Anmelden mit Discord',
com_auth_email: 'E-Mail',
diff --git a/client/src/localization/languages/Eng.tsx b/client/src/localization/languages/Eng.tsx
index 128882402..89886d9d6 100644
--- a/client/src/localization/languages/Eng.tsx
+++ b/client/src/localization/languages/Eng.tsx
@@ -34,6 +34,7 @@ export default {
com_auth_sign_up: 'Sign up',
com_auth_sign_in: 'Sign in',
com_auth_google_login: 'Login with Google',
+ com_auth_facebook_login: 'Login with Facebook',
com_auth_github_login: 'Login with Github',
com_auth_discord_login: 'Login with Discord',
com_auth_email: 'Email',
diff --git a/client/src/localization/languages/Es.tsx b/client/src/localization/languages/Es.tsx
index 58911b310..9442502a1 100644
--- a/client/src/localization/languages/Es.tsx
+++ b/client/src/localization/languages/Es.tsx
@@ -35,6 +35,7 @@ export default {
com_auth_sign_up: 'Registrarse',
com_auth_sign_in: 'Iniciar sesión',
com_auth_google_login: 'Iniciar sesión con Google',
+ com_auth_facebook_login: 'Iniciar sesión con Facebook',
com_auth_github_login: 'Iniciar sesión con GitHub',
com_auth_discord_login: 'Iniciar sesión con Discord',
com_auth_email: 'Email',
diff --git a/client/src/localization/languages/Fr.tsx b/client/src/localization/languages/Fr.tsx
index 0a4c0c931..eb7c95da1 100644
--- a/client/src/localization/languages/Fr.tsx
+++ b/client/src/localization/languages/Fr.tsx
@@ -35,6 +35,7 @@ export default {
com_auth_sign_up: 'S\'inscrire',
com_auth_sign_in: 'Se connecter',
com_auth_google_login: 'Se connecter avec Google',
+ com_auth_facebook_login: 'Se connecter avec Facebook',
com_auth_github_login: 'Se connecter avec Github',
com_auth_discord_login: 'Se connecter avec Discord',
com_auth_email: 'Courriel',
diff --git a/client/src/localization/languages/It.tsx b/client/src/localization/languages/It.tsx
index b37868851..e9f4ec8b0 100644
--- a/client/src/localization/languages/It.tsx
+++ b/client/src/localization/languages/It.tsx
@@ -35,6 +35,7 @@ export default {
com_auth_sign_up: 'Registrati',
com_auth_sign_in: 'Accedi',
com_auth_google_login: 'Accedi con Google',
+ com_auth_facebook_login: 'Accedi con Facebook',
com_auth_github_login: 'Accedi con Github',
com_auth_discord_login: 'Accedi con Discord',
com_auth_email: 'Email',
diff --git a/client/src/localization/languages/Zh.tsx b/client/src/localization/languages/Zh.tsx
index 6a8c2460e..46911e856 100644
--- a/client/src/localization/languages/Zh.tsx
+++ b/client/src/localization/languages/Zh.tsx
@@ -31,6 +31,7 @@ export default {
com_auth_sign_up: '注册',
com_auth_sign_in: '登录',
com_auth_google_login: '谷歌登录',
+ com_auth_facebook_login: 'Facebook登录',
com_auth_github_login: 'Github登录',
com_auth_discord_login: 'Discord登录',
com_auth_email: '电子邮箱',
diff --git a/docs/install/user_auth_system.md b/docs/install/user_auth_system.md
index b5f707539..3afe44b07 100644
--- a/docs/install/user_auth_system.md
+++ b/docs/install/user_auth_system.md
@@ -45,6 +45,24 @@ To enable Google login, you must create an application in the [Google Cloud Cons
---
+## Facebook Authentication
+### (It only works with a domain, not with localhost)
+
+1. Go to [Facebook Developer Portal](https://developers.facebook.com/)
+2. Create a new Application and give it a name
+4. In the Dashboard tab select product and select "Facebook login", then tap on "Configure" and "Settings". Male sure "OAuth client access", "Web OAuth access", "Apply HTTPS" and "Use limited mode for redirect URIs" are **enabled**
+5. In the Valid OAuth Redirect URIs add "your-domain/oauth/facebook/callback" (example: http://example.com/oauth/facebook/callback)
+6. Save changes and in the "settings" tab, reset the Client Secret
+7. Put the Client ID and Client Secret in the .env file:
+```bash
+FACEBOOK_CLIENT_ID=your_client_id
+FACEBOOK_CLIENT_SECRET=your_client_secret
+FACEBOOK_CALLBACK_URL=/oauth/facebook/callback # this should be the same for everyone
+```
+8. Save the .env file
+
+---
+
## OpenID Authentication with Azure AD
1. Go to the [Azure Portal](https://portal.azure.com/) and sign in with your account.
@@ -132,6 +150,7 @@ DISCORD_CLIENT_SECRET=your_client_secret
DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone
```
8. Save the .env file
+
---
## **Email and Password Reset**
diff --git a/package-lock.json b/package-lock.json
index 5760a96a5..568fc29d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,8 @@
"packages/*"
],
"dependencies": {
- "axios": "^1.4.0"
+ "axios": "^1.4.0",
+ "passport-facebook": "^3.0.0"
},
"devDependencies": {
"@playwright/test": "^1.32.1",
diff --git a/package.json b/package.json
index 17453da22..e8313b10b 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,8 @@
},
"homepage": "https://github.com/danny-avila/LibreChat#readme",
"dependencies": {
- "axios": "^1.4.0"
+ "axios": "^1.4.0",
+ "passport-facebook": "^3.0.0"
},
"devDependencies": {
"@playwright/test": "^1.32.1",
diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts
index ecf8adbd8..f3b9286e1 100644
--- a/packages/data-provider/src/types.ts
+++ b/packages/data-provider/src/types.ts
@@ -159,6 +159,7 @@ export type TResetPassword = {
export type TStartupConfig = {
appTitle: string;
googleLoginEnabled: boolean;
+ facebookLoginEnabled: boolean;
openidLoginEnabled: boolean;
githubLoginEnabled: boolean;
openidLabel: string;