Skip to content

Commit

Permalink
#115 chore: basic authentication
Browse files Browse the repository at this point in the history
+ basic login screen for username/pass
+ authentication cookie to keep the session alive until logout or expiration
+ some refactors
  • Loading branch information
joseantmazonsb committed Mar 16, 2022
1 parent e898915 commit a5f38f3
Show file tree
Hide file tree
Showing 42 changed files with 646 additions and 177 deletions.
5 changes: 5 additions & 0 deletions Linguard/Auth/Exceptions/LoginException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Auth.Exceptions;

public class LoginException : Exception {
public LoginException(string message) : base(message) { }
}
8 changes: 2 additions & 6 deletions Linguard/Auth/Models/Credentials.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
namespace Auth.Models;

public class Credentials : ICredentials {
public Credentials(string login, string password) {
Login = login;
Password = password;
}
public string Login { get; }
public string Password { get; }
public string Login { get; set; }
public string Password { get; set; }
}
4 changes: 2 additions & 2 deletions Linguard/Auth/Models/ICredentials.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Auth.Models;

public interface ICredentials {
public string Login { get; }
public string Password { get;}
public string Login { get; set; }
public string Password { get; set; }
}
6 changes: 0 additions & 6 deletions Linguard/Auth/Models/IToken.cs

This file was deleted.

11 changes: 0 additions & 11 deletions Linguard/Auth/Models/Token.cs

This file was deleted.

8 changes: 0 additions & 8 deletions Linguard/Auth/Services/IAuthService.cs

This file was deleted.

