diff --git a/api/package.json b/api/package.json index 733d705295..80e28ce179 100644 --- a/api/package.json +++ b/api/package.json @@ -51,7 +51,9 @@ "openai": "^3.2.1", "openid-client": "^5.4.2", "passport": "^0.6.0", + "passport-discord": "^0.1.4", "passport-facebook": "^3.0.0", + "passport-github2": "^0.1.12", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", diff --git a/api/server/index.js b/api/server/index.js index c574432a18..5cb7b321cd 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -11,6 +11,15 @@ const passport = require('passport'); const port = process.env.PORT || 3080; const host = process.env.HOST || 'localhost'; const projectPath = path.join(__dirname, '..', '..', 'client'); +const { + jwtLogin, + passportLogin, + googleLogin, + githubLogin, + discordLogin, + facebookLogin, + setupOpenId, +} = require('../strategies'); // Init the config and validate it const config = require('../../config/loader'); @@ -40,19 +49,19 @@ config.validate(); // Validate the config // OAUTH app.use(passport.initialize()); - require('../strategies/jwtStrategy'); - require('../strategies/localStrategy'); + passport.use(await jwtLogin()); + passport.use(await passportLogin()); if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { - require('../strategies/googleStrategy'); + passport.use(await googleLogin()); } if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) { - require('../strategies/facebookStrategy'); + passport.use(await facebookLogin()); } if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) { - require('../strategies/githubStrategy'); + passport.use(await githubLogin()); } if (process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET) { - require('../strategies/discordStrategy'); + passport.use(await discordLogin()); } if ( process.env.OPENID_CLIENT_ID && @@ -69,7 +78,7 @@ config.validate(); // Validate the config }), ); app.use(passport.session()); - require('../strategies/openidStrategy'); + await setupOpenId(); } app.use('/oauth', routes.oauth); // api endpoint diff --git a/api/strategies/discordStrategy.js b/api/strategies/discordStrategy.js index a2d01b60c7..685c81a47f 100644 --- a/api/strategies/discordStrategy.js +++ b/api/strategies/discordStrategy.js @@ -1,51 +1,51 @@ -const passport = require('passport'); const { Strategy: DiscordStrategy } = require('passport-discord'); const User = require('../models/User'); const config = require('../../config/loader'); const domains = config.domains; -const discordLogin = new DiscordStrategy( - { - clientID: process.env.DISCORD_CLIENT_ID, - clientSecret: process.env.DISCORD_CLIENT_SECRET, - callbackURL: `${domains.server}${process.env.DISCORD_CALLBACK_URL}`, - scope: ['identify', 'email'], // Request scopes - authorizationURL: 'https://discord.com/api/oauth2/authorize?prompt=none', // Add the prompt query parameter - }, - async (accessToken, refreshToken, profile, cb) => { - try { - const email = profile.email; - const discordId = profile.id; +const discordLogin = async () => + new DiscordStrategy( + { + clientID: process.env.DISCORD_CLIENT_ID, + clientSecret: process.env.DISCORD_CLIENT_SECRET, + callbackURL: `${domains.server}${process.env.DISCORD_CALLBACK_URL}`, + scope: ['identify', 'email'], // Request scopes + authorizationURL: 'https://discord.com/api/oauth2/authorize?prompt=none', // Add the prompt query parameter + }, + async (accessToken, refreshToken, profile, cb) => { + try { + const email = profile.email; + const discordId = profile.id; - const oldUser = await User.findOne({ email }); - if (oldUser) { - return cb(null, oldUser); + const oldUser = await User.findOne({ email }); + if (oldUser) { + return cb(null, oldUser); + } + + let avatarURL; + if (profile.avatar) { + const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'; + avatarURL = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`; + } else { + const defaultAvatarNum = Number(profile.discriminator) % 5; + avatarURL = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNum}.png`; + } + + const newUser = await User.create({ + provider: 'discord', + discordId, + username: profile.username, + email, + name: profile.global_name, + avatar: avatarURL, + }); + + cb(null, newUser); + } catch (err) { + console.error(err); + cb(err); } + }, + ); - let avatarURL; - if (profile.avatar) { - const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'; - avatarURL = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`; - } else { - const defaultAvatarNum = Number(profile.discriminator) % 5; - avatarURL = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNum}.png`; - } - - const newUser = await User.create({ - provider: 'discord', - discordId, - username: profile.username, - email, - name: profile.global_name, - avatar: avatarURL, - }); - - cb(null, newUser); - } catch (err) { - console.error(err); - cb(err); - } - }, -); - -passport.use(discordLogin); +module.exports = discordLogin; diff --git a/api/strategies/facebookStrategy.js b/api/strategies/facebookStrategy.js index f7700fd51e..91afda7e02 100644 --- a/api/strategies/facebookStrategy.js +++ b/api/strategies/facebookStrategy.js @@ -1,59 +1,59 @@ -const passport = require('passport'); const FacebookStrategy = require('passport-facebook').Strategy; const User = require('../models/User'); const config = require('../../config/loader'); const domains = config.domains; // facebook strategy -const facebookLogin = new FacebookStrategy( - { - clientID: process.env.FACEBOOK_APP_ID, - clientSecret: process.env.FACEBOOK_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)' - // ] - }, - async (accessToken, refreshToken, profile, done) => { - console.log('facebookLogin => profile', profile); - try { - const oldUser = await User.findOne({ email: profile.emails[0].value }); +const facebookLogin = async () => + new FacebookStrategy( + { + clientID: process.env.FACEBOOK_APP_ID, + clientSecret: process.env.FACEBOOK_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)' + // ] + }, + async (accessToken, refreshToken, profile, done) => { + console.log('facebookLogin => profile', profile); + try { + const oldUser = await User.findOne({ email: profile.emails[0].value }); - if (oldUser) { - console.log('FACEBOOK LOGIN => found user', oldUser); - return done(null, oldUser); + if (oldUser) { + console.log('FACEBOOK LOGIN => found user', oldUser); + return done(null, oldUser); + } + } catch (err) { + console.log(err); } - } catch (err) { - console.log(err); - } - // register user - try { - const newUser = await new User({ - provider: 'facebook', - facebookId: profile.id, - username: profile.name.givenName + profile.name.familyName, - email: profile.emails[0].value, - name: profile.displayName, - avatar: profile.photos[0].value, - }).save(); + // register user + try { + const newUser = await new User({ + provider: 'facebook', + facebookId: profile.id, + username: profile.name.givenName + profile.name.familyName, + email: profile.emails[0].value, + name: profile.displayName, + avatar: profile.photos[0].value, + }).save(); - done(null, newUser); - } catch (err) { - console.log(err); - } - }, -); + done(null, newUser); + } catch (err) { + console.log(err); + } + }, + ); -passport.use(facebookLogin); +module.exports = facebookLogin; diff --git a/api/strategies/githubStrategy.js b/api/strategies/githubStrategy.js index 62e6075b1e..e021afbce1 100644 --- a/api/strategies/githubStrategy.js +++ b/api/strategies/githubStrategy.js @@ -1,4 +1,3 @@ -const passport = require('passport'); const { Strategy: GitHubStrategy } = require('passport-github2'); const config = require('../../config/loader'); const domains = config.domains; @@ -6,42 +5,43 @@ 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 githubLogin = async () => + 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); } + }, + ); - 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); +module.exports = githubLogin; diff --git a/api/strategies/googleStrategy.js b/api/strategies/googleStrategy.js index ff6e07f0b6..7b02757e30 100644 --- a/api/strategies/googleStrategy.js +++ b/api/strategies/googleStrategy.js @@ -1,4 +1,3 @@ -const passport = require('passport'); const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); const config = require('../../config/loader'); const domains = config.domains; @@ -6,38 +5,39 @@ const domains = config.domains; const User = require('../models/User'); // google strategy -const googleLogin = new GoogleStrategy( - { - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: `${domains.server}${process.env.GOOGLE_CALLBACK_URL}`, - proxy: true, - }, - async (accessToken, refreshToken, profile, cb) => { - try { - const oldUser = await User.findOne({ email: profile.emails[0].value }); - if (oldUser) { - return cb(null, oldUser); +const googleLogin = async () => + new GoogleStrategy( + { + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: `${domains.server}${process.env.GOOGLE_CALLBACK_URL}`, + proxy: true, + }, + async (accessToken, refreshToken, profile, cb) => { + try { + const oldUser = await User.findOne({ email: profile.emails[0].value }); + if (oldUser) { + return cb(null, oldUser); + } + } catch (err) { + console.log(err); } - } catch (err) { - console.log(err); - } - try { - const newUser = await new User({ - provider: 'google', - googleId: profile.id, - username: profile.name.givenName, - email: profile.emails[0].value, - emailVerified: profile.emails[0].verified, - name: `${profile.name.givenName} ${profile.name.familyName}`, - avatar: profile.photos[0].value, - }).save(); - cb(null, newUser); - } catch (err) { - console.log(err); - } - }, -); + try { + const newUser = await new User({ + provider: 'google', + googleId: profile.id, + username: profile.name.givenName, + email: profile.emails[0].value, + emailVerified: profile.emails[0].verified, + name: `${profile.name.givenName} ${profile.name.familyName}`, + avatar: profile.photos[0].value, + }).save(); + cb(null, newUser); + } catch (err) { + console.log(err); + } + }, + ); -passport.use(googleLogin); +module.exports = googleLogin; diff --git a/api/strategies/index.js b/api/strategies/index.js new file mode 100644 index 0000000000..1c49c2b1cd --- /dev/null +++ b/api/strategies/index.js @@ -0,0 +1,17 @@ +const passportLogin = require('./localStrategy'); +const googleLogin = require('./googleStrategy'); +const githubLogin = require('./githubStrategy'); +const discordLogin = require('./discordStrategy'); +const jwtLogin = require('./jwtStrategy'); +const facebookLogin = require('./facebookStrategy'); +const setupOpenId = require('./openidStrategy'); + +module.exports = { + passportLogin, + googleLogin, + githubLogin, + discordLogin, + jwtLogin, + facebookLogin, + setupOpenId, +}; diff --git a/api/strategies/jwtStrategy.js b/api/strategies/jwtStrategy.js index 8d1aeb9b8f..d27124d21b 100644 --- a/api/strategies/jwtStrategy.js +++ b/api/strategies/jwtStrategy.js @@ -1,26 +1,26 @@ -const passport = require('passport'); const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); const User = require('../models/User'); // JWT strategy -const jwtLogin = new JwtStrategy( - { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: process.env.JWT_SECRET, - }, - async (payload, done) => { - try { - const user = await User.findById(payload.id); - if (user) { - done(null, user); - } else { - console.log('JwtStrategy => no user found'); - done(null, false); +const jwtLogin = async () => + new JwtStrategy( + { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET, + }, + async (payload, done) => { + try { + const user = await User.findById(payload.id); + if (user) { + done(null, user); + } else { + console.log('JwtStrategy => no user found'); + done(null, false); + } + } catch (err) { + done(err, false); } - } catch (err) { - done(err, false); - } - }, -); + }, + ); -passport.use(jwtLogin); +module.exports = jwtLogin; diff --git a/api/strategies/localStrategy.js b/api/strategies/localStrategy.js index e225cef32c..014f1cb751 100644 --- a/api/strategies/localStrategy.js +++ b/api/strategies/localStrategy.js @@ -1,62 +1,60 @@ -const passport = require('passport'); const PassportLocalStrategy = require('passport-local').Strategy; const User = require('../models/User'); const { loginSchema } = require('./validators'); const DebugControl = require('../utils/debug.js'); -const passportLogin = new PassportLocalStrategy( - { - usernameField: 'email', - passwordField: 'password', - session: false, - passReqToCallback: true, - }, - async (req, email, password, done) => { - const { error } = loginSchema.validate(req.body); - if (error) { - log({ - title: 'Passport Local Strategy - Validation Error', - parameters: [{ name: 'req.body', value: req.body }], - }); - return done(null, false, { message: error.details[0].message }); - } - - try { - const user = await User.findOne({ email: email.trim() }); - if (!user) { +const passportLogin = async () => + new PassportLocalStrategy( + { + usernameField: 'email', + passwordField: 'password', + session: false, + passReqToCallback: true, + }, + async (req, email, password, done) => { + const { error } = loginSchema.validate(req.body); + if (error) { log({ - title: 'Passport Local Strategy - User Not Found', - parameters: [{ name: 'email', value: email }], + title: 'Passport Local Strategy - Validation Error', + parameters: [{ name: 'req.body', value: req.body }], }); - return done(null, false, { message: 'Email does not exists.' }); + return done(null, false, { message: error.details[0].message }); } - user.comparePassword(password, function (err, isMatch) { - if (err) { + try { + const user = await User.findOne({ email: email.trim() }); + if (!user) { log({ - title: 'Passport Local Strategy - Compare password error', - parameters: [{ name: 'error', value: err }], + title: 'Passport Local Strategy - User Not Found', + parameters: [{ name: 'email', value: email }], }); - return done(err); - } - if (!isMatch) { - log({ - title: 'Passport Local Strategy - Password does not match', - parameters: [{ name: 'isMatch', value: isMatch }], - }); - return done(null, false, { message: 'Incorrect password.' }); + return done(null, false, { message: 'Email does not exists.' }); } - return done(null, user); - }); - } catch (err) { - return done(err); - } - }, -); + user.comparePassword(password, function (err, isMatch) { + if (err) { + log({ + title: 'Passport Local Strategy - Compare password error', + parameters: [{ name: 'error', value: err }], + }); + return done(err); + } + if (!isMatch) { + log({ + title: 'Passport Local Strategy - Password does not match', + parameters: [{ name: 'isMatch', value: isMatch }], + }); + return done(null, false, { message: 'Incorrect password.' }); + } -passport.use(passportLogin); + return done(null, user); + }); + } catch (err) { + return done(err); + } + }, + ); function log({ title, parameters }) { DebugControl.log.functionName(title); @@ -64,3 +62,5 @@ function log({ title, parameters }) { DebugControl.log.parameters(parameters); } } + +module.exports = passportLogin; diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index cf49fa24a1..1fffe462e8 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -36,8 +36,9 @@ const downloadImage = async (url, imagePath, accessToken) => { } }; -Issuer.discover(process.env.OPENID_ISSUER) - .then((issuer) => { +async function setupOpenId() { + try { + const issuer = await Issuer.discover(process.env.OPENID_ISSUER); const client = new issuer.Client({ client_id: process.env.OPENID_CLIENT_ID, client_secret: process.env.OPENID_CLIENT_SECRET, @@ -128,7 +129,9 @@ Issuer.discover(process.env.OPENID_ISSUER) ); passport.use('openid', openidLogin); - }) - .catch((err) => { + } catch (err) { console.error(err); - }); + } +} + +module.exports = setupOpenId; diff --git a/package-lock.json b/package-lock.json index 9260f78def..3e2e0e19b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,7 @@ "packages/*" ], "dependencies": { - "axios": "^1.4.0", - "passport": "^0.6.0", - "passport-discord": "^0.1.4", - "passport-github2": "^0.1.12" + "axios": "^1.4.0" }, "devDependencies": { "@playwright/test": "^1.32.1", @@ -78,7 +75,9 @@ "openai": "^3.2.1", "openid-client": "^5.4.2", "passport": "^0.6.0", + "passport-discord": "^0.1.4", "passport-facebook": "^3.0.0", + "passport-github2": "^0.1.12", "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", diff --git a/package.json b/package.json index 5ba56f0970..ad8fc0c3c3 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,7 @@ }, "homepage": "https://github.com/danny-avila/LibreChat#readme", "dependencies": { - "axios": "^1.4.0", - "passport": "^0.6.0", - "passport-discord": "^0.1.4", - "passport-github2": "^0.1.12" + "axios": "^1.4.0" }, "devDependencies": { "@playwright/test": "^1.32.1",