From 573aa49a22a102f5fb8df84b75ec30ddb287eed2 Mon Sep 17 00:00:00 2001 From: logikonline Date: Sun, 15 Mar 2026 23:05:58 -0400 Subject: [PATCH] fix(blog): allow public access to blog featured images Allow public access to blog post featured images even when repository is private, if the post is published and blog is enabled. This supports public landing pages with blog sections that display featured images. Adds IsPublishedBlogFeaturedImage query and isBlogFeaturedImage check in attachment serving. Also removes redundant SafeHTML filter from blog content templates (already HTML-safe). --- models/blog/blog_post.go | 8 ++++++++ routers/web/repo/attachment.go | 29 +++++++++++++++++++++++++-- templates/pages/bold-marketing.tmpl | 2 +- templates/pages/minimalist-docs.tmpl | 2 +- templates/pages/open-source-hero.tmpl | 2 +- templates/pages/saas-conversion.tmpl | 2 +- 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/models/blog/blog_post.go b/models/blog/blog_post.go index 3362d67106..b6bb08484f 100644 --- a/models/blog/blog_post.go +++ b/models/blog/blog_post.go @@ -420,6 +420,14 @@ func HasSubscriptionOnlyBlogPosts(ctx context.Context, repoID int64) (bool, erro return count > 0, err } +// IsPublishedBlogFeaturedImage checks if the given attachment ID is used as a +// featured image by any published blog post. +func IsPublishedBlogFeaturedImage(ctx context.Context, attachmentID int64) (bool, error) { + return db.GetEngine(ctx). + Where("featured_image_id = ? AND status >= ?", attachmentID, BlogPostPublic). + Exist(new(BlogPost)) +} + // CreateBlogPost inserts a new blog post. func CreateBlogPost(ctx context.Context, p *BlogPost) error { _, err := db.GetEngine(ctx).Insert(p) diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index 47742be2d8..54db8f131a 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + blog_model "code.gitcaddy.com/server/v3/models/blog" access_model "code.gitcaddy.com/server/v3/models/perm/access" repo_model "code.gitcaddy.com/server/v3/models/repo" "code.gitcaddy.com/server/v3/models/unit" @@ -122,8 +123,12 @@ func ServeAttachment(ctx *context.Context, uuid string) { return } if !perm.CanRead(unit.TypeCode) { - ctx.HTTPError(http.StatusNotFound) - return + // Allow public access to blog featured images when the repo has blog enabled, + // even if the repo is private. This supports public landing pages with blog sections. + if !isBlogFeaturedImage(ctx, attach) { + ctx.HTTPError(http.StatusNotFound) + return + } } } else if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader ctx.HTTPError(http.StatusNotFound) @@ -171,6 +176,26 @@ func ServeAttachment(ctx *context.Context, uuid string) { common.ServeContentByReadSeeker(ctx.Base, attach.Name, util.ToPointer(attach.CreatedUnix.AsTime()), fr) } +// isBlogFeaturedImage checks if the attachment is used as a featured image for a +// published blog post on a repo that has blog enabled. This allows public access +// to these images even when the repo is private, supporting public landing pages. +func isBlogFeaturedImage(ctx *context.Context, attach *repo_model.Attachment) bool { + if attach.RepoID == 0 { + return false + } + repo, err := repo_model.GetRepositoryByID(ctx, attach.RepoID) + if err != nil || !repo.BlogEnabled { + return false + } + // Check if any published blog post uses this attachment as its featured image + has, err := blog_model.IsPublishedBlogFeaturedImage(ctx, attach.ID) + if err != nil { + log.Warn("isBlogFeaturedImage: %v", err) + return false + } + return has +} + // GetAttachment serve attachments func GetAttachment(ctx *context.Context) { ServeAttachment(ctx, ctx.PathParam("uuid")) diff --git a/templates/pages/bold-marketing.tmpl b/templates/pages/bold-marketing.tmpl index b8a9a2ad9f..fb53f8f2df 100644 --- a/templates/pages/bold-marketing.tmpl +++ b/templates/pages/bold-marketing.tmpl @@ -1201,7 +1201,7 @@ {{if .BlogTags}}·{{range .BlogTags}}{{.}} {{end}}{{end}}
- {{.BlogRenderedContent | SafeHTML}} + {{.BlogRenderedContent}}
diff --git a/templates/pages/minimalist-docs.tmpl b/templates/pages/minimalist-docs.tmpl index 4a3bd83096..f0dc0ff6fe 100644 --- a/templates/pages/minimalist-docs.tmpl +++ b/templates/pages/minimalist-docs.tmpl @@ -1063,7 +1063,7 @@ {{if .BlogTags}}·{{range .BlogTags}}{{.}} {{end}}{{end}}
- {{.BlogRenderedContent | SafeHTML}} + {{.BlogRenderedContent}}
diff --git a/templates/pages/open-source-hero.tmpl b/templates/pages/open-source-hero.tmpl index e7b523534b..a8ae001c71 100644 --- a/templates/pages/open-source-hero.tmpl +++ b/templates/pages/open-source-hero.tmpl @@ -1036,7 +1036,7 @@ {{if .BlogTags}}·{{range .BlogTags}}{{.}} {{end}}{{end}}
- {{.BlogRenderedContent | SafeHTML}} + {{.BlogRenderedContent}}
diff --git a/templates/pages/saas-conversion.tmpl b/templates/pages/saas-conversion.tmpl index bf7b242882..599dc71b5b 100644 --- a/templates/pages/saas-conversion.tmpl +++ b/templates/pages/saas-conversion.tmpl @@ -1174,7 +1174,7 @@ {{if .BlogTags}}·{{range .BlogTags}}{{.}} {{end}}{{end}}
- {{.BlogRenderedContent | SafeHTML}} + {{.BlogRenderedContent}}