Add endpoint to create users

This commit is contained in:
Sipke Schoorstra 2023-03-27 00:43:39 +02:00
parent 63c104d428
commit bf810e7d7a
7 changed files with 115 additions and 9 deletions

View file

@ -27,7 +27,7 @@
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
"Name": "admin",
"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
"HashedSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
"Roles": ["admin"]
}
],

View file

@ -1,6 +1,5 @@
using Elsa.Identity.Contracts;
using Elsa.Identity.Entities;
using Elsa.Identity.Models;
using Elsa.Workflows.Core.Contracts;
using FastEndpoints;
using JetBrains.Annotations;
@ -70,7 +69,16 @@ internal class Create : Endpoint<Request, Response>
await _applicationStore.SaveAsync(application, cancellationToken);
var response = new Response(apiKey);
var response = new Response(
id,
application.Name,
application.Roles,
clientId,
clientSecret,
apiKey,
hashedApiKey,
hashedClientSecret);
await SendOkAsync(response, cancellationToken);
}
}

View file

@ -1,3 +1,5 @@
using Elsa.Identity.Models;
namespace Elsa.Identity.Endpoints.Applications.Create;
internal class Request
@ -6,4 +8,13 @@ internal class Request
public ICollection<string>? Roles { get; set; }
}
internal record Response(string ApiKey);
internal record Response(
string Id,
string Name,
ICollection<string> Roles,
string ClientId,
string ClientSecret,
string ApiKey,
HashedSecret HashedApiKey,
HashedSecret HashedClientSecret
);

View file

@ -0,0 +1,69 @@
using Elsa.Abstractions;
using Elsa.Identity.Contracts;
using Elsa.Identity.Entities;
using Elsa.Workflows.Core.Contracts;
using JetBrains.Annotations;
namespace Elsa.Identity.Endpoints.Users.Create;
/// <summary>
/// An endpoint that creates a new user. Requires the <code>SecurityRoot</code> policy.
/// </summary>
[PublicAPI]
internal class Create : ElsaEndpoint<Request, Response>
{
private readonly IIdentityGenerator _identityGenerator;
private readonly ISecretGenerator _secretGenerator;
private readonly ISecretHasher _secretHasher;
private readonly IUserStore _userStore;
private readonly IRoleStore _roleStore;
public Create(
IIdentityGenerator identityGenerator,
ISecretGenerator secretGenerator,
ISecretHasher secretHasher,
IUserStore userStore,
IRoleStore roleStore)
{
_identityGenerator = identityGenerator;
_secretGenerator = secretGenerator;
_secretHasher = secretHasher;
_userStore = userStore;
_roleStore = roleStore;
}
/// <inheritdoc />
public override void Configure()
{
Post("/identity/users");
ConfigurePermissions("create:user");
}
/// <inheritdoc />
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var id = _identityGenerator.GenerateId();
var password = string.IsNullOrWhiteSpace(request.Password) ? _secretGenerator.Generate() : request.Password.Trim();
var hashedPassword = _secretHasher.HashSecret(password);
var user = new User
{
Id = id,
Name = request.Name,
Roles = request.Roles ?? new List<string>(),
HashedPassword = hashedPassword.EncodeSecret(),
HashedPasswordSalt = hashedPassword.EncodeSalt()
};
await _userStore.SaveAsync(user, cancellationToken);
var response = new Response(
id,
user.Name,
password,
user.Roles,
hashedPassword);
await SendOkAsync(response, cancellationToken);
}
}

View file

@ -0,0 +1,18 @@
using Elsa.Identity.Models;
namespace Elsa.Identity.Endpoints.Users.Create;
internal class Request
{
public string Name { get; set; } = default!;
public string? Password { get; set; }
public ICollection<string>? Roles { get; set; }
}
internal record Response(
string Id,
string Name,
string Password,
ICollection<string> Roles,
HashedSecret HashedPassword
);

View file

@ -3,9 +3,9 @@ namespace Elsa.Identity.Models;
/// <summary>
/// Represents a hashed secret.
/// </summary>
/// <param name="Password">A base64 encoded string representing the hashed secret.</param>
/// <param name="Secret">A base64 encoded string representing the hashed secret.</param>
/// <param name="Salt">A base64 encoded string representing the salt.</param>
public record HashedSecret(byte[] Password, byte[] Salt)
public record HashedSecret(byte[] Secret, byte[] Salt)
{
/// <summary>
/// Creates a new instance of <see cref="HashedSecret"/> from a byte array representing the hashed secret and the salt.
@ -27,7 +27,7 @@ public record HashedSecret(byte[] Password, byte[] Salt)
/// Encodes the secret using base64.
/// </summary>
/// <returns>The base64-encoded secret.</returns>
public string EncodeSecret() => Encode(Password);
public string EncodeSecret() => Encode(Secret);
/// <summary>
/// Encodes the salt using base64.

View file

@ -33,10 +33,10 @@ public class DefaultSecretHasher : ISecretHasher
/// <inheritdoc />
public bool VerifySecret(string clearTextSecret, HashedSecret hashedSecret)
{
var password = hashedSecret.Password;
var password = hashedSecret.Secret;
var salt = hashedSecret.Salt;
var providedHashedPassword = HashSecret(clearTextSecret, salt);
return providedHashedPassword.Password.SequenceEqual(password);
return providedHashedPassword.Secret.SequenceEqual(password);
}
/// <inheritdoc />