Skip to content

Commit

Permalink
Merge pull request #5 from alexfdezsauco/feature/fingerprint-authoriz…
Browse files Browse the repository at this point in the history
…ation

Implements fingerprint authentication and authorization
  • Loading branch information
alexfdezsauco authored Apr 26, 2023
2 parents ce6bf87 + 0d50bd3 commit 8952eb9
Show file tree
Hide file tree
Showing 22 changed files with 275 additions and 28 deletions.
6 changes: 6 additions & 0 deletions src/Nothing.Nauta.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@

namespace Nothing.Nauta.App
{
/// <summary>
/// The App class.
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
this.InitializeComponent();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Nothing.Nauta.App.Authorization;

using Microsoft.AspNetCore.Authorization;

public class FingerprintAuthorizationRequirement : IAuthorizationRequirement
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Nothing.Nauta.App.Authorization;

using Microsoft.AspNetCore.Authorization;
using Plugin.Fingerprint.Abstractions;

public class FingerprintAuthorizationRequirementHandler : AuthorizationHandler<FingerprintAuthorizationRequirement>
{
private readonly IFingerprint fingerprint;

public FingerprintAuthorizationRequirementHandler(IFingerprint fingerprint)
{
this.fingerprint = fingerprint;
}

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FingerprintAuthorizationRequirement requirement)
{
// Required to display devices authentication request controls (fingerprint)
context.Succeed(requirement);

if (await this.fingerprint.IsAvailableAsync())
{
var authenticationRequestConfiguration = new AuthenticationRequestConfiguration("Nauta Session", "Please unlock with your fingerprint to proceed");
var authenticationResult = await this.fingerprint.AuthenticateAsync(authenticationRequestConfiguration);
if (!authenticationResult.Authenticated)
{
context.Fail();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Nothing.Nauta.App.Authorization;

using System.Security.Claims;

using Microsoft.AspNetCore.Components.Authorization;

public class FingerprintAuthorizationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(Array.Empty<Claim>(), "Fingerprint"));
return Task.FromResult(new AuthenticationState(claimsPrincipal));
}
}
6 changes: 6 additions & 0 deletions src/Nothing.Nauta.App/Authorization/Policies.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Nothing.Nauta.App.Authorization;

public class Policies
{
public const string Fingerprint = nameof(Fingerprint);
}
24 changes: 24 additions & 0 deletions src/Nothing.Nauta.App/Main.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Nothing.Nauta.App
{
using Microsoft.AspNetCore.Components;

/// <summary>
/// The Main class.
/// </summary>
public partial class Main
{
/// <summary>
/// Gets or sets navigation manager.
/// </summary>
[Inject]
public NavigationManager? NavigationManager { get; set; }

/// <summary>
/// Called on unlock button click.
/// </summary>
private void OnUnlockButtonClick()
{
this.NavigationManager?.NavigateTo("/");
}
}
}
41 changes: 29 additions & 12 deletions src/Nothing.Nauta.App/Main.razor
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
@using AddProfile = Nothing.Nauta.App.Shared.MainLayout
<Router AppAssembly="@typeof(Main).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Main).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<Authorizing>
<MudPaper Class="d-flex align-content-center justify-center flex-wrap flex-grow-1 gap-4" Height="800px" Width="100%" Elevation="0">
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Large" Color="Color.Primary"/>
</MudPaper>
</Authorizing>
<NotAuthorized>
<MudPaper Class="d-flex align-content-center justify-center flex-wrap flex-grow-1 gap-4 border-dashed" Height="800px" Width="100%" Elevation="0">
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudText Color="Color.Primary" Typo="Typo.h5">Nauta Session locked.</MudText>
<MudButton Color="Color.Primary" StartIcon="@Icons.Material.Filled.Lock" Size="Size.Large" OnClick="OnUnlockButtonClick">Unlock</MudButton>
</MudStack>
</MudPaper>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

45 changes: 43 additions & 2 deletions src/Nothing.Nauta.App/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@

namespace Nothing.Nauta.App
{
using System.Diagnostics;

using Blorc.Services;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Maui.LifecycleEvents;

using MudBlazor.Services;

using Nothing.Nauta.App.Authorization;
using Nothing.Nauta.App.Data;
using Nothing.Nauta.App.Services;
using Nothing.Nauta.App.Services.Interfaces;
using Nothing.Nauta.Interfaces;

using Plugin.Fingerprint;

using Microsoft.Maui.LifecycleEvents;

/// <summary>
/// The MAUI program.
/// </summary>
Expand All @@ -30,10 +40,40 @@ public static class MauiProgram
/// </returns>
public static MauiApp CreateMauiApp()
{
MauiApp? app = null;

var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
builder.UseMauiApp<App>().ConfigureLifecycleEvents(
events =>
{
#if ANDROID
events.AddAndroid(android => android.OnStart(activity =>
{
if (app is not null)
{
var authenticationService = app.Services.GetRequiredService<IAuthenticationService>();
authenticationService.ExpireSession();
}
}));
#endif
});

builder.Services.AddBlorcCore();
builder.Services.AddAuthorizationCore(
options =>
{
options.AddPolicy(
Policies.Fingerprint,
policyBuilder =>
{
policyBuilder.AddRequirements(new FingerprintAuthorizationRequirement());
});
});

builder.Services.AddSingleton<AuthenticationStateProvider, FingerprintAuthorizationStateProvider>();
builder.Services.AddScoped<IAuthorizationHandler, FingerprintAuthorizationRequirementHandler>();
builder.Services.AddSingleton<IAuthenticationService, AuthenticationService>();

builder.Services.AddMudServices();
builder.Services.AddMauiBlazorWebView();
#if DEBUG
Expand All @@ -49,10 +89,11 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<ISessionHandler, SessionHandler>();
builder.Services.AddSingleton<IAccountRepository, AccountRepository>();

builder.Services.AddSingleton(_ => CrossFingerprint.Current);
builder.Services.AddSingleton(_ => SecureStorage.Default);
builder.Services.AddSingleton(_ => DeviceDisplay.Current);

var app = builder.Build();
app = builder.Build();

var serviceScope = app.Services.CreateScope();
var appDbContext = serviceScope.ServiceProvider.GetRequiredService<AppDbContext>();
Expand Down
3 changes: 3 additions & 0 deletions src/Nothing.Nauta.App/Nothing.Nauta.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
<ItemGroup>
<PackageReference Include="Blorc.Core" Version="1.2.0" />
<PackageReference Include="DeepCloner" Version="0.10.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.16" />
<PackageReference Include="MudBlazor" Version="6.2.2" />
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.5" />
<PackageReference Include="Polly" Version="7.2.3" />
</ItemGroup>

Expand Down
8 changes: 8 additions & 0 deletions src/Nothing.Nauta.App/Pages/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

namespace Nothing.Nauta.App.Pages
{
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using MudBlazor;

using Nothing.Nauta.App.Authorization;
using Nothing.Nauta.App.Components.Attributes;

[Authorize(Policy = Policies.Fingerprint)]
public partial class Index
{
[Inject]
Expand All @@ -19,5 +23,9 @@ public partial class Index
[Inject]
[ViewToViewModel]
public IDialogService? DialogService { get; set; }

[Inject]
[ViewToViewModel]
public NavigationManager? NavigationManager { get; set; }
}
}
17 changes: 8 additions & 9 deletions src/Nothing.Nauta.App/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@

</MudStack>

@*<MudToolBar Style="margin-left: -10px; margin-right: -15px" >
<MudSpacer/>
</MudToolBar>
*@
@if (ViewModel.IsReloading)
@if (ViewModel.Accounts is null || ViewModel.IsReloading)
{
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
<MudPaper Class="d-flex align-content-center justify-center flex-wrap flex-grow-1 gap-4" Height="800px" Width="100%" Elevation="0">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large"/>
</MudPaper>
}
else if (ViewModel.Accounts?.Count > 0)
{
Expand Down Expand Up @@ -87,9 +84,11 @@ else if (ViewModel.Accounts?.Count > 0)
</MudStack>
}
}
else
else
{
<MudAlert Severity="Severity.Info">Add a new account</MudAlert>
<MudPaper Class="d-flex align-content-center justify-center flex-wrap flex-grow-1 gap-4" Height="800px" Width="100%" Elevation="0">
<MudAlert Severity="Severity.Info">Add a new account</MudAlert>
</MudPaper>
}

<MudOverlay Visible="@ViewModel.IsOverlayVisible" DarkBackground="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
</manifest>
9 changes: 9 additions & 0 deletions src/Nothing.Nauta.App/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ namespace Nothing.Nauta.App
using Android.Content.PM;
using Android.OS;

using Microsoft.Maui.LifecycleEvents;

using Plugin.Fingerprint;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
CrossFingerprint.SetCurrentActivityResolver(() => this);
}
}
}
1 change: 1 addition & 0 deletions src/Nothing.Nauta.App/Platforms/Android/MainApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
{
}


protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}
18 changes: 18 additions & 0 deletions src/Nothing.Nauta.App/Services/AuthenticationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Nothing.Nauta.App.Services.Interfaces;

namespace Nothing.Nauta.App.Services;

public class AuthenticationService : IAuthenticationService
{
public event EventHandler? SessionExpired;

public void ExpireSession()
{
this.OnSessionExpired();
}

protected virtual void OnSessionExpired()
{
this.SessionExpired?.Invoke(this, System.EventArgs.Empty);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Nothing.Nauta.App.Services.Interfaces;

public interface IAuthenticationService
{
event EventHandler SessionExpired;

void ExpireSession();
}
Loading

0 comments on commit 8952eb9

Please sign in to comment.