Merge pull request 'fix: prevent subdomain change to already-in-use subdomains' (#54) from staging into main
All checks were successful
Docker Build and Push / build-and-push (push) Successful in 5m20s
renovate / renovate (push) Successful in 38s

Reviewed-on: #54
This commit was merged in pull request #54.
This commit is contained in:
2025-12-30 12:42:05 +00:00
2 changed files with 168 additions and 3 deletions

View File

@@ -22,6 +22,131 @@ const (
paddingRight = 4 paddingRight = 4
) )
var forbiddenSlugs = []string{ var forbiddenSlugs = map[string]struct{}{
"ping", "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": {},
} }

View File

@@ -205,7 +205,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(tea.ClearScreen, textinput.Blink) return m, tea.Batch(tea.ClearScreen, textinput.Blink)
case "enter": case "enter":
inputValue := m.slugInput.Value() 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.tunnelURL = buildURL(m.protocol, inputValue, m.domain)
m.editingSlug = false m.editingSlug = false
m.slugError = "" m.slugError = ""
@@ -800,3 +813,30 @@ func buildURL(protocol, subdomain, domain string) string {
func generateRandomSubdomain() string { func generateRandomSubdomain() string {
return utils.GenerateRandomString(20) 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
}