Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Twitter Login #782

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9402e02
twitter strategy
berry-13 Aug 9, 2023
f3274d9
fix: Twitter icon
fuegovic Aug 10, 2023
9a437c0
.env.example update
berry-13 Aug 10, 2023
e4fc7a2
Create twitter-text.md
berry-13 Aug 10, 2023
1d75e28
Rename docs/install/apis_and_tokens.md to docs/install/API & Auth/api…
berry-13 Aug 10, 2023
ffbbe82
Rename docs/install/free_ai_apis.md to docs/install/API & Auth/free_a…
berry-13 Aug 10, 2023
8cc9e2e
Rename docs/install/user_auth_system.md to docs/install/API & Auth/us…
berry-13 Aug 10, 2023
ce930f0
Update user_auth_system.md
berry-13 Aug 10, 2023
cd95e12
Update user_auth_system.md
berry-13 Aug 10, 2023
02c0e79
Rename apis_and_tokens.md to apis_and_tokens.md
berry-13 Aug 10, 2023
cf5d6cf
Rename free_ai_apis.md to free_ai_apis.md
berry-13 Aug 10, 2023
c98c416
Rename twitter-text.md to twitter-text.md
berry-13 Aug 10, 2023
e52ef50
Rename user_auth_system.md to user_auth_system.md
berry-13 Aug 10, 2023
d44c01e
Update README.md
berry-13 Aug 10, 2023
8e13257
Update mkdocs.yml
berry-13 Aug 10, 2023
4e75c63
Update user_auth_system.md
fuegovic Aug 10, 2023
9edab6e
changed .env variable
berry-13 Aug 10, 2023
afcea3b
Merge branch 'main' into twitter-strategy
berry-13 Aug 10, 2023
9b498c7
moved nodemon and passport-twitter
berry-13 Aug 10, 2023
3130ae0
Merge branch 'twitter-strategy' of https://github.com/Berry-13/Librec…
berry-13 Aug 10, 2023
cab3998
Merge branch 'main' into twitter-strategy
berry-13 Aug 10, 2023
a022aac
added Include email scope
berry-13 Aug 10, 2023
bb7ed5b
Merge branch 'twitter-strategy' of https://github.com/Berry-13/Librec…
berry-13 Aug 10, 2023
85278b8
changed TwitterStrategy from profile to user
berry-13 Aug 10, 2023
8b5a4f4
changed const email from profile to user
berry-13 Aug 10, 2023
067bfad
test includeEmail: true
berry-13 Aug 10, 2023
f8d0b88
Merge branch 'main' into twitter-strategy
berry-13 Aug 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,23 @@ SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7

GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyone
GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyone so dont change it

# Discord:
# Get the Client ID and Secret from your Discord Application
# Add your Github Client ID and Client Secret here:

DISCORD_CLIENT_ID=your_client_id
DISCORD_CLIENT_SECRET=your_client_secret
DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone
DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone so dont change it

# Twitter:
# Get the API Key and Secret from your Twitter Application
# Add your "API Key" in Client ID and your "Secret" in Client Secret here:

TWITTER_API_KEY=your_api_key
TWITTER_API_SECRET=your_secret
TWITTER_CALLBACK_URL=/oauth/twitter/callback # this should be the same for everyone so dont change it

