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:
Marco Beretta 2023-07-04 21:23:42 +02:00 committed by GitHub
parent 8819e83d2c
commit d0078d478d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 199 additions and 4 deletions

View file

@ -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
###########################

View file

@ -70,6 +70,11 @@ const userSchema = mongoose.Schema(
unique: true,
sparse: true
},
githubId: {
type: String,
unique: true,
sparse: true
},
plugins: {
type: Array,
default: []

View file

@ -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) {

View file

@ -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',
});

View file

@ -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});

View file

@ -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;

View 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);

View file

@ -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;

View file

@ -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>
);

View file

@ -26,6 +26,7 @@ const setup = ({
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidImageUrl: 'http://test-server.com',
githubLoginEnabled: true,
registrationEnabled: true,
serverDomain: 'mock-server'
}

View file

@ -26,6 +26,7 @@ const setup = ({
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidImageUrl: 'http://test-server.com',
githubLoginEnabled: true,
registrationEnabled: true,
serverDomain: 'mock-server'
}

View file

@ -243,6 +243,7 @@ export type TStartupConfig = {
openidLoginEnabled: boolean;
openidLabel: string;
openidImageUrl: string;
githubLoginEnabled: boolean;
serverDomain: string;
registrationEnabled: boolean;
}

View file

@ -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.

24
package-lock.json generated
View file

@ -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",

View file

@ -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",