// routes/password_reset_complete.go package routes import ( "bssapp-backend/auth" "bssapp-backend/internal/auditlog" "bssapp-backend/internal/security" "bssapp-backend/repository" "database/sql" "encoding/json" "net/http" "time" "golang.org/x/crypto/bcrypt" ) func CompletePasswordResetHandler(db *sql.DB) http.HandlerFunc { type request struct { Token string `json:"token"` Password string `json:"password"` } return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") var req request if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Token == "" || req.Password == "" { http.Error(w, "invalid payload", http.StatusBadRequest) return } if err := security.ValidatePassword(req.Password); err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } tokenHash := security.HashToken(req.Token) // 1) token doğrula var userID int64 var expiresAt time.Time err := db.QueryRow(` SELECT mk_dfusr_id, expires_at FROM mk_dfusr_password_reset WHERE token = $1 AND used_at IS NULL `, tokenHash).Scan(&userID, &expiresAt) if err != nil || time.Now().After(expiresAt) { http.Error(w, "invalid or expired token", http.StatusUnprocessableEntity) return } // 2) yeni hash passHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { http.Error(w, "hash error", http.StatusInternalServerError) return } // 3) tx: şifre + used_at tx, err := db.Begin() if err != nil { http.Error(w, "tx error", http.StatusInternalServerError) return } defer tx.Rollback() // mk_dfusr güncelle if _, err := tx.Exec(` UPDATE mk_dfusr SET password_hash = $1, force_password_change = false, password_updated_at = now(), updated_at = now() WHERE id = $2 `, string(passHash), userID); err != nil { http.Error(w, "update failed", http.StatusInternalServerError) return } // token tüket if _, err := tx.Exec(` UPDATE mk_dfusr_password_reset SET used_at = now() WHERE token = $1 `, tokenHash); err != nil { http.Error(w, "token update failed", http.StatusInternalServerError) return } if err := tx.Commit(); err != nil { http.Error(w, "commit failed", http.StatusInternalServerError) return } // 4) refresh revoke _ = repository.NewRefreshTokenRepository(db).RevokeAllForUser(userID) // 5) user + perms + jwt mkRepo := repository.NewMkUserRepository(db) u, err := mkRepo.GetByID(userID) if err != nil { http.Error(w, "user fetch failed", http.StatusInternalServerError) return } permRepo := repository.NewPermissionRepository(db) perms, _ := permRepo.GetPermissionsByRoleID(u.RoleID) // ✅ CLAIMS BUILD claims := auth.BuildClaimsFromUser(u, 15*time.Minute) // ✅ TOKEN jwtStr, err := auth.GenerateToken( claims, u.Username, false, ) if err != nil { http.Error(w, "token generation failed", http.StatusInternalServerError) return } auditlog.Write(auditlog.ActivityLog{ ActionType: "PASSWORD_RESET_COMPLETED", ActionCategory: "security", ActionTarget: u.Username, IsSuccess: true, }) _ = json.NewEncoder(w).Encode(map[string]any{ "success": true, "token": jwtStr, "user": map[string]any{ "id": u.ID, "username": u.Username, "email": u.Email, "is_active": u.IsActive, "role_id": u.RoleID, "role_code": u.RoleCode, "force_password_change": false, "v3_username": u.V3Username, "v3_usergroup": u.V3UserGroup, }, "permissions": perms, }) } }