[frontend] Add basic login flow and session management.
This commit is contained in:
parent
1d262ab326
commit
adb9267cd2
13 changed files with 241 additions and 5 deletions
|
@ -1,6 +1,13 @@
|
|||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Iceshrimp.Frontend.Components
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin/>
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
@* <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/> *@
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
|
|
5
Iceshrimp.Frontend/Components/Note.razor
Normal file
5
Iceshrimp.Frontend/Components/Note.razor
Normal file
|
@ -0,0 +1,5 @@
|
|||
<h3>Note</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
0
Iceshrimp.Frontend/Components/Note.razor.css
Normal file
0
Iceshrimp.Frontend/Components/Note.razor.css
Normal file
10
Iceshrimp.Frontend/Components/RedirectToLogin.razor
Normal file
10
Iceshrimp.Frontend/Components/RedirectToLogin.razor
Normal file
|
@ -0,0 +1,10 @@
|
|||
@* FixMe! Make this actually re-direct back to the content! *@
|
||||
@inject NavigationManager Navigation
|
||||
<h3>RedirectToLogin</h3>
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Navigation.NavigateTo("/login");
|
||||
}
|
||||
}
|
9
Iceshrimp.Frontend/Core/Schemas/StoredUser.cs
Normal file
9
Iceshrimp.Frontend/Core/Schemas/StoredUser.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using Iceshrimp.Shared.Schemas;
|
||||
|
||||
namespace Iceshrimp.Frontend.Core.Schemas;
|
||||
|
||||
public class StoredUser() : UserResponse
|
||||
{
|
||||
[JsonPropertyName("token")] public required string Token { get; set; }
|
||||
}
|
22
Iceshrimp.Frontend/Core/Services/CustomAuthStateProvider.cs
Normal file
22
Iceshrimp.Frontend/Core/Services/CustomAuthStateProvider.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Security.Claims;
|
||||
|
||||
namespace Iceshrimp.Frontend.Core.Services;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
internal class CustomAuthStateProvider (SessionService sessionService) : AuthenticationStateProvider
|
||||
{
|
||||
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
if (sessionService.Current != null) {
|
||||
var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, sessionService.Current.Username), }, "Custom Authentication");
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
return Task.FromResult(new AuthenticationState(user));
|
||||
}
|
||||
else
|
||||
{
|
||||
var identity = new ClaimsIdentity();
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
return Task.FromResult(new AuthenticationState(user));
|
||||
}
|
||||
}
|
||||
}
|
71
Iceshrimp.Frontend/Core/Services/SessionService.cs
Normal file
71
Iceshrimp.Frontend/Core/Services/SessionService.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using Blazored.LocalStorage;
|
||||
using Iceshrimp.Frontend.Core.Schemas;
|
||||
using Iceshrimp.Shared.Schemas;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Iceshrimp.Frontend.Core.Services;
|
||||
|
||||
internal class SessionService
|
||||
{
|
||||
[Inject] public ISyncLocalStorageService LocalStorage { get; }
|
||||
[Inject] public ApiService ApiService { get; }
|
||||
public Dictionary<string, StoredUser> Users { get; }
|
||||
public UserResponse? Current { get; private set; }
|
||||
|
||||
public SessionService(ApiService apiService, ISyncLocalStorageService localStorage)
|
||||
{
|
||||
ApiService = apiService;
|
||||
LocalStorage = localStorage;
|
||||
Users = LocalStorage.GetItem<Dictionary<string, StoredUser>>("Users") ?? [];
|
||||
var lastUser = LocalStorage.GetItem<string?>("last_user");
|
||||
if (lastUser != null)
|
||||
{
|
||||
SetSession(lastUser);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteUsers()
|
||||
{
|
||||
LocalStorage.SetItem("Users", Users);
|
||||
}
|
||||
|
||||
public void AddUser(StoredUser user)
|
||||
{
|
||||
try
|
||||
{
|
||||
Users.Add(user.Id, user);
|
||||
}
|
||||
catch( ArgumentException ) // Update user if it already exists.
|
||||
{
|
||||
Users[user.Id] = user;
|
||||
}
|
||||
WriteUsers();
|
||||
}
|
||||
|
||||
public void DeleteUser(string id)
|
||||
{
|
||||
if (id == Current?.Id) throw new ArgumentException("Cannot remove current user.");
|
||||
Users.Remove(id);
|
||||
WriteUsers();
|
||||
}
|
||||
|
||||
private StoredUser? GetUserById(string id)
|
||||
{
|
||||
var user = Users[id];
|
||||
return user;
|
||||
}
|
||||
|
||||
public void EndSession()
|
||||
{
|
||||
Current = null;
|
||||
LocalStorage.RemoveItem("last_user");
|
||||
}
|
||||
public void SetSession(string id)
|
||||
{
|
||||
var user = GetUserById(id);
|
||||
if (user == null) throw new Exception("Did not find User in Local Storage");
|
||||
ApiService.SetBearerToken(user.Token);
|
||||
Current = user;
|
||||
LocalStorage.SetItem("last_user", user.Id);
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.6" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@page "/"
|
||||
@page "/home"
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
|
|
|
@ -1,8 +1,72 @@
|
|||
@page "/login"
|
||||
@using Iceshrimp.Frontend.Core.Miscellaneous
|
||||
@using Iceshrimp.Frontend.Core.Schemas
|
||||
@using Iceshrimp.Frontend.Core.Services
|
||||
@using Iceshrimp.Shared.Schemas
|
||||
@inject ApiService Api
|
||||
@inject SessionService SessionService
|
||||
@inject NavigationManager Navigation
|
||||
<h3>Login</h3>
|
||||
<div>
|
||||
<input
|
||||
@bind="@Username" />
|
||||
<input
|
||||
@bind="@Password" />
|
||||
<button @onclick="Submit" disabled="@Loading">Login</button>
|
||||
</div>
|
||||
@if (Loading)
|
||||
{
|
||||
<span>Loading!</span>
|
||||
}
|
||||
@if (Failure)
|
||||
{
|
||||
<span>Authentication Failed</span>
|
||||
}
|
||||
|
||||
<p>A login page is being constructed here.</p>
|
||||
|
||||
@code {
|
||||
|
||||
private string? Password { get; set; }
|
||||
private string? Username { get; set; }
|
||||
private bool Loading { get; set; }
|
||||
private bool Failure { get; set; }
|
||||
|
||||
private async void Submit()
|
||||
{
|
||||
Loading = true;
|
||||
try
|
||||
{
|
||||
var res = await Api.Auth.Login(new AuthRequest { Username = Username, Password = Password });
|
||||
switch (res.Status)
|
||||
{
|
||||
case AuthStatusEnum.Authenticated:
|
||||
SessionService.AddUser(new StoredUser
|
||||
{ // Token nor user will ever be null on an authenticated response
|
||||
Id = res.User!.Id,
|
||||
Username = res.User.Username,
|
||||
DisplayName = res.User.DisplayName,
|
||||
AvatarUrl = res.User.AvatarUrl,
|
||||
BannerUrl = res.User.BannerUrl,
|
||||
InstanceName = res.User.InstanceName,
|
||||
InstanceIconUrl = res.User.InstanceIconUrl,
|
||||
Token = res.Token!
|
||||
});
|
||||
SessionService.SetSession(res.User.Id);
|
||||
Navigation.NavigateTo("/");
|
||||
break;
|
||||
case AuthStatusEnum.Guest:
|
||||
Failure = true;
|
||||
Loading = false;
|
||||
break;
|
||||
case AuthStatusEnum.TwoFactor:
|
||||
//FixMe: Implement Two Factor
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ApiException)
|
||||
{
|
||||
Loading = false;
|
||||
Failure = true;
|
||||
}
|
||||
}
|
||||
}
|
0
Iceshrimp.Frontend/Pages/Login.razor.css
Normal file
0
Iceshrimp.Frontend/Pages/Login.razor.css
Normal file
39
Iceshrimp.Frontend/Pages/Timeline.razor
Normal file
39
Iceshrimp.Frontend/Pages/Timeline.razor
Normal file
|
@ -0,0 +1,39 @@
|
|||
@page "/"
|
||||
@attribute [Authorize]
|
||||
@using Iceshrimp.Frontend.Core.Services
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@inject SessionService Session
|
||||
@inject NavigationManager Navigation
|
||||
<h3>Timeline</h3>
|
||||
@if (username != null)
|
||||
{
|
||||
<span>You are logged in as @username </span>
|
||||
<AuthorizeView>
|
||||
<p>Authorization says you are @context.User.Identity!.Name</p>
|
||||
</AuthorizeView>
|
||||
<button @onclick="Logout">Logout</button>
|
||||
}else
|
||||
{
|
||||
<span>Not logged in</span>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
private string? username;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (Session.Current != null)
|
||||
{
|
||||
username = Session.Current.Username;
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
Session.EndSession();
|
||||
Navigation.NavigateTo("/login");
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Iceshrimp.Frontend;
|
||||
using Iceshrimp.Frontend.Core.Services;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
|
@ -10,5 +12,10 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
|
|||
builder.Services.AddSingleton(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
builder.Services.AddSingleton<ApiClient>();
|
||||
builder.Services.AddSingleton<ApiService>();
|
||||
builder.Services.AddSingleton<SessionService>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddBlazoredLocalStorageAsSingleton();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
await builder.Build().RunAsync();
|
Loading…
Add table
Reference in a new issue