Skip to content

Commit efd341d

Browse files
authored
Merge pull request #37 from samims/fix/bugs-about-user-association
Fix bugs and add feature about user association
2 parents 1f28003 + 30ba608 commit efd341d

File tree

13 files changed

+269
-86
lines changed

13 files changed

+269
-86
lines changed

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ services:
4040
- ./infra/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
4141
networks:
4242
- hcaas_backend_network
43+
ports:
44+
- "5432:5432"
4345

4446
hcaas_prometheus:
4547
image: prom/prometheus:latest

services/auth/internal/handler/auth_handler.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,23 @@ func (h *AuthHandler) Validate(w http.ResponseWriter, r *http.Request) {
9797
}
9898

9999
token := strings.TrimPrefix(authHeader, "Bearer ")
100-
userID, err := h.authSvc.ValidateToken(token)
100+
userID, email, err := h.authSvc.ValidateToken(token)
101101
if err != nil {
102102
respondError(w, http.StatusUnauthorized, "invalid token")
103103
}
104104

105-
resp := map[string]string{"user_id": userID}
105+
resp := struct {
106+
UserID string `json:"user_id"`
107+
Email string `json:"email"` // Alternative field name
108+
}{
109+
UserID: userID,
110+
Email: email, // Set both fields for backward compatibility
111+
}
112+
106113
w.Header().Set("Content-Type", "application/json")
107-
json.NewEncoder(w).Encode(resp)
114+
if err := json.NewEncoder(w).Encode(resp); err != nil {
115+
h.logger.Error("Failed to encode validation response",
116+
slog.String("error", err.Error()))
117+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
118+
}
108119
}

services/auth/internal/middleware/auth.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import (
1010

1111
type key string
1212

13-
const userIDKey key = "userID"
13+
const (
14+
contextUserIDKey key = "user_id"
15+
contextEmailKey
16+
)
1417

1518
func UserIDFromContext(ctx context.Context) (string, bool) {
16-
uid, ok := ctx.Value(userIDKey).(string)
19+
uid, ok := ctx.Value(contextUserIDKey).(string)
1720
return uid, ok
1821
}
1922

@@ -27,13 +30,15 @@ func AuthMiddleware(tokenService service.TokenService) func(http.Handler) http.H
2730
}
2831

2932
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
30-
userID, err := tokenService.ValidateToken(tokenStr)
33+
userID, email, err := tokenService.ValidateToken(tokenStr)
3134
if err != nil {
3235
http.Error(w, "invalid or expired token", http.StatusUnauthorized)
3336
return
3437
}
3538

36-
ctx := context.WithValue(r.Context(), userIDKey, userID)
39+
ctx := context.WithValue(r.Context(), contextUserIDKey, userID)
40+
ctx = context.WithValue(ctx, contextEmailKey, email)
41+
3742
next.ServeHTTP(w, r.WithContext(ctx))
3843
})
3944
}

services/auth/internal/service/auth_service.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type AuthService interface {
1919
Register(ctx context.Context, email, password string) (*model.User, error)
2020
Login(ctx context.Context, email, password string) (*model.User, string, error)
2121
GetUserByEmail(ctx context.Context, email string) (*model.User, error)
22-
ValidateToken(token string) (string, error)
22+
ValidateToken(token string) (string, string, error)
2323
}
2424

