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 ---
|
// --- OAuth Routes ---
|
||||||
|
|
||||||
const handleOAuthCallback = (req: Request, res: Response) => {
|
// const handleOAuthCallback = (req: Request, res: Response) => {
|
||||||
const user = req.user as { id: string; email: string };
|
// const user = req.user as { id: string; email: string };
|
||||||
const payload = { id: user.id, email: user.email };
|
// const payload = { id: user.id, email: user.email };
|
||||||
const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
|
// const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
|
||||||
const refreshToken = crypto.randomBytes(64).toString('hex');
|
// const refreshToken = crypto.randomBytes(64).toString('hex');
|
||||||
|
|
||||||
db.saveRefreshToken(user.id, refreshToken).then(() => {
|
// db.saveRefreshToken(user.id, refreshToken).then(() => {
|
||||||
res.cookie('refreshToken', refreshToken, {
|
// res.cookie('refreshToken', refreshToken, {
|
||||||
httpOnly: true,
|
// httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
// secure: process.env.NODE_ENV === 'production',
|
||||||
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
// maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
||||||
});
|
// });
|
||||||
// Redirect to a frontend page that can handle the token
|
// // Redirect to a frontend page that can handle the token
|
||||||
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`);
|
// res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`);
|
||||||
}).catch(err => {
|
// }).catch(err => {
|
||||||
logger.error('Failed to save refresh token during OAuth callback:', { error: err });
|
// logger.error('Failed to save refresh token during OAuth callback:', { error: err });
|
||||||
res.redirect(`${process.env.FRONTEND_URL}/login?error=auth_failed`);
|
// res.redirect(`${process.env.FRONTEND_URL}/login?error=auth_failed`);
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
|
|
||||||
router.get('/google', passport.authenticate('google', { session: false }));
|
// router.get('/google', passport.authenticate('google', { session: false }));
|
||||||
router.get('/google/callback', passport.authenticate('google', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
|
// router.get('/google/callback', passport.authenticate('google', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
|
||||||
|
|
||||||
router.get('/github', passport.authenticate('github', { session: false }));
|
// router.get('/github', passport.authenticate('github', { session: false }));
|
||||||
router.get('/github/callback', passport.authenticate('github', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
|
// router.get('/github/callback', passport.authenticate('github', { session: false, failureRedirect: '/login' }), handleOAuthCallback);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import { Strategy as LocalStrategy } from 'passport-local';
|
import { Strategy as LocalStrategy } from 'passport-local';
|
||||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
//import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||||
import { Strategy as GitHubStrategy } from 'passport-github2';
|
//import { Strategy as GitHubStrategy } from 'passport-github2';
|
||||||
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
|
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
@@ -89,110 +89,110 @@ passport.use(new LocalStrategy(
|
|||||||
));
|
));
|
||||||
|
|
||||||
// --- Passport Google OAuth 2.0 Strategy ---
|
// --- Passport Google OAuth 2.0 Strategy ---
|
||||||
passport.use(new GoogleStrategy({
|
// passport.use(new GoogleStrategy({
|
||||||
clientID: process.env.GOOGLE_CLIENT_ID!,
|
// clientID: process.env.GOOGLE_CLIENT_ID!,
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
callbackURL: '/api/auth/google/callback', // Must match the one in Google Cloud Console
|
// callbackURL: '/api/auth/google/callback', // Must match the one in Google Cloud Console
|
||||||
scope: ['profile', 'email']
|
// scope: ['profile', 'email']
|
||||||
},
|
// },
|
||||||
async (accessToken, refreshToken, profile, done) => {
|
// async (accessToken, refreshToken, profile, done) => {
|
||||||
try {
|
// try {
|
||||||
const email = profile.emails?.[0]?.value;
|
// const email = profile.emails?.[0]?.value;
|
||||||
if (!email) {
|
// if (!email) {
|
||||||
return done(new Error("No email found in Google profile."), false);
|
// return done(new Error("No email found in Google profile."), false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if user already exists in our database
|
// // Check if user already exists in our database
|
||||||
const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
|
// const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
|
||||||
|
|
||||||
if (user) {
|
// if (user) {
|
||||||
// User exists, proceed to log them in.
|
// // User exists, proceed to log them in.
|
||||||
logger.info(`Google OAuth successful for existing user: ${email}`);
|
// logger.info(`Google OAuth successful for existing user: ${email}`);
|
||||||
// The password_hash is intentionally destructured and discarded for security.
|
// // The password_hash is intentionally destructured and discarded for security.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { password_hash, ...userWithoutHash } = user;
|
// const { password_hash, ...userWithoutHash } = user;
|
||||||
return done(null, userWithoutHash);
|
// return done(null, userWithoutHash);
|
||||||
} else {
|
// } else {
|
||||||
// User does not exist, create a new account for them.
|
// // User does not exist, create a new account for them.
|
||||||
logger.info(`Google OAuth: creating new user for email: ${email}`);
|
// logger.info(`Google OAuth: creating new user for email: ${email}`);
|
||||||
|
|
||||||
// Since this is an OAuth user, they don't have a password.
|
// // Since this is an OAuth user, they don't have a password.
|
||||||
// We pass `null` for the password hash.
|
// // We pass `null` for the password hash.
|
||||||
const newUser = await db.createUser(email, null, {
|
// const newUser = await db.createUser(email, null, {
|
||||||
full_name: profile.displayName,
|
// full_name: profile.displayName,
|
||||||
avatar_url: profile.photos?.[0]?.value
|
// avatar_url: profile.photos?.[0]?.value
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Send a welcome email to the new user
|
// // Send a welcome email to the new user
|
||||||
try {
|
// try {
|
||||||
await sendWelcomeEmail(email, profile.displayName);
|
// await sendWelcomeEmail(email, profile.displayName);
|
||||||
} catch (emailError) {
|
// } catch (emailError) {
|
||||||
logger.error(`Failed to send welcome email to new Google user ${email}`, { error: emailError });
|
// logger.error(`Failed to send welcome email to new Google user ${email}`, { error: emailError });
|
||||||
// Don't block the login flow if email fails.
|
// // Don't block the login flow if email fails.
|
||||||
}
|
// }
|
||||||
|
|
||||||
// The `createUser` function returns the user object without the password hash.
|
// // The `createUser` function returns the user object without the password hash.
|
||||||
return done(null, newUser);
|
// return done(null, newUser);
|
||||||
}
|
// }
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
logger.error('Error during Google authentication strategy:', { error: err });
|
// logger.error('Error during Google authentication strategy:', { error: err });
|
||||||
return done(err, false);
|
// return done(err, false);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
));
|
// ));
|
||||||
|
|
||||||
// --- Passport GitHub OAuth 2.0 Strategy ---
|
// --- Passport GitHub OAuth 2.0 Strategy ---
|
||||||
passport.use(new GitHubStrategy({
|
// passport.use(new GitHubStrategy({
|
||||||
clientID: process.env.GITHUB_CLIENT_ID!,
|
// clientID: process.env.GITHUB_CLIENT_ID!,
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
// clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||||
callbackURL: '/api/auth/github/callback', // Must match the one in GitHub OAuth App settings
|
// callbackURL: '/api/auth/github/callback', // Must match the one in GitHub OAuth App settings
|
||||||
scope: ['user:email'] // Request email access
|
// scope: ['user:email'] // Request email access
|
||||||
},
|
// },
|
||||||
async (accessToken, refreshToken, profile, done) => {
|
// async (accessToken, refreshToken, profile, done) => {
|
||||||
try {
|
// try {
|
||||||
const email = profile.emails?.[0]?.value;
|
// const email = profile.emails?.[0]?.value;
|
||||||
if (!email) {
|
// if (!email) {
|
||||||
return done(new Error("No public email found in GitHub profile. Please ensure your primary email is public or add one."), false);
|
// 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
|
// // Check if user already exists in our database
|
||||||
const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
|
// const user = await db.findUserByEmail(email); // Changed to const as 'user' is not reassigned
|
||||||
|
|
||||||
if (user) {
|
// if (user) {
|
||||||
// User exists, proceed to log them in.
|
// // User exists, proceed to log them in.
|
||||||
logger.info(`GitHub OAuth successful for existing user: ${email}`);
|
// logger.info(`GitHub OAuth successful for existing user: ${email}`);
|
||||||
// The password_hash is intentionally destructured and discarded for security.
|
// // The password_hash is intentionally destructured and discarded for security.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { password_hash, ...userWithoutHash } = user;
|
// const { password_hash, ...userWithoutHash } = user;
|
||||||
return done(null, userWithoutHash);
|
// return done(null, userWithoutHash);
|
||||||
} else {
|
// } else {
|
||||||
// User does not exist, create a new account for them.
|
// // User does not exist, create a new account for them.
|
||||||
logger.info(`GitHub OAuth: creating new user for email: ${email}`);
|
// logger.info(`GitHub OAuth: creating new user for email: ${email}`);
|
||||||
|
|
||||||
// Since this is an OAuth user, they don't have a password.
|
// // Since this is an OAuth user, they don't have a password.
|
||||||
// We pass `null` for the password hash.
|
// // We pass `null` for the password hash.
|
||||||
const newUser = await db.createUser(email, null, {
|
// const newUser = await db.createUser(email, null, {
|
||||||
full_name: profile.displayName || profile.username, // GitHub profile might not have displayName
|
// full_name: profile.displayName || profile.username, // GitHub profile might not have displayName
|
||||||
avatar_url: profile.photos?.[0]?.value
|
// avatar_url: profile.photos?.[0]?.value
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Send a welcome email to the new user
|
// // Send a welcome email to the new user
|
||||||
try {
|
// try {
|
||||||
await sendWelcomeEmail(email, profile.displayName || profile.username);
|
// await sendWelcomeEmail(email, profile.displayName || profile.username);
|
||||||
} catch (emailError) {
|
// } catch (emailError) {
|
||||||
logger.error(`Failed to send welcome email to new GitHub user ${email}`, { error: emailError });
|
// logger.error(`Failed to send welcome email to new GitHub user ${email}`, { error: emailError });
|
||||||
// Don't block the login flow if email fails.
|
// // Don't block the login flow if email fails.
|
||||||
}
|
// }
|
||||||
|
|
||||||
// The `createUser` function returns the user object without the password hash.
|
// // The `createUser` function returns the user object without the password hash.
|
||||||
return done(null, newUser);
|
// return done(null, newUser);
|
||||||
}
|
// }
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
logger.error('Error during GitHub authentication strategy:', { error: err });
|
// logger.error('Error during GitHub authentication strategy:', { error: err });
|
||||||
return done(err, false);
|
// return done(err, false);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
));
|
// ));
|
||||||
|
|
||||||
// --- Passport JWT Strategy (for protecting API routes) ---
|
// --- Passport JWT Strategy (for protecting API routes) ---
|
||||||
const jwtOptions = {
|
const jwtOptions = {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ interface DbUser {
|
|||||||
email: string;
|
email: string;
|
||||||
password_hash: string;
|
password_hash: string;
|
||||||
refresh_token?: string | null;
|
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> {
|
export async function findUserByEmail(email: string): Promise<DbUser | undefined> {
|
||||||
try {
|
try {
|
||||||
const res = await getPool().query<DbUser>(
|
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]
|
[email]
|
||||||
);
|
);
|
||||||
return res.rows[0];
|
return res.rows[0];
|
||||||
|
|||||||
Reference in New Issue
Block a user