Skip to content

Commit 6412973

Browse files
raischpwdel
andauthored
Fix/surface better error messages from api (#516)
* add tmp/ for stuff i don't want to publish * improved handling of errors from api * Added a slightly more formal logger. Happy to backout if needed. * improved error msging * removed ver from openssl * Updating conventions and test file. Tests passed. --------- Co-authored-by: Patrick Delaney <patrick.del@gmail.com>
1 parent 5f46901 commit 6412973

File tree

10 files changed

+374
-30
lines changed

10 files changed

+374
-30
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ go.work
4040
# Keys
4141
.playground_key_pointer
4242

43+
# Local temporary files
44+
tmp/
45+
4346
# JetBrains files
4447
/.idea
4548

backend/handlers/users/changepassword.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package usershandlers
33
import (
44
"encoding/json"
55
"net/http"
6+
"socialpredict/logger"
67
"socialpredict/middleware"
78
"socialpredict/security"
89
"socialpredict/util"
10+
11+
"fmt"
912
)
1013

1114
type ChangePasswordRequest struct {
@@ -19,48 +22,58 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) {
1922
return
2023
}
2124

25+
logger.LogInfo("ChangePassword", "ChangePassword", "ChangePassword handler called")
26+
2227
// Initialize security service
2328
securityService := security.NewSecurityService()
2429

2530
db := util.GetDB()
2631
user, httperr := middleware.ValidateTokenAndGetUser(r, db)
2732
if httperr != nil {
2833
http.Error(w, "Invalid token: "+httperr.Error(), http.StatusUnauthorized)
34+
logger.LogError("ChangePassword", "ValidateTokenAndGetUser", httperr)
2935
return
3036
}
3137

3238
var req ChangePasswordRequest
3339
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
3440
http.Error(w, "Error decoding request body", http.StatusBadRequest)
41+
logger.LogError("ChangePassword", "DecodeRequestBody", err)
3542
return
3643
}
3744

3845
// Validate input fields
3946
if req.CurrentPassword == "" {
4047
http.Error(w, "Current password is required", http.StatusBadRequest)
48+
logger.LogError("ChangePassword", "ValidateInputFields", fmt.Errorf("Current password is required"))
4149
return
4250
}
4351

4452
if req.NewPassword == "" {
4553
http.Error(w, "New password is required", http.StatusBadRequest)
54+
logger.LogError("ChangePassword", "ValidateInputFields", fmt.Errorf("New password is required"))
4655
return
4756
}
4857

4958
// Check if the current password is correct
5059
if !user.CheckPasswordHash(req.CurrentPassword) {
5160
http.Error(w, "Current password is incorrect", http.StatusUnauthorized)
61+
logger.LogError("ChangePassword", "CheckPasswordHash", fmt.Errorf("Current password is incorrect"))
5262
return
5363
}
5464

5565
// Validate new password strength
56-
if err := securityService.Sanitizer.SanitizePassword(req.NewPassword); err != nil {
57-
http.Error(w, "New password does not meet security requirements: "+err.Error(), http.StatusBadRequest)
66+
if _, err := securityService.Sanitizer.SanitizePassword(req.NewPassword); err != nil {
67+
http.Error(w, err.Error(), http.StatusBadRequest)
68+
// http.Error(w, "New password does not meet security requirements: "+err.Error(), http.StatusBadRequest)
69+
logger.LogError("ChangePassword", "ValidateNewPasswordStrength", err)
5870
return
5971
}
6072

6173
// Hash the new password
6274
if err := user.HashPassword(req.NewPassword); err != nil {
6375
http.Error(w, "Failed to hash new password", http.StatusInternalServerError)
76+
logger.LogError("ChangePassword", "HashNewPassword", err)
6477
return
6578
}
6679

@@ -70,10 +83,12 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) {
7083
// Update the password and MustChangePassword in the database
7184
if result := db.Save(&user); result.Error != nil {
7285
http.Error(w, "Failed to update password", http.StatusInternalServerError)
86+
logger.LogError("ChangePassword", "UpdatePasswordInDB", result.Error)
7387
return
7488
}
7589

7690
// Send a success response
7791
w.WriteHeader(http.StatusOK)
7892
w.Write([]byte("Password changed successfully"))
93+
logger.LogInfo("ChangePassword", "ChangePassword", "Password changed successfully for user "+user.Username)
7994
}

