package handlers import ( "context" "net/http" "git.haelnorr.com/h/golib/cookies" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/golib/hwsauth" "github.com/pkg/errors" "github.com/uptrace/bun" "git.haelnorr.com/h/oslstats/internal/config" "git.haelnorr.com/h/oslstats/internal/db" "git.haelnorr.com/h/oslstats/internal/discord" "git.haelnorr.com/h/oslstats/internal/rbac" "git.haelnorr.com/h/oslstats/internal/respond" "git.haelnorr.com/h/oslstats/internal/store" "git.haelnorr.com/h/oslstats/internal/throw" authview "git.haelnorr.com/h/oslstats/internal/view/authview" "git.haelnorr.com/h/oslstats/pkg/slapshotapi" ) func Register( s *hws.Server, auth *hwsauth.Authenticator[*db.User, bun.Tx], conn *db.DB, slapAPI *slapshotapi.SlapAPI, cfg *config.Config, store *store.Store, ) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { attempts, exceeded, track := store.TrackRedirect(r, "/register", 3) if exceeded { err := track.Error(attempts) store.ClearRedirectTrack(r, "/register") throw.BadRequest(s, w, r, "Cookies appear to be blocked or disabled. Please enable cookies in your browser and try again", err) return } sessionCookie, err := r.Cookie("registration_session") if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } details, ok := store.GetRegistrationSession(sessionCookie.Value) if !ok { http.Redirect(w, r, "/login", http.StatusSeeOther) return } store.ClearRedirectTrack(r, "/register") if r.Method == "GET" { renderSafely(authview.RegisterPage(""), s, r, w) return } username := r.FormValue("username") unique := false var user *db.User audit := db.NewAudit(r.RemoteAddr, r.UserAgent(), user) if ok := conn.WithWriteTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) { unique, err = db.IsUnique(ctx, tx, (*db.User)(nil), "username", username) if err != nil { return false, errors.Wrap(err, "db.IsUsernameUnique") } if !unique { return true, nil } user, err = registerUser(ctx, tx, username, details, cfg.RBAC, audit) if err != nil { return false, errors.Wrap(err, "registerUser") } err = ConnectSlapID(ctx, tx, user, details.Token, slapAPI, audit) if err != nil { return false, errors.Wrap(err, "connectSlapID") } return true, nil }); !ok { throw.InternalServiceError(s, w, r, "Registration failed", err) return } if !unique { respond.Conflict(w, errors.New("username is taken")) } else { err = auth.Login(w, r, user, true) if err != nil { throw.InternalServiceError(s, w, r, "Login failed", err) return } pageFrom := cookies.CheckPageFrom(w, r) respond.HXRedirect(w, "%s", pageFrom) } }, ) } func registerUser(ctx context.Context, tx bun.Tx, username string, details *store.RegistrationSession, rbac *rbac.Config, audit *db.AuditMeta, ) (*db.User, error) { // Register the user user, err := db.CreateUser(ctx, tx, username, details.DiscordUser, audit) if err != nil { return nil, errors.Wrap(err, "db.CreateUser") } err = user.UpdateDiscordToken(ctx, tx, details.Token) if err != nil { return nil, errors.Wrap(err, "db.UpdateDiscordToken") } err = user.ConnectPlayer(ctx, tx, audit) if err != nil { return nil, errors.Wrap(err, "db.ConnectPlayer") } // Check if they should be an admin if shouldGrantAdmin(user, rbac) { err := ensureUserHasAdminRole(ctx, tx, user) if err != nil { return nil, errors.Wrap(err, "ensureUserHasAdminRole") } } return user, nil } // ConnectSlapID attempts to link a player's Slapshot ID via their Discord Steam connection. // If fails due to no steam connection or no slapID, fails silently and returns nil. func ConnectSlapID(ctx context.Context, tx bun.Tx, user *db.User, token *discord.Token, slapAPI *slapshotapi.SlapAPI, audit *db.AuditMeta, ) error { session, err := discord.NewOAuthSession(token) if err != nil { return errors.Wrap(err, "discord.NewOAuthSession") } steamID, err := session.GetSteamID() if err != nil { if err == discord.ErrNoSteam { return nil } return errors.Wrap(err, "session.GetSteamID") } slapID, err := slapAPI.GetSlapID(ctx, steamID) if err != nil { if err == slapshotapi.ErrNoSlapID { return nil } return errors.Wrap(err, "slapAPI.GetSlapID") } // slapID exists, we can update their player connection err = db.UpdatePlayerSlapID(ctx, tx, user.Player.ID, slapID, audit) if err != nil { return errors.Wrap(err, "db.UpdatePlayerSlapID") } return nil }