diff --git a/Akari.Prototype.Server/Cli/Commands/ApplicationCommands.cs b/Akari.Prototype.Server/Cli/Commands/ApplicationCommands.cs new file mode 100644 index 0000000..232f31c --- /dev/null +++ b/Akari.Prototype.Server/Cli/Commands/ApplicationCommands.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Akari.Prototype.Server.Services; +using Akari.Prototype.Server.Utils; +using Akari.Prototype.Server.Utils.Extensions; +using CliFx; +using CliFx.Attributes; +using CliFx.Exceptions; +using CliFx.Infrastructure; +using Spectre.Console; + +namespace Akari.Prototype.Server.Cli.Commands +{ + [Command("app", Description = "Manage applications")] + public class ApplicationCommands : ICommand + { + [Command("app a", Description = "Create an application")] + public class AddApplicationCommand : ICommand + { + [CommandParameter(0, Description = "The new application name")] + public string Name { get; init; } + + private readonly IApplicationsManager _applications; + private readonly IMasterKeyService _masterKeyService; + + public AddApplicationCommand(IApplicationsManager applications, IMasterKeyService masterKeyService) + { + _applications = applications; + _masterKeyService = masterKeyService; + } + + public ValueTask ExecuteAsync(IConsole console) + { + if (string.IsNullOrWhiteSpace(Name)) + { + throw new CommandException("Application name can't be empty"); + } + + if (_applications.Contains(Name)) + { + throw new CommandException("An application with this name already exists"); + } + + if (!CliUtils.Login(_masterKeyService, console.AsAnsiConsole())) + { + throw new CommandException("Can't proceed without master service login"); + } + + if (!_applications.TryCreate(Name, out var token)) + { + throw new CommandException("Can't create new application"); + } + + console.AsAnsiConsole().MarkupLine($"[green]Successfully created {Name}\n[/]"); + console.AsAnsiConsole().MarkupLine($"[yellow]Token: {token}[/]"); + console.AsAnsiConsole().MarkupLine($"[silver]Note that you won't be able to retrieve the [yellow]token[/] later, it's [red]never stored[/][/]"); + + return default; + } + } + + [Command("fp r", Description = "Remove an application")] + public class RemoveApplicationCommand : ICommand + { + [CommandParameter(0, Description = "The application name")] + public string Name { get; init; } + + private readonly IApplicationsManager _applications; + + public RemoveApplicationCommand(IApplicationsManager applications) + { + _applications = applications; + } + + public ValueTask ExecuteAsync(IConsole console) + { + if (!_applications.Remove(Name)) + { + throw new CommandException($"The application {Name} doesn't exist"); + } + + console.AsAnsiConsole().Markup($"[green]Successfully deleted {Name}[/]"); + + return default; + } + } + + private readonly IApplicationsManager _applications; + + public ApplicationCommands(IApplicationsManager applications) + { + _applications = applications; + } + + public ValueTask ExecuteAsync(IConsole console) + { + var ansiConsole = console.AsAnsiConsole(); + + var table = new Table(); + + table.AddColumn(new TableColumn("[bold yellow]Name[/]").LeftAligned()); + table.AddColumn(new TableColumn("[bold blue]Token Hash[/]").LeftAligned()); + table.AddColumn(new TableColumn("[bold blue]Fingerprints[/]").LeftAligned()); + + foreach (var application in _applications) + { + table.AddRow($"[silver]{application.Name}[/]", $"[grey]{application.TokenHash}[/]", $"[grey]{application.Fingerprints}[/]"); + } + + ansiConsole.Write(table); + + return default; + } + } +} diff --git a/Akari.Prototype.Server/Utils/CliUtils.cs b/Akari.Prototype.Server/Utils/CliUtils.cs new file mode 100644 index 0000000..64c92d0 --- /dev/null +++ b/Akari.Prototype.Server/Utils/CliUtils.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Akari.Prototype.Server.Services; +using Spectre.Console; + +namespace Akari.Prototype.Server.Utils +{ + public static class CliUtils + { + private const int LoginMaxTries = 3; + + public static bool Login(IMasterKeyService masterKeyService, IAnsiConsole ansiConsole) + { + string password; + int tries = 0; + + do + { + tries++; + + ansiConsole.WriteLine(); + + password = ansiConsole.Prompt( + new TextPrompt("Enter [green]master password[/]") + .PromptStyle("red") + .Secret() + ); + + ansiConsole.WriteLine(); + } while (!masterKeyService.Login(password) && tries < LoginMaxTries); + + return masterKeyService.IsLoggedIn; + } + } +}