OpenID Authentication (#495)

* Squashed commit of the following:

commit 26ab03fb36fcc7fcee63fdf3ae8c2dfb29027eff
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:23:23 2023 -0500

    Update Registration.spec.tsx

commit e908dd82fe9ef1b43c75ee64c183d2f654bdac1c
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:23:01 2023 -0500

    Update Login.spec.tsx

commit 223734820fb77d7fb5af4802af642d1c1fd7c1f5
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:22:39 2023 -0500

    Update Registration.tsx

commit 7036d3dd0538979ee397d958ebc113bb0ea32411
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:21:55 2023 -0500

    Update Login.tsx

commit 76bb78221db3195fd930fe9cfd6a5da7194fa759
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:21:03 2023 -0500

    Update envConstants.js

commit ee2f69f33d75fbb57022afbcd9564bca38a46bee
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:20:08 2023 -0500

    Update docker-compose.yml

commit 5ac72d789b3446884c6e2f4f595cbf67d731d43c
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:18:41 2023 -0500

    Update Dockerfile

commit d24341db2bd5b17eb89ab01e171a5f51f3beab0a
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:16:38 2023 -0500

    Update .env.example

commit 22154f4a09c5fcdfee95d43609fb01a5a883b7a9
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:48 2023 -0500

    Update Registration.spec.tsx

commit 5163f7d372a6a03c94f4357b358211a03369456e
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:30 2023 -0500

    Update Login.spec.tsx

commit 61da49e330a9376e130b24dc944854f97ab58d80
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:07:00 2023 -0500

    Update Registration.tsx

commit 0e45d3f0dbde34388ff2f0b2dc51b983b472eb05
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:06:18 2023 -0500

    Update Login.tsx

commit dca1e5367e5f3b468c7964218cc5914ca53095af
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:05:07 2023 -0500

    Update envConstants.js

commit f48c058465d82b03716ba85224e9f97007e014d2
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Tue Jun 13 00:04:05 2023 -0500

    Update .env.example

commit 818226c9cb079acae4fcbfe5997e4aa9e3c6d2cc
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:59:08 2023 -0500

    Update .env.example

commit 9a805439189b352a38ac7654d7a31bb28f0f58dd
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:58:31 2023 -0500

    Update env.d.ts

commit 3f37ce54758b017c9281b7fad9b040a47630ec66
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:57:04 2023 -0500

    Update .env.example

commit 1026036f4dd529e9531c53084450ce768cfca4c1
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:50:36 2023 -0500

    Update docker-compose.yml

commit a61cf7b8c51d4a9bd73a20bd67abc29891c11463
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:50:00 2023 -0500

    Update Dockerfile

commit 79610d6648755cd5ec45215b9fdbe04ba8242fcf
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:35:34 2023 -0500

    Update package-lock.json

commit e40853fd2b77f2db5be1c3dfd8b170d650e23271
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:30:17 2023 -0500

    Update envConstants.js

commit 5529bc61b43f279fb4418c3851be2f9011b6454d
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:25:58 2023 -0500

    Update docker-compose.yml

commit 07848cc464a64f7cad484e24a1310dc61aa03b18
Merge: ec628a3 72e9828
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:24:03 2023 -0500

    Merge branch 'danny-avila:main' into openid-client

commit ec628a3044ba963b4e733c72229400074e7c2bc4
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:23:16 2023 -0500

    Update envConstants.js

commit 21272221db0f58c244f08335482d45b177d338ab
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:21:59 2023 -0500

    Update Registration.spec.tsx

commit d3f2949c0484d5760e7b689501852f86209992a3
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:21:12 2023 -0500

    Update Login.spec.tsx

commit f2cf23ddd6708a3bb8d032dde5f1ce300dbe8cad
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:20:15 2023 -0500

    Update Registration.tsx

commit 482c346b2a7baf958665c9474223d2557504dee5
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:17:53 2023 -0500

    Update Login.tsx

commit 2f017aa5bf4ef91b73fe027fb346132e1a5d8b87
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:14:17 2023 -0500

    Update env.d.ts

commit addfd95cf93ef19cae05bab652d634af64313e6a
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:13:16 2023 -0500

    Create openidStrategy.js

commit 84c3b5c2f078494d8380f3a02e3ba2d935d8d79f
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:09:02 2023 -0500

    Update oauth.js

commit 63225cdf33b7f42005b4a446797acbd91b7ee4a7
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:07:35 2023 -0500

    Update index.js

commit 6efe4dafd4359ed1c3139468bf9d43f70bbaf6aa
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:04:55 2023 -0500

    Update package.json

commit 201badbbb5a5c8d48f5c4cba3a1349d4cfc7a070
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:03:37 2023 -0500

    Update User.js

commit 7d13d5c303465be9b1268e5f6d9bdf7bb8dfb2e4
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:02:29 2023 -0500

    Update Dockerfile

commit 2ef7f84ea77f281c3dce61211d9fd841a6424e65
Author: bsu3338 <bsu3338@users.noreply.github.com>
Date:   Mon Jun 12 23:00:42 2023 -0500

    Update .env.example

* Update openidStrategy.js

* Update .env.example

* Update .env.example

* Update docker-compose.yml

* Update env.d.ts

* Update .env.example

* Update .env.example

* Update config.js

* Update Login.tsx

* Update config.js

* Update Login.tsx

* Update Registration.tsx

* Update docker-compose.yml

* Update openidStrategy.js

* Update docker-compose.yml

* Update config.spec.js

* Update Login.spec.tsx

* Update Registration.spec.tsx

* Update types.ts

* Update .env.example

* Update package-lock.json

* Update openidStrategy.js

* Update openidStrategy.js

* Update config.js

* Update config.js

* Update Login.tsx

* Update Registration.tsx

* Update oauth.js

* Update openidStrategy.js

* Update openidStrategy.js

* Update Registration.tsx

* Update Login.tsx

* Update Login.tsx

* Update Registration.tsx

* Update Registration.tsx

* Update index.js

* Update index.js

* Update .env.example

* Update user_auth_system.md

updated instruction that includes OpenID set up

* Update package.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update package-lock.json

* Update openidStrategy.js

* Update openidStrategy.js

Lookup user based on openID instead of email.  This is because not all AzureAD users may have an email tied to their account

* Update openidStrategy.js

First try to match an email, then try openIdID

* Update openidStrategy.js

* Update openidStrategy.js

Consider a family name or given name is not provided

---------

Co-authored-by: Fuegovic <32828263+fuegovic@users.noreply.github.com>
This commit is contained in:
bsu3338 2023-06-24 21:45:52 -05:00 committed by GitHub
parent 7efb90366f
commit eceba36f54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 44617 additions and 44101 deletions

View file

@ -187,6 +187,22 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET= GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=/oauth/google/callback GOOGLE_CALLBACK_URL=/oauth/google/callback
# OpenID:
# See OpenID provider to get the below values
# Create random string for OPENID_SESSION_SECRET
# For Azure AD
# ISSUER: https://login.microsoftonline.com/(tenant id)/v2.0/
# SCOPE: openid profile email
OPENID_CLIENT_ID=
OPENID_CLIENT_SECRET=
OPENID_ISSUER=
OPENID_SESSION_SECRET=
OPENID_SCOPE="openid profile email"
OPENID_CALLBACK_URL=/oauth/openid/callback
# If LABEL and URL are left empty, then the default OpenID label and logo are used.
VITE_OPENID_LABEL=
VITE_OPENID_URL=
# Set the expiration delay for the secure cookie with the JWT token # Set the expiration delay for the secure cookie with the JWT token
# Delay is in millisecond e.g. 7 days is 1000*60*60*24*7 # Delay is in millisecond e.g. 7 days is 1000*60*60*24*7
SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7 SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7

View file

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

View file

@ -38,6 +38,7 @@
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.17.3",
"googleapis": "^118.0.0", "googleapis": "^118.0.0",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"html": "^1.0.0", "html": "^1.0.0",
@ -52,6 +53,7 @@
"mongoose": "^7.1.1", "mongoose": "^7.1.1",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.1",
"openai": "^3.2.1", "openai": "^3.2.1",
"openid-client": "^5.4.2",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",

View file

@ -1,4 +1,5 @@
const express = require('express'); const express = require('express');
const session = require('express-session');
const connectDb = require('../lib/db/connectDb'); const connectDb = require('../lib/db/connectDb');
const migrateDb = require('../lib/db/migrateDb'); const migrateDb = require('../lib/db/migrateDb');
const indexSync = require('../lib/db/indexSync'); const indexSync = require('../lib/db/indexSync');
@ -41,6 +42,15 @@ config.validate(); // Validate the config
if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) { if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) {
require('../strategies/facebookStrategy'); require('../strategies/facebookStrategy');
} }
if (process.env.OPENID_CLIENT_ID && process.env.OPENID_CLIENT_SECRET && process.env.OPENID_ISSUER && process.env.OPENID_SCOPE && process.env.OPENID_SESSION_SECRET) {
app.use(session({
secret: process.env.OPENID_SESSION_SECRET,
resave: false,
saveUninitialized: false
}));
app.use(passport.session());
require('../strategies/openidStrategy');
}
app.use('/oauth', routes.oauth); app.use('/oauth', routes.oauth);
// api endpoint // api endpoint
app.use('/api/auth', routes.auth); app.use('/api/auth', routes.auth);

