Files
Aaru.Server/Aaru.Server.New/Components/Account/Pages/ExternalLogin.razor

199 lines
7.5 KiB
Plaintext

@page "/Account/ExternalLogin"
@using System.ComponentModel.DataAnnotations
@using System.Security.Claims
@using System.Text
@using System.Text.Encodings.Web
@using Microsoft.AspNetCore.Identity
@using Microsoft.AspNetCore.WebUtilities
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IUserStore<IdentityUser> UserStore
@inject IEmailSender<IdentityUser> EmailSender
@inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager
@inject ILogger<ExternalLogin> Logger
<PageTitle>Register</PageTitle>
<StatusMessage Message="@message"/>
<h1>Register</h1>
<h2>Associate your @ProviderDisplayName account.</h2>
<hr/>
<div class="alert alert-info">
You've successfully authenticated with <strong>@ProviderDisplayName</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</div>
<div class="row">
<div class="col-md-4">
<EditForm FormName="confirmation" method="post" Model="Input" OnValidSubmit="OnValidSubmitAsync">
<DataAnnotationsValidator/>
<ValidationSummary class="text-danger" role="alert"/>
<div class="form-floating mb-3">
<InputText autocomplete="email" @bind-Value="Input.Email" class="form-control" placeholder="Please enter your email."/>
<label class="form-label" for="email">Email</label>
<ValidationMessage For="() => Input.Email"/>
</div>
<button class="btn btn-lg btn-primary w-100" type="submit">Register</button>
</EditForm>
</div>
</div>
@code {
public const string LoginCallbackAction = "LoginCallback";
private string? message;
private ExternalLoginInfo externalLoginInfo = default!;
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm]
private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery]
private string? RemoteError { get; set; }
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
[SupplyParameterFromQuery]
private string? Action { get; set; }
private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName;
protected override async Task OnInitializedAsync()
{
if(RemoteError is not null)
{
RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext);
}
ExternalLoginInfo? info = await SignInManager.GetExternalLoginInfoAsync();
if(info is null)
{
RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext);
}
externalLoginInfo = info;
if(HttpMethods.IsGet(HttpContext.Request.Method))
{
if(Action == LoginCallbackAction)
{
await OnLoginCallbackAsync();
return;
}
// We should only reach this page via the login callback, so redirect back to
// the login page if we get here some other way.
RedirectManager.RedirectTo("Account/Login");
}
}
private async Task OnLoginCallbackAsync()
{
// Sign in the user with this external login provider if the user already has a login.
SignInResult result = await SignInManager.ExternalLoginSignInAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, false, true);
if(result.Succeeded)
{
Logger.LogInformation("{Name} logged in with {LoginProvider} provider.", externalLoginInfo.Principal.Identity?.Name, externalLoginInfo.LoginProvider);
RedirectManager.RedirectTo(ReturnUrl);
}
else if(result.IsLockedOut)
{
RedirectManager.RedirectTo("Account/Lockout");
}
// If the user does not have an account, then ask the user to create an account.
if(externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input.Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? "";
}
}
private async Task OnValidSubmitAsync()
{
IUserEmailStore<IdentityUser> emailStore = GetEmailStore();
IdentityUser user = CreateUser();
await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
IdentityResult result = await UserManager.CreateAsync(user);
if(result.Succeeded)
{
result = await UserManager.AddLoginAsync(user, externalLoginInfo);
if(result.Succeeded)
{
Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider);
string userId = await UserManager.GetUserIdAsync(user);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
string callbackUrl = NavigationManager.GetUriWithQueryParameters(NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
new Dictionary<string, object?>
{
["userId"] = userId,
["code"] = code
});
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
// If account confirmation is required, we need to show the link if we don't have a real email sender
if(UserManager.Options.SignIn.RequireConfirmedAccount)
{
RedirectManager.RedirectTo("Account/RegisterConfirmation",
new Dictionary<string, object?>
{
["email"] = Input.Email
});
}
await SignInManager.SignInAsync(user, false, externalLoginInfo.LoginProvider);
RedirectManager.RedirectTo(ReturnUrl);
}
}
message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}";
}
private IdentityUser CreateUser()
{
try
{
return Activator.CreateInstance<IdentityUser>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor");
}
}
private IUserEmailStore<IdentityUser> GetEmailStore()
{
if(!UserManager.SupportsUserEmail)
{
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<IdentityUser>)UserStore;
}
private sealed class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = "";
}
}