Co-authored-by: edgeofsanity76 <79632647+edgeofsanity76@users.noreply.github.com>
130 lines
6.0 KiB
Plaintext
130 lines
6.0 KiB
Plaintext
@page "/login"
|
|
@layout AuthLayout
|
|
@using Microsoft.AspNetCore.Authentication
|
|
@using Microsoft.AspNetCore.Identity
|
|
@inject SignInManager<ApplicationUser> SignInManager
|
|
@inject NavigationManager Navigation
|
|
@inject IAuthenticationSchemeProvider SchemeProvider
|
|
|
|
<PageTitle>Budgeteer Beta — Sign In</PageTitle>
|
|
|
|
<div class="text-center mb-4">
|
|
<span style="font-size: 3rem;">💰</span>
|
|
<h2 class="mt-2 mb-0 fw-bold">Budgeteer <span class="badge bg-warning text-dark" style="font-size: 0.45em; vertical-align: middle;">beta</span></h2>
|
|
<p class="text-muted">Sign in to your account</p>
|
|
</div>
|
|
|
|
<EditForm Model="Model" OnValidSubmit="HandleLogin" FormName="login">
|
|
<DataAnnotationsValidator />
|
|
|
|
@if (!string.IsNullOrEmpty(errorMessage))
|
|
{
|
|
<div class="alert alert-danger">@errorMessage</div>
|
|
}
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Email</label>
|
|
<InputText @bind-Value="Model.Email" class="form-control" placeholder="you@example.com" />
|
|
<ValidationMessage For="() => Model.Email" />
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<InputText @bind-Value="Model.Password" type="password" class="form-control" />
|
|
<ValidationMessage For="() => Model.Password" />
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary w-100">Sign In</button>
|
|
</EditForm>
|
|
|
|
@if (externalProviders.Count > 0)
|
|
{
|
|
<hr class="my-4" />
|
|
<p class="text-center text-muted mb-3">Or sign in with</p>
|
|
<div class="d-flex flex-column gap-2">
|
|
@foreach (var provider in externalProviders)
|
|
{
|
|
<form method="post" action="/api/auth/external-login">
|
|
<input type="hidden" name="provider" value="@provider.Name" />
|
|
<input type="hidden" name="returnUrl" value="/" />
|
|
<input type="hidden" name="__RequestVerificationToken" value="@antiforgeryToken" />
|
|
<button type="submit" class="btn btn-outline-secondary w-100 d-flex align-items-center justify-content-center gap-2">
|
|
@GetProviderIcon(provider.DisplayName ?? provider.Name)
|
|
<span>@(provider.DisplayName ?? provider.Name)</span>
|
|
</button>
|
|
</form>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<p class="text-center mt-3">
|
|
Don't have an account? <a href="/register">Register</a>
|
|
</p>
|
|
|
|
@code {
|
|
[SupplyParameterFromForm]
|
|
private LoginModel Model { get; set; } = new();
|
|
|
|
private string? errorMessage;
|
|
private List<AuthenticationScheme> externalProviders = [];
|
|
private string antiforgeryToken = string.Empty;
|
|
|
|
[CascadingParameter]
|
|
private HttpContext? HttpContext { get; set; }
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
var schemes = await SchemeProvider.GetAllSchemesAsync();
|
|
externalProviders = schemes
|
|
.Where(s => !string.IsNullOrEmpty(s.DisplayName)
|
|
&& s.Name != IdentityConstants.ApplicationScheme
|
|
&& s.Name != IdentityConstants.ExternalScheme
|
|
&& s.Name != IdentityConstants.TwoFactorUserIdScheme
|
|
&& s.Name != IdentityConstants.TwoFactorRememberMeScheme)
|
|
.ToList();
|
|
|
|
if (HttpContext is not null)
|
|
{
|
|
var tokens = HttpContext.RequestServices
|
|
.GetRequiredService<Microsoft.AspNetCore.Antiforgery.IAntiforgery>()
|
|
.GetAndStoreTokens(HttpContext);
|
|
antiforgeryToken = tokens.RequestToken ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
private async Task HandleLogin()
|
|
{
|
|
errorMessage = null;
|
|
|
|
var result = await SignInManager.PasswordSignInAsync(
|
|
Model.Email, Model.Password, isPersistent: false, lockoutOnFailure: false);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
Navigation.NavigateTo("/", forceLoad: true);
|
|
}
|
|
else
|
|
{
|
|
errorMessage = "Invalid email or password.";
|
|
}
|
|
}
|
|
|
|
private static MarkupString GetProviderIcon(string provider) => provider switch
|
|
{
|
|
"Google" => new MarkupString("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"><path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.27-4.74 3.27-8.1z\"/><path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/><path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/><path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/></svg>"),
|
|
"Microsoft" => new MarkupString("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 23 23\"><rect fill=\"#f25022\" x=\"1\" y=\"1\" width=\"10\" height=\"10\"/><rect fill=\"#00a4ef\" x=\"1\" y=\"12\" width=\"10\" height=\"10\"/><rect fill=\"#7fba00\" x=\"12\" y=\"1\" width=\"10\" height=\"10\"/><rect fill=\"#ffb900\" x=\"12\" y=\"12\" width=\"10\" height=\"10\"/></svg>"),
|
|
"Facebook" => new MarkupString("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"><path fill=\"#1877F2\" d=\"M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.413c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z\"/></svg>"),
|
|
_ => new MarkupString("")
|
|
};
|
|
|
|
private sealed class LoginModel
|
|
{
|
|
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Email is required")]
|
|
[System.ComponentModel.DataAnnotations.EmailAddress]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Password is required")]
|
|
public string Password { get; set; } = string.Empty;
|
|
}
|
|
}
|