diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json index 915ad5851..63fbb2d01 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json +++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json @@ -27,7 +27,7 @@ "Id": "a2323f46-42db-4e15-af8b-94238717d817", "Name": "admin", "HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=", - "HashedSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=", + "HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=", "Roles": ["admin"] } ], diff --git a/src/modules/Elsa.Identity/Endpoints/Applications/Create/Endpoint.cs b/src/modules/Elsa.Identity/Endpoints/Applications/Create/Endpoint.cs index 14ceb987d..a296bc288 100644 --- a/src/modules/Elsa.Identity/Endpoints/Applications/Create/Endpoint.cs +++ b/src/modules/Elsa.Identity/Endpoints/Applications/Create/Endpoint.cs @@ -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 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); } } \ No newline at end of file diff --git a/src/modules/Elsa.Identity/Endpoints/Applications/Create/Models.cs b/src/modules/Elsa.Identity/Endpoints/Applications/Create/Models.cs index 2ea7cb8db..983ef9a43 100644 --- a/src/modules/Elsa.Identity/Endpoints/Applications/Create/Models.cs +++ b/src/modules/Elsa.Identity/Endpoints/Applications/Create/Models.cs @@ -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? Roles { get; set; } } -internal record Response(string ApiKey); \ No newline at end of file +internal record Response( + string Id, + string Name, + ICollection Roles, + string ClientId, + string ClientSecret, + string ApiKey, + HashedSecret HashedApiKey, + HashedSecret HashedClientSecret +); \ No newline at end of file diff --git a/src/modules/Elsa.Identity/Endpoints/Users/Create/Endpoint.cs b/src/modules/Elsa.Identity/Endpoints/Users/Create/Endpoint.cs new file mode 100644 index 000000000..4fdad2f4e --- /dev/null +++ b/src/modules/Elsa.Identity/Endpoints/Users/Create/Endpoint.cs @@ -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; + +/// +/// An endpoint that creates a new user. Requires the SecurityRoot policy. +/// +[PublicAPI] +internal class Create : ElsaEndpoint +{ + 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; + } + + /// + public override void Configure() + { + Post("/identity/users"); + ConfigurePermissions("create:user"); + } + + /// + 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(), + 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); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Identity/Endpoints/Users/Create/Models.cs b/src/modules/Elsa.Identity/Endpoints/Users/Create/Models.cs new file mode 100644 index 000000000..5a6578f4c --- /dev/null +++ b/src/modules/Elsa.Identity/Endpoints/Users/Create/Models.cs @@ -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? Roles { get; set; } +} + +internal record Response( + string Id, + string Name, + string Password, + ICollection Roles, + HashedSecret HashedPassword +); \ No newline at end of file diff --git a/src/modules/Elsa.Identity/Models/HashedSecret.cs b/src/modules/Elsa.Identity/Models/HashedSecret.cs index f0a19338d..a9ab4b115 100644 --- a/src/modules/Elsa.Identity/Models/HashedSecret.cs +++ b/src/modules/Elsa.Identity/Models/HashedSecret.cs @@ -3,9 +3,9 @@ namespace Elsa.Identity.Models; /// /// Represents a hashed secret. /// -/// A base64 encoded string representing the hashed secret. +/// A base64 encoded string representing the hashed secret. /// A base64 encoded string representing the salt. -public record HashedSecret(byte[] Password, byte[] Salt) +public record HashedSecret(byte[] Secret, byte[] Salt) { /// /// Creates a new instance of 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. /// /// The base64-encoded secret. - public string EncodeSecret() => Encode(Password); + public string EncodeSecret() => Encode(Secret); /// /// Encodes the salt using base64. diff --git a/src/modules/Elsa.Identity/Services/DefaultSecretHasher.cs b/src/modules/Elsa.Identity/Services/DefaultSecretHasher.cs index b1164cb0d..eecf0bf59 100644 --- a/src/modules/Elsa.Identity/Services/DefaultSecretHasher.cs +++ b/src/modules/Elsa.Identity/Services/DefaultSecretHasher.cs @@ -33,10 +33,10 @@ public class DefaultSecretHasher : ISecretHasher /// 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); } ///