mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
GIthub Login (#578)
* Add files via upload * Create linode-setup.md * Create cloudflare-setup.md * Update cloudflare-setup.md * Delete 4-linode.png * Delete 3-linode.png * Add files via upload * Add files via upload * Update cloudflare-setup.md * Update linode-setup.md * Rename cloudflare-setup.md to cloudflare.md * Rename linode-setup.md to linode.md * Update mkdocs.yml * Update cloudflare.md * Update linode.md * Update README.md * Update README.md * Update linode.md sentence in Italian * v1 The frontend has been completed, along with the .env variables. However, there is an issue of infinite loading thereafter. * Fix email and remove deprecated GitHub passport * Update user_auth_system.md add How to Set Up a Github Authentication * Update .env.example Improved the comment above the GitHub client ID and secret. * Update user_auth_system.md * Update package.json * Remove unnecessary passport GitHub package * fixed conflicts fixed conflicts between Berry-13:main and danny-avila:main in api/server/index.js 45:54 * Delete e -i HEAD~2
This commit is contained in:
parent
8819e83d2c
commit
d0078d478d
15 changed files with 199 additions and 4 deletions
|
|
@ -218,6 +218,14 @@ OPENID_IMAGE_URL=
|
|||
# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7
|
||||
SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
||||
|
||||
# Github:
|
||||
# Get the Client ID and Secret from your Github Application
|
||||
# Add your Github Client ID and Client Secret here:
|
||||
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
GITHUB_CALLBACK_URL=/oauth/github/callback
|
||||
|
||||
###########################
|
||||
# Application Domains
|
||||
###########################
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@ const userSchema = mongoose.Schema(
|
|||
unique: true,
|
||||
sparse: true
|
||||
},
|
||||
githubId: {
|
||||
type: String,
|
||||
unique: true,
|
||||
sparse: true
|
||||
},
|
||||
plugins: {
|
||||
type: Array,
|
||||
default: []
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ config.validate(); // Validate the config
|
|||
if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) {
|
||||
require('../strategies/facebookStrategy');
|
||||
}
|
||||
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
|
||||
require('../strategies/githubStrategy');
|
||||
}
|
||||
if (process.env.OPENID_CLIENT_ID && process.env.OPENID_CLIENT_SECRET &&
|
||||
process.env.OPENID_ISSUER && process.env.OPENID_SCOPE &&
|
||||
process.env.OPENID_SESSION_SECRET) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ afterEach(() => {
|
|||
delete process.env.OPENID_SESSION_SECRET;
|
||||
delete process.env.OPENID_BUTTON_LABEL;
|
||||
delete process.env.OPENID_AUTH_URL;
|
||||
delete process.env.GITHUB_CLIENT_ID;
|
||||
delete process.env.GITHUB_CLIENT_SECRET;
|
||||
delete process.env.DOMAIN_SERVER;
|
||||
delete process.env.ALLOW_REGISTRATION;
|
||||
});
|
||||
|
|
@ -32,6 +34,8 @@ describe.skip('GET /', () => {
|
|||
process.env.OPENID_SESSION_SECRET= 'Test Secret';
|
||||
process.env.OPENID_BUTTON_LABEL= 'Test OpenID';
|
||||
process.env.OPENID_AUTH_URL= 'http://test-server.com';
|
||||
process.env.GITHUB_CLIENT_ID = 'Test Github client Id';
|
||||
process.env.GITHUB_CLIENT_SECRET= 'Test Github client Secret';
|
||||
process.env.DOMAIN_SERVER = 'http://test-server.com';
|
||||
process.env.ALLOW_REGISTRATION = 'true';
|
||||
|
||||
|
|
@ -44,6 +48,7 @@ describe.skip('GET /', () => {
|
|||
openidLoginEnabled: true,
|
||||
openidLabel: 'Test OpenID',
|
||||
openidImageUrl: 'http://test-server.com',
|
||||
githubLoginEnabled: true,
|
||||
serverDomain: 'http://test-server.com',
|
||||
registrationEnabled: 'true',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ router.get('/', async function (req, res) {
|
|||
&& !!process.env.OPENID_SESSION_SECRET;
|
||||
const openidLabel = process.env.OPENID_BUTTON_LABEL || 'Login with OpenID';
|
||||
const openidImageUrl = process.env.OPENID_IMAGE_URL;
|
||||
const githubLoginEnabled = !!process.env.GITHUB_CLIENT_ID && !!process.env.GITHUB_CLIENT_SECRET;
|
||||
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
|
||||
const registrationEnabled = process.env.ALLOW_REGISTRATION === 'true';
|
||||
|
||||
return res.status(200).send({appTitle, googleLoginEnabled, openidLoginEnabled, openidLabel, openidImageUrl, serverDomain, registrationEnabled});
|
||||
return res.status(200).send({appTitle, googleLoginEnabled, openidLoginEnabled, openidLabel, openidImageUrl, githubLoginEnabled, serverDomain, registrationEnabled});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send({error: err.message});
|
||||
|
|
|
|||
|
|
@ -87,4 +87,32 @@ router.get(
|
|||
}
|
||||
);
|
||||
|
||||
|
||||
router.get(
|
||||
'/github',
|
||||
passport.authenticate('github', {
|
||||
scope: ['user:email', 'read:user'],
|
||||
session: false
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/github/callback',
|
||||
passport.authenticate('github', {
|
||||
failureRedirect: `${domains.client}/login`,
|
||||
failureMessage: true,
|
||||
session: false,
|
||||
scope: ['user:email', 'read:user']
|
||||
}),
|
||||
(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;
|
||||
|
|
|
|||
47
api/strategies/githubStrategy.js
Normal file
47
api/strategies/githubStrategy.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const passport = require('passport');
|
||||
const { Strategy: GitHubStrategy } = require('passport-github2');
|
||||
const config = require('../../config/loader');
|
||||
const domains = config.domains;
|
||||
|
||||
const User = require('../models/User');
|
||||
|
||||
// GitHub strategy
|
||||
const githubLogin = new GitHubStrategy(
|
||||
{
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL: `${domains.server}${process.env.GITHUB_CALLBACK_URL}`,
|
||||
proxy: false,
|
||||
scope: ['user:email'] // Request email scope
|
||||
},
|
||||
async (accessToken, refreshToken, profile, cb) => {
|
||||
try {
|
||||
let email;
|
||||
if (profile.emails && profile.emails.length > 0) {
|
||||
email = profile.emails[0].value;
|
||||
}
|
||||
|
||||
const oldUser = await User.findOne({ email });
|
||||
if (oldUser) {
|
||||
return cb(null, oldUser);
|
||||
}
|
||||
|
||||
const newUser = await new User({
|
||||
provider: 'github',
|
||||
githubId: profile.id,
|
||||
username: profile.username,
|
||||
email,
|
||||
emailVerified: profile.emails[0].verified,
|
||||
name: profile.displayName,
|
||||
avatar: profile.photos[0].value
|
||||
}).save();
|
||||
|
||||
cb(null, newUser);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
passport.use(githubLogin);
|
||||
|
|
@ -102,9 +102,38 @@ function Login() {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
{startupConfig?.githubLoginEnabled && (
|
||||
<>
|
||||
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
|
||||
<div className="absolute bg-white px-3 text-xs">Or</div>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2">
|
||||
<a
|
||||
aria-label="Login with GitHub"
|
||||
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/github`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
id="github"
|
||||
className="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 0a12 12 0 0 0-3.84 23.399c.608.112.832-.256.832-.576v-2.015c-3.395.736-4.115-1.632-4.115-1.632a3.241 3.241 0 0 0-1.359-1.792c-1.104-.736.064-.736.064-.736a2.566 2.566 0 0 1 1.824 1.216a2.638 2.638 0 0 0 3.616 1.024a2.607 2.607 0 0 1 .768-1.6c-2.688-.32-5.504-1.344-5.504-5.984a4.677 4.677 0 0 1 1.216-3.168a4.383 4.383 0 0 1 .128-3.136s1.024-.32 3.36 1.216a11.66 11.66 0 0 1 6.112 0c2.336-1.536 3.36-1.216 3.36-1.216a4.354 4.354 0 0 1 .128 3.136a4.628 4.628 0 0 1 1.216 3.168c0 4.672-2.848 5.664-5.536 5.952a2.881 2.881 0 0 1 .832 2.24v3.36c0 .32.224.672.832.576A12 12 0 0 0 12 0z"
|
||||
/>
|
||||
</svg>
|
||||
<p>Login with GitHub</p>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Login;
|
||||
|
|
|
|||
|
|
@ -340,6 +340,34 @@ function Registration() {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
{startupConfig?.githubLoginEnabled && (
|
||||
<>
|
||||
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
|
||||
<div className="absolute bg-white px-3 text-xs">Or</div>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2">
|
||||
<a
|
||||
aria-label="Login with GitHub"
|
||||
href={`${startupConfig.serverDomain}/oauth/github`}
|
||||
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"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
id="github"
|
||||
className="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 0a12 12 0 0 0-3.84 23.399c.608.112.832-.256.832-.576v-2.015c-3.395.736-4.115-1.632-4.115-1.632a3.241 3.241 0 0 0-1.359-1.792c-1.104-.736.064-.736.064-.736a2.566 2.566 0 0 1 1.824 1.216a2.638 2.638 0 0 0 3.616 1.024a2.607 2.607 0 0 1 .768-1.6c-2.688-.32-5.504-1.344-5.504-5.984a4.677 4.677 0 0 1 1.216-3.168a4.383 4.383 0 0 1 .128-3.136s1.024-.32 3.36 1.216a11.66 11.66 0 0 1 6.112 0c2.336-1.536 3.36-1.216 3.36-1.216a4.354 4.354 0 0 1 .128 3.136a4.628 4.628 0 0 1 1.216 3.168c0 4.672-2.848 5.664-5.536 5.952a2.881 2.881 0 0 1 .832 2.24v3.36c0 .32.224.672.832.576A12 12 0 0 0 12 0z"
|
||||
/>
|
||||
</svg>
|
||||
<p>Login with GitHub</p>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const setup = ({
|
|||
openidLoginEnabled: true,
|
||||
openidLabel: 'Test OpenID',
|
||||
openidImageUrl: 'http://test-server.com',
|
||||
githubLoginEnabled: true,
|
||||
registrationEnabled: true,
|
||||
serverDomain: 'mock-server'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const setup = ({
|
|||
openidLoginEnabled: true,
|
||||
openidLabel: 'Test OpenID',
|
||||
openidImageUrl: 'http://test-server.com',
|
||||
githubLoginEnabled: true,
|
||||
registrationEnabled: true,
|
||||
serverDomain: 'mock-server'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ export type TStartupConfig = {
|
|||
openidLoginEnabled: boolean;
|
||||
openidLabel: string;
|
||||
openidImageUrl: string;
|
||||
githubLoginEnabled: boolean;
|
||||
serverDomain: string;
|
||||
registrationEnabled: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,19 @@ OPENID_CALLBACK_URL=/oauth/openid/callback
|
|||
|
||||
---
|
||||
|
||||
## How to Set Up Github Authentication
|
||||
|
||||
1. Go to your [Github Developer settings](https://github.com/settings/apps)
|
||||
2. Create a new Github app
|
||||
3. Give it a GitHub App name and set in the Homepage URL your [DOMAIN_CLIENT](https://github.com/danny-avila/LibreChat/blob/main/.env.example#L219) (example: http://localhost:3080)
|
||||
4. Add a callback URL and set it as "[Your DOMAIN_CLIENT](https://github.com/danny-avila/LibreChat/blob/main/.env.example#L219)/oauth/github/callback" (example: http://localhost:3080/oauth/github/callback)
|
||||
5. Remove the Active checkbox in the Webhook section
|
||||
6. Save changes and generate a Client Secret
|
||||
7. In the Permissions & events tab select, open the Account Permissions and set Email addresses to Read-only
|
||||
8. Put the Client ID and Client Secret in the .env file
|
||||
9. Save the .env file
|
||||
|
||||
---
|
||||
## **Email and Password Reset**
|
||||
|
||||
Most of the code is in place for sending password reset emails, but is not yet feature-complete as I have not setup an email server to test it. Currently, submitting a password reset request will then display a link with the one-time reset token that can then be used to reset the password. Understanding that this is a considerable security hazard, email integration will be included in the next release.
|
||||
|
|
@ -83,4 +96,4 @@ If you previously implemented your own user system using the original scaffoldin
|
|||
### For user updating from an older version of the app:
|
||||
|
||||
When the first account is registered, the application will automatically migrate any conversations and presets that you created before the user system was implemented to that account.
|
||||
if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account.
|
||||
if you use login for the first time with a social login account (eg. Google, facebook, etc.), the conversations and presets that you created before the user system was implemented will NOT be migrated to that account.
|
||||
|
|
|
|||
24
package-lock.json
generated
24
package-lock.json
generated
|
|
@ -14,7 +14,10 @@
|
|||
"client"
|
||||
],
|
||||
"dependencies": {
|
||||
"langchain": "^0.0.91"
|
||||
"axios": "^1.4.0",
|
||||
"langchain": "^0.0.91",
|
||||
"passport": "^0.6.0",
|
||||
"passport-github2": "^0.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.32.1",
|
||||
|
|
@ -20320,6 +20323,17 @@
|
|||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-github2": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz",
|
||||
"integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==",
|
||||
"dependencies": {
|
||||
"passport-oauth2": "1.x.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-google-oauth20": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
||||
|
|
@ -40406,6 +40420,14 @@
|
|||
"passport-oauth2": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-github2": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz",
|
||||
"integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==",
|
||||
"requires": {
|
||||
"passport-oauth2": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-google-oauth20": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@
|
|||
},
|
||||
"homepage": "https://github.com/danny-avila/LibreChat#readme",
|
||||
"dependencies": {
|
||||
"langchain": "^0.0.91"
|
||||
"axios": "^1.4.0",
|
||||
"langchain": "^0.0.91",
|
||||
"passport": "^0.6.0",
|
||||
"passport-github2": "^0.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.32.1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue