diff --git a/SW.Bitween.Api/Resources/Accounts/Create.cs b/SW.Bitween.Api/Resources/Accounts/Create.cs index f1ba298..f8e1014 100644 --- a/SW.Bitween.Api/Resources/Accounts/Create.cs +++ b/SW.Bitween.Api/Resources/Accounts/Create.cs @@ -12,11 +12,13 @@ public class Create : ICommandHandler { private readonly BitweenDbContext dbContext; private readonly RequestContext _requestContext; + private readonly BitweenOptions _bitweenOptions; - public Create(BitweenDbContext dbContext, RequestContext requestContext) + public Create(BitweenDbContext dbContext, RequestContext requestContext, BitweenOptions bitweenOptions) { this.dbContext = dbContext; _requestContext = requestContext; + _bitweenOptions = bitweenOptions; } public async Task Handle(CreateAccountModel request) @@ -24,16 +26,20 @@ public async Task Handle(CreateAccountModel request) _requestContext.EnsureAccess(AccountRole.Admin); if (string.IsNullOrEmpty(request.Name) || string.IsNullOrEmpty(request.Email) || - string.IsNullOrEmpty(request.Password)) + (!_bitweenOptions.DisableEmailPasswordLogin && string.IsNullOrEmpty(request.Password))) throw new SWValidationException("INVALID_PAYLOAD", "The payload is invalid"); if (await dbContext.Set().AnyAsync(a => a.Email == request.Email)) throw new SWValidationException("ACCOUNT_EXISTS", $"Account with email {request.Email} exists"); + var password = _bitweenOptions.DisableEmailPasswordLogin + ? null + : SecurePasswordHasher.Hash(request.Password); + var newAccount = new Account( request.Name, request.Email, - SecurePasswordHasher.Hash(request.Password), + password, (AccountRole)request.Role); dbContext.Add(newAccount); @@ -44,11 +50,11 @@ public async Task Handle(CreateAccountModel request) private class Validate : AbstractValidator { - public Validate() + public Validate(BitweenOptions bitweenOptions) { RuleFor(i => i.Name).NotEmpty(); RuleFor(i => i.Email).NotEmpty(); - RuleFor(i => i.Password).NotEmpty(); + RuleFor(i => i.Password).NotEmpty().When(_ => !bitweenOptions.DisableEmailPasswordLogin); RuleFor(i => i.Role).NotNull(); } } diff --git a/SW.Bitween.Api/Resources/Accounts/Login.cs b/SW.Bitween.Api/Resources/Accounts/Login.cs index e38d12d..b031b2b 100644 --- a/SW.Bitween.Api/Resources/Accounts/Login.cs +++ b/SW.Bitween.Api/Resources/Accounts/Login.cs @@ -61,6 +61,13 @@ public async Task Handle(UserLogin request) } } + if (string.IsNullOrEmpty(refreshTokenValue) && string.IsNullOrEmpty(request.MsToken) && + _BitweenSettings.DisableEmailPasswordLogin) + { + _logger.LogWarning("Email/password login attempt rejected: DisableEmailPasswordLogin is enabled."); + throw new SWException("Email and password login is disabled. Please sign in with Microsoft."); + } + if (!string.IsNullOrEmpty(refreshTokenValue)) { // account query already filtered above diff --git a/SW.Bitween.Api/Resources/Settings/Config.cs b/SW.Bitween.Api/Resources/Settings/Config.cs index 938740c..c6a95b5 100644 --- a/SW.Bitween.Api/Resources/Settings/Config.cs +++ b/SW.Bitween.Api/Resources/Settings/Config.cs @@ -23,6 +23,7 @@ public async Task Handle() _BitweenOptions.MsalClientId, _BitweenOptions.MsalRedirectUri, _BitweenOptions.MsalTenantId, + _BitweenOptions.DisableEmailPasswordLogin, IsRabbitMqManagementConfigured = !string.IsNullOrWhiteSpace(_BitweenOptions.RabbitMqManagementUrl) && !string.IsNullOrWhiteSpace(_BitweenOptions.RabbitMqManagementUsername) && !string.IsNullOrWhiteSpace(_BitweenOptions.RabbitMqManagementPassword), diff --git a/SW.Bitween.Api/Services/BitweenOptions.cs b/SW.Bitween.Api/Services/BitweenOptions.cs index 69eb754..a5b05aa 100644 --- a/SW.Bitween.Api/Services/BitweenOptions.cs +++ b/SW.Bitween.Api/Services/BitweenOptions.cs @@ -45,6 +45,14 @@ public BitweenOptions() public string MsalRedirectUri { get; set; } public string MsalTenantId { get; set; } + + /// + /// When true, disables email/password login and account creation with a password. + /// Only Microsoft (MSAL) login is allowed: the login page hides the email/password + /// form, new accounts are created without a password, and the Login handler rejects + /// any email/password login attempt outright. + /// + public bool DisableEmailPasswordLogin { get; set; } public int JwtExpiryMinutes { get; set; } public bool ConsumeLegacyEventMessages { get; set; } public string QueuePrefix { get; set; }