View file

@ -1,7 +1,6 @@
const request = require('supertest'); const request = require('supertest');
const express = require('express'); const express = require('express');
const routes = require('../'); const routes = require('../');
const app = express(); const app = express();
app.use('/api/config', routes.config); app.use('/api/config', routes.config);
@ -9,6 +8,12 @@ afterEach(() => {
delete process.env.APP_TITLE; delete process.env.APP_TITLE;
delete process.env.GOOGLE_CLIENT_ID; delete process.env.GOOGLE_CLIENT_ID;
delete process.env.GOOGLE_CLIENT_SECRET; delete process.env.GOOGLE_CLIENT_SECRET;
delete process.env.OPENID_CLIENT_ID;
delete process.env.OPENID_CLIENT_SECRET;
delete process.env.OPENID_ISSUER;
delete process.env.OPENID_SESSION_SECRET;
delete process.env.VITE_OPENID_LABEL;
delete process.env.VITE_OPENID_URL;
delete process.env.DOMAIN_SERVER; delete process.env.DOMAIN_SERVER;
delete process.env.ALLOW_REGISTRATION; delete process.env.ALLOW_REGISTRATION;
}); });
@ -21,6 +26,12 @@ describe.skip('GET /', () => {
process.env.APP_TITLE = 'Test Title'; process.env.APP_TITLE = 'Test Title';
process.env.GOOGLE_CLIENT_ID = 'Test Google Client Id'; process.env.GOOGLE_CLIENT_ID = 'Test Google Client Id';
process.env.GOOGLE_CLIENT_SECRET = 'Test Google Client Secret'; process.env.GOOGLE_CLIENT_SECRET = 'Test Google 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';
process.env.OPENID_SESSION_SECRET= 'Test Secret';
process.env.VITE_OPENID_LABEL= 'Test OpenID';
process.env.VITE_OPENID_URL= 'http://test-server.com';
process.env.DOMAIN_SERVER = 'http://test-server.com'; process.env.DOMAIN_SERVER = 'http://test-server.com';
process.env.ALLOW_REGISTRATION = 'true'; process.env.ALLOW_REGISTRATION = 'true';
@ -30,8 +41,11 @@ describe.skip('GET /', () => {
expect(response.body).toEqual({ expect(response.body).toEqual({
appTitle: 'Test Title', appTitle: 'Test Title',
googleLoginEnabled: true, googleLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidUrl: 'http://test-server.com',
serverDomain: 'http://test-server.com', serverDomain: 'http://test-server.com',
registrationEnabled: 'true', registrationEnabled: 'true',
}); });
}); });
}); });

