Files
games/features/auth/handlers.go
Ryan Hamamura c6885a069b
All checks were successful
CI / Deploy / test (pull_request) Successful in 14s
CI / Deploy / lint (pull_request) Successful in 25s
CI / Deploy / deploy (pull_request) Has been skipped
refactor: rename Go module from c4 to games
Rename module path github.com/ryanhamamura/c4 to
github.com/ryanhamamura/games across go.mod, all source files,
and golangci config.
2026-03-02 20:41:20 -10:00

135 lines
4.2 KiB
Go

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
}
}