2 changes: 0 additions & 2 deletions Linguard/Core/Managers/ConfigurationManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ protected ConfigurationManagerBase(IConfiguration configuration, IWorkingDirecto

public IConfiguration Configuration { get; set; }
public IWorkingDirectory WorkingDirectory { get; set; }
public bool IsSetupNeeded { get; set; } = true;

public void LoadDefaults() {
LoadWebDefaults();
Expand Down Expand Up @@ -68,7 +67,6 @@ private void LoadWireguardDefaults() {
public abstract void Load();

public void Save() {
IsSetupNeeded = false;
ApplyChanges();
DoSave();
}
Expand Down
4 changes: 0 additions & 4 deletions Linguard/Core/Managers/IConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ public interface IConfigurationManager {
/// </summary>
IWorkingDirectory WorkingDirectory { get; set; }
/// <summary>
/// Flag used to tell whether the initial setup has been completed.
/// </summary>
bool IsSetupNeeded { get; set; }
/// <summary>
/// Load default options.
/// </summary>
void LoadDefaults();
Expand Down
11 changes: 8 additions & 3 deletions Linguard/Log/SimpleFileLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ public class SimpleFileLogger : ILinguardLogger {
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception? exception, Func<TState, Exception?, string> formatter) {
if (!IsEnabled(logLevel)) return;
Target?.WriteLine($"{DateTime.Now.ToString(DateTimeFormat)} [{logLevel.ToString().ToUpper()}] " +
$"{formatter(state, exception)}");
var message = $"{DateTime.Now.ToString(DateTimeFormat)} [{logLevel.ToString().ToUpper()}] " +
$"{formatter(state, exception)}";
if (exception != default) message += $"{Environment.NewLine}The following exception was raised:" +
$"{Environment.NewLine}{exception}";
Target?.WriteLine(message);
}

public bool IsEnabled(LogLevel logLevel) {
Expand All @@ -25,7 +28,9 @@ public bool IsEnabled(LogLevel logLevel) {

public static class Extensions {
public static ILoggingBuilder AddSimpleFileLogger(this ILoggingBuilder builder) {
builder.Services.TryAddSingleton<ILinguardLogger, SimpleFileLogger>();
var logger = new SimpleFileLogger();
builder.Services.TryAddSingleton<ILogger>(logger);
builder.Services.TryAddSingleton<ILinguardLogger>(logger);
return builder;
}

Expand Down
27 changes: 15 additions & 12 deletions Linguard/Web/App.razor
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Oops, it looks like there's nothing here.</p>
</LayoutView>
</NotFound>
</Router>
@using Linguard.Web.Shared.Layouts
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Oops, it looks like there's nothing here.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
10 changes: 10 additions & 0 deletions Linguard/Web/Auth/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Linguard.Web.Auth;

public class ApplicationDbContext : IdentityDbContext {
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) {
}
}
17 changes: 17 additions & 0 deletions Linguard/Web/Auth/AuthenticationCookieFormatBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Authentication.Cookies;

namespace Linguard.Web.Auth;

public class AuthenticationCookieFormatBase : IAuthenticationCookieFormat {
public AuthenticationCookieFormatBase(string scheme, string name) {
Scheme = scheme;
Name = name;
}
public string Scheme { get; }
public string Name { get; }
}

public static class AuthenticationCookieFormat {
public static readonly IAuthenticationCookieFormat Default =
new AuthenticationCookieFormatBase(CookieAuthenticationDefaults.AuthenticationScheme, "Auth");
}
6 changes: 6 additions & 0 deletions Linguard/Web/Auth/IAuthenticationCookieFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Linguard.Web.Auth;

public interface IAuthenticationCookieFormat {
public string Scheme { get; }
public string Name { get; }
}
55 changes: 55 additions & 0 deletions Linguard/Web/Auth/IdentityAuthenticationStateProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

namespace Linguard.Web.Auth;

public class IdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class {
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;

public IdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor)
: base(loggerFactory) {
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
}

protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);

protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken) {
// Get the user manager from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
try {
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally {
if (scope is IAsyncDisposable asyncDisposable) {
await asyncDisposable.DisposeAsync();
}
else {
scope.Dispose();
}
}
}

private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal) {
var user = await userManager.GetUserAsync(principal);
if (user == null) {
return false;
}
if (!userManager.SupportsUserSecurityStamp) {
return true;
}
var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
}
}
11 changes: 11 additions & 0 deletions Linguard/Web/Helpers/IWebHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Linguard.Core.Models.Wireguard;

namespace Linguard.Web.Helpers;

public interface IWebHelper {
Task Download(string data, string filename);
Task DownloadConfiguration();
Task DownloadWireguardModel(IWireguardPeer peer);
void RemoveWireguardModel(IWireguardPeer peer);
byte[] GetQrCode(IWireguardPeer peer);
}
71 changes: 71 additions & 0 deletions Linguard/Web/Helpers/WebHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Text;
using Linguard.Core.Configuration;
using Linguard.Core.Managers;
using Linguard.Core.Models.Wireguard;
using Linguard.Core.Services;
using Linguard.Core.Utils;
using Linguard.Core.Utils.Wireguard;
using Microsoft.JSInterop;
using QRCoder;

namespace Linguard.Web.Helpers;

public class WebHelper : IWebHelper {

public WebHelper(IJSRuntime jsRuntime, IWireguardService wireguardService,
IConfigurationManager configurationManager, QRCodeGenerator qrCodeGenerator) {
JsRuntime = jsRuntime;
WireguardService = wireguardService;
ConfigurationManager = configurationManager;
QrCodeGenerator = qrCodeGenerator;
}

private IJSRuntime JsRuntime { get; }
private IWireguardService WireguardService { get; }
private IConfigurationManager ConfigurationManager { get; }
private IWireguardConfiguration Configuration => ConfigurationManager.Configuration.Wireguard;
private QRCodeGenerator QrCodeGenerator {get; }

public async Task Download(string data, string filename) {
var bytes = Encoding.UTF8.GetBytes(data);
var fileStream = new MemoryStream(bytes);
using var streamRef = new DotNetStreamReference(fileStream);
await JsRuntime.InvokeVoidAsync("downloadFileFromStream", filename, streamRef);
}

public Task DownloadConfiguration() {
return Download(ConfigurationManager.Export(), $"{AssemblyInfo.Product.ToLower()}.config");
}

public Task DownloadWireguardModel(IWireguardPeer peer) {
return Download(WireguardUtils.GenerateWireguardConfiguration(peer), $"{peer.Name}.conf");
}

public void RemoveWireguardModel(IWireguardPeer peer) {
switch (peer) {
case Client client:
RemoveClient(client);
break;
case Interface iface:
RemoveInterface(iface);
break;
}
ConfigurationManager.Save();
}

public byte[] GetQrCode(IWireguardPeer peer) {
var qrCodeData = QrCodeGenerator.CreateQrCode(
WireguardUtils.GenerateWireguardConfiguration(peer), QRCodeGenerator.ECCLevel.Q);
return new PngByteQRCode(qrCodeData).GetGraphic(20);
}

private void RemoveClient(Client client) {
WireguardService.RemoveClient(client);
Configuration.GetInterface(client)?.Clients.Remove(client);
}

private void RemoveInterface(Interface iface) {
Configuration.Interfaces.Remove(iface);
WireguardService.RemoveInterface(iface);
}
}
3 changes: 2 additions & 1 deletion Linguard/Web/Pages/AddInterface.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
@using Linguard.Web.Services
@using FluentValidation
@using Linguard.Core.Configuration
@using Linguard.Web.Helpers

@inject IConfigurationManager _configurationManager
@inject IWireguardService _wireguardService
@inject IWebService _webService;
@inject IWebHelper _webHelper;
@inject NotificationService _notificationService
@inject NavigationManager _navigationManager
@inject IJSRuntime _js
Expand Down
3 changes: 2 additions & 1 deletion Linguard/Web/Pages/EditInterface.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
@using Linguard.Web.Services
@using FluentValidation
@using Linguard.Core.Configuration
@using Linguard.Web.Helpers

@inject IConfigurationManager _configurationManager
@inject IWireguardService _wireguardService
@inject IWebService _webService;
@inject IWebHelper _webHelper;
@inject NotificationService _notificationService
@inject DialogService _dialogService
@inject NavigationManager _navigationManager
Expand Down
3 changes: 2 additions & 1 deletion Linguard/Web/Pages/ImportInterface.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@using Linguard.Web.Services
@using FluentValidation
@using Linguard.Core.Configuration
@using Linguard.Web.Helpers

<PageTitle>@($"{AssemblyInfo.Product} | {Title}")</PageTitle>

Expand Down Expand Up @@ -47,7 +48,7 @@

@inject IConfigurationManager _configurationManager
@inject IWireguardService _wireguardService
@inject IWebService _webService;
@inject IWebHelper _webHelper;
@inject NotificationService _notificationService
@inject NavigationManager _navigationManager
@inject IJSRuntime _js
Expand Down
Loading

0 comments on commit a5f38f3

Please sign in to comment.