fix identity stuff + add validator extensions + localizer updates
This commit is contained in:
@@ -17,6 +17,5 @@ public static partial class Constants
|
||||
public static partial class Database
|
||||
{
|
||||
public const string ReservationPrefix = "zero.";
|
||||
public const string Expires = Raven.Client.Constants.Documents.Metadata.Expires;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ public class ZeroIdentityConstants
|
||||
|
||||
public static readonly string IsZero = ClaimPrefix + ".iszero";
|
||||
public static readonly string UserId = ClaimPrefix + ".userid";
|
||||
public static readonly string UserName = ClaimPrefix + ".username";
|
||||
public static readonly string Username = ClaimPrefix + ".username";
|
||||
public static readonly string Name = ClaimPrefix + ".name";
|
||||
public static readonly string Nickname = ClaimPrefix + ".nickname";
|
||||
public static readonly string Role = ClaimPrefix + ".rolealias";
|
||||
public static readonly string SecurityStamp = ClaimPrefix + ".securitystamp";
|
||||
public static readonly string Email = ClaimPrefix + ".email";
|
||||
|
||||
@@ -106,7 +106,7 @@ public static class ZeroIdentityExtensions
|
||||
services.Configure<IdentityOptions>(opts =>
|
||||
{
|
||||
opts.ClaimsIdentity.UserIdClaimType = ZeroIdentityConstants.Claims.UserId;
|
||||
opts.ClaimsIdentity.UserNameClaimType = ZeroIdentityConstants.Claims.UserName;
|
||||
opts.ClaimsIdentity.UserNameClaimType = ZeroIdentityConstants.Claims.Username;
|
||||
opts.ClaimsIdentity.RoleClaimType = ZeroIdentityConstants.Claims.Role;
|
||||
opts.ClaimsIdentity.SecurityStampClaimType = ZeroIdentityConstants.Claims.SecurityStamp;
|
||||
opts.ClaimsIdentity.EmailClaimType = ZeroIdentityConstants.Claims.Email;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using Raven.Client.Exceptions;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using zero.Identity;
|
||||
@@ -71,18 +68,11 @@ public class ZeroRoleStore<TRole> :
|
||||
/// <inheritdoc/>
|
||||
public async Task<IdentityResult> UpdateAsync(TRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Result<TRole> result = await Db.Update(role);
|
||||
Result<TRole> result = await Db.Update(role);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return Fail(result);
|
||||
}
|
||||
}
|
||||
catch (ConcurrencyException)
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
|
||||
return Fail(result);
|
||||
}
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
@@ -91,18 +81,11 @@ public class ZeroRoleStore<TRole> :
|
||||
/// <inheritdoc/>
|
||||
public async Task<IdentityResult> DeleteAsync(TRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Result<TRole> result = await Db.Delete(role);
|
||||
Result<TRole> result = await Db.Delete(role);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return Fail(result);
|
||||
}
|
||||
}
|
||||
catch (ConcurrencyException)
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
|
||||
return Fail(result);
|
||||
}
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using Raven.Client.Exceptions;
|
||||
using zero.Identity;
|
||||
|
||||
namespace zero.Identity;
|
||||
@@ -94,18 +91,11 @@ public partial class ZeroUserStore<TUser> :
|
||||
/// <inheritdoc />
|
||||
public async Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Result<TUser> result = await Db.Delete(user);
|
||||
Result<TUser> result = await Db.Delete(user);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return Fail(result);
|
||||
}
|
||||
}
|
||||
catch (ConcurrencyException)
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
|
||||
return Fail(result);
|
||||
}
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using zero.Identity;
|
||||
|
||||
namespace zero.Identity;
|
||||
@@ -31,7 +29,7 @@ public partial class ZeroUserStore<TUser, TRole> : ZeroUserStore<TUser>,
|
||||
/// <inheritdoc />
|
||||
public async Task<IList<TUser>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
|
||||
{
|
||||
return await Db.FindAll<TUser>(x => roleName.In(x.RoleIds), cancellationToken);
|
||||
return await Db.FindAll<TUser>(x => x.RoleIds.Contains(roleName), cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Session;
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace zero.Models;
|
||||
|
||||
@@ -65,12 +64,5 @@ public class ZeroEntity : ZeroIdEntity, ISupportsDbConventions, ISupportsRouting
|
||||
/// Additional properties for this entity
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Properties { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// [Warning] This field is always empty when bound to the database.
|
||||
/// It is only filled in the app-code for routing.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Mvc;
|
||||
|
||||
public class ZeroMvcModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddRouting(opts => opts.LowercaseUrls = true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Net.Mime;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Mvc;
|
||||
|
||||
public abstract class ZeroPageModel : PageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Get access to the zero context for this request
|
||||
/// </summary>
|
||||
public IZeroContext ZeroContext => _zeroContext ?? (_zeroContext = HttpContext?.RequestServices?.GetService<IZeroContext>());
|
||||
IZeroContext _zeroContext;
|
||||
|
||||
/// <summary>
|
||||
/// Get access to the localizer
|
||||
/// </summary>
|
||||
public ILocalizer Localizer => _localizer ?? (_localizer = HttpContext?.RequestServices?.GetService<ILocalizer>());
|
||||
ILocalizer _localizer;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace zero.Utils;
|
||||
|
||||
@@ -16,20 +16,20 @@ public class ObjectCopycat
|
||||
public static T Clone<T>(T obj)
|
||||
{
|
||||
Type type = obj.GetType();
|
||||
return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type);
|
||||
return (T)JsonSerializer.Deserialize(JsonSerializer.Serialize(obj), type);
|
||||
}
|
||||
|
||||
|
||||
public static T CloneSpecific<T>(T obj)
|
||||
{
|
||||
Type type = obj.GetType();
|
||||
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
|
||||
return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj));
|
||||
}
|
||||
|
||||
|
||||
public static bool ContentEquals<T>(T obj1, T obj2)
|
||||
{
|
||||
return (obj1 == null && obj2 == null) || (obj1 != null && obj2 != null && JsonConvert.SerializeObject(obj1) == JsonConvert.SerializeObject(obj2));
|
||||
return (obj1 == null && obj2 == null) || (obj1 != null && obj2 != null && JsonSerializer.Serialize(obj1) == JsonSerializer.Serialize(obj2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace zero.Validation;
|
||||
|
||||
public static class ModelStateDictionaryExtensions
|
||||
{
|
||||
const char DOT = '.';
|
||||
|
||||
public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState, bool convertUnresolvedPropertiesToModel = true, string prefix = "Form", Dictionary<string, string> aliases = default, IEnumerable<string> removePrefixes = default)
|
||||
{
|
||||
if (result == null || result.IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] formProperties = modelState.Keys.ToArray();
|
||||
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
string propertyName = error.PropertyName;
|
||||
|
||||
if (propertyName != null && removePrefixes != null && removePrefixes.Any())
|
||||
{
|
||||
foreach (var removePrefix in removePrefixes)
|
||||
{
|
||||
if (propertyName.StartsWith(removePrefix, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
propertyName = propertyName.Substring(removePrefix.Length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string key = prefix.IsNullOrEmpty() ? propertyName : (propertyName.IsNullOrEmpty() ? String.Empty : prefix + DOT + propertyName);
|
||||
string message = error.ErrorMessage;
|
||||
bool found = formProperties.Contains(key, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
// find property by alias
|
||||
if (!found && aliases != null && aliases.ContainsKey(propertyName))
|
||||
{
|
||||
key = aliases[propertyName];
|
||||
key = prefix.IsNullOrEmpty() ? key : (key.IsNullOrEmpty() ? String.Empty : prefix + DOT + key);
|
||||
found = formProperties.Contains(key, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
// move property errors to model if they are not part of the form
|
||||
if (!found && convertUnresolvedPropertiesToModel)
|
||||
{
|
||||
key = String.Empty;
|
||||
}
|
||||
|
||||
modelState.AddModelError(key, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentValidation;
|
||||
using Raven.Client.Documents;
|
||||
using System.Globalization;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace zero.Validation;
|
||||
|
||||
@@ -38,22 +38,7 @@ public static class ValidatorExtensions
|
||||
/// </summary>
|
||||
public static IRuleBuilderOptions<T, string> Email<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.Must((root, value, context) =>
|
||||
{
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int index = value.IndexOf(KLAMMERAFFE);
|
||||
|
||||
if (index < 0 || index == value.Length - 1 || index != value.LastIndexOf(KLAMMERAFFE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).WithMessage("@errors.forms.email_invalid");
|
||||
return ruleBuilder.Must((root, value, context) => ValidateEmail(value)).WithMessage("@errors.forms.email_invalid");
|
||||
}
|
||||
|
||||
|
||||
@@ -78,9 +63,7 @@ public static class ValidatorExtensions
|
||||
|
||||
foreach (string mail in mails)
|
||||
{
|
||||
int index = mail.IndexOf(KLAMMERAFFE);
|
||||
|
||||
if (index < 0 || index == mail.Length - 1 || index != mail.LastIndexOf(KLAMMERAFFE))
|
||||
if (!ValidateEmail(mail))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -91,6 +74,23 @@ public static class ValidatorExtensions
|
||||
}
|
||||
|
||||
|
||||
private static bool ValidateEmail(string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = value.IndexOf(KLAMMERAFFE);
|
||||
|
||||
return
|
||||
index > 0 &&
|
||||
index != value.Length - 1 &&
|
||||
index == value.LastIndexOf(KLAMMERAFFE) &&
|
||||
MailAddress.TryCreate(value, out _);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validates a culture identifier
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using zero.Mvc;
|
||||
|
||||
namespace zero;
|
||||
|
||||
@@ -42,6 +43,7 @@ public class ZeroBuilder
|
||||
new AssemblyDiscovery(Mvc).Execute(_startupOptions.AssemblyDiscoveryRules);
|
||||
|
||||
Modules.Add<ZeroCommunicationModule>();
|
||||
Modules.Add<ZeroMvcModule>();
|
||||
Modules.Add<ZeroConfigurationModule>();
|
||||
Modules.Add<ZeroValidationModule>();
|
||||
Modules.Add<ZeroContextModule>();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
|
||||
<PackageReference Include="RavenDB.Client" Version="5.4.5" />
|
||||
<PackageReference Include="FluentValidation" Version="11.4.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
|
||||
Reference in New Issue
Block a user