chore: prevent sensitive data caching
This commit is contained in:
parent
eaef04a0a0
commit
6b0f90f355
6 changed files with 35 additions and 5 deletions
|
|
@ -238,6 +238,24 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
|
||||||
case "email":
|
case "email":
|
||||||
update.Email = &request.User.Email
|
update.Email = &request.User.Email
|
||||||
case "avatar_url":
|
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
|
update.AvatarURL = &request.User.AvatarUrl
|
||||||
case "description":
|
case "description":
|
||||||
update.Description = &request.User.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") {
|
if util.HasPrefixes(c.Path(), "/api", "/memos.api.v1") {
|
||||||
return true
|
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" {
|
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
|
return false
|
||||||
}
|
}
|
||||||
// Set Cache-Control header for static assets.
|
// Set Cache-Control header for static assets.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
<link rel="icon" type="image/webp" href="/logo.webp" />
|
<link rel="icon" type="image/webp" href="/logo.webp" />
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,9 @@ const UserMenu = observer((props: Props) => {
|
||||||
|
|
||||||
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
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 (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,9 @@ function getAuthFailureRedirect(currentPath: string): string | null {
|
||||||
|
|
||||||
function performRedirect(redirectUrl: string | null): void {
|
function performRedirect(redirectUrl: string | null): void {
|
||||||
if (redirectUrl) {
|
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 (!currentUser) {
|
||||||
// If disallowPublicVisibility is enabled, redirect to the login page if the user is not logged in.
|
// If disallowPublicVisibility is enabled, redirect to the login page if the user is not logged in.
|
||||||
if (instanceStore.state.memoRelatedSetting.disallowPublicVisibility) {
|
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;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
([Routes.ROOT, Routes.ATTACHMENTS, Routes.INBOX, Routes.ARCHIVED, Routes.SETTING] as string[]).includes(location.pathname)
|
([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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue