WIP making ClientCreate endpoint
This commit is contained in:
parent
138f335af0
commit
eb872a4f44
28 changed files with 365 additions and 121 deletions
55
IdentityShroud.Api/Apis/ClientApi.cs
Normal file
55
IdentityShroud.Api/Apis/ClientApi.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using FluentResults;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Services;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
|
||||
public record ClientCreateReponse(int Id, string ClientId);
|
||||
|
||||
/// <summary>
|
||||
/// The part of the api below realms/{slug}/clients
|
||||
/// </summary>
|
||||
public static class ClientApi
|
||||
{
|
||||
public const string ClientGetRouteName = "ClientGet";
|
||||
|
||||
public static void MapEndpoints(this IEndpointRouteBuilder erp)
|
||||
{
|
||||
erp.MapPost("", ClientCreate)
|
||||
.Validate<ClientCreateRequest>()
|
||||
.WithName("ClientCreate")
|
||||
.Produces(StatusCodes.Status201Created);
|
||||
erp.MapGet("{clientId}", ClientGet)
|
||||
.WithName(ClientGetRouteName);
|
||||
}
|
||||
|
||||
private static Task ClientGet(HttpContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static async Task<Results<Created<ClientCreateReponse>, InternalServerError>>
|
||||
ClientCreate(
|
||||
ClientCreateRequest request,
|
||||
[FromServices] IClientService service,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Realm realm = context.GetValidatedRealm();
|
||||
Result<Client> result = await service.Create(realm.Id, request, cancellationToken);
|
||||
|
||||
// Should i have two set of paths? one for actual REST and one for openid
|
||||
// openid: auth/realms/{realmSlug}/.well-known/openid-configuration
|
||||
// openid: auth/realms/{realmSlug}/openid-connect/(auth|token|jwks)
|
||||
// api: api/v1/realms/{realmId}/....
|
||||
// api: api/v1/realms/{realmId}/clients/{clientId}
|
||||
|
||||
//return Results.CreatedAtRoute(ClientGetRouteName, [ "realmSlug" = realmId!?])
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace IdentityShroud.Core.Messages;
|
||||
|
|
@ -25,17 +28,46 @@ public class JsonWebKey
|
|||
|
||||
// RSA Public Key Components
|
||||
[JsonPropertyName("n")]
|
||||
public required string Modulus { get; set; }
|
||||
public string? Modulus { get; set; }
|
||||
|
||||
[JsonPropertyName("e")]
|
||||
public required string Exponent { get; set; }
|
||||
public string? Exponent { get; set; }
|
||||
|
||||
// ECdsa
|
||||
public string? Curve { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[]? X { get; set; }
|
||||
[JsonConverter(typeof(Base64UrlConverter))]
|
||||
public byte[]? Y { get; set; }
|
||||
|
||||
// Optional fields
|
||||
[JsonPropertyName("x5c")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public List<string>? X509CertificateChain { get; set; }
|
||||
|
||||
[JsonPropertyName("x5t")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? X509CertificateThumbprint { get; set; }
|
||||
// [JsonPropertyName("x5c")]
|
||||
// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
// public List<string>? X509CertificateChain { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("x5t")]
|
||||
// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
// public string? X509CertificateThumbprint { get; set; }
|
||||
}
|
||||
|
||||
public class Base64UrlConverter : JsonConverter<byte[]>
|
||||
{
|
||||
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// GetValueSpan gives you the raw UTF-8 bytes of the JSON string value
|
||||
if (reader.HasValueSequence)
|
||||
{
|
||||
var valueSequence = reader.ValueSequence.ToArray();
|
||||
return Base64Url.DecodeFromUtf8(valueSequence);
|
||||
}
|
||||
return Base64Url.DecodeFromUtf8(reader.ValueSpan);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
|
||||
{
|
||||
int encodedLength = Base64Url.GetEncodedLength(value.Length);
|
||||
Span<byte> buffer = encodedLength <= 256 ? stackalloc byte[encodedLength] : new byte[encodedLength];
|
||||
Base64Url.EncodeToUtf8(value, buffer);
|
||||
writer.WriteStringValue(buffer);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,26 +9,44 @@ namespace IdentityShroud.Api.Mappers;
|
|||
|
||||
public class KeyMapper(IEncryptionService encryptionService)
|
||||
{
|
||||
public JsonWebKey KeyToJsonWebKey(Key key)
|
||||
public JsonWebKey? KeyToJsonWebKey(RealmKey realmKey)
|
||||
{
|
||||
using var rsa = RsaHelper.LoadFromPkcs8(key.GetPrivateKey(encryptionService));
|
||||
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false);
|
||||
|
||||
return new JsonWebKey()
|
||||
|
||||
JsonWebKey result = new()
|
||||
{
|
||||
KeyType = rsa.SignatureAlgorithm,
|
||||
KeyId = key.Id.ToString(),
|
||||
KeyId = realmKey.Id.ToString(),
|
||||
Use = "sig",
|
||||
Exponent = WebEncoders.Base64UrlEncode(parameters.Exponent!),
|
||||
Modulus = WebEncoders.Base64UrlEncode(parameters.Modulus!),
|
||||
};
|
||||
switch (realmKey.KeyType)
|
||||
{
|
||||
case "RSA":
|
||||
using (var rsa = RsaHelper.LoadFromPkcs8(realmKey.GetPrivateKey(encryptionService)))
|
||||
{
|
||||
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false);
|
||||
result.KeyType = rsa.SignatureAlgorithm;
|
||||
result.Exponent = WebEncoders.Base64UrlEncode(parameters.Exponent!);
|
||||
result.Modulus = WebEncoders.Base64UrlEncode(parameters.Modulus!);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public JsonWebKeySet KeyListToJsonWebKeySet(IEnumerable<Key> keys)
|
||||
public JsonWebKeySet KeyListToJsonWebKeySet(IEnumerable<RealmKey> keys)
|
||||
{
|
||||
return new JsonWebKeySet()
|
||||
JsonWebKeySet wks = new();
|
||||
foreach (var k in keys)
|
||||
{
|
||||
Keys = keys.Select(e => KeyToJsonWebKey(e)).ToList(),
|
||||
};
|
||||
var wk = KeyToJsonWebKey(k);
|
||||
if (wk is {})
|
||||
{
|
||||
wks.Keys.Add(wk);
|
||||
}
|
||||
}
|
||||
return wks;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using FluentResults;
|
||||
using IdentityShroud.Api.Mappers;
|
||||
using IdentityShroud.Api.Validation;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Messages;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
|
@ -19,18 +19,21 @@ public static class HttpContextExtensions
|
|||
|
||||
public static class RealmApi
|
||||
{
|
||||
public static void MapRealmEndpoints(this IEndpointRouteBuilder app)
|
||||
public static void MapRealmEndpoints(this IEndpointRouteBuilder erp)
|
||||
{
|
||||
var realmsGroup = app.MapGroup("/realms");
|
||||
var realmsGroup = erp.MapGroup("/realms");
|
||||
realmsGroup.MapPost("", RealmCreate)
|
||||
.Validate<RealmCreateRequest>()
|
||||
.WithName("Create Realm")
|
||||
.Produces(StatusCodes.Status201Created);
|
||||
|
||||
var realmSlugGroup = realmsGroup.MapGroup("{slug}")
|
||||
var realmSlugGroup = realmsGroup.MapGroup("{realmSlug}")
|
||||
.AddEndpointFilter<SlugValidationFilter>();
|
||||
realmSlugGroup.MapGet(".well-known/openid-configuration", GetOpenIdConfiguration);
|
||||
|
||||
RouteGroupBuilder clientsGroup = realmSlugGroup.MapGroup("clients");
|
||||
|
||||
|
||||
var openidConnect = realmSlugGroup.MapGroup("openid-connect");
|
||||
openidConnect.MapPost("auth", OpenIdConnectAuth);
|
||||
openidConnect.MapPost("token", OpenIdConnectToken);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=apis_005Cdto/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=apis_005Cfilters/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=apis_005Cfilters/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=validation/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using FluentValidation;
|
||||
using IdentityShroud.Api;
|
||||
using IdentityShroud.Api.Mappers;
|
||||
using IdentityShroud.Api.Validation;
|
||||
using IdentityShroud.Core;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Security;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace IdentityShroud.Api.Validation;
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
public static class EndpointRouteBuilderExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using FluentValidation;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
namespace IdentityShroud.Api.Validation;
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
public class RealmCreateRequestValidator : AbstractValidator<RealmCreateRequest>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace IdentityShroud.Api.Validation;
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
public class ValidateFilter<T> : IEndpointFilter where T : class
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue