-
Notifications
You must be signed in to change notification settings - Fork 179
Add secrets studio module #826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
10966ac
Add secrets studio module
sfmskywalker 5e700fe
Address Greptile secrets studio feedback
sfmskywalker c8f64b6
Handle secrets studio API failures
sfmskywalker 9b649df
Guard secrets list loading failures
sfmskywalker a748c1e
Polish secrets revoke and delete feedback
sfmskywalker 0c7fd38
Merge main into secrets studio branch
sfmskywalker e23c9f1
Address Greptile secrets studio feedback
sfmskywalker 0c6a70a
Confirm secret revoke feedback
sfmskywalker 122dde9
Keep secret picker load retryable
sfmskywalker 8aab75f
Require authorization for secrets module
sfmskywalker f05b172
Avoid redundant secret detail reloads
sfmskywalker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| using Elsa.Studio.Secrets.Models; | ||
| using Refit; | ||
|
|
||
| namespace Elsa.Studio.Secrets.Client; | ||
|
|
||
| public interface ISecretsApi | ||
| { | ||
| [Get("/secrets")] | ||
| Task<ListSecretsResponse> ListAsync(string? search = null, string? typeName = null, string? storeName = null, string? scope = null, SecretStatus? status = null, int? page = null, int? pageSize = null, CancellationToken cancellationToken = default); | ||
|
|
||
| [Get("/secrets/{name}")] | ||
| Task<SecretModel> GetAsync(string name, CancellationToken cancellationToken = default); | ||
|
|
||
| [Post("/secrets")] | ||
| Task<SecretModel> CreateAsync([Body] CreateSecretRequest request, CancellationToken cancellationToken = default); | ||
|
|
||
| [Post("/secrets/{name}/rotate")] | ||
| Task<SecretModel> RotateAsync(string name, [Body] RotateSecretRequest request, CancellationToken cancellationToken = default); | ||
|
|
||
| [Post("/secrets/{name}/revoke")] | ||
| Task<SecretModel> RevokeAsync(string name, CancellationToken cancellationToken = default); | ||
|
|
||
| [Delete("/secrets/{name}")] | ||
| Task DeleteAsync(string name, CancellationToken cancellationToken = default); | ||
|
|
||
| [Post("/secrets/{name}/test")] | ||
| Task<SecretTestResponse> TestAsync(string name, CancellationToken cancellationToken = default); | ||
|
|
||
| [Get("/secrets/descriptors")] | ||
| Task<SecretDescriptorsResponse> GetDescriptorsAsync(CancellationToken cancellationToken = default); | ||
|
|
||
| [Post("/secrets/picker")] | ||
| Task<SecretPickerResponse> PickAsync([Body] SecretPickerRequest request, CancellationToken cancellationToken = default); | ||
| } |
86 changes: 86 additions & 0 deletions
86
src/modules/Elsa.Studio.Secrets/Components/CreateSecretDialog.razor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| @using Elsa.Studio.Contracts | ||
| @using Elsa.Studio.Secrets.Client | ||
| @inject IBackendApiClientProvider ApiClientProvider | ||
|
|
||
| <MudDialog> | ||
| <DialogContent> | ||
| <MudForm @ref="_form"> | ||
| <MudStack Spacing="4"> | ||
| <MudTextField @bind-Value="_model.Name" Label="Technical name" Required="true" Variant="Variant.Outlined" /> | ||
| <MudTextField @bind-Value="_model.DisplayName" Label="Display name" Variant="Variant.Outlined" /> | ||
| <MudTextField @bind-Value="_model.Description" Label="Description" Lines="3" Variant="Variant.Outlined" /> | ||
|
|
||
| <MudSelect T="string" Value="_model.TypeName" ValueChanged="OnTypeChanged" Label="Type" Required="true" Variant="Variant.Outlined"> | ||
| @foreach (var type in Descriptors.Types) | ||
| { | ||
| <MudSelectItem T="string" Value="@type.Name">@type.DisplayName</MudSelectItem> | ||
| } | ||
| </MudSelect> | ||
|
|
||
| <MudSelect T="string" @bind-Value="_model.StoreName" Label="Store" Required="true" Variant="Variant.Outlined"> | ||
| @foreach (var store in CompatibleStores) | ||
| { | ||
| <MudSelectItem T="string" Value="@store.Name">@store.DisplayName</MudSelectItem> | ||
| } | ||
| </MudSelect> | ||
|
|
||
| @if (_model.StoreName == SecretStoreNames.Configuration) | ||
| { | ||
| <MudTextField @bind-Value="_model.ConfigurationKey" Label="Configuration key" Required="true" Variant="Variant.Outlined" /> | ||
| } | ||
| else | ||
| { | ||
| <MudTextField @bind-Value="_model.Value" Label="Value" Required="true" InputType="InputType.Password" Variant="Variant.Outlined" /> | ||
| } | ||
|
|
||
| <MudTextField @bind-Value="_model.Scope" Label="Scope" Variant="Variant.Outlined" /> | ||
| </MudStack> | ||
| </MudForm> | ||
| </DialogContent> | ||
| <DialogActions> | ||
| <MudButton OnClick="Cancel">Cancel</MudButton> | ||
| <MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit">Create</MudButton> | ||
| </DialogActions> | ||
| </MudDialog> | ||
|
|
||
| @code { | ||
| private MudForm _form = default!; | ||
| private readonly CreateSecretRequest _model = new(); | ||
|
|
||
| [Parameter] public SecretDescriptorsResponse Descriptors { get; set; } = new(); | ||
| [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!; | ||
|
|
||
| private IEnumerable<SecretStoreDescriptor> CompatibleStores | ||
| { | ||
| get | ||
| { | ||
| var type = Descriptors.Types.FirstOrDefault(x => x.Name == _model.TypeName); | ||
| return type == null | ||
| ? Descriptors.Stores | ||
| : Descriptors.Stores.Where(x => type.SupportedStoreNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)); | ||
| } | ||
| } | ||
|
|
||
| protected override void OnParametersSet() | ||
| { | ||
| _model.TypeName = Descriptors.Types.FirstOrDefault()?.Name ?? SecretTypeNames.Text; | ||
| _model.StoreName = CompatibleStores.FirstOrDefault()?.Name ?? SecretStoreNames.Encrypted; | ||
| } | ||
|
|
||
| private void OnTypeChanged(string typeName) | ||
| { | ||
| _model.TypeName = typeName; | ||
| _model.StoreName = CompatibleStores.FirstOrDefault()?.Name ?? SecretStoreNames.Encrypted; | ||
| } | ||
|
|
||
| private void Cancel() => MudDialog.Cancel(); | ||
|
|
||
| private async Task Submit() | ||
| { | ||
| await _form.Validate(); | ||
| if (!_form.IsValid) | ||
| return; | ||
|
|
||
| MudDialog.Close(_model); | ||
| } | ||
| } |
90 changes: 90 additions & 0 deletions
90
src/modules/Elsa.Studio.Secrets/Components/SecretPicker.razor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| @using Elsa.Studio.Contracts | ||
| @using Elsa.Studio.Secrets.Client | ||
| @inject IBackendApiClientProvider ApiClientProvider | ||
| @inject IDialogService DialogService | ||
| @inject ISnackbar Snackbar | ||
|
|
||
| <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> | ||
| <MudSelect T="string" Value="Value" ValueChanged="OnValueChanged" Label="@Label" Dense="true" Variant="Variant.Outlined" Clearable="true" Class="flex-grow-1"> | ||
| @foreach (var secret in _items) | ||
| { | ||
| <MudSelectItem T="string" Value="@secret.Name">@secret.DisplayName (@secret.Name)</MudSelectItem> | ||
| } | ||
| </MudSelect> | ||
| @if (AllowInlineCreate && _canCreateInline) | ||
| { | ||
| <MudTooltip Text="Create secret"> | ||
| <MudIconButton Icon="@Icons.Material.Filled.Add" Color="Color.Primary" OnClick="CreateInlineAsync" /> | ||
| </MudTooltip> | ||
| } | ||
| </MudStack> | ||
|
|
||
| @code { | ||
| private ICollection<SecretModel> _items = []; | ||
| private SecretDescriptorsResponse _descriptors = new(); | ||
| private bool _canCreateInline; | ||
| private string[]? _loadedTypeNames; | ||
| private string? _loadedScope; | ||
|
|
||
| [Parameter] public string? Value { get; set; } | ||
| [Parameter] public EventCallback<string?> ValueChanged { get; set; } | ||
| [Parameter] public string Label { get; set; } = "Secret"; | ||
| [Parameter] public ICollection<string> TypeNames { get; set; } = []; | ||
| [Parameter] public string? Scope { get; set; } | ||
| [Parameter] public bool AllowInlineCreate { get; set; } = true; | ||
|
|
||
| protected override async Task OnParametersSetAsync() | ||
| { | ||
| if (ShouldLoad()) | ||
| await LoadAsync(); | ||
| } | ||
|
|
||
| private async Task LoadAsync() | ||
| { | ||
| try | ||
| { | ||
| var api = await ApiClientProvider.GetApiAsync<ISecretsApi>(); | ||
| _descriptors = await api.GetDescriptorsAsync(); | ||
| var response = await api.PickAsync(new SecretPickerRequest { TypeNames = TypeNames, Scope = Scope }); | ||
| _items = response.Items; | ||
| _canCreateInline = response.CanCreateInline; | ||
| _loadedTypeNames = TypeNames.ToArray(); | ||
| _loadedScope = Scope; | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| _items = []; | ||
| _canCreateInline = false; | ||
| Snackbar.Add(e.Message, Severity.Error); | ||
| } | ||
| } | ||
|
|
||
| private bool ShouldLoad() | ||
| { | ||
| return _loadedTypeNames == null || !TypeNames.SequenceEqual(_loadedTypeNames) || !string.Equals(Scope, _loadedScope, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| private Task OnValueChanged(string? value) => ValueChanged.InvokeAsync(value); | ||
|
|
||
| private async Task CreateInlineAsync() | ||
| { | ||
| var parameters = new DialogParameters<CreateSecretDialog> { { x => x.Descriptors, _descriptors } }; | ||
| var options = new DialogOptions { CloseOnEscapeKey = true, CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small }; | ||
| var dialog = await DialogService.ShowAsync<CreateSecretDialog>("Create Secret", parameters, options); | ||
| var result = await dialog.Result; | ||
| if (result is not { Canceled: false, Data: CreateSecretRequest request }) | ||
| return; | ||
|
|
||
| try | ||
| { | ||
| var api = await ApiClientProvider.GetApiAsync<ISecretsApi>(); | ||
| var created = await api.CreateAsync(request); | ||
| await LoadAsync(); | ||
| await ValueChanged.InvokeAsync(created.Name); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| Snackbar.Add(e.Message, Severity.Error); | ||
| } | ||
| } | ||
| } | ||
17 changes: 17 additions & 0 deletions
17
src/modules/Elsa.Studio.Secrets/Elsa.Studio.Secrets.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||
|
|
||
| <PropertyGroup> | ||
| <Description>Adds secret management and picker UX to Elsa Studio.</Description> | ||
| <PackageTags>elsa studio module secrets security</PackageTags> | ||
| <NoWarn>$(NoWarn);CS1591</NoWarn> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <SupportedPlatform Include="browser" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\framework\Elsa.Studio.Shared\Elsa.Studio.Shared.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
19 changes: 19 additions & 0 deletions
19
src/modules/Elsa.Studio.Secrets/Extensions/ServiceCollectionExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| using Elsa.Studio.Contracts; | ||
| using Elsa.Studio.Extensions; | ||
| using Elsa.Studio.Models; | ||
| using Elsa.Studio.Secrets.Client; | ||
| using Elsa.Studio.Secrets.Menu; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| namespace Elsa.Studio.Secrets.Extensions; | ||
|
|
||
| public static class ServiceCollectionExtensions | ||
| { | ||
| public static IServiceCollection AddSecretsModule(this IServiceCollection services, BackendApiConfig backendApiConfig) | ||
| { | ||
| return services | ||
| .AddScoped<IFeature, Feature>() | ||
| .AddScoped<IMenuProvider, SecretsMenu>() | ||
| .AddRemoteApi<ISecretsApi>(backendApiConfig); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using Elsa.Studio.Abstractions; | ||
| using Elsa.Studio.Attributes; | ||
|
|
||
| namespace Elsa.Studio.Secrets; | ||
|
|
||
| [RemoteFeature(RemoteFeatureName)] | ||
| public class Feature : FeatureBase | ||
| { | ||
| public const string RemoteFeatureName = "Elsa.Secrets.ShellFeatures.Secrets"; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <Weavers> | ||
| <ConfigureAwait ContinueOnCapturedContext="false" /> | ||
| </Weavers> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.