Skip to content

Commit 867e6aa

Browse files
committed
Fix Codacy code quality issues
- Add curly braces around single-line if statements (S121) - Fix redundant assignment in template rendering (S3441) - Add Regex timeout to prevent potential ReDoS (Semgrep security)
1 parent f71ed8a commit 867e6aa

3 files changed

Lines changed: 67 additions & 7 deletions

File tree

Commands/ViewCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,7 +1433,7 @@ public static (string key, int? occurrence) ParseKeyWithOccurrence(string input)
14331433
return (input, null);
14341434

14351435
// Match pattern like "KeyName [2]" at the end
1436-
var match = Regex.Match(input, @"^(.+?)\s*\[(\d+)\]$");
1436+
var match = Regex.Match(input, @"^(.+?)\s*\[(\d+)\]$", RegexOptions.None, TimeSpan.FromSeconds(1));
14371437
if (match.Success)
14381438
{
14391439
var key = match.Groups[1].Value.Trim();

cloud/src/LrmCloud.Api/Helpers/PasswordValidator.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,36 @@
22

33
namespace LrmCloud.Api.Helpers;
44

5-
public static class PasswordValidator
5+
public static partial class PasswordValidator
66
{
7+
private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1);
8+
79
public static (bool IsValid, string? ErrorMessage) Validate(string password)
810
{
911
if (password.Length < 12)
12+
{
1013
return (false, "Password must be at least 12 characters");
14+
}
1115

12-
if (!Regex.IsMatch(password, @"[A-Z]"))
16+
if (!Regex.IsMatch(password, @"[A-Z]", RegexOptions.None, RegexTimeout))
17+
{
1318
return (false, "Password must contain at least one uppercase letter");
19+
}
1420

15-
if (!Regex.IsMatch(password, @"[a-z]"))
21+
if (!Regex.IsMatch(password, @"[a-z]", RegexOptions.None, RegexTimeout))
22+
{
1623
return (false, "Password must contain at least one lowercase letter");
24+
}
1725

18-
if (!Regex.IsMatch(password, @"[0-9]"))
26+
if (!Regex.IsMatch(password, @"[0-9]", RegexOptions.None, RegexTimeout))
27+
{
1928
return (false, "Password must contain at least one digit");
29+
}
2030

21-
if (!Regex.IsMatch(password, @"[^a-zA-Z0-9]"))
31+
if (!Regex.IsMatch(password, @"[^a-zA-Z0-9]", RegexOptions.None, RegexTimeout))
32+
{
2233
return (false, "Password must contain at least one special character");
34+
}
2335

2436
return (true, null);
2537
}

cloud/src/LrmCloud.Api/Services/AuthService.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private async Task SendAccountExistsEmailAsync(string email)
125125

126126
var html = await template.RenderAsync(new
127127
{
128-
email = email,
128+
email,
129129
login_link = loginLink,
130130
reset_link = resetLink
131131
});
@@ -149,20 +149,30 @@ await _mailService.TrySendEmailAsync(_logger,
149149
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant());
150150

151151
if (user == null)
152+
{
152153
return (false, "Invalid verification link");
154+
}
153155

154156
if (user.EmailVerified)
157+
{
155158
return (false, "Email already verified");
159+
}
156160

157161
if (user.EmailVerificationExpiresAt == null || user.EmailVerificationExpiresAt < DateTime.UtcNow)
162+
{
158163
return (false, "Verification link expired");
164+
}
159165

160166
if (string.IsNullOrEmpty(user.EmailVerificationTokenHash))
167+
{
161168
return (false, "Invalid verification token");
169+
}
162170

163171
// Verify token by hashing and comparing
164172
if (!BCrypt.Net.BCrypt.Verify(token, user.EmailVerificationTokenHash))
173+
{
165174
return (false, "Invalid verification token");
175+
}
166176

167177
// Mark as verified
168178
user.EmailVerified = true;
@@ -259,14 +269,20 @@ await _mailService.TrySendEmailAsync(_logger,
259269
return (false, "Password reset is only available for email accounts");
260270

261271
if (string.IsNullOrEmpty(user.PasswordResetTokenHash))
272+
{
262273
return (false, "No password reset requested");
274+
}
263275

264276
if (user.PasswordResetExpires == null || user.PasswordResetExpires < DateTime.UtcNow)
277+
{
265278
return (false, "Password reset link expired");
279+
}
266280

267281
// Verify token by hashing and comparing
268282
if (!BCrypt.Net.BCrypt.Verify(request.Token, user.PasswordResetTokenHash))
283+
{
269284
return (false, "Invalid password reset token");
285+
}
270286

271287
// Hash new password
272288
var passwordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword, 12);
@@ -663,24 +679,34 @@ public async Task<bool> RevokeRefreshTokenAsync(string refreshToken, string? ipA
663679
var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId);
664680

665681
if (user == null)
682+
{
666683
return (false, "User not found");
684+
}
667685

668686
// Only allow password change for email auth users
669687
if (user.AuthType != "email")
688+
{
670689
return (false, "Password change is only available for email/password accounts");
690+
}
671691

672692
// Verify current password
673693
if (string.IsNullOrEmpty(user.PasswordHash) || !BCrypt.Net.BCrypt.Verify(request.CurrentPassword, user.PasswordHash))
694+
{
674695
return (false, "Current password is incorrect");
696+
}
675697

676698
// Validate new password strength
677699
var (isValid, validationError) = PasswordValidator.Validate(request.NewPassword);
678700
if (!isValid)
701+
{
679702
return (false, validationError);
703+
}
680704

681705
// Check if new password is same as current
682706
if (BCrypt.Net.BCrypt.Verify(request.NewPassword, user.PasswordHash))
707+
{
683708
return (false, "New password must be different from current password");
709+
}
684710

685711
// Hash new password
686712
var newPasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword, 12);
@@ -727,12 +753,16 @@ public async Task<bool> RevokeRefreshTokenAsync(string refreshToken, string? ipA
727753

728754
// Check if new email is same as current
729755
if (newEmail == currentEmail)
756+
{
730757
return (false, "New email is the same as current email");
758+
}
731759

732760
// Check if new email is already in use by another user
733761
var emailExists = await _db.Users.AnyAsync(u => u.Email!.ToLower() == newEmail && u.Id != userId);
734762
if (emailExists)
763+
{
735764
return (false, "This email address is already in use");
765+
}
736766

737767
// Generate verification token
738768
var verificationToken = TokenGenerator.GenerateSecureToken(32);
@@ -765,11 +795,15 @@ public async Task<bool> RevokeRefreshTokenAsync(string refreshToken, string? ipA
765795
var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId);
766796

767797
if (user == null)
798+
{
768799
return (false, "User not found");
800+
}
769801

770802
// Check if there's a pending email change
771803
if (string.IsNullOrEmpty(user.PendingEmail) || string.IsNullOrEmpty(user.PendingEmailTokenHash))
804+
{
772805
return (false, "No pending email change found");
806+
}
773807

774808
// Check if token expired
775809
if (user.PendingEmailExpiresAt == null || user.PendingEmailExpiresAt < DateTime.UtcNow)
@@ -785,7 +819,9 @@ public async Task<bool> RevokeRefreshTokenAsync(string refreshToken, string? ipA
785819

786820
// Verify token
787821
if (!BCrypt.Net.BCrypt.Verify(token, user.PendingEmailTokenHash))
822+
{
788823
return (false, "Invalid verification token");
824+
}
789825

790826
// Check if new email is still available (might have been taken during verification period)
791827
var newEmailLower = user.PendingEmail.ToLowerInvariant();
@@ -1004,11 +1040,15 @@ public async Task<List<SessionDto>> GetSessionsAsync(int userId, string? current
10041040
.FirstOrDefaultAsync(rt => rt.Id == sessionId && rt.UserId == userId);
10051041

10061042
if (session == null)
1043+
{
10071044
return (false, "Session not found");
1045+
}
10081046

10091047
// Check if already revoked
10101048
if (session.RevokedAt != null)
1049+
{
10111050
return (false, "Session already revoked");
1051+
}
10121052

10131053
// Revoke the session
10141054
session.RevokedAt = DateTime.UtcNow;
@@ -1040,7 +1080,9 @@ public async Task<List<SessionDto>> GetSessionsAsync(int userId, string? current
10401080
}
10411081

10421082
if (currentSession == null)
1083+
{
10431084
return (false, 0, "Current session not found");
1085+
}
10441086

10451087
// Revoke all sessions except current
10461088
int revokedCount = 0;
@@ -1065,13 +1107,17 @@ public async Task<List<SessionDto>> GetSessionsAsync(int userId, string? current
10651107
{
10661108
var user = await _db.Users.FirstOrDefaultAsync(u => u.Id == userId);
10671109
if (user == null)
1110+
{
10681111
return (false, "User not found");
1112+
}
10691113

10701114
// Verify password for confirmation (only if user has password set)
10711115
if (!string.IsNullOrEmpty(user.PasswordHash))
10721116
{
10731117
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
1118+
{
10741119
return (false, "Incorrect password");
1120+
}
10751121
}
10761122
else
10771123
{
@@ -1111,7 +1157,9 @@ public async Task<List<SessionDto>> GetSessionsAsync(int userId, string? current
11111157
private async Task SendAccountDeletionConfirmationAsync(User user)
11121158
{
11131159
if (string.IsNullOrEmpty(user.Email))
1160+
{
11141161
return;
1162+
}
11151163

11161164
try
11171165
{

0 commit comments

Comments
 (0)