Files
the-bible/backend/src/auth.js

211 lines
6.5 KiB
JavaScript

const passport = require('passport');
const OpenIDConnectStrategy = require('passport-openidconnect').Strategy;
const { userOps } = require('./database');
// Configure OpenID Connect strategy
function configureAuth(app) {
// Check if OIDC is configured
const isOIDCConfigured = process.env.OIDC_ISSUER &&
process.env.OIDC_CLIENT_ID &&
process.env.OIDC_CLIENT_SECRET &&
process.env.OIDC_AUTH_URL &&
process.env.OIDC_TOKEN_URL &&
process.env.OIDC_USERINFO_URL;
if (!isOIDCConfigured) {
console.log('OpenID Connect not configured. Authentication features disabled.');
console.log('To enable authentication, set the following environment variables:');
console.log('- OIDC_ISSUER');
console.log('- OIDC_CLIENT_ID');
console.log('- OIDC_CLIENT_SECRET');
console.log('- OIDC_AUTH_URL');
console.log('- OIDC_TOKEN_URL');
console.log('- OIDC_USERINFO_URL');
console.log('- OIDC_CALLBACK_URL (optional, defaults to /auth/callback)');
// Add disabled auth routes
app.get('/auth/login', (req, res) => {
res.status(501).json({ error: 'Authentication not configured' });
});
app.get('/auth/user', (req, res) => {
res.status(401).json({ error: 'Authentication not configured' });
});
app.post('/auth/logout', (req, res) => {
res.status(501).json({ error: 'Authentication not configured' });
});
return;
}
// Session configuration
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to false for development/HTTP
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: 'lax'
}
}));
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Log OIDC configuration for debugging
console.log('OIDC Configuration:');
console.log('Issuer:', process.env.OIDC_ISSUER);
console.log('Auth URL:', process.env.OIDC_AUTH_URL);
console.log('Token URL:', process.env.OIDC_TOKEN_URL);
console.log('UserInfo URL:', process.env.OIDC_USERINFO_URL);
console.log('Client ID:', process.env.OIDC_CLIENT_ID);
console.log('Callback URL:', process.env.OIDC_CALLBACK_URL || '/auth/callback');
// Configure OpenID Connect strategy
passport.use('oidc', new OpenIDConnectStrategy({
issuer: process.env.OIDC_ISSUER,
authorizationURL: process.env.OIDC_AUTH_URL,
tokenURL: process.env.OIDC_TOKEN_URL,
userInfoURL: process.env.OIDC_USERINFO_URL,
clientID: process.env.OIDC_CLIENT_ID,
clientSecret: process.env.OIDC_CLIENT_SECRET,
callbackURL: process.env.OIDC_CALLBACK_URL || '/auth/callback',
scope: 'openid email profile',
skipUserProfile: false
}, (issuer, sub, profile, accessToken, refreshToken, done) => {
console.log('OIDC Strategy callback:');
console.log('Issuer:', issuer);
console.log('Profile data (sub):', sub);
// Based on debugging: profile is actually the callback function
const callback = profile;
console.log('Using profile parameter as callback:', typeof callback);
// Extract user info from sub parameter (which contains the actual profile data)
const userProfile = {
sub: sub.id,
email: sub.emails?.[0]?.value,
name: sub.displayName || sub.name?.givenName || sub.username
};
console.log('Extracted user profile:', userProfile);
userOps.findOrCreateUser(userProfile, (err, user) => {
if (err) {
console.error('Database error in findOrCreateUser:', err);
return callback(err);
}
console.log('User from database:', user);
callback(null, user);
});
}));
// Serialize user for session
passport.serializeUser((user, done) => {
done(null, user.id);
});
// Deserialize user from session
passport.deserializeUser((id, done) => {
userOps.findById(id, (err, user) => {
done(err, user);
});
});
// Auth routes
app.get('/auth/login', passport.authenticate('oidc'));
app.get('/auth/callback', (req, res, next) => {
console.log('Auth callback received');
console.log('Query params:', req.query);
console.log('Body:', req.body);
passport.authenticate('oidc', (err, user, info) => {
console.log('Passport authenticate callback:');
console.log('Error:', err);
console.log('User:', user);
console.log('Info:', info);
if (err) {
console.error('Authentication error:', err);
return res.redirect('/?error=auth_failed');
}
if (!user) {
console.error('No user returned from authentication');
return res.redirect('/?error=no_user');
}
req.logIn(user, (err) => {
if (err) {
console.error('Login error:', err);
return res.redirect('/?error=login_failed');
}
console.log('User successfully logged in:', user);
res.redirect('/');
});
})(req, res, next);
});
app.post('/auth/logout', (req, res) => {
req.logout((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Session destruction failed' });
}
res.json({ message: 'Logged out successfully' });
});
});
});
// Get current user
app.get('/auth/user', (req, res) => {
console.log('Auth check - Session ID:', req.sessionID);
console.log('Auth check - Is authenticated:', req.isAuthenticated());
console.log('Auth check - User:', req.user);
console.log('Auth check - Session:', req.session);
if (req.isAuthenticated()) {
res.json({
user: {
id: req.user.id,
email: req.user.email,
name: req.user.name
}
});
} else {
res.status(401).json({ error: 'Not authenticated' });
}
});
}
// Middleware to require authentication
function requireAuth(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ error: 'Authentication required' });
}
// Middleware to optionally include user info
function optionalAuth(req, res, next) {
// Always proceed, but user info will be available if authenticated
next();
}
module.exports = {
configureAuth,
requireAuth,
optionalAuth
};