2024-01-19 17:25:56 +01:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
using SecureBank.API.Helpers ;
using SecureBank.API.Authentication ;
2024-01-23 15:41:59 +01:00
using SecureBank.Authentication ;
2024-01-19 17:25:56 +01:00
using SecureBank.Common ;
using SecureBank.Common.Accounts ;
using SecureBank.Database ;
using SecureBank.Extensions ;
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Net.Mail ;
using System.Runtime.InteropServices ;
using System.Runtime.Intrinsics.Arm ;
using System.Security.Cryptography ;
using System.Text ;
using System.Threading.Tasks ;
2024-01-23 15:41:59 +01:00
using SecureBank.API.Encryption ;
using Microsoft.AspNetCore.Mvc ;
using System.Security.Claims ;
using Microsoft.Identity.Client ;
2024-01-19 17:25:56 +01:00
namespace SecureBank.API.Services
{
public interface IAccountsService
{
Task < APIResponse < int > > CreateAccount ( CreateAccountRequest data ) ;
Task < APIResponse < GetPasswordVariantResponse > > GetPasswordVariant ( int accountId ) ;
2024-01-23 15:41:59 +01:00
Task < APIResponse < string > > Authentication ( AuthenticationRequest data ) ;
Task < APIResponse < string > > AuthenticationRefresh ( Claims claims ) ;
Task < APIResponse > ChangePassword ( Claims claims , ChangePasswordRequest data ) ;
Task < APIResponse < IEnumerable < AccountResponse > > > GetAccounts ( string? iban , int? id , Claims claims ) ;
Task < APIResponse > ResetPassword ( int accountId ) ;
Task < APIResponse > UnlockAccount ( int accountId ) ;
2024-01-19 17:25:56 +01:00
}
public class AccountsService : IAccountsService
{
#region SERVICES
private AuthenticationHelper _authenticationHelper ;
2024-01-23 15:41:59 +01:00
private EncryptionHelper _encryptionHelper ;
2024-01-19 17:25:56 +01:00
private DatabaseContext _database ;
private ILogger < AccountsService > _logger ;
#endregion
#region CONSTRUCTORS
2024-01-23 15:41:59 +01:00
public AccountsService ( AuthenticationHelper authenticationHelper , EncryptionHelper encryptionHelper , DatabaseContext database , ILogger < AccountsService > logger )
2024-01-19 17:25:56 +01:00
{
_authenticationHelper = authenticationHelper ;
2024-01-23 15:41:59 +01:00
_encryptionHelper = encryptionHelper ;
2024-01-19 17:25:56 +01:00
_database = database ;
_logger = logger ;
}
#endregion
#region PUBLIC METHODS
public async Task < APIResponse < int > > CreateAccount ( CreateAccountRequest data )
{
Check < CreateAccountRequest > [ ] checks = new Check < CreateAccountRequest > [ ]
{
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > x is null ) ,
Message = "Body cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . FirstName ) ) ,
Message = "First name cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . LastName ) ) ,
Message = "Last name cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . Email ) ) ,
Message = "Email cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = >
{
try
{
MailAddress m = new MailAddress ( x . Email ) ;
}
catch ( FormatException ex )
{
return true ;
}
return false ;
} ) ,
Message = "Invalid email"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . PhoneNumber ) ) ,
Message = "Phone number cannot be empty"
} ,
2024-01-23 15:41:59 +01:00
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . Address ) ) ,
Message = "Address cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . PESEL ) ) ,
Message = "PESEL cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > x . PESEL . Length ! = 11 ) ,
Message = "PESEL must be 11 charaters long"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > string . IsNullOrWhiteSpace ( x . IdCardNumber ) ) ,
Message = "Id card number cannot be empty"
} ,
new Check < CreateAccountRequest >
{
CheckAction = new Predicate < CreateAccountRequest > ( ( x ) = > x . IdCardNumber . Length ! = 9 ) ,
Message = "Id card number must be 9 characters long"
} ,
2024-01-19 17:25:56 +01:00
} ;
foreach ( Check < CreateAccountRequest > check in checks )
{
if ( check . CheckAction . Invoke ( data ) )
{
return new APIResponse < int >
{
Message = check . Message ,
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
2024-01-19 17:25:56 +01:00
} ;
}
}
2024-01-23 15:41:59 +01:00
byte [ ] pesel = _encryptionHelper . Encrypt ( data . PESEL ) ;
byte [ ] idCardNumber = _encryptionHelper . Encrypt ( data . IdCardNumber ) ;
byte [ ] cardCVV = _encryptionHelper . Encrypt ( StringExtensions . CreateRandom ( 3 , "1234567890" ) ) ;
byte [ ] cardExpirationDate = _encryptionHelper . Encrypt ( DateTime . Now . AddYears ( 5 ) . ToString ( "MM/yy" ) ) ;
2024-01-19 17:25:56 +01:00
Account account = new Account
{
FirstName = data . FirstName ,
LastName = data . LastName ,
Email = data . Email ,
PhoneNumber = data . PhoneNumber . Replace ( " " , string . Empty ) ,
2024-01-23 15:41:59 +01:00
Address = data . Address ,
PESEL = pesel ,
IdCardNumber = idCardNumber ,
IBAN = string . Empty ,
CardNumber = new byte [ 0 ] ,
CardCVV = cardCVV ,
CardExpirationDate = cardExpirationDate
2024-01-19 17:25:56 +01:00
} ;
await _database . Accounts . AddAsync ( account ) ;
await _database . SaveChangesAsync ( ) ;
2024-01-23 15:41:59 +01:00
string ibanGen = $"549745{StringExtensions.CreateRandom(12, " 1234567890 ")}{account.Id:00000000}" ;
string cardNumberGen = $"49{StringExtensions.CreateRandom(6, " 1234567890 ")}{account.Id:00000000}" ;
byte [ ] cardNumber = _encryptionHelper . Encrypt ( cardNumberGen ) ;
account . IBAN = ibanGen ;
account . CardNumber = cardNumber ;
await _database . SaveChangesAsync ( ) ;
2024-01-19 17:25:56 +01:00
string password = GeneratePassword ( ) ;
await GeneratePasswordVariants ( password , account . Id ) ;
//Send client code and temporary password to client by mail
_logger . LogInformation ( $"INFO DIRECTLY TO CLIENT: Your client code is {account.Id:00000000}. Your temporary password is {password}. You will be prompted to change it at first login" ) ;
2024-01-23 15:41:59 +01:00
return new APIResponse < int >
2024-01-19 17:25:56 +01:00
{
2024-01-23 15:41:59 +01:00
Data = account . Id
2024-01-19 17:25:56 +01:00
} ;
}
public async Task < APIResponse < GetPasswordVariantResponse > > GetPasswordVariant ( int accountId )
{
Account ? account = await _database . Accounts . FirstOrDefaultAsync ( x = > x . Id = = accountId ) ;
if ( account is null )
{
return new APIResponse < GetPasswordVariantResponse >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
2024-01-19 17:25:56 +01:00
Message = $"Account does not exists"
} ;
}
if ( account . LoginFailedCount > = 3 )
{
return new APIResponse < GetPasswordVariantResponse >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
2024-01-19 17:25:56 +01:00
Message = $"The number of failed login attempts for this account has exceeded 3. Contact your bank to confirm your identity and unlock your account."
} ;
}
if ( account . LockReason is not null )
{
return new APIResponse < GetPasswordVariantResponse >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
2024-01-19 17:25:56 +01:00
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
} ;
}
IEnumerable < AccountPassword > accountPasswords = await _database . AccountPasswords . Where ( x = > x . AccountId = = accountId ) . ToArrayAsync ( ) ;
int randomIndex = Random . Shared . Next ( 0 , accountPasswords . Count ( ) ) ;
AccountPassword passwordVariant = accountPasswords . ElementAt ( randomIndex ) ;
AccountPasswordIndex [ ] indexes = await _database . AccountPasswordIndexes . Where ( x = > x . AccountPasswordId = = passwordVariant . Id ) . ToArrayAsync ( ) ;
DateTime validTo = DateTime . Now . AddMinutes ( 5 ) ;
AccountLoginRequest loginRequest = new AccountLoginRequest
{
AccountPasswordId = passwordVariant . Id ,
ValidTo = validTo ,
} ;
await _database . AccountLoginRequests . AddAsync ( loginRequest ) ;
await _database . SaveChangesAsync ( ) ;
return new APIResponse < GetPasswordVariantResponse >
{
Data = new GetPasswordVariantResponse
{
LoginRequestId = loginRequest . Id ,
Indexes = indexes . Select ( x = > ( int ) x . Index ) . ToArray ( ) ,
ValidTo = validTo
}
} ;
}
2024-01-23 15:41:59 +01:00
public async Task < APIResponse < string > > Authentication ( AuthenticationRequest data )
2024-01-19 17:25:56 +01:00
{
2024-01-23 15:41:59 +01:00
AccountLoginRequest ? loginRequest = await _database . AccountLoginRequests . FirstOrDefaultAsync ( x = > x . Id = = data . LoginRequestId ) ;
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
if ( loginRequest is null )
2024-01-19 17:25:56 +01:00
{
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
Message = $"Login request does not exist"
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
AccountPassword password = loginRequest . AccountPassword ;
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
Account loginRequestAccount = password . Account ;
APIResponse < string > ? accountCheck = CheckAccount ( loginRequestAccount ) ;
if ( accountCheck is not null )
{
return accountCheck ;
}
if ( loginRequest . ValidTo < DateTime . Now )
2024-01-19 17:25:56 +01:00
{
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
ActionCode = 1 ,
Message = $"Login request has expired. Go back and try again."
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
byte [ ] passwordDb = password . Password ;
byte [ ] passwordProvided = HashPassword ( data . Password , password . LeftSalt , password . RightSalt ) ;
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
if ( ! Enumerable . SequenceEqual ( passwordDb , passwordProvided ) )
2024-01-19 17:25:56 +01:00
{
2024-01-23 15:41:59 +01:00
loginRequestAccount . LoginFailedCount + + ;
2024-01-19 17:25:56 +01:00
await _database . SaveChangesAsync ( ) ;
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
Message = $"Incorrect password"
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
loginRequestAccount . LoginFailedCount = 0 ;
await _database . SaveChangesAsync ( ) ;
string token = _authenticationHelper . GenerateToken ( Guid . NewGuid ( ) , loginRequestAccount , loginRequestAccount . TemporaryPassword ) ;
return new APIResponse < string >
{
ActionCode = loginRequestAccount . TemporaryPassword ? 2 : 0 ,
Data = token
} ;
}
public async Task < APIResponse < string > > AuthenticationRefresh ( Claims claims )
{
if ( claims . IsOneTimeToken )
2024-01-19 17:25:56 +01:00
{
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
Message = $"One time token cannot be refreshed."
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
Account ? account = await _database . Accounts . FirstOrDefaultAsync ( x = > x . Id = = claims . AccountId ) ;
APIResponse < string > ? accountCheck = CheckAccount ( account ) ;
if ( accountCheck is not null )
{
return accountCheck ;
}
string token = _authenticationHelper . GenerateToken ( Guid . NewGuid ( ) , account , false ) ;
return new APIResponse < string >
{
Data = token
} ;
}
public async Task < APIResponse > ChangePassword ( Claims claims , ChangePasswordRequest data )
{
string password = data . Password ;
Account ? account = await _database . Accounts . FirstOrDefaultAsync ( x = > x . Id = = claims . AccountId ) ;
if ( account is null )
2024-01-19 17:25:56 +01:00
{
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
Message = $"Account does not exists"
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
IEnumerable < string > passwordChecks = CheckPassword ( password ) ;
if ( passwordChecks . Any ( ) )
{
StringBuilder sb = new StringBuilder ( ) ;
sb . AppendLine ( "Provided password does not meet the security requirements:" ) ;
foreach ( string check in passwordChecks )
{
sb . AppendLine ( check ) ;
}
return new APIResponse
{
Status = ResponseStatus . BadRequest ,
Message = sb . ToString ( )
} ;
}
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
IEnumerable < AccountPasswordIndex > indexes = await _database . AccountPasswordIndexes . Where ( x = > x . AccountPassword . AccountId = = claims . AccountId ) . ToListAsync ( ) ;
_database . AccountPasswordIndexes . AttachRange ( indexes ) ;
_database . AccountPasswordIndexes . RemoveRange ( indexes ) ;
await _database . SaveChangesAsync ( ) ;
IEnumerable < AccountPassword > variants = await _database . AccountPasswords . Where ( x = > x . AccountId = = claims . AccountId ) . ToListAsync ( ) ;
_database . AccountPasswords . AttachRange ( variants ) ;
_database . AccountPasswords . RemoveRange ( variants ) ;
await _database . SaveChangesAsync ( ) ;
await GeneratePasswordVariants ( password , claims . AccountId ) ;
account . TemporaryPassword = false ;
await _database . SaveChangesAsync ( ) ;
return new APIResponse ( ) ;
}
public async Task < APIResponse < IEnumerable < AccountResponse > > > GetAccounts ( string? iban , int? id , Claims claims )
{
IEnumerable < Account > accounts = await _database . Accounts . ToListAsync ( ) ;
if ( id is not null )
2024-01-19 17:25:56 +01:00
{
2024-01-23 15:41:59 +01:00
accounts = accounts . Where ( x = > x . Id = = id ) ;
}
if ( iban is not null )
{
accounts = accounts . Where ( x = > x . IBAN = = iban ) ;
}
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
if ( accounts . Any ( x = > x . Id ! = claims . AccountId ) & & ! claims . IsAdmin )
{
return new APIResponse < IEnumerable < AccountResponse > >
{
Status = ResponseStatus . Unauthorized ,
Message = $"You don't have permission to get information about accounts that aren't yours"
} ;
}
List < AccountResponse > data = new List < AccountResponse > ( ) ;
foreach ( Account account in accounts )
{
data . Add ( new AccountResponse
{
Id = account . Id ,
FirstName = account . FirstName ,
LastName = account . LastName ,
Email = account . Email ,
PhoneNumber = account . PhoneNumber ,
Address = account . Address ,
PESEL = _encryptionHelper . Decrypt ( account . PESEL ) ,
IdCardNumber = _encryptionHelper . Decrypt ( account . IdCardNumber ) ,
IBAN = account . IBAN ,
CardNumber = _encryptionHelper . Decrypt ( account . CardNumber ) ,
CardExpirationDate = _encryptionHelper . Decrypt ( account . CardExpirationDate ) ,
CardCVV = _encryptionHelper . Decrypt ( account . CardCVV ) ,
IsAdmin = account . IsAdmin ,
LoginFailedCount = account . LoginFailedCount ,
TemporaryPassword = account . TemporaryPassword ,
LockReason = account . LockReason ,
} ) ;
}
return new APIResponse < IEnumerable < AccountResponse > >
{
Data = data
} ;
}
public async Task < APIResponse > ResetPassword ( int accountId )
{
Account ? account = await _database . Accounts . FirstOrDefaultAsync ( x = > x . Id = = accountId ) ;
if ( account is null )
{
2024-01-19 17:25:56 +01:00
return new APIResponse < string >
{
2024-01-23 15:41:59 +01:00
Status = ResponseStatus . BadRequest ,
Message = $"Account does not exists"
2024-01-19 17:25:56 +01:00
} ;
}
2024-01-23 15:41:59 +01:00
await PasswordReset ( account ) ;
2024-01-19 17:25:56 +01:00
2024-01-23 15:41:59 +01:00
return new APIResponse < int >
2024-01-19 17:25:56 +01:00
{
2024-01-23 15:41:59 +01:00
Data = account . Id
} ;
}
public async Task < APIResponse > UnlockAccount ( int accountId )
{
Account ? account = await _database . Accounts . FirstOrDefaultAsync ( x = > x . Id = = accountId ) ;
if ( account is null )
{
return new APIResponse < string >
{
Status = ResponseStatus . BadRequest ,
Message = $"Account does not exists"
} ;
}
await PasswordReset ( account ) ;
account . LockReason = null ;
account . LoginFailedCount = 0 ;
await _database . SaveChangesAsync ( ) ;
return new APIResponse < int >
{
Data = account . Id
2024-01-19 17:25:56 +01:00
} ;
}
#endregion
#region PRIVATE METHODS
2024-01-23 15:41:59 +01:00
protected async Task PasswordReset ( Account account )
{
IEnumerable < AccountPasswordIndex > indexes = await _database . AccountPasswordIndexes . Where ( x = > x . AccountPassword . AccountId = = account . Id ) . ToListAsync ( ) ;
_database . AccountPasswordIndexes . AttachRange ( indexes ) ;
_database . AccountPasswordIndexes . RemoveRange ( indexes ) ;
await _database . SaveChangesAsync ( ) ;
IEnumerable < AccountPassword > variants = await _database . AccountPasswords . Where ( x = > x . AccountId = = account . Id ) . ToListAsync ( ) ;
_database . AccountPasswords . AttachRange ( variants ) ;
_database . AccountPasswords . RemoveRange ( variants ) ;
await _database . SaveChangesAsync ( ) ;
string password = GeneratePassword ( ) ;
await GeneratePasswordVariants ( password , account . Id ) ;
account . TemporaryPassword = true ;
await _database . SaveChangesAsync ( ) ;
_logger . LogInformation ( $"INFO DIRECTLY TO CLIENT: Your new temporary password is {password}. You will be prompted to change it at first login" ) ;
}
protected APIResponse < string > ? CheckAccount ( Account ? account )
{
if ( account is null )
{
return new APIResponse < string >
{
Status = ResponseStatus . BadRequest ,
Message = $"Account does not exists."
} ;
}
if ( account . LockReason is not null )
{
return new APIResponse < string >
{
Status = ResponseStatus . BadRequest ,
Message = $"Account is locked. Contact your bank to confirm your identity and unlock your account."
} ;
}
if ( account . LoginFailedCount > = 3 )
{
return new APIResponse < string >
{
Status = ResponseStatus . BadRequest ,
Message = $"The number of failed login attempts for this account has exceeded 3. Contact your bank to confirm your identity and unlock your account."
} ;
}
return null ;
}
2024-01-19 17:25:56 +01:00
protected byte [ ] HashPassword ( string password , string leftSalt , string rightSalt )
{
SHA512 sha = SHA512 . Create ( ) ;
string toHash = password ;
for ( int c = 0 ; c < 100 ; c + + )
{
string before = $"{leftSalt}{toHash}{rightSalt}" ;
byte [ ] bytes = sha . ComputeHash ( Encoding . UTF8 . GetBytes ( before ) ) ;
toHash = Encoding . UTF8 . GetString ( bytes ) ;
}
return Encoding . UTF8 . GetBytes ( toHash ) ;
}
protected async Task GeneratePasswordVariants ( string password , int accountId )
{
int charCount = password . Length / 2 ;
IEnumerable < int > charIndexes = Enumerable . Range ( 0 , password . Length ) ;
IEnumerable < IEnumerable < int > > indexesVariants = charIndexes . GetCombinations ( charCount ) . OrderBy ( x = > Random . Shared . Next ( ) ) . Take ( 50 ) ;
foreach ( IEnumerable < int > indexes in indexesVariants )
{
List < char > chars = new List < char > ( ) ;
foreach ( int i in indexes )
{
chars . Add ( password [ i ] ) ;
}
string leftSalt = StringExtensions . CreateRandom ( 20 ) ;
string rightSalt = StringExtensions . CreateRandom ( 20 ) ;
string toHash = string . Join ( string . Empty , chars ) ;
byte [ ] hashed = HashPassword ( toHash , leftSalt , rightSalt ) ;
AccountPassword accountPassword = new AccountPassword
{
AccountId = accountId ,
LeftSalt = leftSalt ,
RightSalt = rightSalt ,
Password = hashed ,
} ;
await _database . AccountPasswords . AddAsync ( accountPassword ) ;
await _database . SaveChangesAsync ( ) ;
IEnumerable < AccountPasswordIndex > indexesDB = indexes . Select ( x = > new AccountPasswordIndex
{
AccountPasswordId = accountPassword . Id ,
Index = ( byte ) x
} ) ;
await _database . AccountPasswordIndexes . AddRangeAsync ( indexesDB ) ;
await _database . SaveChangesAsync ( ) ;
}
}
protected string GeneratePassword ( )
{
string passwordDigits = StringExtensions . CreateRandom ( 2 , "1234567890" ) ;
string passwordSymbols = StringExtensions . CreateRandom ( 2 , "`~!@#$%^&*()-_=+[{]};:'\"\\|,<.>/?" ) ;
string passwordSmall = StringExtensions . CreateRandom ( 2 , "qwertyuiopasdfghjklzxcvbnm" ) ;
string passwordBig = StringExtensions . CreateRandom ( 2 , "QWERTYUIOPASDFGHJKLZXCVBNM" ) ;
return string . Concat ( passwordDigits , passwordSymbols , passwordSmall , passwordBig ) . Shuffle ( ) ;
}
protected IEnumerable < string > CheckPassword ( string password )
{
int minLength = 8 ;
2024-01-23 15:41:59 +01:00
uint maxLength = 20 ;
2024-01-19 17:25:56 +01:00
if ( password . Length < minLength )
{
yield return $"Password must be at least {minLength} characters long" ;
}
2024-01-23 15:41:59 +01:00
if ( password . Length > maxLength )
{
yield return $"Password cannot be longer than {maxLength} characters" ;
}
2024-01-19 17:25:56 +01:00
if ( ! password . Any ( x = > Char . IsUpper ( x ) ) )
{
yield return $"Password must contain at least one uppercase character" ;
}
if ( ! password . Any ( x = > Char . IsLower ( x ) ) )
{
yield return $"Password must contain at least one lowercase character" ;
}
if ( ! password . Any ( x = > Char . IsDigit ( x ) ) )
{
yield return $"Password must contain at least one digit" ;
}
2024-01-23 15:41:59 +01:00
if ( ! password . Any ( x = > ! Char . IsDigit ( x ) & & ! Char . IsUpper ( x ) & & ! Char . IsLower ( x ) ) )
2024-01-19 17:25:56 +01:00
{
yield return $"Password must contain at least one special character" ;
}
}
#endregion
}
}