Add signup endpoint and handler
This commit is contained in:
parent
6b8f02fa0f
commit
e1b63d5deb
4 changed files with 78 additions and 2 deletions
|
|
@ -39,6 +39,7 @@ func main() {
|
||||||
// Routes
|
// Routes
|
||||||
mux.HandleFunc("/login", handler.LoginHandler)
|
mux.HandleFunc("/login", handler.LoginHandler)
|
||||||
mux.HandleFunc("/user", handler.UserHandler)
|
mux.HandleFunc("/user", handler.UserHandler)
|
||||||
|
mux.HandleFunc("/signup", handler.SignUpHandler)
|
||||||
|
|
||||||
// Apply middleware (CORS first, then logging)
|
// Apply middleware (CORS first, then logging)
|
||||||
muxMiddleware := handlers.LoggingMiddleware(handlers.CORSMiddleware(mux))
|
muxMiddleware := handlers.LoggingMiddleware(handlers.CORSMiddleware(mux))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -9,6 +10,32 @@ import (
|
||||||
"golang.org/x/crypto/argon2"
|
"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 {
|
func VerifyPassword(hashedPassword, password string) bool {
|
||||||
// Parse the Argon2 hash format: $argon2id$v=19$m=65536,t=3,p=4$salt$hash
|
// Parse the Argon2 hash format: $argon2id$v=19$m=65536,t=3,p=4$salt$hash
|
||||||
parts := strings.Split(hashedPassword, "$")
|
parts := strings.Split(hashedPassword, "$")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"go-backend/internal/auth"
|
||||||
"go-backend/internal/database/sqlc"
|
"go-backend/internal/database/sqlc"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,12 +38,46 @@ func (h *Handler) SignUpHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
if user != nil {
|
||||||
http.Error(w, "Username already taken", http.StatusBadRequest)
|
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"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ import (
|
||||||
"go-backend/internal/auth"
|
"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) {
|
func (h *Handler) UserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
|
@ -43,6 +49,12 @@ func (h *Handler) UserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := UserResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
Email: user.Email,
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(user)
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue