package auth import ( "database/sql" "net/http" "github.com/alexedwards/scs/v2" "github.com/google/uuid" "github.com/starfederation/datastar-go/datastar" "github.com/ryanhamamura/games/auth" "github.com/ryanhamamura/games/db/repository" "github.com/ryanhamamura/games/features/auth/pages" ) type LoginSignals struct { Username string `json:"username"` Password string `json:"password"` //nolint:gosec // form input, not stored } type RegisterSignals struct { Username string `json:"username"` Password string `json:"password"` //nolint:gosec // form input, not stored Confirm string `json:"confirm"` } func HandleLoginPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := pages.LoginPage().Render(r.Context(), w); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } } func HandleRegisterPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := pages.RegisterPage().Render(r.Context(), w); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } } func HandleLogin(queries *repository.Queries, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var signals LoginSignals if err := datastar.ReadSignals(r, &signals); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } sse := datastar.NewSSE(w, r) user, err := queries.GetUserByUsername(r.Context(), signals.Username) if err == sql.ErrNoRows { sse.MarshalAndPatchSignals(map[string]any{"error": "Invalid username or password"}) //nolint:errcheck return } if err != nil { sse.MarshalAndPatchSignals(map[string]any{"error": "An error occurred"}) //nolint:errcheck return } if !auth.CheckPassword(signals.Password, user.PasswordHash) { sse.MarshalAndPatchSignals(map[string]any{"error": "Invalid username or password"}) //nolint:errcheck return } sessions.RenewToken(r.Context()) //nolint:errcheck sessions.Put(r.Context(), "user_id", user.ID) sessions.Put(r.Context(), "username", user.Username) sessions.Put(r.Context(), "nickname", user.Username) redirectURL := "/" if returnURL := sessions.GetString(r.Context(), "return_url"); returnURL != "" { sessions.Put(r.Context(), "return_url", "") redirectURL = returnURL } sse.Redirect(redirectURL) //nolint:errcheck } } func HandleRegister(queries *repository.Queries, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var signals RegisterSignals if err := datastar.ReadSignals(r, &signals); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } sse := datastar.NewSSE(w, r) if err := auth.ValidateUsername(signals.Username); err != nil { sse.MarshalAndPatchSignals(map[string]any{"error": err.Error()}) //nolint:errcheck return } if err := auth.ValidatePassword(signals.Password); err != nil { sse.MarshalAndPatchSignals(map[string]any{"error": err.Error()}) //nolint:errcheck return } if signals.Password != signals.Confirm { sse.MarshalAndPatchSignals(map[string]any{"error": "Passwords do not match"}) //nolint:errcheck return } hash, err := auth.HashPassword(signals.Password) if err != nil { sse.MarshalAndPatchSignals(map[string]any{"error": "An error occurred"}) //nolint:errcheck return } user, err := queries.CreateUser(r.Context(), repository.CreateUserParams{ ID: uuid.New().String(), Username: signals.Username, PasswordHash: hash, }) if err != nil { sse.MarshalAndPatchSignals(map[string]any{"error": "Username already taken"}) //nolint:errcheck return } sessions.RenewToken(r.Context()) //nolint:errcheck sessions.Put(r.Context(), "user_id", user.ID) sessions.Put(r.Context(), "username", user.Username) sessions.Put(r.Context(), "nickname", user.Username) redirectURL := "/" if returnURL := sessions.GetString(r.Context(), "return_url"); returnURL != "" { sessions.Put(r.Context(), "return_url", "") redirectURL = returnURL } sse.Redirect(redirectURL) //nolint:errcheck } }