backend/handlers/users/changepassword_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func TestPasswordValidation(t *testing.T) {
7777

7878
// Test password strength validation
7979
securityService := security.NewSecurityService()
80-
err := securityService.Sanitizer.SanitizePassword(tt.newPass)
80+
_, err := securityService.Sanitizer.SanitizePassword(tt.newPass)
8181

8282
if tt.expectedValid {
8383
if err != nil {
@@ -151,7 +151,7 @@ func TestPasswordStrengthRequirements(t *testing.T) {
151151

152152
for _, tt := range tests {
153153
t.Run(tt.name, func(t *testing.T) {
154-
err := securityService.Sanitizer.SanitizePassword(tt.password)
154+
_, err := securityService.Sanitizer.SanitizePassword(tt.password)
155155

156156
if tt.shouldPass {
157157
if err != nil {
@@ -186,7 +186,7 @@ func TestPasswordSecurityFeatures(t *testing.T) {
186186
testName = testName[:10]
187187
}
188188
t.Run("Security_Test_"+testName, func(t *testing.T) {
189-
err := securityService.Sanitizer.SanitizePassword(maliciousPass)
189+
_, err := securityService.Sanitizer.SanitizePassword(maliciousPass)
190190

191191
// These should either be rejected for weakness or pass sanitization
192192
// The key is that they shouldn't cause any security issues
@@ -235,7 +235,7 @@ func TestPasswordLengthValidation(t *testing.T) {
235235
password = "A" + "a" + "1" + strings.Repeat("x", remainingLength)
236236
}
237237

238-
err := securityService.Sanitizer.SanitizePassword(password)
238+
_, err := securityService.Sanitizer.SanitizePassword(password)
239239

240240
if tt.shouldPass {
241241
if err != nil && strings.Contains(err.Error(), "length") {
@@ -287,7 +287,7 @@ func TestPasswordComplexityPatterns(t *testing.T) {
287287

288288
for _, pattern := range patterns {
289289
t.Run(pattern.name, func(t *testing.T) {
290-
err := securityService.Sanitizer.SanitizePassword(pattern.password)
290+
_, err := securityService.Sanitizer.SanitizePassword(pattern.password)
291291

292292
if pattern.shouldPass {
293293
if err != nil {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
### SocialPredict Logging — simplelogging.go Conventions & Usage Overview
2+
3+
Purpose: provide consistent INFO, WARN, and ERROR lines that include the original caller’s file:line plus your message.
4+
5+
Output format (stdout): YYYY/MM/DD HH:MM:SS LEVEL file.go:line logger.(*CustomLogger).Level() - Context - Function: Message
6+
7+
Where to view (Docker): run and watch backend logs
8+
docker logs -f socialpredict-backend-container
9+
10+
Current Logger with levels: (backend/logger/simplelogging.go)
11+
12+
```
13+
// LogInfo is a convenience function to log informational messages with context.
14+
func LogInfo(context, function, message string) {
15+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
16+
logger.Info(fmt.Sprintf("%s - %s: %s", context, function, message))
17+
}
18+
19+
// LogWarn is a convenience function to log warning messages with context.
20+
func LogWarn(context, function, message string) {
21+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
22+
logger.Warn(fmt.Sprintf("%s - %s: %s", context, function, message))
23+
}
24+
25+
// LogError is a convenience function to log errors with context.
26+
func LogError(context, function string, err error) {
27+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
28+
logger.Error(fmt.Sprintf("%s - %s: %v", context, function, err))
29+
}
30+
```
31+
32+
#### Usage in code (handlers or core)
33+
34+
```
35+
Import the logger package
36+
import "socialpredict/logger"
37+
38+
Log at key points in your function
39+
40+
logger.LogInfo("ChangePassword", "ChangePassword", "ChangePassword handler called")
41+
42+
securityService := security.NewSecurityService()
43+
db := util.GetDB()
44+
45+
user, httperr := middleware.ValidateTokenAndGetUser(r, db)
46+
if httperr != nil {
47+
http.Error(w, "Invalid token: "+httperr.Error(), http.StatusUnauthorized)
48+
logger.LogError("ChangePassword", "ValidateTokenAndGetUser", httperr)
49+
return
50+
}
51+
52+
if _, err := securityService.Sanitizer.SanitizePassword(req.NewPassword); err != nil {
53+
http.Error(w, err.Error(), http.StatusBadRequest)
54+
logger.LogError("ChangePassword", "ValidateNewPasswordStrength", err)
55+
return
56+
}
57+
```
58+
59+
#### Conventions for message content
60+
61+
Format: Context - Function: Message
62+
63+
Context examples: ChangePassword, BuyPosition, ResolveMarket
64+
65+
Function examples: DecodeRequestBody, ValidateInputFields, UpdatePasswordInDB
66+
67+
Message: concise, human-readable, and safe
68+
69+
Do not include passwords, raw tokens, or full request bodies
70+
71+
Example outputs
72+
73+
```
74+
2025/10/06 11:36:13 INFO changepassword.go:25 logger.(*CustomLogger).Info() - ChangePassword - ChangePassword: ChangePassword handler called
75+
76+
2025/10/06 11:36:13 ERROR changepassword.go:54 logger.(*CustomLogger).Error() - ChangePassword - ValidateInputFields: New password is required
77+
```

backend/logger/simplelogging.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package logger
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"path"
8+
"runtime"
9+
)
10+
11+
// CustomLogger provides logging with enhanced details.
12+
type CustomLogger struct {
13+
logger *log.Logger
14+
}
15+
16+
// NewCustomLogger creates a new CustomLogger instance.
17+
func NewCustomLogger(output *os.File, prefix string, flags int) *CustomLogger {
18+
return &CustomLogger{
19+
logger: log.New(output, prefix, flags),
20+
}
21+
}
22+
23+
// Info logs an informational message with caller details.
24+
func (l *CustomLogger) Info(message string) {
25+
l.logWithCaller("INFO", message)
26+
}
27+
28+
// Warn logs a warning message with caller details.
29+
func (l *CustomLogger) Warn(message string) {
30+
l.logWithCaller("WARN", message)
31+
}
32+
33+
// Error logs an error message with caller details.
34+
func (l *CustomLogger) Error(message string) {
35+
l.logWithCaller("ERROR", message)
36+
}
37+
38+
// logWithCaller retrieves caller information and logs the message.
39+
func (l *CustomLogger) logWithCaller(level, message string) {
40+
_, file, line, ok := runtime.Caller(3) // Caller(3) gets the caller of the Info/Error method
41+
if !ok {
42+
file = "???"
43+
line = 0
44+
}
45+
funcName := "???"
46+
pc, _, _, ok := runtime.Caller(1) // Caller(1) gets the immediate caller of logWithCaller
47+
if ok {
48+
fn := runtime.FuncForPC(pc)
49+
if fn != nil {
50+
funcName = fn.Name()
51+
}
52+
}
53+
54+
formattedMessage := fmt.Sprintf("%s %s:%d %s() - %s", level, path.Base(file), line, path.Base(funcName), message)
55+
l.logger.Println(formattedMessage)
56+
}
57+
58+
// LogInfo is a convenience function to log informational messages with context.
59+
func LogInfo(context, function, message string) {
60+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
61+
logger.Info(fmt.Sprintf("%s - %s: %s", context, function, message))
62+
}
63+
64+
// LogWarn is a convenience function to log warning messages with context.
65+
func LogWarn(context, function, message string) {
66+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
67+
logger.Warn(fmt.Sprintf("%s - %s: %s", context, function, message))
68+
}
69+
70+
// LogError is a convenience function to log errors with context.
71+
func LogError(context, function string, err error) {
72+
logger := NewCustomLogger(os.Stdout, "", log.LstdFlags)
73+
logger.Error(fmt.Sprintf("%s - %s: %v", context, function, err))
74+
}

0 commit comments

Comments
 (0)