Skip to content

Commit 46bea7c

Browse files
committed
update handlers and readme
1 parent 7c3d5ce commit 46bea7c

File tree

6 files changed

+499
-67
lines changed

6 files changed

+499
-67
lines changed

internal/auth/password.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,30 @@ import (
44
"golang.org/x/crypto/bcrypt"
55
)
66

7-
// HashPassword generates a bcrypt hash for the given password.
8-
func HashPassword(password string) (string, error) {
7+
// PasswordHasher defines the interface for hashing and verifying passwords.
8+
type PasswordHasher interface {
9+
HashPassword(password string) (string, error)
10+
CheckPassword(hashedPassword, password string) error
11+
}
12+
13+
// BcryptPasswordHasher implements PasswordHasher using bcrypt.
14+
type BcryptPasswordHasher struct{}
15+
16+
// NewBcryptPasswordHasher creates a new instance of BcryptPasswordHasher.
17+
func NewBcryptPasswordHasher() PasswordHasher {
18+
return &BcryptPasswordHasher{}
19+
}
20+
21+
// HashPassword hashes the given password using bcrypt.
22+
func (h *BcryptPasswordHasher) HashPassword(password string) (string, error) {
923
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
1024
if err != nil {
1125
return "", err
1226
}
1327
return string(hashedPassword), nil
1428
}
1529

16-
// CheckPasswordHash compares a plain text password with a stored bcrypt hash.
17-
func CheckPasswordHash(password, hash string) bool {
18-
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
19-
return err == nil // Returns true if the password matches the hash
30+
// CheckPassword compares a hashed password with a plaintext password.
31+
func (h *BcryptPasswordHasher) CheckPassword(hashedPassword, password string) error {
32+
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
2033
}

internal/handlers/auth_handler.go

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,30 @@ package handlers
22

33
import (
44
"bullet-cloud-api/internal/auth"
5+
"bullet-cloud-api/internal/models"
56
"bullet-cloud-api/internal/users"
67
"bullet-cloud-api/internal/webutils"
8+
"context"
9+
"encoding/json"
710
"errors"
811
"net/http"
12+
"strings"
913
"time"
1014
)
1115

1216
// AuthHandler handles authentication requests.
1317
type AuthHandler struct {
1418
UserRepo users.UserRepository
19+
Hasher auth.PasswordHasher
1520
JwtSecret string
1621
TokenExpiryDuration time.Duration
1722
}
1823

1924
// NewAuthHandler creates a new AuthHandler.
20-
func NewAuthHandler(userRepo users.UserRepository, jwtSecret string, tokenExpiry time.Duration) *AuthHandler {
25+
func NewAuthHandler(userRepo users.UserRepository, hasher auth.PasswordHasher, jwtSecret string, tokenExpiry time.Duration) *AuthHandler {
2126
return &AuthHandler{
2227
UserRepo: userRepo,
28+
Hasher: hasher,
2329
JwtSecret: jwtSecret,
2430
TokenExpiryDuration: tokenExpiry,
2531
}
@@ -46,79 +52,100 @@ type LoginResponse struct {
4652

4753
// Register handles new user registration.
4854
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
49-
var req RegisterRequest
50-
if err := webutils.ReadJSON(r, &req); err != nil {
55+
var req struct {
56+
Name string `json:"name"`
57+
Email string `json:"email"`
58+
Password string `json:"password"`
59+
}
60+
61+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
5162
webutils.ErrorJSON(w, errors.New("invalid request body"), http.StatusBadRequest)
5263
return
5364
}
5465

55-
// Basic validation
66+
req.Name = strings.TrimSpace(req.Name)
67+
req.Email = strings.TrimSpace(strings.ToLower(req.Email))
68+
5669
if req.Name == "" || req.Email == "" || req.Password == "" {
5770
webutils.ErrorJSON(w, errors.New("name, email, and password are required"), http.StatusBadRequest)
5871
return
5972
}
60-
// TODO: Add more robust validation (email format, password strength)
6173

62-
// Hash password
63-
hashedPassword, err := auth.HashPassword(req.Password)
74+
// Check if email already exists
75+
_, err := h.UserRepo.FindByEmail(context.Background(), req.Email)
76+
if err == nil {
77+
webutils.ErrorJSON(w, errors.New("email already registered"), http.StatusConflict)
78+
return
79+
} else if !errors.Is(err, users.ErrUserNotFound) {
80+
webutils.ErrorJSON(w, errors.New("failed to check email existence"), http.StatusInternalServerError)
81+
return
82+
}
83+
84+
// Hash the password using the injected hasher
85+
hashedPassword, err := h.Hasher.HashPassword(req.Password)
6486
if err != nil {
65-
webutils.ErrorJSON(w, errors.New("failed to hash password"), http.StatusInternalServerError)
87+
webutils.ErrorJSON(w, errors.New("failed to register user"), http.StatusInternalServerError)
6688
return
6789
}
6890

69-
// Create user
70-
newUser, err := h.UserRepo.Create(r.Context(), req.Name, req.Email, hashedPassword)
91+
user := &models.User{
92+
Name: req.Name,
93+
Email: req.Email,
94+
PasswordHash: hashedPassword,
95+
}
96+
97+
// Call UserRepo.Create with individual fields as per current repo signature
98+
createdUser, err := h.UserRepo.Create(context.Background(), user.Name, user.Email, user.PasswordHash)
7199
if err != nil {
72-
if errors.Is(err, users.ErrEmailAlreadyExists) {
73-
webutils.ErrorJSON(w, err, http.StatusConflict)
74-
} else {
75-
webutils.ErrorJSON(w, errors.New("failed to create user"), http.StatusInternalServerError)
76-
}
100+
webutils.ErrorJSON(w, errors.New("failed to register user"), http.StatusInternalServerError)
77101
return
78102
}
79103

80-
// Return created user (ensure password hash is not included)
81-
newUser.PasswordHash = "" // Already done in repository, but good practice to double-check
82-
webutils.WriteJSON(w, http.StatusCreated, newUser)
104+
webutils.WriteJSON(w, http.StatusCreated, createdUser)
83105
}
84106

85107
// Login handles user login and JWT generation.
86108
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
87-
var req LoginRequest
88-
if err := webutils.ReadJSON(r, &req); err != nil {
109+
var req struct {
110+
Email string `json:"email"`
111+
Password string `json:"password"`
112+
}
113+
114+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
89115
webutils.ErrorJSON(w, errors.New("invalid request body"), http.StatusBadRequest)
90116
return
91117
}
92118

119+
req.Email = strings.TrimSpace(strings.ToLower(req.Email))
120+
93121
if req.Email == "" || req.Password == "" {
94122
webutils.ErrorJSON(w, errors.New("email and password are required"), http.StatusBadRequest)
95123
return
96124
}
97125

98-
// Find user by email
99-
user, err := h.UserRepo.FindByEmail(r.Context(), req.Email)
126+
user, err := h.UserRepo.FindByEmail(context.Background(), req.Email)
100127
if err != nil {
101128
if errors.Is(err, users.ErrUserNotFound) {
102129
webutils.ErrorJSON(w, errors.New("invalid email or password"), http.StatusUnauthorized)
103130
} else {
104-
webutils.ErrorJSON(w, errors.New("failed to find user"), http.StatusInternalServerError)
131+
webutils.ErrorJSON(w, errors.New("login failed"), http.StatusInternalServerError)
105132
}
106133
return
107134
}
108135

109-
// Check password
110-
if !auth.CheckPasswordHash(req.Password, user.PasswordHash) {
136+
// Check the password using the injected hasher
137+
err = h.Hasher.CheckPassword(user.PasswordHash, req.Password)
138+
if err != nil { // bcrypt.CompareHashAndPassword returns error on mismatch
111139
webutils.ErrorJSON(w, errors.New("invalid email or password"), http.StatusUnauthorized)
112140
return
113141
}
114142

115-
// Generate JWT
116-
tokenString, err := auth.GenerateToken(user.ID, h.JwtSecret, h.TokenExpiryDuration)
143+
// Generate JWT using the correct function name
144+
token, err := auth.GenerateToken(user.ID, h.JwtSecret, h.TokenExpiryDuration)
117145
if err != nil {
118-
webutils.ErrorJSON(w, errors.New("failed to generate token"), http.StatusInternalServerError)
146+
webutils.ErrorJSON(w, errors.New("login failed"), http.StatusInternalServerError)
119147
return
120148
}
121149

122-
// Return token
123-
webutils.WriteJSON(w, http.StatusOK, LoginResponse{Token: tokenString})
150+
webutils.WriteJSON(w, http.StatusOK, map[string]string{"token": token})
124151
}

0 commit comments

Comments
 (0)