appview/signup: username blacklist #408

merged
opened by anirudh.fi targeting master from push-qlzpkvltqlzm
Changed files
+80 -21
appview
config
pages
templates
signup
+6 -5
appview/config/config.go
···
)
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"`
}
type OAuthConfig struct {
···
)
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"`
+
DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"`
}
type OAuthConfig struct {
+1 -1
appview/pages/templates/user/completeSignup.html
···
name="code"
tabindex="1"
required
-
placeholder="pds-tngl-sh-foo-bar"
/>
<span class="text-sm text-gray-500 mt-1">
Enter the code sent to your email.
···
name="code"
tabindex="1"
required
+
placeholder="tngl-sh-foo-bar"
/>
<span class="text-sm text-gray-500 mt-1">
Enter the code sent to your email.
+73 -15
appview/signup/signup.go
···
package signup
import (
"fmt"
"log/slog"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/posthog/posthog-go"
···
)
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
}
func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup {
···
}
}
return &Signup{
-
config: cfg,
-
db: database,
-
posthog: pc,
-
idResolver: idResolver,
-
cf: cf,
-
pages: pages,
-
l: l,
}
}
func (s *Signup) Router() http.Handler {
···
return
}
email, err := db.GetEmailForCode(s.db, code)
if err != nil {
s.l.Error("failed to get email for code", "error", err)
···
package signup
import (
+
"bufio"
"fmt"
"log/slog"
"net/http"
+
"os"
+
"strings"
"github.com/go-chi/chi/v5"
"github.com/posthog/posthog-go"
···
)
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
+
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 {
···
}
}
+
disallowedNicknames := loadDisallowedNicknames(cfg.Core.DisallowedNicknamesFile, l)
+
return &Signup{
+
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 {
···
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)