Commit 6b0f90f3 authored by Johnny's avatar Johnny

chore: prevent sensitive data caching

parent eaef04a0
......@@ -238,6 +238,24 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
case "email":
update.Email = &request.User.Email
case "avatar_url":
// Validate avatar MIME type to prevent XSS during upload
if request.User.AvatarUrl != "" {
imageType, _, err := extractImageInfo(request.User.AvatarUrl)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid avatar format: %v", err)
}
// Only allow safe image formats for avatars
allowedAvatarTypes := map[string]bool{
"image/png": true,
"image/jpeg": true,
"image/jpg": true,
"image/gif": true,
"image/webp": true,
}
if !allowedAvatarTypes[imageType] {
return nil, status.Errorf(codes.InvalidArgument, "invalid avatar image type: %s. Only PNG, JPEG, GIF, and WebP are allowed", imageType)
}
}
update.AvatarURL = &request.User.AvatarUrl
case "description":
update.Description = &request.User.Description
......
......@@ -35,8 +35,12 @@ func (*FrontendService) Serve(_ context.Context, e *echo.Echo) {
if util.HasPrefixes(c.Path(), "/api", "/memos.api.v1") {
return true
}
// Skip setting cache headers for index.html
// For index.html and root path, set no-cache headers to prevent browser caching
// This prevents sensitive data from being accessible via browser back button after logout
if c.Path() == "/" || c.Path() == "/index.html" {
c.Response().Header().Set(echo.HeaderCacheControl, "no-cache, no-store, must-revalidate")
c.Response().Header().Set("Pragma", "no-cache")
c.Response().Header().Set("Expires", "0")
return false
}
// Set Cache-Control header for static assets.
......
......@@ -2,6 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/webp" href="/logo.webp" />
<link rel="manifest" href="/site.webmanifest" />
......
......@@ -64,7 +64,9 @@ const UserMenu = observer((props: Props) => {
keysToRemove.forEach((key) => localStorage.removeItem(key));
window.location.href = Routes.AUTH;
// Use replace() instead of href to prevent back button from showing cached sensitive data
// This removes the current page from browser history
window.location.replace(Routes.AUTH);
};
return (
......
......@@ -92,7 +92,9 @@ function getAuthFailureRedirect(currentPath: string): string | null {
function performRedirect(redirectUrl: string | null): void {
if (redirectUrl) {
window.location.href = redirectUrl;
// Use replace() instead of href to prevent back button from showing cached sensitive data
// This removes the current page from browser history after authentication failure
window.location.replace(redirectUrl);
}
}
......
......@@ -24,12 +24,13 @@ const RootLayout = observer(() => {
if (!currentUser) {
// If disallowPublicVisibility is enabled, redirect to the login page if the user is not logged in.
if (instanceStore.state.memoRelatedSetting.disallowPublicVisibility) {
window.location.href = Routes.AUTH;
// Use replace() to prevent back button from showing cached sensitive data
window.location.replace(Routes.AUTH);
return;
} else if (
([Routes.ROOT, Routes.ATTACHMENTS, Routes.INBOX, Routes.ARCHIVED, Routes.SETTING] as string[]).includes(location.pathname)
) {
window.location.href = Routes.EXPLORE;
window.location.replace(Routes.EXPLORE);
return;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment