From dcdc30c85a509512e0b2c38ad91509ae9606d435 Mon Sep 17 00:00:00 2001 From: brookjeynes Date: Thu, 16 Oct 2025 15:22:29 +1000 Subject: [PATCH] fix: accounts not created on sign-in Change-Id: lokxzmtxxkxoqlllntnnnpulooqxtsmz Signed-off-by: brookjeynes --- internal/server/app.go | 2 +- internal/server/handlers/login.go | 4 +- internal/server/handlers/profile.go | 2 +- internal/server/oauth/handler.go | 94 +++++++++++++++++++++++++++-- internal/server/oauth/oauth.go | 5 +- 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/internal/server/app.go b/internal/server/app.go index 20bc6bd..96af718 100644 --- a/internal/server/app.go +++ b/internal/server/app.go @@ -63,7 +63,7 @@ func Make(ctx context.Context, config *config.Config) (*Server, error) { idResolver := atproto.DefaultResolver() - oauth, err := oauth.New(config, posthog, log.SubLogger(logger, "oauth")) + oauth, err := oauth.New(config, posthog, idResolver, log.SubLogger(logger, "oauth")) if err != nil { return nil, fmt.Errorf("failed to start oauth handler: %w", err) } diff --git a/internal/server/handlers/login.go b/internal/server/handlers/login.go index a9b6c5b..444a088 100644 --- a/internal/server/handlers/login.go +++ b/internal/server/handlers/login.go @@ -56,14 +56,14 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { // Basic handle validation if !strings.Contains(handle, ".") { l.Error("invalid handle format", "handle", handle) - htmx.HxError(w, http.StatusBadGateway, fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle)) + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle)) return } resolved, err := h.IdResolver.ResolveIdent(context.Background(), handle) if err != nil { l.Error("failed to resolve handle", "handle", handle, "err", err) - htmx.HxError(w, http.StatusBadGateway, fmt.Sprintf("'%s' is an invalid handle", handle)) + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", handle)) return } else { if !h.Config.Core.Dev && resolved.DID.String() != "" { diff --git a/internal/server/handlers/profile.go b/internal/server/handlers/profile.go index 76f51ed..076716f 100644 --- a/internal/server/handlers/profile.go +++ b/internal/server/handlers/profile.go @@ -130,7 +130,7 @@ func (h *Handler) HandleProfilePage(w http.ResponseWriter, r *http.Request) { }) if err := g.Wait(); err != nil { - l.Error("failed to fetch critical profile data for", "did", profileDid, "err", err) + l.Error("failed to fetch critical profile data", "did", profileDid, "err", err) htmx.HxError(w, http.StatusInternalServerError, "Failed to fetch profile data, try again later.") return } diff --git a/internal/server/oauth/handler.go b/internal/server/oauth/handler.go index f0243e5..7db5107 100644 --- a/internal/server/oauth/handler.go +++ b/internal/server/oauth/handler.go @@ -1,14 +1,22 @@ package oauth import ( + "context" "encoding/json" + "fmt" "net/http" + "time" + comatproto "github.com/bluesky-social/indigo/api/atproto" + lexutil "github.com/bluesky-social/indigo/lex/util" "github.com/go-chi/chi/v5" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/posthog/posthog-go" + "yoten.app/api/yoten" ph "yoten.app/internal/clients/posthog" + "yoten.app/internal/db" + "yoten.app/internal/server/htmx" ) func (o *OAuth) Router() http.Handler { @@ -63,6 +71,7 @@ func (o *OAuth) jwks(w http.ResponseWriter, r *http.Request) { } func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { + l := o.Logger.With("handler", "callback") ctx := r.Context() sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query()) @@ -73,18 +82,91 @@ func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { } if err := o.SaveSession(w, r, sessData); err != nil { - o.Logger.Error("failed to save session", "err", err) + l.Error("failed to save session", "err", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - if !o.Config.Core.Dev { - err = o.Posthog.Enqueue(posthog.Capture{ - DistinctId: sessData.AccountDID.String(), - Event: ph.UserSignInSuccessEvent, + did := sessData.AccountDID.String() + resolved, err := o.IdResolver.ResolveIdent(context.Background(), did) + if err != nil { + l.Error("failed to resolve handle", "handle", resolved.Handle.String(), "err", err) + htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", resolved.Handle.String())) + return + } + + client, err := o.AuthorizedClient(r) + if err != nil { + l.Error("failed to get authorized client", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", yoten.ActorProfileNSID, did, "self") + var cid *string + if ex != nil { + cid = ex.Cid + } + + // This should only occur once per account + if ex == nil { + createdAt := time.Now().Format(time.RFC3339) + atresp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ + Collection: yoten.ActorProfileNSID, + Repo: did, + Rkey: "self", + Record: &lexutil.LexiconTypeDecoder{ + Val: &yoten.ActorProfile{ + DisplayName: resolved.Handle.String(), + Description: db.ToPtr(""), + Languages: make([]string, 0), + Location: db.ToPtr(""), + CreatedAt: createdAt, + }}, + + SwapRecord: cid, }) if err != nil { - o.Logger.Error("failed to enqueue posthog event", "err", err) + l.Error("failed to create profile record", "err", err) + htmx.HxError(w, http.StatusInternalServerError, "Failed to announce profile creation, try again later") + return + } + + l.Debug("created profile record", "uri", atresp.Uri) + + if !o.Config.Core.Dev { + err = o.Posthog.Enqueue(posthog.Capture{ + DistinctId: sessData.AccountDID.String(), + Event: ph.UserSignInSuccessEvent, + }) + if err != nil { + l.Error("failed to enqueue posthog event", "err", err) + } + + properties := posthog.NewProperties(). + Set("display_name", resolved.Handle.String()). + Set("language_count", 0). + Set("$set_once", posthog.NewProperties(). + Set("initial_did", did). + Set("initial_handle", resolved.Handle.String()). + Set("created_at", createdAt), + ) + + err = o.Posthog.Enqueue(posthog.Identify{ + DistinctId: did, + Properties: properties, + }) + if err != nil { + l.Error("failed to enqueue posthog identify event", "err", err) + } + + err = o.Posthog.Enqueue(posthog.Capture{ + DistinctId: did, + Event: ph.ProfileRecordCreatedEvent, + }) + if err != nil { + l.Error("failed to enqueue posthog event", "err", err) + } } } diff --git a/internal/server/oauth/oauth.go b/internal/server/oauth/oauth.go index 1e5a41a..eb3b483 100644 --- a/internal/server/oauth/oauth.go +++ b/internal/server/oauth/oauth.go @@ -15,6 +15,7 @@ import ( "github.com/gorilla/sessions" "github.com/posthog/posthog-go" + "yoten.app/internal/atproto" "yoten.app/internal/server/config" "yoten.app/internal/types" ) @@ -26,9 +27,10 @@ type OAuth struct { JwksUri string Posthog posthog.Client Logger *slog.Logger + IdResolver *atproto.Resolver } -func New(config *config.Config, ph posthog.Client, logger *slog.Logger) (*OAuth, error) { +func New(config *config.Config, ph posthog.Client, idResolver *atproto.Resolver, logger *slog.Logger) (*OAuth, error) { var oauthConfig oauth.ClientConfig var clientUri string @@ -58,6 +60,7 @@ func New(config *config.Config, ph posthog.Client, logger *slog.Logger) (*OAuth, SessionStore: sessStore, JwksUri: jwksUri, Posthog: ph, + IdResolver: idResolver, Logger: logger, }, nil -- 2.43.0