From e1b63d5debbc3438b80fff531157224361951d2f Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 17 Aug 2026 11:15:45 +0300 Subject: [PATCH] Add signup endpoint and handler --- cmd/main.go | 1 + internal/auth/password.go | 27 ++++++++++++++++++++++++++ internal/handlers/signup.go | 38 ++++++++++++++++++++++++++++++++++++- internal/handlers/user.go | 14 +++++++++++++- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c4c8e9d..f596df9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -39,6 +39,7 @@ func main() { // Routes mux.HandleFunc("/login", handler.LoginHandler) mux.HandleFunc("/user", handler.UserHandler) + mux.HandleFunc("/signup", handler.SignUpHandler) // Apply middleware (CORS first, then logging) muxMiddleware := handlers.LoggingMiddleware(handlers.CORSMiddleware(mux)) diff --git a/internal/auth/password.go b/internal/auth/password.go index 9a2f185..b0446f5 100644 --- a/internal/auth/password.go +++ b/internal/auth/password.go @@ -1,6 +1,7 @@ package auth import ( + "crypto/rand" "crypto/subtle" "encoding/base64" "fmt" @@ -9,6 +10,32 @@ import ( "golang.org/x/crypto/argon2" ) +const ( + argon2Time = 3 + argon2Memory = 65536 + argon2Threads = 4 + argon2KeyLen = 32 +) + +func HashPassword(password string) (string, error) { + salt := make([]byte, 16) + _, err := rand.Read(salt) + + if err != nil { + return "", err + } + + hash := argon2.IDKey([]byte(password), salt, argon2Time, argon2Memory, argon2Threads, argon2KeyLen) + + hashedPassword := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", + argon2.Version, argon2Memory, argon2Time, argon2Threads, + base64.RawStdEncoding.EncodeToString(salt), + base64.RawStdEncoding.EncodeToString(hash), + ) + + return hashedPassword, nil +} + func VerifyPassword(hashedPassword, password string) bool { // Parse the Argon2 hash format: $argon2id$v=19$m=65536,t=3,p=4$salt$hash parts := strings.Split(hashedPassword, "$") diff --git a/internal/handlers/signup.go b/internal/handlers/signup.go index 10ad379..9df0050 100644 --- a/internal/handlers/signup.go +++ b/internal/handlers/signup.go @@ -2,7 +2,9 @@ package handlers import ( "encoding/json" + "go-backend/internal/auth" "go-backend/internal/database/sqlc" + "log" "net/http" ) @@ -36,12 +38,46 @@ func (h *Handler) SignUpHandler(w http.ResponseWriter, r *http.Request) { if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) + return } if user != nil { http.Error(w, "Username already taken", http.StatusBadRequest) + return } - user, err = h.users.CreateUser(r.Context(), sqlc.InsertUserParams{Username: req.Username, Email: req.Email, PasswordHash: req.Password}) + hashedPassword, err := auth.HashPassword(req.Password) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + user, err = h.users.CreateUser(r.Context(), sqlc.InsertUserParams{Username: req.Username, Email: req.Email, PasswordHash: hashedPassword}) + + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + session, err := h.auth.CreateSession(r.Context(), user.ID) + + if err != nil { + log.Printf("Error creating session: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + cookie := &http.Cookie{ + Name: "session", + Value: session.Token, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + MaxAge: 86400, // 24 hours + Path: "/", + } + http.SetCookie(w, cookie) + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Success")) } diff --git a/internal/handlers/user.go b/internal/handlers/user.go index e240e09..0480a69 100644 --- a/internal/handlers/user.go +++ b/internal/handlers/user.go @@ -7,6 +7,12 @@ import ( "go-backend/internal/auth" ) +type UserResponse struct { + ID string `json:"id"` + Username string `json:"username"` + Email string `json:"email"` +} + func (h *Handler) UserHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -43,6 +49,12 @@ func (h *Handler) UserHandler(w http.ResponseWriter, r *http.Request) { return } + resp := UserResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + } + w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(user) + json.NewEncoder(w).Encode(resp) }