Add validation to RealmCreate
This commit is contained in:
parent
09480eb1e4
commit
ddbb1f42d7
16 changed files with 326 additions and 23 deletions
|
|
@ -1,3 +1,3 @@
|
|||
namespace IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
public record RealmCreateRequest(Guid? Id, string Slug, string Description);
|
||||
public record RealmCreateRequest(Guid? Id, string? Slug, string Name);
|
||||
85
IdentityShroud.Core/Helpers/SlugHelper.cs
Normal file
85
IdentityShroud.Core/Helpers/SlugHelper.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace IdentityShroud.Core.Helpers;
|
||||
|
||||
public static class SlugHelper
|
||||
{
|
||||
public static string GenerateSlug(string text, int maxLength = 40)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return string.Empty;
|
||||
|
||||
// Normalize to decomposed form (separates accents from letters)
|
||||
string normalized = text.Normalize(NormalizationForm.FormD);
|
||||
|
||||
StringBuilder sb = new StringBuilder(normalized.Length);
|
||||
bool lastWasHyphen = false;
|
||||
|
||||
foreach (char c in normalized)
|
||||
{
|
||||
// Skip diacritics (accents)
|
||||
if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
|
||||
continue;
|
||||
|
||||
char lower = char.ToLowerInvariant(c);
|
||||
|
||||
// Convert valid characters
|
||||
if ((lower >= 'a' && lower <= 'z') || (lower >= '0' && lower <= '9'))
|
||||
{
|
||||
sb.Append(lower);
|
||||
lastWasHyphen = false;
|
||||
}
|
||||
// Convert spaces, underscores, and other separators to hyphen
|
||||
else if (char.IsWhiteSpace(c) || c == '_' || c == '-')
|
||||
{
|
||||
if (!lastWasHyphen && sb.Length > 0)
|
||||
{
|
||||
sb.Append('-');
|
||||
lastWasHyphen = true;
|
||||
}
|
||||
}
|
||||
// Skip all other characters
|
||||
}
|
||||
|
||||
// Trim trailing hyphen if any
|
||||
if (sb.Length > 0 && sb[sb.Length - 1] == '-')
|
||||
sb.Length--;
|
||||
|
||||
string slug = sb.ToString();
|
||||
|
||||
// Handle truncation with hash suffix for long strings
|
||||
if (slug.Length > maxLength)
|
||||
{
|
||||
// Generate hash of original text
|
||||
string hashSuffix = GenerateHashSuffix(text);
|
||||
int contentLength = maxLength - hashSuffix.Length;
|
||||
|
||||
// Truncate at word boundary if possible
|
||||
int cutPoint = contentLength;
|
||||
int lastHyphen = slug.LastIndexOf('-', contentLength - 1);
|
||||
|
||||
if (lastHyphen > contentLength / 2)
|
||||
cutPoint = lastHyphen;
|
||||
|
||||
slug = slug.Substring(0, cutPoint).TrimEnd('-') + hashSuffix;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
private static string GenerateHashSuffix(string text)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
|
||||
|
||||
// Take first 4 bytes (will become ~5-6 base64url chars)
|
||||
string base64Url = WebEncoders.Base64UrlEncode(hash, 0, 4);
|
||||
return "-" + base64Url;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
IdentityShroud.Core/Services/IRealmService.cs
Normal file
8
IdentityShroud.Core/Services/IRealmService.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
namespace IdentityShroud.Core.Services;
|
||||
|
||||
public interface IRealmService
|
||||
{
|
||||
Task<Result<RealmCreateResponse>> Create(RealmCreateRequest request, CancellationToken ct = default);
|
||||
}
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Helpers;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
||||
namespace IdentityShroud.Core.Services;
|
||||
|
||||
public record RealmCreateResponse(Realm Realm);
|
||||
public record RealmCreateResponse(Guid Id, string Slug, string Name);
|
||||
|
||||
public class RealmService(
|
||||
Db db,
|
||||
IEncryptionService encryptionService)
|
||||
IEncryptionService encryptionService) : IRealmService
|
||||
{
|
||||
public async Task<Result<RealmCreateResponse>> Create(RealmCreateRequest request, CancellationToken ct = default)
|
||||
{
|
||||
Realm realm = new()
|
||||
{
|
||||
Id = request.Id ?? Guid.CreateVersion7(),
|
||||
Slug = request.Slug,
|
||||
Name = request.Description,
|
||||
Slug = request.Slug ?? SlugHelper.GenerateSlug(request.Name),
|
||||
Name = request.Name,
|
||||
};
|
||||
|
||||
using RSA rsa = RSA.Create(2048);
|
||||
|
|
@ -26,6 +27,7 @@ public class RealmService(
|
|||
db.Add(realm);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
return new RealmCreateResponse(realm);
|
||||
return new RealmCreateResponse(
|
||||
realm.Id, realm.Slug, realm.Name);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue