no more 401 error for publicly browsing user

This commit is contained in:
jackiettran
2025-10-07 11:43:05 -04:00
parent 9a9e96d007
commit 299522b3a6
8 changed files with 1272 additions and 249 deletions

View File

@@ -37,7 +37,10 @@ const apiLogger = (req, res, next) => {
}; };
if (res.statusCode >= 400 && res.statusCode < 500) { if (res.statusCode >= 400 && res.statusCode < 500) {
reqLogger.warn('API Response - Client Error', responseData); // Don't log 401s for /users/profile - these are expected auth checks
if (!(res.statusCode === 401 && req.url === '/profile')) {
reqLogger.warn('API Response - Client Error', responseData);
}
} else if (res.statusCode >= 500) { } else if (res.statusCode >= 500) {
reqLogger.error('API Response - Server Error', responseData); reqLogger.error('API Response - Server Error', responseData);
} else { } else {

View File

@@ -58,4 +58,40 @@ const authenticateToken = async (req, res, next) => {
} }
}; };
module.exports = { authenticateToken }; // Optional authentication - doesn't return 401 if no token, just continues
const optionalAuth = async (req, res, next) => {
// Try to get token from cookie
let token = req.cookies?.accessToken;
if (!token) {
// No token is fine for optional auth, just continue
req.user = null;
return next();
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const userId = decoded.id;
if (!userId) {
req.user = null;
return next();
}
const user = await User.findByPk(userId);
if (!user) {
req.user = null;
return next();
}
req.user = user;
next();
} catch (error) {
// Token invalid/expired is fine for optional auth
req.user = null;
next();
}
};
module.exports = { authenticateToken, optionalAuth };

1424
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-sdk/client-ses": "^3.896.0", "@aws-sdk/client-ses": "^3.896.0",
"@aws-sdk/credential-providers": "^3.901.0",
"@googlemaps/google-maps-services-js": "^3.4.2", "@googlemaps/google-maps-services-js": "^3.4.2",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",

View File

@@ -416,4 +416,19 @@ router.post("/logout", (req, res) => {
res.json({ message: "Logged out successfully" }); res.json({ message: "Logged out successfully" });
}); });
// Auth status check endpoint - returns 200 regardless of auth state
const { optionalAuth } = require("../middleware/auth");
router.get("/status", optionalAuth, async (req, res) => {
if (req.user) {
res.json({
authenticated: true,
user: req.user
});
} else {
res.json({
authenticated: false
});
}
});
module.exports = router; module.exports = router;

View File

@@ -230,7 +230,7 @@ const AuthModal: React.FC<AuthModalProps> = ({
disabled={loading} disabled={loading}
> >
<i className="bi bi-google me-2"></i> <i className="bi bi-google me-2"></i>
Sign in with Google Continue with Google
</button> </button>
<div className="text-center mt-3"> <div className="text-center mt-3">

View File

@@ -44,14 +44,16 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const checkAuth = async () => { const checkAuth = async () => {
try { try {
// The axios interceptor will automatically handle token refresh if needed // Use the status endpoint which returns 200 for both authenticated and unauthenticated states
const response = await userAPI.getProfile(); const response = await authAPI.getStatus();
setUser(response.data);
if (response.data.authenticated) {
setUser(response.data.user);
} else {
setUser(null);
}
} catch (error: any) { } catch (error: any) {
// If we get here, either: // If we get here, there's a network or server error
// 1. User is not logged in (expected for public browsing)
// 2. Token refresh failed (user needs to login again)
// In both cases, silently set user to null without logging errors
setUser(null); setUser(null);
} }
}; };

View File

@@ -157,6 +157,7 @@ export const authAPI = {
logout: () => api.post("/auth/logout"), logout: () => api.post("/auth/logout"),
refresh: () => api.post("/auth/refresh"), refresh: () => api.post("/auth/refresh"),
getCSRFToken: () => api.get("/auth/csrf-token"), getCSRFToken: () => api.get("/auth/csrf-token"),
getStatus: () => api.get("/auth/status"),
}; };
export const userAPI = { export const userAPI = {
@@ -280,23 +281,4 @@ export const conditionCheckAPI = {
getAvailableChecks: () => api.get("/condition-checks"), getAvailableChecks: () => api.get("/condition-checks"),
}; };
export const notificationAPI = {
getNotifications: (params?: { limit?: number; page?: number }) =>
api.get("/notifications", { params }),
getUnreadCount: () => api.get("/notifications/unread-count"),
markAsRead: (notificationId: string) =>
api.patch(`/notifications/${notificationId}/read`),
markAllAsRead: () => api.patch("/notifications/mark-all-read"),
// Development endpoints
createTestNotification: (data: {
type?: string;
title: string;
message: string;
metadata?: any;
}) => api.post("/notifications/test", data),
triggerConditionReminders: () =>
api.post("/notifications/test/condition-reminders"),
cleanupExpired: () => api.post("/notifications/test/cleanup-expired"),
};
export default api; export default api;