Subject: [PATCH links] Fix panic when trying to render an error template. This panic is covering up other errors in edge cases.

From: Peter Sanchez <[email-masked]>
Date: Fri, 29 Aug 2025 07:27:40 -0600

Changelog-added: domains.SafeForContext for template handling.
---
 core/templates.go    | 10 ++++++----
 domain/middleware.go |  9 ++++++++-
 models/utils.go      |  4 ++--
 3 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/core/templates.go b/core/templates.go
index 6b7de02..a6e32ea 100644
--- a/core/templates.go
+++ b/core/templates.go
@@ -68,10 +68,12 @@ func AddGlobalTmpl(c echo.Context) gobwebs.Map {
 		gmap["settingSection"] = true
 	}
 
-	dom := domain.ForContext(c.Request().Context())
-	if dom.Level == models.DomainLevelUser && dom.OrgID.Valid {
-		gmap["isUserDomain"] = true
-		gmap["hideNav"] = true
+	dom := domain.SafeForContext(c.Request().Context())
+	if dom != nil {
+		if dom.Level == models.DomainLevelUser && dom.OrgID.Valid {
+			gmap["isUserDomain"] = true
+			gmap["hideNav"] = true
+		}
 	}
 
 	if gctx.User != nil && gctx.User.IsAuthenticated() {
diff --git a/domain/middleware.go b/domain/middleware.go
index 5400dd3..261ce4b 100644
--- a/domain/middleware.go
+++ b/domain/middleware.go
@@ -33,6 +33,13 @@ func ForContext(ctx context.Context) *models.Domain {
 	return domain
 }
 
+// SafeForContext fetches current domain from the request context
+// Returns nil if domain is not found in context (safe for use in templates/error handlers)
+func SafeForContext(ctx context.Context) *models.Domain {
+	domain, _ := ctx.Value(domainCtxKey).(*models.Domain)
+	return domain
+}
+
 func badDomainRedirect(c echo.Context, d string) error {
 	gctx := c.(*server.Context)
 	mainDomain, ok := gctx.Server.Config.File.Get("links", "links-service-domain")
@@ -141,7 +148,7 @@ func DomainRedirect(next echo.HandlerFunc) echo.HandlerFunc {
 		// Remove :PORT if present
 		host := strings.SplitN(mainDomain, ":", 2)[0]
 		domain := ForContext(c.Request().Context())
-		if strings.ToLower(domain.LookupName) != strings.ToLower(host) &&
+		if strings.EqualFold(domain.LookupName, host) &&
 			domain.Level == models.DomainLevelSystem {
 			// XXX Populate this from links.InvalidSlugs or better yet, c.Echo().Routes()
 			redirectPaths := []redir{
diff --git a/models/utils.go b/models/utils.go
index d4a6dfc..2859806 100644
--- a/models/utils.go
+++ b/models/utils.go
@@ -5,7 +5,7 @@ import (
 	"database/sql"
 	"encoding/json"
 	"fmt"
-	mrand "math/rand"
+	"math/rand"
 	"strings"
 	"time"
 
@@ -15,7 +15,7 @@ import (
 
 func getShortCode(ctx context.Context, tx *sql.Tx, table, field string, filter sq.Sqlizer) (string, error) {
 	chars := "abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789"
-	r := mrand.New(mrand.NewSource(time.Now().UnixNano()))
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
 
 	codelen := 4
 	code := make([]byte, codelen)
-- 
2.49.1