2525
type authService struct {
@@ -109,13 +109,13 @@ func (s *authService) GetUserByEmail(ctx context.Context, email string) (*model.
109109
return user, nil
110110
}
111111

112-
func (s *authService) ValidateToken(token string) (string, error) {
112+
func (s *authService) ValidateToken(token string) (string, string, error) {
113113
s.logger.Info("ValidateToken called")
114-
userID, err := s.tokenSvc.ValidateToken(token)
114+
userID, email, err := s.tokenSvc.ValidateToken(token)
115115
if err != nil {
116116
s.logger.Info("Token validation failed", slog.String("error", err.Error()))
117-
return "", err
117+
return "", "", err
118118
}
119-
return userID, nil
119+
return userID, email, nil
120120

121121
}

services/auth/internal/service/token_service.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
type TokenService interface {
1414
GenerateToken(user *model.User) (string, error)
15-
ValidateToken(tokenStr string) (string, error)
15+
ValidateToken(tokenStr string) (string, string, error)
1616
}
1717

1818
type jwtService struct {
@@ -42,25 +42,27 @@ func (s *jwtService) GenerateToken(user *model.User) (string, error) {
4242
return token.SignedString([]byte(s.secret))
4343
}
4444

45-
func (s *jwtService) ValidateToken(tokenStr string) (string, error) {
45+
func (s *jwtService) ValidateToken(tokenStr string) (string, string, error) {
4646
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
4747
return []byte(s.secret), nil
4848
})
4949

5050
if err != nil || !token.Valid {
5151
s.logger.Error("Invalid token ")
52-
return "", err
52+
return "", "", err
5353
}
5454

5555
claims, ok := token.Claims.(jwt.MapClaims)
5656
if !ok {
5757
s.logger.Error("token verification failed malformed")
58-
return "", jwt.ErrTokenMalformed
58+
return "", "", jwt.ErrTokenMalformed
5959
}
60-
sub, ok := claims["sub"].(string)
60+
userID, ok := claims["sub"].(string)
6161
if !ok {
6262
s.logger.Error("token verification failed malformed!")
63-
return "", jwt.ErrTokenMalformed
63+
return "", "", jwt.ErrTokenMalformed
6464
}
65-
return sub, nil
65+
email, ok := claims["email"].(string)
66+
67+
return userID, email, nil
6668
}

services/url/internal/handler/url.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,23 @@ func NewURLHandler(s service.URLService, logger *slog.Logger) *URLHandler {
2424
func (h *URLHandler) GetAll(w http.ResponseWriter, r *http.Request) {
2525
urls, err := h.svc.GetAll(r.Context())
2626
if err != nil {
27-
h.logger.Error("GetAll failed", "error", err)
27+
h.logger.Error("GetAll failed", slog.Any("error", err))
2828
http.Error(w, err.Error(), http.StatusInternalServerError)
2929
return
3030
}
3131
json.NewEncoder(w).Encode(urls)
3232
}
3333

34+
func (h *URLHandler) GetAllByUserID(w http.ResponseWriter, r *http.Request) {
35+
urls, err := h.svc.GetAllByUserID(r.Context())
36+
if err != nil {
37+
h.logger.Error("GetAllByUSerID failed", slog.Any("error", err))
38+
http.Error(w, err.Error(), http.StatusInternalServerError)
39+
}
40+
41+
json.NewEncoder(w).Encode(urls)
42+
}
43+
3444
func (h *URLHandler) GetByID(w http.ResponseWriter, r *http.Request) {
3545
id := chi.URLParam(r, "id")
3646
url, err := h.svc.GetByID(r.Context(), id)

services/url/internal/middleware/auth.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"log/slog"
88
"net/http"
99
"strings"
10+
11+
"github.com/samims/hcaas/services/url/internal/model"
1012
)
1113

1214
func AuthMiddleware(authServiceURL string, logger *slog.Logger) func(http.Handler) http.Handler {
@@ -20,7 +22,13 @@ func AuthMiddleware(authServiceURL string, logger *slog.Logger) func(http.Handle
2022
}
2123
token := strings.TrimPrefix(authHeader, "Bearer ")
2224

23-
req, err := http.NewRequest(http.MethodGet, authServiceURL+"auth/validate", nil)
25+
validateURL := authServiceURL + "auth/validate"
26+
logger.Debug("Calling auth service validation endpoint",
27+
"url", validateURL,
28+
"method", r.Method,
29+
"path", r.URL.Path)
30+
31+
req, err := http.NewRequest(http.MethodGet, validateURL, nil)
2432
if err != nil {
2533
logger.Error("Failed to create request to auth service", "error", err)
2634
http.Error(w, "Unauthorized", http.StatusUnauthorized)
@@ -43,16 +51,53 @@ func AuthMiddleware(authServiceURL string, logger *slog.Logger) func(http.Handle
4351
return
4452
}
4553

46-
var data struct {
54+
bodyBytes, err := io.ReadAll(resp.Body)
55+
if err != nil {
56+
logger.Error("Failed to read auth response body", "error", err)
57+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
58+
return
59+
}
60+
61+
logger.Debug("Auth service response", "body", string(bodyBytes))
62+
63+
var authResponse struct {
4764
UserID string `json:"user_id"`
65+
Email string `json:"email"`
4866
}
49-
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
50-
logger.Error("Failed to decode auth service response", "error", err)
67+
68+
if err := json.Unmarshal(bodyBytes, &authResponse); err != nil {
69+
logger.Error("Failed to decode auth service response",
70+
"error", err,
71+
"response", string(bodyBytes))
5172
http.Error(w, "Unauthorized", http.StatusUnauthorized)
5273
return
5374
}
5475

55-
ctx := context.WithValue(r.Context(), "userID", data.UserID)
76+
if authResponse.UserID == "" {
77+
logger.Error("No user identifier found in auth response",
78+
slog.String("response", string(bodyBytes)))
79+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
80+
return
81+
}
82+
83+
if authResponse.Email == "" {
84+
logger.Error("No email found in auth response", slog.String("response", string(bodyBytes)))
85+
}
86+
87+
ctx := context.WithValue(r.Context(), model.ContextUserIDKey, authResponse.UserID)
88+
ctx = context.WithValue(ctx, model.ContextEmailKey, authResponse.Email)
89+
logger.Info("User authenticated",
90+
"user_id", authResponse.UserID,
91+
"method", r.Method,
92+
"path", r.URL.Path)
93+
94+
// Verify context value is set correctly
95+
if ctx.Value(model.ContextUserIDKey) == nil {
96+
logger.Error("Failed to set user_id in context")
97+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
98+
return
99+
}
100+
56101
next.ServeHTTP(w, r.WithContext(ctx))
57102
})
58103
}

services/url/internal/model/url.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ package model
22

33
import "time"
44

5+
const (
6+
ContextUserIDKey = "user_id"
7+
ContextEmailKey = "email"
8+
)
9+
510
type URL struct {
611
ID string `json:"id"`
12+
UserID string `json:"user_id"`
713
Address string `json:"address"`
814
Status string `json:"status"` // "up" or "down"
915
CheckedAt time.Time `json:"checked_at"` // last checked time

services/url/internal/router/router.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ func NewRouter(h *handler.URLHandler, healthHandler *handler.HealthHandler, logg
2727
r.Use(middleware.Recoverer)
2828
r.Use(middleware.Timeout(30 * time.Second))
2929

30-
r.With(authMiddleware).Route("/urls", func(r chi.Router) {
30+
r.Route("/urls", func(r chi.Router) {
31+
r.Use(authMiddleware)
3132
r.Get("/", h.GetAll)
3233
r.Get("/{id}", h.GetByID)
34+
r.Get("/me", h.GetAllByUserID)
3335
r.Post("/", h.Add)
34-
r.Put("/{id}", h.UpdateStatus)
3536
})
3637

3738
// Health & Readiness Routes

services/url/internal/service/health.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type HealthService interface {
1414
}
1515

1616
type healthService struct {
17-
store storage.HealthCheckStorage
17+
store storage.Storage
1818
logger *slog.Logger
1919
}
2020

@@ -38,7 +38,7 @@ func (s healthService) Readiness(ctx context.Context) error {
3838
return nil
3939
}
4040

41-
func NewHealthService(store storage.HealthCheckStorage, logger *slog.Logger) HealthService {
41+
func NewHealthService(store storage.Storage, logger *slog.Logger) HealthService {
4242
l := logger.With("layer", "service", "component", "healthService")
4343
return &healthService{store: store, logger: l}
4444
}

0 commit comments

Comments
 (0)