From 9cb6dc86165cb5ecd0bd0b35f6c3eb3116a24038 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 20 Feb 2026 12:11:18 +0200 Subject: [PATCH] appview/signup: auto-claim pds handle domain at signup Signed-off-by: Anirudh Oppiliappan --- appview/oauth/handler.go | 29 +++++++++++++++++++++++++++++ appview/signup/signup.go | 18 ++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/appview/oauth/handler.go b/appview/oauth/handler.go index 8d66de7f..7be729a0 100644 --- a/appview/oauth/handler.go +++ b/appview/oauth/handler.go @@ -9,6 +9,7 @@ import ( "log/slog" "net/http" "slices" + "strings" "time" comatproto "github.com/bluesky-social/indigo/api/atproto" @@ -91,6 +92,7 @@ func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { go o.addToDefaultKnot(sessData.AccountDID.String()) go o.addToDefaultSpindle(sessData.AccountDID.String()) go o.ensureTangledProfile(sessData) + go o.autoClaimTnglShDomain(sessData.AccountDID.String()) if !o.Config.Core.Dev { err = o.Posthog.Enqueue(posthog.Capture{ @@ -413,6 +415,33 @@ func (s *AppPasswordSession) putRecord(record any, collection string) error { return nil } +// autoClaimTnglShDomain checks if the user has a .tngl.sh handle and, if so, +// ensures their corresponding sites domain is claimed. This is idempotent — +// ClaimDomain is a no-op if the claim already exists. +func (o *OAuth) autoClaimTnglShDomain(did string) { + l := o.Logger.With("did", did) + + pdsDomain := strings.TrimPrefix(o.Config.Pds.Host, "https://") + pdsDomain = strings.TrimPrefix(pdsDomain, "http://") + + resolved, err := o.IdResolver.ResolveIdent(context.Background(), did) + if err != nil { + l.Error("autoClaimTnglShDomain: failed to resolve ident", "err", err) + return + } + + handle := resolved.Handle.String() + if !strings.HasSuffix(handle, "."+pdsDomain) { + return + } + + if err := db.ClaimDomain(o.Db, did, handle); err != nil { + l.Warn("autoClaimTnglShDomain: failed to claim domain", "domain", handle, "err", err) + } else { + l.Info("autoClaimTnglShDomain: claimed domain", "domain", handle) + } +} + // getAppPasswordSession returns a cached AppPasswordSession, creating one if needed. func (o *OAuth) getAppPasswordSession() (*AppPasswordSession, error) { o.appPasswordSessionMu.Lock() diff --git a/appview/signup/signup.go b/appview/signup/signup.go index 79db58be..cb758b9f 100644 --- a/appview/signup/signup.go +++ b/appview/signup/signup.go @@ -311,6 +311,24 @@ func (s *Signup) executeSignupTransaction(ctx context.Context, username, passwor } emailAdded = true + // step 4: auto-claim . for this user. + // All signups through this flow receive a .tngl.sh handle + // (or whatever the configured PDS host is), so we claim the matching + // sites subdomain on their behalf. This is the only way to obtain a + // *.tngl.sh sites domain; it cannot be claimed manually via settings. + pdsDomain := strings.TrimPrefix(s.config.Pds.Host, "https://") + pdsDomain = strings.TrimPrefix(pdsDomain, "http://") + autoClaimDomain := username + "." + pdsDomain + if err := db.ClaimDomain(s.db, did, autoClaimDomain); err != nil { + s.l.Warn("failed to auto-claim sites domain at signup", + "domain", autoClaimDomain, + "did", did, + "error", err, + ) + } else { + s.l.Info("auto-claimed sites domain at signup", "domain", autoClaimDomain, "did", did) + } + // if we get here, we've successfully created the account and added the email success = true -- 2.51.2