no more 401 error for publicly browsing user
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
1424
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user