View file

@ -5,10 +5,13 @@ router.get('/', async function (req, res) {
try { try {
const appTitle = process.env.APP_TITLE || 'LibreChat'; const appTitle = process.env.APP_TITLE || 'LibreChat';
const googleLoginEnabled = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET; const googleLoginEnabled = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
const openidLoginEnabled = !!process.env.OPENID_CLIENT_ID && !!process.env.OPENID_CLIENT_SECRET && !!process.env.OPENID_ISSUER && !!process.env.OPENID_SESSION_SECRET;
const openidLabel = process.env.VITE_OPENID_LABEL || 'Login with OpenID';
const openidUrl = process.env.VITE_OPENID_URL;
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080'; const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
const registrationEnabled = process.env.ALLOW_REGISTRATION || true; const registrationEnabled = process.env.ALLOW_REGISTRATION || true;
return res.status(200).send({appTitle, googleLoginEnabled, serverDomain, registrationEnabled}); return res.status(200).send({appTitle, googleLoginEnabled, openidLoginEnabled, openidLabel, openidUrl, serverDomain, registrationEnabled});
} catch (err) { } catch (err) {
console.error(err); console.error(err);
return res.status(500).send({error: err.message}); return res.status(500).send({error: err.message});

View file

@ -62,4 +62,29 @@ router.get(
} }
); );
router.get(
'/openid',
passport.authenticate('openid', {
session: false
})
);
router.get(
'/openid/callback',
passport.authenticate('openid', {
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; module.exports = router;

View file

@ -0,0 +1,123 @@
const passport = require('passport');
const jwt = require('jsonwebtoken');
const { Issuer, Strategy: OpenIDStrategy } = require('openid-client');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const config = require('../../config/loader');
const domains = config.domains;
const User = require('../models/User');
let crypto;
try {
crypto = require('node:crypto');
} catch (err) {
console.error('crypto support is disabled!');
}
const downloadImage = async (url, imagePath, accessToken) => {
try {
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
},
responseType: 'arraybuffer'
});
fs.mkdirSync(path.dirname(imagePath), { recursive: true });
fs.writeFileSync(imagePath, response.data);
const fileName = path.basename(imagePath);
return `/images/openid/${fileName}`;
} catch (error) {
console.error(`Error downloading image at URL "${url}": ${error}`);
return '';
}
};
Issuer.discover(process.env.OPENID_ISSUER)
.then(issuer => {
const client = new issuer.Client({
client_id: process.env.OPENID_CLIENT_ID,
client_secret: process.env.OPENID_CLIENT_SECRET,
redirect_uris: [domains.server + process.env.OPENID_CALLBACK_URL]
});
const openidLogin = new OpenIDStrategy(
{
client,
params: {
scope: process.env.OPENID_SCOPE
}
},
async (tokenset, userinfo, done) => {
try {
let user = await User.findOne({ openidId: userinfo.sub });
if (!user) {
user = await User.findOne({ email: userinfo.email });
}
let fullName = '';
if (userinfo.given_name && userinfo.family_name) {
fullName = userinfo.given_name + ' ' + userinfo.family_name;
} else if (userinfo.given_name) {
fullName = userinfo.given_name;
} else if (userinfo.family_name) {
fullName = userinfo.family_name;
}
if (!user) {
user = new User({
provider: 'openid',
openidId: userinfo.sub,
username: userinfo.given_name || '',
email: userinfo.email || '',
emailVerified: userinfo.email_verified || false,
name: fullName
});
} else {
user.provider = 'openid';
user.openidId = userinfo.sub;
user.username = userinfo.given_name || '';
user.name = fullName;
}
if (userinfo.picture) {
const imageUrl = userinfo.picture;
let fileName;
if (crypto) {
const hash = crypto.createHash('sha256');
hash.update(userinfo.sub);
fileName = hash.digest('hex') + '.png';
} else {
fileName = userinfo.sub + '.png';
}
const imagePath = path.join(__dirname, '..', '..', 'client', 'public', 'images', 'openid', fileName);
const imagePathOrEmpty = await downloadImage(imageUrl, imagePath, tokenset.access_token);
user.avatar = imagePathOrEmpty;
} else {
user.avatar = '';
}
await user.save();
done(null, user);
} catch (err) {
done(err);
}
}
);
passport.use('openid', openidLogin);
})
.catch(err => {
console.error(err);
});

3
client/env.d.ts vendored
View file

@ -3,6 +3,9 @@ interface ImportMetaEnv {
readonly VITE_SERVER_URL_DEV: string; readonly VITE_SERVER_URL_DEV: string;
readonly VITE_SERVER_URL_PROD: string; readonly VITE_SERVER_URL_PROD: string;
readonly VITE_SHOW_GOOGLE_LOGIN_OPTION: string; readonly VITE_SHOW_GOOGLE_LOGIN_OPTION: string;
readonly ALLOW_OPENID: string;
readonly VITE_OPENID_LABEL: string;
readonly VITE_OPENID_URL: string;
readonly VITE_CLIENT_URL_DEV: string; readonly VITE_CLIENT_URL_DEV: string;
readonly VITE_CLIENT_URL_PROD: string; readonly VITE_CLIENT_URL_PROD: string;
} }

