···5151 name="code"5252 tabindex="1"5353 required5454- placeholder="pds-tngl-sh-foo-bar"5454+ placeholder="tngl-sh-foo-bar"5555 />5656 <span class="text-sm text-gray-500 mt-1">5757 Enter the code sent to your email.
+73-15
appview/signup/signup.go
···11package signup2233import (44+ "bufio"45 "fmt"56 "log/slog"67 "net/http"88+ "os"99+ "strings"710811 "github.com/go-chi/chi/v5"912 "github.com/posthog/posthog-go"···2118)22192320type Signup struct {2424- config *config.Config2525- db *db.DB2626- cf *dns.Cloudflare2727- posthog posthog.Client2828- xrpc *xrpcclient.Client2929- idResolver *idresolver.Resolver3030- pages *pages.Pages3131- l *slog.Logger2121+ config *config.Config2222+ db *db.DB2323+ cf *dns.Cloudflare2424+ posthog posthog.Client2525+ xrpc *xrpcclient.Client2626+ idResolver *idresolver.Resolver2727+ pages *pages.Pages2828+ l *slog.Logger2929+ disallowedNicknames map[string]bool3230}33313432func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup {···4238 }4339 }44404141+ disallowedNicknames := loadDisallowedNicknames(cfg.Core.DisallowedNicknamesFile, l)4242+4543 return &Signup{4646- config: cfg,4747- db: database,4848- posthog: pc,4949- idResolver: idResolver,5050- cf: cf,5151- pages: pages,5252- l: l,4444+ config: cfg,4545+ db: database,4646+ posthog: pc,4747+ idResolver: idResolver,4848+ cf: cf,4949+ pages: pages,5050+ l: l,5151+ disallowedNicknames: disallowedNicknames,5352 }5353+}5454+5555+func loadDisallowedNicknames(filepath string, logger *slog.Logger) map[string]bool {5656+ disallowed := make(map[string]bool)5757+5858+ if filepath == "" {5959+ logger.Debug("no disallowed nicknames file configured")6060+ return disallowed6161+ }6262+6363+ file, err := os.Open(filepath)6464+ if err != nil {6565+ logger.Warn("failed to open disallowed nicknames file", "file", filepath, "error", err)6666+ return disallowed6767+ }6868+ defer file.Close()6969+7070+ scanner := bufio.NewScanner(file)7171+ lineNum := 07272+ for scanner.Scan() {7373+ lineNum++7474+ line := strings.TrimSpace(scanner.Text())7575+ if line == "" || strings.HasPrefix(line, "#") {7676+ continue // skip empty lines and comments7777+ }7878+7979+ nickname := strings.ToLower(line)8080+ if userutil.IsValidSubdomain(nickname) {8181+ disallowed[nickname] = true8282+ } else {8383+ logger.Warn("invalid nickname format in disallowed nicknames file",8484+ "file", filepath, "line", lineNum, "nickname", nickname)8585+ }8686+ }8787+8888+ if err := scanner.Err(); err != nil {8989+ logger.Error("error reading disallowed nicknames file", "file", filepath, "error", err)9090+ }9191+9292+ logger.Info("loaded disallowed nicknames", "count", len(disallowed), "file", filepath)9393+ return disallowed9494+}9595+9696+// isNicknameAllowed checks if a nickname is allowed (not in the disallowed list)9797+func (s *Signup) isNicknameAllowed(nickname string) bool {9898+ return !s.disallowedNicknames[strings.ToLower(nickname)]5499}5510056101func (s *Signup) Router() http.Handler {···181128182129 if !userutil.IsValidSubdomain(username) {183130 s.pages.Notice(w, "signup-error", "Invalid username. Username must be 4–63 characters, lowercase letters, digits, or hyphens, and can't start or end with a hyphen.")131131+ return132132+ }133133+134134+ if !s.isNicknameAllowed(username) {135135+ s.pages.Notice(w, "signup-error", "This username is not available. Please choose a different one.")184136 return185137 }186138