prod broken
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 1m40s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 1m40s
This commit is contained in:
@@ -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;
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user