Compare commits
11 Commits
878d26653c
...
3658bdcba0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3658bdcba0 | |||
| 4d51d4f00e | |||
| 1ec090eca4 | |||
| 35c1665950 | |||
| bd191b113a | |||
| c50477bbd3 | |||
| a3349e23c9 | |||
| 8027229a7f | |||
| 5893d86e13 | |||
| ee9bc9ad33 | |||
| 76c9bd06af |
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Akari.Prototype.Server.Options;
|
||||
using Akari.Prototype.Server.Services;
|
||||
using Akari.Prototype.Server.Utils;
|
||||
using Akari.Prototype.Server.Utils.Extensions;
|
||||
@@ -9,6 +11,10 @@ using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Akari.Prototype.Server.Cli.Commands
|
||||
@@ -61,7 +67,7 @@ namespace Akari.Prototype.Server.Cli.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("fp r", Description = "Remove an application")]
|
||||
[Command("app r", Description = "Remove an application")]
|
||||
public class RemoveApplicationCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0, Description = "The application name")]
|
||||
@@ -81,12 +87,77 @@ namespace Akari.Prototype.Server.Cli.Commands
|
||||
throw new CommandException($"The application {Name} doesn't exist");
|
||||
}
|
||||
|
||||
console.AsAnsiConsole().Markup($"[green]Successfully deleted {Name}[/]");
|
||||
console.AsAnsiConsole().MarkupLine($"[green]Successfully deleted {Name}[/]");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("app f", Description = "Add a fingerprint to and application")]
|
||||
public class AddFingerprintToApplicationCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0, Description = "Application to add fingerprint to")]
|
||||
public string Application { get; init; }
|
||||
|
||||
[CommandParameter(1, Description = "Fingerprint to add")]
|
||||
public string Fingerprint { get; init; }
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IApplicationsManager _applications;
|
||||
private readonly IMasterKeyService _masterKeyService;
|
||||
|
||||
public AddFingerprintToApplicationCommand(IServiceProvider serviceProvider, IApplicationsManager applications, IMasterKeyService masterKeyService)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_applications = applications;
|
||||
_masterKeyService = masterKeyService;
|
||||
}
|
||||
|
||||
public async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var ansiConsole = console.AsAnsiConsole();
|
||||
|
||||
if (!_applications.Contains(Application))
|
||||
{
|
||||
throw new CommandException("No application exist with this name");
|
||||
}
|
||||
|
||||
if (_applications.IsFingerprintRegistered(Application, Fingerprint))
|
||||
{
|
||||
throw new CommandException($"'{Fingerprint}' is already registered for '{Application}'");
|
||||
}
|
||||
|
||||
if (!CliUtils.Login(_masterKeyService, ansiConsole))
|
||||
{
|
||||
throw new CommandException("Can't proceed without master service login");
|
||||
}
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
using var provider = new TcpProviderService(services.GetRequiredService<ILogger<TcpProviderService>>(),
|
||||
services.GetRequiredService<IOptions<TcpProviderOptions>>(),
|
||||
services.GetRequiredService<IHostApplicationLifetime>(),
|
||||
services.GetRequiredService<IFingerprintManager>());
|
||||
|
||||
var source = new CancellationTokenSource();
|
||||
|
||||
await provider.StartAsync(source.Token);
|
||||
|
||||
ansiConsole.WriteLine("Press a key when the fingerprint has been auth");
|
||||
|
||||
console.Input.Read();
|
||||
|
||||
if (!_applications.AddFingerprint(Application, Fingerprint))
|
||||
{
|
||||
throw new CommandException($"Fingerprint '{Fingerprint}' has not been auth");
|
||||
}
|
||||
|
||||
ansiConsole.WriteLine($"[green]Successfully added '{Fingerprint}' to '{Application}'[/]");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IApplicationsManager _applications;
|
||||
|
||||
public ApplicationCommands(IApplicationsManager applications)
|
||||
@@ -101,12 +172,12 @@ namespace Akari.Prototype.Server.Cli.Commands
|
||||
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());
|
||||
table.AddColumn(new TableColumn("[bold blue]Token Hash[/]").LeftAligned());
|
||||
|
||||
foreach (var application in _applications)
|
||||
{
|
||||
table.AddRow($"[silver]{application.Name}[/]", $"[grey]{application.TokenHash}[/]", $"[grey]{application.Fingerprints}[/]");
|
||||
table.AddRow($"[silver]{application.Name}[/]", $"[grey]{string.Join(", ", application.Fingerprints)}[/]", $"[grey]{application.TokenHash}[/]");
|
||||
}
|
||||
|
||||
ansiConsole.Write(table);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Akari.Prototype.Server.Services;
|
||||
using Akari.Prototype.Server.Options;
|
||||
using Akari.Prototype.Server.Services;
|
||||
using Akari.Prototype.Server.Utils;
|
||||
using Akari.Prototype.Server.Utils.Extensions;
|
||||
using CliFx;
|
||||
@@ -6,12 +7,17 @@ using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Extensibility;
|
||||
using CliFx.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Akari.Prototype.Server.Cli.Commands
|
||||
@@ -46,6 +52,11 @@ namespace Akari.Prototype.Server.Cli.Commands
|
||||
throw new CommandException("Name can't be more than 200 bytes (UTF-8)");
|
||||
}
|
||||
|
||||
if (Name.Contains('$'))
|
||||
{
|
||||
throw new CommandException("Name can't contain a '$'");
|
||||
}
|
||||
|
||||
if (_fingerprintManager.Contains(Name))
|
||||
{
|
||||
throw new CommandException("A fingerprint with this name is already registered.");
|
||||
@@ -88,12 +99,66 @@ namespace Akari.Prototype.Server.Cli.Commands
|
||||
throw new CommandException($"The fingerprint {Name} doesn't exist");
|
||||
}
|
||||
|
||||
console.AsAnsiConsole().Markup($"[green]Successfully deleted {Name}[/]");
|
||||
console.AsAnsiConsole().MarkupLine($"[green]Successfully deleted {Name}[/]");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("fp t", Description = "Test a fingerprint")]
|
||||
public class TestFingerprintCommand : ICommand
|
||||
{
|
||||
public const int AuthTimeout = 5_000;
|
||||
|
||||
[CommandParameter(0, Description = "The fingerprint name")]
|
||||
public string Name { get; init; }
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IAuthManager _authManager;
|
||||
private readonly IFingerprintManager _fingerprintManager;
|
||||
|
||||
public TestFingerprintCommand(IServiceProvider serviceProvider, IAuthManager authManager, IFingerprintManager fingerprintManager)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_authManager = authManager;
|
||||
_fingerprintManager = fingerprintManager;
|
||||
}
|
||||
|
||||
public async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var ansiConsole = console.AsAnsiConsole();
|
||||
|
||||
if (!_fingerprintManager.Contains(Name))
|
||||
{
|
||||
throw new CommandException("This fingerprint does not exist");
|
||||
}
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
using var provider = new TcpProviderService(services.GetRequiredService<ILogger<TcpProviderService>>(),
|
||||
services.GetRequiredService<IOptions<TcpProviderOptions>>(),
|
||||
services.GetRequiredService<IHostApplicationLifetime>(),
|
||||
services.GetRequiredService<IFingerprintManager>());
|
||||
|
||||
var source = new CancellationTokenSource(AuthTimeout);
|
||||
|
||||
await provider.StartAsync(source.Token);
|
||||
|
||||
ansiConsole.MarkupLine($"Waiting for [yellow]{Name}[/] auth");
|
||||
|
||||
await Task.Delay(AuthTimeout);
|
||||
|
||||
if (!_authManager.TryGetKey(Name, out _))
|
||||
{
|
||||
throw new CommandException("Timed out while waiting for fingerprint auth");
|
||||
}
|
||||
|
||||
ansiConsole.MarkupLine($"[green]Successfully retrieved '{Name}' key[/]");
|
||||
}
|
||||
}
|
||||
|
||||
public FingerprintCommands(IFingerprintManager fingerprintManager)
|
||||
{
|
||||
_fingerprintManager = fingerprintManager;
|
||||
|
||||
127
Akari.Prototype.Server/Cli/Commands/MasterCommands.cs
Normal file
127
Akari.Prototype.Server/Cli/Commands/MasterCommands.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
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("m", Description = "Manage master password")]
|
||||
public class MasterCommands : ICommand
|
||||
{
|
||||
[Command("m c", Description = "Create master password")]
|
||||
public class CreateMasterPasswordCommand : ICommand
|
||||
{
|
||||
private readonly IMasterKeyService _masterKeyService;
|
||||
|
||||
public CreateMasterPasswordCommand(IMasterKeyService masterKeyService)
|
||||
{
|
||||
_masterKeyService = masterKeyService;
|
||||
}
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var ansiConsole = console.AsAnsiConsole();
|
||||
|
||||
if (_masterKeyService.IsPasswordSet)
|
||||
{
|
||||
throw new CommandException("Can't create master password if it's already set");
|
||||
}
|
||||
|
||||
ansiConsole.WriteLine();
|
||||
|
||||
string password = ansiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter [yellow]master password[/]")
|
||||
.PromptStyle("red")
|
||||
.Secret()
|
||||
);
|
||||
|
||||
string passwordConfirm = ansiConsole.Prompt(
|
||||
new TextPrompt<string>("Confirm [yellow]master password[/]")
|
||||
.PromptStyle("red")
|
||||
.Secret()
|
||||
);
|
||||
|
||||
ansiConsole.WriteLine();
|
||||
|
||||
if (password != passwordConfirm)
|
||||
{
|
||||
throw new CommandException("Password doesn't match");
|
||||
}
|
||||
|
||||
if (!_masterKeyService.CreatePassword(password))
|
||||
{
|
||||
throw new CommandException("Created master password but can't login");
|
||||
}
|
||||
|
||||
ansiConsole.MarkupLine("[green]Successfully created [yellow]master password[/][/]");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("m t", Description = "Test master password")]
|
||||
public class TestMasterPasswordCommand : ICommand
|
||||
{
|
||||
private readonly IMasterKeyService _masterKeyService;
|
||||
|
||||
public TestMasterPasswordCommand(IMasterKeyService masterKeyService)
|
||||
{
|
||||
_masterKeyService = masterKeyService;
|
||||
}
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var ansiConsole = console.AsAnsiConsole();
|
||||
|
||||
if (!_masterKeyService.IsPasswordSet)
|
||||
{
|
||||
throw new CommandException("Can't login if no master password has been set");
|
||||
}
|
||||
|
||||
ansiConsole.WriteLine();
|
||||
|
||||
if (!CliUtils.Login(_masterKeyService, ansiConsole))
|
||||
{
|
||||
ansiConsole.MarkupLine("[red]Invalid [yellow]master password[/][/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
ansiConsole.MarkupLine("[green]Successfully logged in[/]");
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IMasterKeyService _masterKeyService;
|
||||
|
||||
public MasterCommands(IMasterKeyService masterKeyService)
|
||||
{
|
||||
_masterKeyService = masterKeyService;
|
||||
}
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var ansiConsole = console.AsAnsiConsole();
|
||||
|
||||
if (!_masterKeyService.IsPasswordSet)
|
||||
{
|
||||
ansiConsole.MarkupLine("[yellow]Master password[/] is not set");
|
||||
}
|
||||
else
|
||||
{
|
||||
ansiConsole.MarkupLine("[yellow]Master password[/] is set");
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,9 +108,12 @@ namespace Akari.Prototype.Server.Services
|
||||
return _applications.ContainsKey(applicationName);
|
||||
}
|
||||
|
||||
public bool IsFingerprintRegistered(string applicationName, string fingerprintName) => _applications.TryGetValue(applicationName, out var application)
|
||||
&& application.Fingerprints.Contains(fingerprintName);
|
||||
|
||||
public bool Remove(string applicationName)
|
||||
{
|
||||
if (!_applications.ContainsKey(applicationName))
|
||||
if (!_applications.TryGetValue(applicationName, out var application))
|
||||
{
|
||||
_logger.LogDebug($"Can't remove non existing application: {applicationName}");
|
||||
|
||||
@@ -118,9 +121,9 @@ namespace Akari.Prototype.Server.Services
|
||||
}
|
||||
|
||||
// Clear keys
|
||||
_keyManager.Clear(applicationName);
|
||||
_keyManager.Clear(application);
|
||||
|
||||
_applications.Remove(applicationName);
|
||||
_applications.Remove(application.Name);
|
||||
|
||||
SaveApplications();
|
||||
|
||||
@@ -129,6 +132,13 @@ namespace Akari.Prototype.Server.Services
|
||||
|
||||
public bool AddFingerprint(string applicationName, string fingerprintName)
|
||||
{
|
||||
if (IsFingerprintRegistered(applicationName, fingerprintName))
|
||||
{
|
||||
_logger.LogDebug($"'{fingerprintName}' is already registered for '{applicationName}'");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify master password
|
||||
if (!_masterKeyService.IsLoggedIn)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Akari.Prototype.Server.Services
|
||||
|
||||
bool Contains(string applicationName);
|
||||
|
||||
bool IsFingerprintRegistered(string applicationName, string fingerprintName);
|
||||
|
||||
bool Remove(string applicationName);
|
||||
|
||||
bool AddFingerprint(string applicationName, string fingerprintName);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Akari.Prototype.Server.Models;
|
||||
|
||||
namespace Akari.Prototype.Server.Services
|
||||
{
|
||||
@@ -10,7 +11,7 @@ namespace Akari.Prototype.Server.Services
|
||||
{
|
||||
public bool AddFingerprint(string applicationName, string fingerprintName, AesGcm fingerprintKey);
|
||||
|
||||
public void Clear(string applicationName);
|
||||
public void Clear(Application application);
|
||||
|
||||
public bool Create(string applicationName);
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ namespace Akari.Prototype.Server.Services
|
||||
public interface IMasterKeyService
|
||||
{
|
||||
public bool IsLoggedIn { get; }
|
||||
|
||||
public bool CheckConfig();
|
||||
public bool IsPasswordSet { get; }
|
||||
|
||||
public bool Login(string password);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Akari.Prototype.Server.Models;
|
||||
using Akari.Prototype.Server.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -58,12 +59,19 @@ namespace Akari.Prototype.Server.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear(string applicationName)
|
||||
public void Clear(Application application)
|
||||
{
|
||||
_logger.LogDebug($"Deleted keys for {applicationName}");
|
||||
// Do it safely to avoid deleting unwanted files
|
||||
File.Delete(GetMasterKeyPath(application.Name));
|
||||
|
||||
_logger.LogDebug($"Going to delete {GetKeyDirectoryPath(applicationName)}");
|
||||
//Directory.Delete(GetKeyDirectoryPath(applicationName));
|
||||
foreach (var fingerprint in application.Fingerprints)
|
||||
{
|
||||
File.Delete(GetKeyPath(application.Name, fingerprint));
|
||||
}
|
||||
|
||||
Directory.Delete(GetKeyDirectoryPath(application.Name), recursive: false);
|
||||
|
||||
_logger.LogDebug($"Deleted keys for {application.Name}");
|
||||
}
|
||||
|
||||
public bool Create(string applicationName)
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Akari.Prototype.Server.Services
|
||||
public const int MasterKeyLength = 256 / 8;
|
||||
|
||||
public bool IsLoggedIn => _key is not null;
|
||||
public bool IsPasswordSet => File.Exists(_akariPath.GetPath(ConfigPath));
|
||||
|
||||
private readonly ILogger<MasterKeyService> _logger;
|
||||
private readonly AkariPath _akariPath;
|
||||
@@ -33,8 +34,6 @@ namespace Akari.Prototype.Server.Services
|
||||
_akariPath = akariPath;
|
||||
}
|
||||
|
||||
public bool CheckConfig() => File.Exists(_akariPath.GetPath(ConfigPath));
|
||||
|
||||
public bool Login(string password)
|
||||
{
|
||||
if (_key is not null)
|
||||
@@ -81,7 +80,7 @@ namespace Akari.Prototype.Server.Services
|
||||
|
||||
public bool CreatePassword(string password)
|
||||
{
|
||||
if (CheckConfig())
|
||||
if (IsPasswordSet)
|
||||
{
|
||||
_logger.LogDebug("Tried to create password but it's already set.");
|
||||
|
||||
@@ -103,7 +102,7 @@ namespace Akari.Prototype.Server.Services
|
||||
|
||||
private bool LoadConfig()
|
||||
{
|
||||
if (!CheckConfig())
|
||||
if (!IsPasswordSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Akari.Prototype.Server
|
||||
services.AddSingleton<IAuthManager, AuthManager>();
|
||||
services.AddSingleton<IKeyManager, KeyManager>();
|
||||
services.AddSingleton<IApplicationsManager, ApplicationsManager>();
|
||||
services.AddSingleton<IMasterKeyService, MasterKeyService>();
|
||||
services.AddSingleton<AkariPath>();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace Akari.Prototype.Server.Utils
|
||||
ansiConsole.WriteLine();
|
||||
|
||||
password = ansiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter [green]master password[/]")
|
||||
new TextPrompt<string>("Enter [yellow]master password[/]")
|
||||
.PromptStyle("red")
|
||||
.Secret()
|
||||
);
|
||||
|
||||
ansiConsole.WriteLine();
|
||||
} while (!masterKeyService.Login(password) && tries < LoginMaxTries);
|
||||
|
||||
ansiConsole.WriteLine();
|
||||
|
||||
return masterKeyService.IsLoggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user