prod broken
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 1m40s

This commit is contained in:
2025-11-22 19:21:49 -08:00
parent 1d00ab2e5d
commit 6d815eca7d
3 changed files with 119 additions and 117 deletions

View File

@@ -236,30 +236,30 @@ router.post('/refresh-token', async (req: Request, res: Response) => {
// --- OAuth Routes ---
const handleOAuthCallback = (req: Request, res: Response) => {
const user = req.user as { id: string; email: string };
const payload = { id: user.id, email: user.email };
const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
const refreshToken = crypto.randomBytes(64).toString('hex');
// const handleOAuthCallback = (req: Request, res: Response) => {
// const user = req.user as { id: string; email: string };
// const payload = { id: user.id, email: user.email };
// const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
// const refreshToken = crypto.randomBytes(64).toString('hex');
db.saveRefreshToken(user.id, refreshToken).then(() => {
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
});
// Redirect to a frontend page that can handle the token
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`);
}).catch(err => {
logger.error('Failed to save refresh token during OAuth callback:', { error: err });
res.redirect(`${process.env.FRONTEND_URL}/login?error=auth_failed`);
});
};
// db.saveRefreshToken(user.id, refreshToken).then(() => {
// res.cookie('refreshToken', refreshToken, {
// httpOnly: true,
// secure: process.env.NODE_ENV === 'production',
// maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
// });
// // Redirect to a frontend page that can handle the token
// res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`);
// }).catch(err => {
// logger.error('Failed to save refresh token during OAuth callback:', { error: err });
// res.redirect(`${process.env.FRONTEND_URL}/login?error=auth_failed`);
// });
// };
router.get('/google', passport.authenticate('google', { session: false }));
router.get('/google/callback', passport.authenticate('google', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
// router.get('/google', passport.authenticate('google', { session: false }));
// router.get('/google/callback', passport.authenticate('google', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
router.get('/github', passport.authenticate('github', { session: false }));
router.get('/github/callback', passport.authenticate('github', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
// router.get('/github', passport.authenticate('github', { session: false }));
// router.get('/github/callback', passport.authenticate('github', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
export default router;

View File

@@ -1,7 +1,7 @@
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';
//import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
//import { Strategy as GitHubStrategy } from 'passport-github2';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import bcrypt from 'bcrypt';
import { Request, Response, NextFunction } from 'express';
@@ -89,110 +89,110 @@ passport.use(new LocalStrategy(
));
// --- Passport Google OAuth 2.0 Strategy ---
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: '/api/auth/google/callback', // Must match the one in Google Cloud Console
scope: ['profile', 'email']
},
async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails?.[0]?.value;
if (!email) {
return done(new Error("No email found in Google profile."), false);
}
// passport.use(new GoogleStrategy({
// clientID: process.env.GOOGLE_CLIENT_ID!,
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// callbackURL: '/api/auth/google/callback', // Must match the one in Google Cloud Console
// scope: ['profile', 'email']
// },
// async (accessToken, refreshToken, profile, done) => {
// try {
// const email = profile.emails?.[0]?.value;
// if (!email) {
// return done(new Error("No email found in Google profile."), false);
// }
// Check if user already exists in our database
const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
// // Check if user already exists in our database
// const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
if (user) {
// User exists, proceed to log them in.
logger.info(`Google OAuth successful for existing user: ${email}`);
// The password_hash is intentionally destructured and discarded for security.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password_hash, ...userWithoutHash } = user;
return done(null, userWithoutHash);
} else {
// User does not exist, create a new account for them.
logger.info(`Google OAuth: creating new user for email: ${email}`);
// if (user) {
// // User exists, proceed to log them in.
// logger.info(`Google OAuth successful for existing user: ${email}`);
// // The password_hash is intentionally destructured and discarded for security.
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// const { password_hash, ...userWithoutHash } = user;
// return done(null, userWithoutHash);
// } else {
// // User does not exist, create a new account for them.
// logger.info(`Google OAuth: creating new user for email: ${email}`);
// Since this is an OAuth user, they don't have a password.
// We pass `null` for the password hash.
const newUser = await db.createUser(email, null, {
full_name: profile.displayName,
avatar_url: profile.photos?.[0]?.value
});
// // Since this is an OAuth user, they don't have a password.
// // We pass `null` for the password hash.
// const newUser = await db.createUser(email, null, {
// full_name: profile.displayName,
// avatar_url: profile.photos?.[0]?.value
// });
// Send a welcome email to the new user
try {
await sendWelcomeEmail(email, profile.displayName);
} catch (emailError) {
logger.error(`Failed to send welcome email to new Google user ${email}`, { error: emailError });
// Don't block the login flow if email fails.
}
// // Send a welcome email to the new user
// try {
// await sendWelcomeEmail(email, profile.displayName);
// } catch (emailError) {
// logger.error(`Failed to send welcome email to new Google user ${email}`, { error: emailError });
// // Don't block the login flow if email fails.
// }
// The `createUser` function returns the user object without the password hash.
return done(null, newUser);
}
} catch (err) {
logger.error('Error during Google authentication strategy:', { error: err });
return done(err, false);
}
}
));
// // The `createUser` function returns the user object without the password hash.
// return done(null, newUser);
// }
// } catch (err) {
// logger.error('Error during Google authentication strategy:', { error: err });
// return done(err, false);
// }
// }
// ));
// --- Passport GitHub OAuth 2.0 Strategy ---
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: '/api/auth/github/callback', // Must match the one in GitHub OAuth App settings
scope: ['user:email'] // Request email access
},
async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails?.[0]?.value;
if (!email) {
return done(new Error("No public email found in GitHub profile. Please ensure your primary email is public or add one."), false);
}
// passport.use(new GitHubStrategy({
// clientID: process.env.GITHUB_CLIENT_ID!,
// clientSecret: process.env.GITHUB_CLIENT_SECRET!,
// callbackURL: '/api/auth/github/callback', // Must match the one in GitHub OAuth App settings
// scope: ['user:email'] // Request email access
// },
// async (accessToken, refreshToken, profile, done) => {
// try {
// const email = profile.emails?.[0]?.value;
// if (!email) {
// return done(new Error("No public email found in GitHub profile. Please ensure your primary email is public or add one."), false);
// }
// Check if user already exists in our database
const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
// // Check if user already exists in our database
// const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
if (user) {
// User exists, proceed to log them in.
logger.info(`GitHub OAuth successful for existing user: ${email}`);
// The password_hash is intentionally destructured and discarded for security.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password_hash, ...userWithoutHash } = user;
return done(null, userWithoutHash);
} else {
// User does not exist, create a new account for them.
logger.info(`GitHub OAuth: creating new user for email: ${email}`);
// if (user) {
// // User exists, proceed to log them in.
// logger.info(`GitHub OAuth successful for existing user: ${email}`);
// // The password_hash is intentionally destructured and discarded for security.
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// const { password_hash, ...userWithoutHash } = user;
// return done(null, userWithoutHash);
// } else {
// // User does not exist, create a new account for them.
// logger.info(`GitHub OAuth: creating new user for email: ${email}`);
// Since this is an OAuth user, they don't have a password.
// We pass `null` for the password hash.
const newUser = await db.createUser(email, null, {
full_name: profile.displayName || profile.username, // GitHub profile might not have displayName
avatar_url: profile.photos?.[0]?.value
});
// // Since this is an OAuth user, they don't have a password.
// // We pass `null` for the password hash.
// const newUser = await db.createUser(email, null, {
// full_name: profile.displayName || profile.username, // GitHub profile might not have displayName
// avatar_url: profile.photos?.[0]?.value
// });
// Send a welcome email to the new user
try {
await sendWelcomeEmail(email, profile.displayName || profile.username);
} catch (emailError) {
logger.error(`Failed to send welcome email to new GitHub user ${email}`, { error: emailError });
// Don't block the login flow if email fails.
}
// // Send a welcome email to the new user
// try {
// await sendWelcomeEmail(email, profile.displayName || profile.username);
// } catch (emailError) {
// logger.error(`Failed to send welcome email to new GitHub user ${email}`, { error: emailError });
// // Don't block the login flow if email fails.
// }
// The `createUser` function returns the user object without the password hash.
return done(null, newUser);
}
} catch (err) {
logger.error('Error during GitHub authentication strategy:', { error: err });
return done(err, false);
}
}
));
// // The `createUser` function returns the user object without the password hash.
// return done(null, newUser);
// }
// } catch (err) {
// logger.error('Error during GitHub authentication strategy:', { error: err });
// return done(err, false);
// }
// }
// ));
// --- Passport JWT Strategy (for protecting API routes) ---
const jwtOptions = {

View File

@@ -12,6 +12,8 @@ interface DbUser {
email: string;
password_hash: string;
refresh_token?: string | null;
failed_login_attempts: number;
last_failed_login: string | null; // This will be a date string from the DB
}
/**
@@ -22,7 +24,7 @@ interface DbUser {
export async function findUserByEmail(email: string): Promise<DbUser | undefined> {
try {
const res = await getPool().query<DbUser>(
'SELECT id, email, password_hash, refresh_token FROM public.users WHERE email = $1',
'SELECT id, email, password_hash, refresh_token, failed_login_attempts, last_failed_login FROM public.users WHERE email = $1',
[email]
);
return res.rows[0];