diff --git a/src/Squidex.Domain.Users/PwnedPasswordValidator.cs b/src/Squidex.Domain.Users/PwnedPasswordValidator.cs new file mode 100644 index 000000000..44087694c --- /dev/null +++ b/src/Squidex.Domain.Users/PwnedPasswordValidator.cs @@ -0,0 +1,55 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using SharpPwned.NET; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Log; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Users +{ + public sealed class PwnedPasswordValidator : IPasswordValidator + { + private const string ErrorCode = "PwnedError"; + private const string ErrorText = "This password has previously appeared in a data breach and should never be used. If you've ever used it anywhere before, change it!"; + private static readonly IdentityResult Error = IdentityResult.Failed(new IdentityError { Code = ErrorCode, Description = ErrorText }); + + private readonly HaveIBeenPwnedRestClient client = new HaveIBeenPwnedRestClient(); + private readonly ISemanticLog log; + + public PwnedPasswordValidator(ISemanticLog log) + { + Guard.NotNull(log, nameof(log)); + + this.log = log; + } + + public async Task ValidateAsync(UserManager manager, IUser user, string password) + { + try + { + var isBreached = await client.IsPasswordPwned(password); + + if (isBreached) + { + return Error; + } + } + catch (Exception ex) + { + log.LogError(ex, w => w + .WriteProperty("operation", "CheckPasswordPwned") + .WriteProperty("status", "Failed")); + } + + return IdentityResult.Success; + } + } +} diff --git a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj index 0ca9385ae..718734926 100644 --- a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj +++ b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index 3d3f3258e..439522fde 100644 --- a/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -57,6 +57,8 @@ namespace Squidex.Areas.IdentityServer.Config services.AddIdentity() .AddDefaultTokenProviders(); + services.AddSingleton, + PwnedPasswordValidator>(); services.AddSingleton, UserClaimsPrincipalFactoryWithEmail>(); services.AddSingleton