View file

@ -78,6 +78,36 @@ function Login() {
</div> </div>
</> </>
)} )}
{startupConfig?.openidLoginEnabled && (
<>
<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 OpenID"
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/openid`}
>
{startupConfig.openidUrl ? (
<img src={startupConfig.openidUrl} alt="OpenID Logo" className="h-5 w-5"/>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
id="openid"
className="h-5 w-5"
>
<path
d="M271.5 432l-68 32C88.5 453.7 0 392.5 0 318.2c0-71.5 82.5-131 191.7-144.3v43c-71.5 12.5-124 53-124 101.3 0 51 58.5 93.3 135.7 103v-340l68-33.2v384zM448 291l-131.3-28.5 36.8-20.7c-19.5-11.5-43.5-20-70-24.8v-43c46.2 5.5 87.7 19.5 120.3 39.3l35-19.8L448 291z"
></path>
</svg>
)}
<p>{startupConfig.openidLabel}</p>
</a>
</div>
</>
)}
</div> </div>
</div> </div>
); );

View file

@ -312,6 +312,36 @@ function Registration() {
</div> </div>
</> </>
)} )}
{startupConfig?.openidLoginEnabled && (
<>
<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 OpenID"
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/openid`}
>
{startupConfig.openidUrl ? (
<img src={startupConfig.openidUrl} alt="OpenID Logo" className="h-5 w-5"/>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
id="openid"
className="h-5 w-5"
>
<path
d="M271.5 432l-68 32C88.5 453.7 0 392.5 0 318.2c0-71.5 82.5-131 191.7-144.3v43c-71.5 12.5-124 53-124 101.3 0 51 58.5 93.3 135.7 103v-340l68-33.2v384zM448 291l-131.3-28.5 36.8-20.7c-19.5-11.5-43.5-20-70-24.8v-43c46.2 5.5 87.7 19.5 120.3 39.3l35-19.8L448 291z"
></path>
</svg>
)}
<p>{startupConfig.openidLabel}</p>
</a>
</div>
</>
)}
</div> </div>
</div> </div>
); );

