From d5eb473f980df24fb4418483dcc6d3e771821e3a Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Thu, 7 Aug 2025 00:02:22 +0300 Subject: [PATCH] appview/signup: username blacklist Change-Id: vptznsxvomqpooqrpmpkzrzkzotvlxsw Signed-off-by: Anirudh Oppiliappan --- appview/config/config.go | 11 +-- .../pages/templates/user/completeSignup.html | 2 +- appview/signup/signup.go | 88 +++++++++++++++---- 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/appview/config/config.go b/appview/config/config.go index 201ea4b..3d50ba8 100644 --- a/appview/config/config.go +++ b/appview/config/config.go @@ -10,11 +10,12 @@ import ( ) type CoreConfig struct { - CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"` - DbPath string `env:"DB_PATH, default=appview.db"` - ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"` - AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"` - Dev bool `env:"DEV, default=false"` + CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"` + DbPath string `env:"DB_PATH, default=appview.db"` + ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"` + AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"` + Dev bool `env:"DEV, default=false"` + DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"` } type OAuthConfig struct { diff --git a/appview/pages/templates/user/completeSignup.html b/appview/pages/templates/user/completeSignup.html index 1dd1f78..c24a8e6 100644 --- a/appview/pages/templates/user/completeSignup.html +++ b/appview/pages/templates/user/completeSignup.html @@ -51,7 +51,7 @@ name="code" tabindex="1" required - placeholder="pds-tngl-sh-foo-bar" + placeholder="tngl-sh-foo-bar" /> Enter the code sent to your email. diff --git a/appview/signup/signup.go b/appview/signup/signup.go index 84aad4f..f8bbe07 100644 --- a/appview/signup/signup.go +++ b/appview/signup/signup.go @@ -1,9 +1,12 @@ package signup import ( + "bufio" "fmt" "log/slog" "net/http" + "os" + "strings" "github.com/go-chi/chi/v5" "github.com/posthog/posthog-go" @@ -18,14 +21,15 @@ import ( ) type Signup struct { - config *config.Config - db *db.DB - cf *dns.Cloudflare - posthog posthog.Client - xrpc *xrpcclient.Client - idResolver *idresolver.Resolver - pages *pages.Pages - l *slog.Logger + config *config.Config + db *db.DB + cf *dns.Cloudflare + posthog posthog.Client + xrpc *xrpcclient.Client + idResolver *idresolver.Resolver + pages *pages.Pages + l *slog.Logger + disallowedNicknames map[string]bool } func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup { @@ -38,15 +42,64 @@ func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idr } } + disallowedNicknames := loadDisallowedNicknames(cfg.Core.DisallowedNicknamesFile, l) + return &Signup{ - config: cfg, - db: database, - posthog: pc, - idResolver: idResolver, - cf: cf, - pages: pages, - l: l, + config: cfg, + db: database, + posthog: pc, + idResolver: idResolver, + cf: cf, + pages: pages, + l: l, + disallowedNicknames: disallowedNicknames, + } +} + +func loadDisallowedNicknames(filepath string, logger *slog.Logger) map[string]bool { + disallowed := make(map[string]bool) + + if filepath == "" { + logger.Debug("no disallowed nicknames file configured") + return disallowed + } + + file, err := os.Open(filepath) + if err != nil { + logger.Warn("failed to open disallowed nicknames file", "file", filepath, "error", err) + return disallowed + } + defer file.Close() + + scanner := bufio.NewScanner(file) + lineNum := 0 + for scanner.Scan() { + lineNum++ + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue // skip empty lines and comments + } + + nickname := strings.ToLower(line) + if userutil.IsValidSubdomain(nickname) { + disallowed[nickname] = true + } else { + logger.Warn("invalid nickname format in disallowed nicknames file", + "file", filepath, "line", lineNum, "nickname", nickname) + } } + + if err := scanner.Err(); err != nil { + logger.Error("error reading disallowed nicknames file", "file", filepath, "error", err) + } + + logger.Info("loaded disallowed nicknames", "count", len(disallowed), "file", filepath) + return disallowed +} + +// isNicknameAllowed checks if a nickname is allowed (not in the disallowed list) +func (s *Signup) isNicknameAllowed(nickname string) bool { + return !s.disallowedNicknames[strings.ToLower(nickname)] } func (s *Signup) Router() http.Handler { @@ -131,6 +184,11 @@ func (s *Signup) complete(w http.ResponseWriter, r *http.Request) { return } + if !s.isNicknameAllowed(username) { + s.pages.Notice(w, "signup-error", "This username is not available. Please choose a different one.") + return + } + email, err := db.GetEmailForCode(s.db, code) if err != nil { s.l.Error("failed to get email for code", "error", err) -- 2.43.0