Files
tm_back/internal/user/reset_password.go
T
Mazyar 241e3b5f2d Implement audit logging functionality across services and middleware
- Introduced a new `auditlog` package to handle audit logging for user actions, including creation, updates, deletions, and authentication events.
- Enhanced existing services (customer, user) to log relevant actions using the new audit logger, capturing details such as actor ID, action type, target type, and success status.
- Added middleware to enrich request context with metadata for audit logging, ensuring comprehensive tracking of user interactions.
- Integrated Elasticsearch for persistent storage of audit logs, with fallback to file-only logging if Elasticsearch is unavailable.
- Updated API documentation to include new audit log endpoints for administrative access.

This update significantly improves the system's ability to track and audit user actions, enhancing security and accountability within the application.
2026-06-29 21:11:33 +03:30

130 lines
3.6 KiB
Go

package user
import (
"context"
"errors"
"fmt"
"time"
"tm/pkg/audit"
"tm/pkg/password"
"tm/pkg/ratelimit"
"go.mongodb.org/mongo-driver/v2/bson"
"golang.org/x/crypto/bcrypt"
)
var (
errInvalidUserID = errors.New("invalid id")
errAdminUserNotFound = errors.New("admin not found")
errCannotResetPassword = errors.New("cannot reset password for this user")
errResetPasswordFailed = errors.New("failed to reset password")
errResetPasswordRateHit = errors.New("too many password reset requests")
errResetPasswordForbidden = errors.New("you are not allowed to reset this password")
)
const adminPasswordResetRateLimit = 10
// AdminResetPassword generates a new password for the target admin user.
func (s *userService) AdminResetPassword(ctx context.Context, actorID, actorRole, targetID string) (*AdminResetPasswordResponse, error) {
if UserRole(actorRole) != UserRoleAdmin {
return nil, errResetPasswordForbidden
}
if _, err := bson.ObjectIDFromHex(targetID); err != nil {
return nil, errInvalidUserID
}
if err := s.checkPasswordResetRateLimit(ctx, actorID, "admin"); err != nil {
return nil, err
}
user, err := s.repository.GetByID(ctx, targetID)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, errAdminUserNotFound
}
s.logger.Error("Failed to load user for password reset", map[string]interface{}{
"error": err.Error(),
"target_id": targetID,
})
return nil, errResetPasswordFailed
}
if user.Status != UserStatusActive {
return nil, errCannotResetPassword
}
plainPassword, err := password.GenerateSecure()
if err != nil {
s.logger.Error("Failed to generate password", map[string]interface{}{
"error": err.Error(),
"target_id": targetID,
})
return nil, errResetPasswordFailed
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)
if err != nil {
s.logger.Error("Failed to hash generated password", map[string]interface{}{
"error": err.Error(),
"target_id": targetID,
})
return nil, errResetPasswordFailed
}
if err := s.repository.UpdatePasswordReset(ctx, targetID, string(hashedPassword), actorID); err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, errAdminUserNotFound
}
s.logger.Error("Failed to persist reset password", map[string]interface{}{
"error": err.Error(),
"target_id": targetID,
})
return nil, errResetPasswordFailed
}
if err := s.authService.InvalidateUserSessions(targetID); err != nil {
s.logger.Error("Failed to invalidate user sessions after password reset", map[string]interface{}{
"error": err.Error(),
"target_id": targetID,
})
}
s.auditLogger.Log(ctx, audit.Entry{
ActorID: actorID,
ActorType: audit.ActorTypeAdmin,
TargetType: audit.TargetTypeAdmin,
TargetID: targetID,
Action: audit.ActionPasswordReset,
Success: true,
})
s.logger.Info("Admin password reset completed", map[string]interface{}{
"actor_id": actorID,
"target_id": targetID,
})
return &AdminResetPasswordResponse{Password: plainPassword}, nil
}
func (s *userService) checkPasswordResetRateLimit(ctx context.Context, actorID, resource string) error {
if s.redisClient == nil {
return nil
}
key := fmt.Sprintf("ratelimit:password_reset:%s:%s", actorID, resource)
allowed, err := ratelimit.Allow(ctx, s.redisClient, key, adminPasswordResetRateLimit, time.Minute)
if err != nil {
s.logger.Warn("Password reset rate limit check failed", map[string]interface{}{
"actor_id": actorID,
"error": err.Error(),
})
return nil
}
if !allowed {
return errResetPasswordRateHit
}
return nil
}