View file

@ -23,6 +23,9 @@ const setup = ({
isError: false, isError: false,
data: { data: {
googleLoginEnabled: true, googleLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidUrl: 'http://test-server.com',
registrationEnabled: true, registrationEnabled: true,
serverDomain: 'mock-server' serverDomain: 'mock-server'
} }

View file

@ -23,6 +23,9 @@ const setup = ({
isError: false, isError: false,
data: { data: {
googleLoginEnabled: true, googleLoginEnabled: true,
openidLoginEnabled: true,
openidLabel: 'Test OpenID',
openidUrl: 'http://test-server.com',
registrationEnabled: true, registrationEnabled: true,
serverDomain: 'mock-server' serverDomain: 'mock-server'
} }

View file

@ -237,6 +237,9 @@ export type TResetPassword = {
export type TStartupConfig = { export type TStartupConfig = {
appTitle: boolean; appTitle: boolean;
googleLoginEnabled: boolean; googleLoginEnabled: boolean;
openidLoginEnabled: boolean;
openidLabel: string;
openidUrl: string;
serverDomain: string; serverDomain: string;
registrationEnabled: boolean; registrationEnabled: boolean;
} }

View file

@ -43,6 +43,7 @@ services:
- ./.env.development:/app/.env.development - ./.env.development:/app/.env.development
- ./.env.production:/app/.env.production - ./.env.production:/app/.env.production
- /app/api/node_modules - /app/api/node_modules
- ./images:/app/client/public/images
mongodb: mongodb:
container_name: chat-mongodb container_name: chat-mongodb
ports: ports:

View file

@ -17,22 +17,18 @@ DOMAIN_CLIENT=http://localhost:3080
*Please Note: If you are wanting this to work in development mode, you will need to create a file called `.env.development` in the root directory and set `DOMAIN_CLIENT` to `http://localhost:3090` or whatever port is provided by vite when runnning `npm run frontend-dev`* *Please Note: If you are wanting this to work in development mode, you will need to create a file called `.env.development` in the root directory and set `DOMAIN_CLIENT` to `http://localhost:3090` or whatever port is provided by vite when runnning `npm run frontend-dev`*
The first time you run the application, you should register a new account by clicking the "Sign up" link on the login page. The first account registered will receive an admin role. The admin account does not currently have extended functionality, but is valuable should you choose to create an admin dashboard for user management. Important: When you run the app for the first time, you need to create a new account by clicking on "Sign up" on the login page. The first account you make will be the admin account. The admin account doesn't have any special features right now, but it might be useful if you want to make an admin dashboard to manage other users later.
## **Migrating Previous Conversations and Presets to new User Account** ⚠️ **__For the first time, you should use a local account (email and password) to sign up and log in.__**
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. ---
⚠️**IMPORTANT**: 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. You should register and login with a local account (email and password) for the first time.
## **OAuth2/Social Login** ## **OAuth2/Social Login**
The application is setup to support OAuth2/Social Login with Google. All of the code is in place for Facebook login as well, but this has not been tested because the setup process with Facebook was honestly just too painful for me to deal with. I plan to add support for other OAuth2 providers including Github and Discord at a later time. ## How to Set Up Google Authentication
To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the `/.env` file. To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the `/.env` file.
### *Instructions for setting up Google login are provided below.*
```
1. Go to "APIs and Services" in your Google Cloud account and click on "Credentials". 1. Go to "APIs and Services" in your Google Cloud account and click on "Credentials".
2. Click on "Configure consent screen" and select "External" as the user type. 2. Click on "Configure consent screen" and select "External" as the user type.
3. Add "profile", "email" and "openid" as the scopes for your app. These are the first three checkboxes when you click on "Add or remove scopes". 3. Add "profile", "email" and "openid" as the scopes for your app. These are the first three checkboxes when you click on "Add or remove scopes".
@ -44,7 +40,33 @@ To enable Google login, you must create an application in the [Google Cloud Cons
9. Click on "Create" and copy your client ID and client secret. 9. Click on "Create" and copy your client ID and client secret.
10. Paste them into your /.env file. 10. Paste them into your /.env file.
11. Enable the feature in the /.env file 11. Enable the feature in the /.env file
---
## How to Set Up OpenID Authentication with Azure AD
1. Go to the Azure Portal and sign in with your account.
2. In the search box, type Azure Active Directory and click on it.
3. On the left menu, click on App registrations and then on New registration.
4. Give your app a name and select Web as the platform type.
5. In the Redirect URI field, enter https://fqdn/oauth/openid/callback and click on Register.
6. You will see an Overview page with some information about your app. Copy the Application (client) ID and the Directory (tenant) ID and save them somewhere.
7. On the left menu, click on Authentication and check the boxes for Access tokens and ID tokens under Implicit grant and hybrid flows.
8. On the left menu, click on Certificates & Secrets and then on New client secret. Give your secret a name and an expiration date and click on Add.
9. You will see a Value column with your secret. Copy it and save it somewhere. Don't share it with anyone!
10. Open the .env file in your project folder and add the following variables with the values you copied:
``` ```
OPENID_CLIENT_ID=Your Application (client) ID
OPENID_CLIENT_SECRET=Your client secret
OPENID_ISSUER=https://login.microsoftonline.com/Your Directory (tenant ID)/v2.0/
OPENID_SESSION_SECRET=Any random string
OPENID_SCOPE=openid profile email
OPENID_CALLBACK_URL=/oauth/openid/callback
```
11. Save the .env file and you're done! You have successfully set up OpenID authentication with Azure AD for your app.
---
## **Email and Password Reset** ## **Email and Password Reset**
@ -58,6 +80,11 @@ To disable or re-enable registration, open up the root `.env` file and set `ALLO
If you previously implemented your own user system using the original scaffolding that was provided, you will no longer see conversations and presets by switching to the new user system. This is because of a design flaw in the scaffolding implementation that was problematic for the inclusion of social login. If you previously implemented your own user system using the original scaffolding that was provided, you will no longer see conversations and presets by switching to the new user system. This is because of a design flaw in the scaffolding implementation that was problematic for the inclusion of social login.
### 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.
--- ---
## [Go Back to ReadMe](../../README.md) ## [Go Back to ReadMe](../../README.md)

88398
package-lock.json generated

File diff suppressed because it is too large Load diff