From ab34b3476508ed06481c3fda5e262e3ad3c22d01 Mon Sep 17 00:00:00 2001 From: bagas Date: Tue, 30 Dec 2025 19:41:33 +0700 Subject: [PATCH] fix: prevent subdomain change to already-in-use subdomains --- session/interaction/constants.go | 129 ++++++++++++++++++++++++++++- session/interaction/interaction.go | 42 +++++++++- 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/session/interaction/constants.go b/session/interaction/constants.go index 1f57aae..91024d3 100644 --- a/session/interaction/constants.go +++ b/session/interaction/constants.go @@ -22,6 +22,131 @@ const ( paddingRight = 4 ) -var forbiddenSlugs = []string{ - "ping", +var forbiddenSlugs = map[string]struct{}{ + "ping": {}, + "staging": {}, + "admin": {}, + "root": {}, + "api": {}, + "www": {}, + "support": {}, + "help": {}, + "status": {}, + "health": {}, + "login": {}, + "logout": {}, + "signup": {}, + "register": {}, + "settings": {}, + "config": {}, + "null": {}, + "undefined": {}, + "example": {}, + "test": {}, + "dev": {}, + "system": {}, + "administrator": {}, + "dashboard": {}, + "account": {}, + "profile": {}, + "user": {}, + "users": {}, + "auth": {}, + "oauth": {}, + "callback": {}, + "webhook": {}, + "webhooks": {}, + "static": {}, + "assets": {}, + "cdn": {}, + "mail": {}, + "email": {}, + "ftp": {}, + "ssh": {}, + "git": {}, + "svn": {}, + "blog": {}, + "news": {}, + "about": {}, + "contact": {}, + "terms": {}, + "privacy": {}, + "legal": {}, + "billing": {}, + "payment": {}, + "checkout": {}, + "cart": {}, + "shop": {}, + "store": {}, + "download": {}, + "uploads": {}, + "images": {}, + "img": {}, + "css": {}, + "js": {}, + "fonts": {}, + "public": {}, + "private": {}, + "internal": {}, + "external": {}, + "proxy": {}, + "cache": {}, + "debug": {}, + "metrics": {}, + "monitoring": {}, + "graphql": {}, + "rest": {}, + "rpc": {}, + "socket": {}, + "ws": {}, + "wss": {}, + "app": {}, + "apps": {}, + "mobile": {}, + "desktop": {}, + "embed": {}, + "widget": {}, + "docs": {}, + "documentation": {}, + "wiki": {}, + "forum": {}, + "community": {}, + "feedback": {}, + "report": {}, + "abuse": {}, + "spam": {}, + "security": {}, + "verify": {}, + "confirm": {}, + "reset": {}, + "password": {}, + "recovery": {}, + "unsubscribe": {}, + "subscribe": {}, + "notifications": {}, + "alerts": {}, + "messages": {}, + "inbox": {}, + "outbox": {}, + "sent": {}, + "draft": {}, + "trash": {}, + "archive": {}, + "search": {}, + "explore": {}, + "discover": {}, + "trending": {}, + "popular": {}, + "featured": {}, + "new": {}, + "latest": {}, + "top": {}, + "best": {}, + "hot": {}, + "random": {}, + "all": {}, + "any": {}, + "none": {}, + "true": {}, + "false": {}, } diff --git a/session/interaction/interaction.go b/session/interaction/interaction.go index 39b2d45..9dcac7d 100644 --- a/session/interaction/interaction.go +++ b/session/interaction/interaction.go @@ -205,7 +205,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(tea.ClearScreen, textinput.Blink) case "enter": inputValue := m.slugInput.Value() - m.interaction.updateClientSlug(m.interaction.slugManager.Get(), inputValue) + + if isForbiddenSlug(inputValue) { + m.slugError = "This subdomain is reserved. Please choose a different one." + return m, nil + } else if !isValidSlug(inputValue) { + m.slugError = "Invalid subdomain. Follow the rules." + return m, nil + } + + if !m.interaction.updateClientSlug(m.interaction.slugManager.Get(), inputValue) { + m.slugError = "Someone already uses this subdomain." + return m, nil + } + m.tunnelURL = buildURL(m.protocol, inputValue, m.domain) m.editingSlug = false m.slugError = "" @@ -800,3 +813,30 @@ func buildURL(protocol, subdomain, domain string) string { func generateRandomSubdomain() string { return utils.GenerateRandomString(20) } + +func isValidSlug(slug string) bool { + if len(slug) < minSlugLength || len(slug) > maxSlugLength { + return false + } + + if slug[0] == '-' || slug[len(slug)-1] == '-' { + return false + } + + for _, c := range slug { + if !isValidSlugChar(byte(c)) { + return false + } + } + + return true +} + +func isValidSlugChar(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' +} + +func isForbiddenSlug(slug string) bool { + _, ok := forbiddenSlugs[slug] + return ok +} -- 2.49.1