###########################
# Application Domains
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ Keep up with the latest updates by visiting the releases page - [Releases](https
* [Mac Install🍎](docs/install/mac_install.md)
* [Windows Install💙](docs/install/windows_install.md)
* Configuration
* [APIs and Tokens](docs/install/apis_and_tokens.md)
* [User Auth System](docs/install/user_auth_system.md)
* [APIs and Tokens](docs/install/API_&_Auth/apis_and_tokens.md)
* [User Auth System](docs/install/API_&_Auth/user_auth_system.md)
* [Online MongoDB Database](docs/install/mongodb.md)
* [Languages](docs/install/languages.md)
</details>
Expand Down
5 changes: 5 additions & 0 deletions api/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ const userSchema = mongoose.Schema(
unique: true,
sparse: true,
},
twitterId: {
type: String,
unique: true,
sparse: true,
},
plugins: {
type: Array,
default: [],
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
},
"devDependencies": {
"jest": "^29.5.0",
"nodemon": "^2.0.20",
"nodemon": "^3.0.1",
"path": "^0.12.7",
"supertest": "^6.3.3"
}
Expand Down
4 changes: 4 additions & 0 deletions api/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {
googleLogin,
githubLogin,
discordLogin,
twitterLogin,
facebookLogin,
setupOpenId,
} = require('../strategies');
Expand Down Expand Up @@ -61,6 +62,9 @@ config.validate(); // Validate the config
if (process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET) {
passport.use(await discordLogin());
}
if (process.env.TWITTER_API_KEY && process.env.TWITTER_API_SECRET) {
passport.use(await twitterLogin());
}
if (
process.env.OPENID_CLIENT_ID &&
process.env.OPENID_CLIENT_SECRET &&
Expand Down
5 changes: 5 additions & 0 deletions api/server/routes/__tests__/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ afterEach(() => {
delete process.env.GITHUB_CLIENT_SECRET;
delete process.env.DISCORD_CLIENT_ID;
delete process.env.DISCORD_CLIENT_SECRET;
delete process.env.TWITTER_API_KEY;
delete process.env.TWITTER_API_SECRET;
delete process.env.DOMAIN_SERVER;
delete process.env.ALLOW_REGISTRATION;
delete process.env.ALLOW_SOCIAL_LOGIN;
Expand All @@ -41,6 +43,8 @@ describe.skip('GET /', () => {
process.env.GITHUB_CLIENT_SECRET = 'Test Github client Secret';
process.env.DISCORD_CLIENT_ID = 'Test Discord client Id';
process.env.DISCORD_CLIENT_SECRET = 'Test Discord client Secret';
process.env.TWITTER_API_KEY = 'Test Twitter api key';
process.env.TWITTER_API_SECRET = 'Test Twitter api Secret';
process.env.DOMAIN_SERVER = 'http://test-server.com';
process.env.ALLOW_REGISTRATION = 'true';
process.env.ALLOW_SOCIAL_LOGIN = 'true';
Expand All @@ -56,6 +60,7 @@ describe.skip('GET /', () => {
openidImageUrl: 'http://test-server.com',
githubLoginEnabled: true,
discordLoginEnabled: true,
twitterLoginEnabled: true,
serverDomain: 'http://test-server.com',
registrationEnabled: 'true',
socialLoginEnabled: 'true',
Expand Down
2 changes: 2 additions & 0 deletions api/server/routes/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ router.get('/', async function (req, res) {
const githubLoginEnabled = !!process.env.GITHUB_CLIENT_ID && !!process.env.GITHUB_CLIENT_SECRET;
const discordLoginEnabled =
!!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET;
const twitterLoginEnabled = !!process.env.TWITTER_API_KEY && !!process.env.TWITTER_API_SECRET;
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
const registrationEnabled = process.env.ALLOW_REGISTRATION === 'true';
const socialLoginEnabled = process.env.ALLOW_SOCIAL_LOGIN === 'true';
Expand All @@ -32,6 +33,7 @@ router.get('/', async function (req, res) {
openidImageUrl,
githubLoginEnabled,
discordLoginEnabled,
twitterLoginEnabled,
serverDomain,
registrationEnabled,
socialLoginEnabled,
Expand Down
20 changes: 20 additions & 0 deletions api/server/routes/oauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,24 @@ router.get(
},
);

router.get('/twitter', passport.authenticate('twitter'));

router.get(
'/twitter/callback',
passport.authenticate('twitter', {
failureRedirect: `${domains.client}/login`,
failureMessage: true,
session: false,
}),
(req, res) => {
const token = req.user.generateToken();
res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false,
secure: isProduction,
});
res.redirect(domains.client);
},
);

module.exports = router;
2 changes: 2 additions & 0 deletions api/strategies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const passportLogin = require('./localStrategy');
const googleLogin = require('./googleStrategy');
const githubLogin = require('./githubStrategy');
const discordLogin = require('./discordStrategy');
const twitterLogin = require('./twitterStrategy');
const jwtLogin = require('./jwtStrategy');
const facebookLogin = require('./facebookStrategy');
const setupOpenId = require('./openidStrategy');
Expand All @@ -11,6 +12,7 @@ module.exports = {
googleLogin,
githubLogin,
discordLogin,
twitterLogin,
jwtLogin,
facebookLogin,
setupOpenId,
Expand Down
42 changes: 42 additions & 0 deletions api/strategies/twitterStrategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { Strategy: TwitterStrategy } = require('passport-twitter');
const config = require('../../config/loader');
const domains = config.domains;

const User = require('../models/User');

const twitterLogin = async () =>
new TwitterStrategy(
{
consumerKey: process.env.TWITTER_API_KEY,
consumerSecret: process.env.TWITTER_API_SECRET,
callbackURL: `${domains.server}${process.env.TWITTER_CALLBACK_URL}`,
proxy: false,
includeEmail: true, // Richiedi il campo email
},
async (token, tokenSecret, profile, cb) => {
try {
const email = profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null;

const oldUser = await User.findOne({ email });
if (oldUser) {
return cb(null, oldUser);
}

const newUser = await new User({
provider: 'twitter',
twitterId: profile.id,
username: profile.username,
email,
name: profile.displayName,
avatar: profile.photos && profile.photos.length > 0 ? profile.photos[0].value : null,
}).save();

cb(null, newUser);
} catch (err) {
console.error(err);
cb(err);
}
},
);

module.exports = twitterLogin;
16 changes: 15 additions & 1 deletion client/src/components/Auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
import { useGetStartupConfig } from 'librechat-data-provider';
import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon, TwitterXIcon } from '~/components';

function Login() {
const { login, error, isAuthenticated } = useAuthContext();
Expand Down Expand Up @@ -115,6 +115,20 @@ function Login() {
</div>
</>
)}
{startupConfig?.twitterLoginEnabled && startupConfig?.socialLoginEnabled && (
<>
<div className="mt-2 flex gap-x-2">
<a
aria-label="Login with Twitter"
className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
href={`${startupConfig.serverDomain}/oauth/twitter`}
>
<TwitterXIcon />
<p>{localize(lang, 'com_auth_twitter_login')}</p>
</a>
</div>
</>
)}
</div>
</div>
);
Expand Down
16 changes: 15 additions & 1 deletion client/src/components/Auth/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
TRegisterUser,
useGetStartupConfig,
} from 'librechat-data-provider';
import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon } from '~/components';
import { GoogleIcon, OpenIDIcon, GithubIcon, DiscordIcon, TwitterXIcon } from '~/components';

function Registration() {
const navigate = useNavigate();
Expand Down Expand Up @@ -356,6 +356,20 @@ function Registration() {
</div>
</>
)}
{startupConfig?.twitterLoginEnabled && startupConfig?.socialLoginEnabled && (
<>
<div className="mt-2 flex gap-x-2">
<a
aria-label="Login with Twitter"
className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
href={`${startupConfig.serverDomain}/oauth/twitter`}
>
<TwitterXIcon />
<p>{localize(lang, 'com_auth_twitter_login')}</p>
</a>
</div>
</>
)}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Auth/__tests__/Login.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const setup = ({
openidImageUrl: 'http://test-server.com',
githubLoginEnabled: true,
discordLoginEnabled: true,
twitterLoginEnabled: true,
registrationEnabled: true,
socialLoginEnabled: true,
serverDomain: 'mock-server',
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Auth/__tests__/Registration.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const setup = ({
openidImageUrl: 'http://test-server.com',
githubLoginEnabled: true,
discordLoginEnabled: true,
twitterLoginEnabled: true,
registrationEnabled: true,
socialLoginEnabled: true,
serverDomain: 'mock-server',
Expand Down
26 changes: 26 additions & 0 deletions client/src/components/svg/TwitterXIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';

export default function TwitterXIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" id="twitterx" className="h-6 w-6">
<g transform="translate(-46.815 -17.104) scale(.13283)">
<circle
cx={834.28}
cy={610.6}
r={481.33}
style={{
stroke: '#fff',
strokeMiterlimit: 10,
}}
/>
<path
d="m485.39 356.79 230.07 307.62-231.52 250.11h52.11l202.7-218.98 163.77 218.98h177.32L836.82 589.6l215.5-232.81h-52.11L813.54 558.46 662.71 356.79zm76.63 38.38h81.46l359.72 480.97h-81.46z"
style={{
fill: '#fff',
}}
transform="translate(52.39 -25.059)"
/>
</g>
</svg>
);
}
1 change: 1 addition & 0 deletions client/src/components/svg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { default as GoogleIcon } from './GoogleIcon';
export { default as OpenIDIcon } from './OpenIDIcon';
export { default as GithubIcon } from './GithubIcon';
export { default as DiscordIcon } from './DiscordIcon';
export { default as TwitterXIcon } from './TwitterXIcon';
export { default as AnthropicIcon } from './AnthropicIcon';
export { default as LinkIcon } from './LinkIcon';
export { default as DotsIcon } from './DotsIcon';
Expand Down
1 change: 1 addition & 0 deletions client/src/localization/languages/Eng.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default {
com_auth_google_login: 'Login with Google',
com_auth_github_login: 'Login with Github',
com_auth_discord_login: 'Login with Discord',
com_auth_twitter_login: 'Login with Twitter',
com_auth_email: 'Email',
com_auth_email_required: 'Email is required',
com_auth_email_min_length: 'Email must be at least 6 characters',
Expand Down
File renamed without changes.
9 changes: 9 additions & 0 deletions docs/install/API_&_Auth/twitter-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Twitter example text for Twitter Developer Portal

### Copy and paste this:

I want to integrate the "Login with Twitter" feature on my website to make registration simpler and faster for users. Allowing users to log in with their Twitter account eliminates the need for them to create separate usernames and passwords just for my site. This way I reduce friction in the sign-up process and encourage more users to register and try out my website.

Once a user logs in via Twitter, I can securely collect publicly available profile data like their name, username, profile picture and more. This allows me to personalize and improve the experience for each user without requiring them to fill out long and boring forms. I can also give them the ability to post content from my site directly to Twitter, which could help drive more traffic back to my site.

Overall, adding Twitter login makes the user experience more seamless, helps acquire more users, provides valuable insights into their public profiles and opens opportunities for engagement and sharing on Twitter. It's a win for all parties involved. By properly implementing Twitter's APIs, I can take advantage of Twitter's huge user base and established social identity without compromising security. This will help my business grow and attract a wider audience.
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ OPENID_CALLBACK_URL=/oauth/openid/callback
```
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyone
GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyone so dont change it
```
9. Save the .env file
---
Expand All @@ -129,10 +129,36 @@ GITHUB_CALLBACK_URL=/oauth/github/callback # this should be the same for everyon
```
DISCORD_CLIENT_ID=your_client_id
DISCORD_CLIENT_SECRET=your_client_secret
DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone
DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for everyone so dont change it
```
8. Save the .env file
---

## Twitter Authentication

1. Go to [Twitter Developer Portal](https://developer.twitter.com/)
2. Sign in and create a free account
3. Describe all of your use cases of Twitter’s data and API (if you don't know what to write, copy and paste [this](./twitter-text.md))
4. In the Project & Apps tab, select "Default project ..." If you want, modify your project name to something like "LibreChat Login" in settings.
5. Under the Default Project..., open the app. If you want, modify the App Name and upload an App Icon.
6. In "User authentication settings," click on "Set up."
7. In the App permissions, select "Read" and "Request email from users." In Type of App, select "Web App, Automated App, or Bot."
8. In the App Info, put the provided information and save.
```
Callback URI / Redirect URL= your-domain/oauth/twitter/callback
Website URL= your-domain
Terms of service= https://example.com
Privacy policy= https://example.com
```
9. In the Keys and tokens, click on Regenerate (the API Key and Secret), copy it and save it somewhere
10. Put the API Key and API Secret in the .env file:
```
TWITTER_API_KEY=your_api_key
TWITTER_API_SECRET=your_api_secret
TWITTER_CALLBACK_URL=/oauth/twitter/callback # this should be the same for everyone so dont change it
```
11. Save the .env file

## **Email and Password Reset**

### General setup
Expand Down
6 changes: 3 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ nav:
- Mac Install: 'install/mac_install.md'
- Windows Install: 'install/windows_install.md'
- Configuration:
- Free AI APIs: 'install/free_ai_apis.md'
- APIs and Tokens: 'install/apis_and_tokens.md'
- User Auth System: 'install/user_auth_system.md'
- Free AI APIs: 'install/API_&_Auth/free_ai_apis.md'
- APIs and Tokens: 'install/API_&_Auth/apis_and_tokens.md'
- User Auth System: 'install/API_&_Auth/user_auth_system.md'
- Online MongoDB Database: 'install/mongodb.md'
- Languages: 'install/languages.md'
- Features:
Expand Down
Loading
Loading