Define KeyPlayerID, KeyUserID, and KeyNickname in the sessions package and use them across all handlers to avoid duplicated magic strings.
136 lines
4.3 KiB
Go
136 lines
4.3 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"
|
|
appsessions "github.com/ryanhamamura/games/sessions"
|
|
)
|
|
|
|
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(), appsessions.KeyUserID, user.ID)
|
|
sessions.Put(r.Context(), "username", user.Username)
|
|
sessions.Put(r.Context(), appsessions.KeyNickname, 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(), appsessions.KeyUserID, user.ID)
|
|
sessions.Put(r.Context(), "username", user.Username)
|
|
sessions.Put(r.Context(), appsessions.KeyNickname, 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
|
|
}
|
|
}
|