Implemented OIDC

This commit is contained in:
Mia Rose Winter 2024-03-11 15:26:03 +01:00
parent 116dd93da2
commit a9269ece30
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
28 changed files with 1842 additions and 497 deletions

View file

@ -8,7 +8,7 @@ internal sealed class IdentityUserAccessor(
IdentityRedirectManager redirectManager) { IdentityRedirectManager redirectManager) {
public async Task<ApplicationUser> GetRequiredUserAsync(HttpContext context) { public async Task<ApplicationUser> GetRequiredUserAsync(HttpContext context) {
var user = await userManager.GetUserAsync(context.User); var user = await userManager.GetUserAsync(context.User);
if (user is null) { if (user is null) {
redirectManager.RedirectToWithStatus("Account/InvalidUser", redirectManager.RedirectToWithStatus("Account/InvalidUser",
$"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context);

View file

@ -18,7 +18,8 @@
<BoardComponent CenterContent="true"> <BoardComponent CenterContent="true">
<BoardCardComponent Heading="@Localizer["Title"]"> <BoardCardComponent Heading="@Localizer["Title"]">
<StatusMessage Message="@Message" /> <StatusMessage Message="@Message" />
<a class="btn btn-primary w-full mt-3" href="/Account/Login">@Localizer["Login_Label"]</a>
</BoardCardComponent> </BoardCardComponent>
</BoardComponent> </BoardComponent>

View file

@ -4,192 +4,197 @@
@using System.Security.Claims @using System.Security.Claims
@using System.Text @using System.Text
@using System.Text.Encodings.Web @using System.Text.Encodings.Web
@using Humanizer
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Microsoft.AspNetCore.WebUtilities @using Microsoft.AspNetCore.WebUtilities
@using Wave.Data @using Wave.Data
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@inject RoleManager<IdentityRole> RoleManager
@inject IUserStore<ApplicationUser> UserStore @inject IUserStore<ApplicationUser> UserStore
@inject IEmailSender<ApplicationUser> EmailSender @inject IEmailSender<ApplicationUser> EmailSender
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject ILogger<ExternalLogin> Logger @inject ILogger<ExternalLogin> Logger
@inject IStringLocalizer<ExternalLogin> Localizer
<PageTitle>Register</PageTitle> <PageTitle>@Localizer["Title"]</PageTitle>
<StatusMessage Message="@message" /> <StatusMessage Message="@message" />
<h1>Register</h1>
<h2>Associate your @ProviderDisplayName account.</h2>
<hr />
<div class="alert alert-info"> <BoardComponent CenterContent="true">
You've successfully authenticated with <strong>@ProviderDisplayName</strong>. <BoardCardComponent Heading="@Localizer["Title"]">
Please enter an email address for this site below and click the Register button to finish <Alert CanRemove="false" Type="Alert.MessageType.Information">
logging in. <p>
</div> @string.Format(Localizer["Message"], ProviderDisplayName)
</p>
</Alert>
<EditForm Model="Input" OnValidSubmit="OnValidSubmitAsync" FormName="confirmation" method="post">
<DataAnnotationsValidator/>
<InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Input.Email">
<InputText @bind-Value="Input.Email" class="input input-bordered w-full"
autocomplete="email" placeholder="@Localizer["Email_Placeholder"]" />
</InputLabelComponent>
<div class="row"> <button type="submit" class="btn btn-primary w-full">
<div class="col-md-4"> @Localizer["Submit"]
<EditForm Model="Input" OnValidSubmit="OnValidSubmitAsync" FormName="confirmation" method="post"> </button>
<DataAnnotationsValidator /> </EditForm>
<ValidationSummary class="text-danger" role="alert" /> </BoardCardComponent>
<div class="form-floating mb-3"> </BoardComponent>
<InputText @bind-Value="Input.Email" class="form-control" autocomplete="email" placeholder="Please enter your email." />
<label for="email" class="form-label">Email</label>
<ValidationMessage For="() => Input.Email" />
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</EditForm>
</div>
</div>
@code { @code {
public const string LoginCallbackAction = "LoginCallback"; public const string LoginCallbackAction = "LoginCallback";
private string? message; private string? message;
private ExternalLoginInfo externalLoginInfo = default!; private ExternalLoginInfo externalLoginInfo = default!;
[CascadingParameter] [CascadingParameter]
private HttpContext HttpContext { get; set; } = default!; private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm] [SupplyParameterFromForm]
private InputModel Input { get; set; } = new(); private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery] [SupplyParameterFromQuery]
private string? RemoteError { get; set; } private string? RemoteError { get; set; }
[SupplyParameterFromQuery] [SupplyParameterFromQuery]
private string? ReturnUrl { get; set; } private string? ReturnUrl { get; set; }
[SupplyParameterFromQuery] [SupplyParameterFromQuery]
private string? Action { get; set; } private string? Action { get; set; }
private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName; private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync() {
{ if (RemoteError is not null) {
if (RemoteError is not null) RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext);
{ }
RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext);
}
var info = await SignInManager.GetExternalLoginInfoAsync(); var info = await SignInManager.GetExternalLoginInfoAsync();
if (info is null) if (info is null) {
{ RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext);
RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); }
}
externalLoginInfo = info; externalLoginInfo = info;
if (HttpMethods.IsGet(HttpContext.Request.Method)) if (HttpMethods.IsGet(HttpContext.Request.Method)) {
{ if (Action == LoginCallbackAction) {
if (Action == LoginCallbackAction) await OnLoginCallbackAsync();
{ return;
await OnLoginCallbackAsync(); }
return;
}
// We should only reach this page via the login callback, so redirect back to // We should only reach this page via the login callback, so redirect back to
// the login page if we get here some other way. // the login page if we get here some other way.
RedirectManager.RedirectTo("Account/Login"); RedirectManager.RedirectTo("Account/Login");
} }
} }
private async Task OnLoginCallbackAsync() private async Task OnLoginCallbackAsync() {
{ // Sign in the user with this external login provider if the user already has a login.
// Sign in the user with this external login provider if the user already has a login. var result = await SignInManager.ExternalLoginSignInAsync(
var result = await SignInManager.ExternalLoginSignInAsync( externalLoginInfo.LoginProvider,
externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey,
externalLoginInfo.ProviderKey, isPersistent: false,
isPersistent: false, bypassTwoFactor: true);
bypassTwoFactor: true);
if (result.Succeeded) if (result.Succeeded) {
{ Logger.LogInformation(
Logger.LogInformation( "{Name} logged in with {LoginProvider} provider.",
"{Name} logged in with {LoginProvider} provider.", externalLoginInfo.Principal.Identity?.Name,
externalLoginInfo.Principal.Identity?.Name, externalLoginInfo.LoginProvider);
externalLoginInfo.LoginProvider); RedirectManager.RedirectTo(ReturnUrl);
RedirectManager.RedirectTo(ReturnUrl); } else if (result.IsLockedOut) {
} RedirectManager.RedirectTo("Account/Lockout");
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 the user does not have an account, then ask the user to create an account.
if (externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) if (externalLoginInfo.Principal.Claims.FirstOrDefault(c => c.Type is ClaimTypes.Email or "email") is {} claim) {
{ Input.Email = claim.Value;
Input.Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? ""; }
} }
}
private async Task OnValidSubmitAsync() private async Task OnValidSubmitAsync() {
{ var emailStore = GetEmailStore();
var emailStore = GetEmailStore(); var user = CreateUser();
var user = CreateUser();
await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await UserManager.CreateAsync(user); var info = await SignInManager.GetExternalLoginInfoAsync();
if (result.Succeeded) if (info?.Principal.Claims.FirstOrDefault(c => c.Type is ClaimTypes.Name or ClaimTypes.GivenName or "name") is {} nameClaim) {
{ user.FullName = nameClaim.Value;
result = await UserManager.AddLoginAsync(user, externalLoginInfo); }
if (result.Succeeded)
{
Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider);
var userId = await UserManager.GetUserIdAsync(user); var result = await UserManager.CreateAsync(user);
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); if (result.Succeeded) {
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); result = await UserManager.AddLoginAsync(user, externalLoginInfo);
if (result.Succeeded) {
Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider);
var callbackUrl = NavigationManager.GetUriWithQueryParameters( string userId = await UserManager.GetUserIdAsync(user);
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, string code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code }); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(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 string callbackUrl = NavigationManager.GetUriWithQueryParameters(
if (UserManager.Options.SignIn.RequireConfirmedAccount) NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
{ new Dictionary<string, object?> {["userId"] = userId, ["code"] = code});
RedirectManager.RedirectTo("Account/RegisterConfirmation", new() { ["email"] = Input.Email }); await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
}
await SignInManager.SignInAsync(user, isPersistent: false, externalLoginInfo.LoginProvider); if (info?.Principal.Claims.FirstOrDefault(c => c.Type is ClaimTypes.Role or "roles") is { } roleClaim) {
RedirectManager.RedirectTo(ReturnUrl); var roles = roleClaim.Value.Split(",").Select(s => s.Trim());
}
}
message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}"; foreach (string role in roles) {
} try {
string r = role.Titleize();
if (r is "Author" or "Reviewer" or "Moderator" or "Admin" &&
!await RoleManager.RoleExistsAsync(r) &&
!(await RoleManager.CreateAsync(new IdentityRole(r))).Succeeded) {
Logger.LogError("Failed to create role {role}. Failed to assign it to user {name}.", r, user.UserName);
continue;
}
private ApplicationUser CreateUser() await UserManager.AddToRoleAsync(user, r);
{ } catch (Exception ex) {
try Logger.LogWarning(ex, "Failed to add newly created user {name} to role {role}.", user.UserName, role);
{ }
return Activator.CreateInstance<ApplicationUser>(); }
} }
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
$"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor");
}
}
private IUserEmailStore<ApplicationUser> GetEmailStore() // 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) {
if (!UserManager.SupportsUserEmail) RedirectManager.RedirectTo("Account/RegisterConfirmation", new() { ["email"] = Input.Email });
{ }
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<ApplicationUser>)UserStore;
}
private sealed class InputModel await SignInManager.SignInAsync(user, isPersistent: false, externalLoginInfo.LoginProvider);
{ RedirectManager.RedirectTo(ReturnUrl);
[Required] }
[EmailAddress] }
public string Email { get; set; } = "";
} message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}";
} }
private ApplicationUser CreateUser() {
try {
return Activator.CreateInstance<ApplicationUser>();
} catch {
throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
$"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor");
}
}
private IUserEmailStore<ApplicationUser> GetEmailStore() {
if (!UserManager.SupportsUserEmail) {
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<ApplicationUser>) UserStore;
}
private sealed class InputModel {
[Required] [EmailAddress]
public string Email { get; set; } = "";
}
}

View file

@ -20,54 +20,49 @@
<StatusMessage Message="@_errorMessage" /> <StatusMessage Message="@_errorMessage" />
<BoardComponent CenterContent="true"> <BoardComponent CenterContent="true">
<BoardCardComponent Heading="@Localizer["Title"]"> <BoardCardComponent Heading="@Localizer["Title"]">
<EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login" class="w-full"> <EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login" class="w-full">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Input.Email"> <InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Input.Email">
<InputText @bind-Value="Input.Email" class="input input-bordered w-full" autocomplete="username" <InputText @bind-Value="Input.Email" class="input input-bordered w-full" autocomplete="username"
required aria-required="true" placeholder="@Localizer["Email_Placeholder"]" /> required aria-required="true" placeholder="@Localizer["Email_Placeholder"]" />
</InputLabelComponent> </InputLabelComponent>
<InputLabelComponent LabelText="@Localizer["Password_Label"]" For="() => Input.Password"> <InputLabelComponent LabelText="@Localizer["Password_Label"]" For="() => Input.Password">
<InputText @bind-Value="Input.Password" class="input input-bordered w-full" autocomplete="current-password" type="password" <InputText @bind-Value="Input.Password" class="input input-bordered w-full" autocomplete="current-password" type="password"
required aria-required="true" placeholder="@Localizer["Password_Placeholder"]" /> required aria-required="true" placeholder="@Localizer["Password_Placeholder"]" />
</InputLabelComponent> </InputLabelComponent>
<div class="form-control"> <div class="form-control">
<label class="label cursor-pointer"> <label class="label cursor-pointer">
<span class="label-text">@Localizer["RememberMe_Label"]</span> <span class="label-text">@Localizer["RememberMe_Label"]</span>
<InputCheckbox @bind-Value="Input.RememberMe" class="checkbox" /> <InputCheckbox @bind-Value="Input.RememberMe" class="checkbox" />
</label> </label>
</div> </div>
<button type="submit" class="btn btn-primary w-full"> <button type="submit" class="btn btn-primary w-full">
@Localizer["Submit"] @Localizer["Submit"]
</button> </button>
</EditForm> </EditForm>
<ul class="mt-3 flex flex-col gap-1 text-center"> <ul class="mt-3 flex flex-col gap-1 text-center">
<li> <li>
<a class="hover:link" href="Account/ForgotPassword"> <a class="hover:link" href="Account/ForgotPassword">
@Localizer["ResetPassword_Label"] @Localizer["ResetPassword_Label"]
</a> </a>
</li> </li>
<li> <li>
<a class="hover:link" href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))"> <a class="hover:link" href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">
@Localizer["Register_Label"] @Localizer["Register_Label"]
</a> </a>
</li> </li>
<li> <li>
<a class="hover:link" href="Account/ResendEmailConfirmation"> <a class="hover:link" href="Account/ResendEmailConfirmation">
@Localizer["ResendMailConfirmation_Label"] @Localizer["ResendMailConfirmation_Label"]
</a> </a>
</li> </li>
</ul> </ul>
</BoardCardComponent> </BoardCardComponent>
<!--
<section class="w-80 max-w-xs"> <ExternalLoginPicker />
<h2 class="text-2xl lg:text-4xl mb-3">Use another service to log in.</h2>
<hr />
<ExternalLoginPicker />
</section>
-->
</BoardComponent> </BoardComponent>
@code { @code {

View file

@ -3,6 +3,7 @@
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Wave.Data @using Wave.Data
@using Wave.Utilities
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@ -10,66 +11,69 @@
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject ILogger<DeletePersonalData> Logger @inject ILogger<DeletePersonalData> Logger
@inject IStringLocalizer<DeletePersonalData> Localizer @inject IStringLocalizer<DeletePersonalData> Localizer
@inject IMessageDisplay Message
<PageTitle>@Localizer["Title"]</PageTitle> <PageTitle>@Localizer["Title"]</PageTitle>
<StatusMessage Message="@message" />
<BoardComponent> <BoardComponent>
<BoardCardComponent Heading="@Localizer["Title"]"> <BoardCardComponent Heading="@Localizer["Title"]">
<Alert Type="Alert.MessageType.Warning" CanRemove="false"> <Alert Type="Alert.MessageType.Warning" CanRemove="false">
<strong class="font-bold">@Localizer["Delete_FinalWarning"]</strong> <strong class="font-bold">@Localizer["Delete_FinalWarning"]</strong>
</Alert> </Alert>
<EditForm Model="Input" FormName="delete-user" OnValidSubmit="OnValidSubmitAsync" method="post" class="w-full"> <EditForm Model="Input" FormName="delete-user" OnValidSubmit="OnValidSubmitAsync" method="post" class="w-full mt-3">
<DataAnnotationsValidator /> <DataAnnotationsValidator/>
<ValidationSummary class="text-error" role="alert" /> @if (RequirePassword) {
@if (requirePassword) { <InputLabelComponent LabelText="@Localizer["Delete_Password_Label"]" For="() => Input.Password">
<InputLabelComponent LabelText="@Localizer["Delete_Password_Label"]" For="() => Input.Password"> <InputText type="password" @bind-Value="Input.Password" class="input input-bordered w-full" required
<InputText type="password" @bind-Value="Input.Password" class="input input-bordered w-full" required autocomplete="current-password" placeholder="@Localizer["Delete_Password_Placeholder"]"/>
autocomplete="current-password" placeholder="@Localizer["Delete_Password_Placeholder"]" /> </InputLabelComponent>
</InputLabelComponent> } else {
} <!-- our model binding fails without zero bound properties -->
<button class="btn btn-lg btn-error w-full" type="submit">@Localizer["Delete_FinalSubmit"]</button> <InputText type="hidden" @bind-Value="Input.Password" />
</EditForm> }
</BoardCardComponent> <button class="btn btn-lg btn-error w-full" type="submit">@Localizer["Delete_FinalSubmit"]</button>
</EditForm>
</BoardCardComponent>
</BoardComponent> </BoardComponent>
@code { @code {
private string? message; private ApplicationUser User { get; set; } = default!;
private ApplicationUser user = default!; private bool RequirePassword { get; set; }
private bool requirePassword;
[CascadingParameter] [CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm]
private InputModel Input { get; set; } = new();
protected override async Task OnInitializedAsync() { [SupplyParameterFromForm(FormName = "delete-user")]
user = await UserAccessor.GetRequiredUserAsync(HttpContext); private InputModel Input { get; set; } = new();
requirePassword = await UserManager.HasPasswordAsync(user);
}
private async Task OnValidSubmitAsync() { protected override async Task OnInitializedAsync() {
if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) { User = await UserAccessor.GetRequiredUserAsync(HttpContext);
message = Localizer["Delete_ErrorWrongPassword"]; RequirePassword = await UserManager.HasPasswordAsync(User);
return; }
}
var result = await UserManager.DeleteAsync(user); private async Task OnValidSubmitAsync() {
if (!result.Succeeded) { if (RequirePassword && !await UserManager.CheckPasswordAsync(User, Input.Password)) {
throw new InvalidOperationException(Localizer["Delete_ErrorUnknown"]); Message.ShowError(Localizer["Delete_ErrorWrongPassword"]);
} return;
}
await SignInManager.SignOutAsync(); var result = await UserManager.DeleteAsync(User);
if (!result.Succeeded) {
throw new InvalidOperationException(Localizer["Delete_ErrorUnknown"]);
}
string userId = await UserManager.GetUserIdAsync(user); await SignInManager.SignOutAsync();
Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
RedirectManager.RedirectToWithStatus("/", Localizer["Delete_Success"], HttpContext); string userId = await UserManager.GetUserIdAsync(User);
} Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
private sealed class InputModel { Message.ShowSuccess(Localizer["Delete_Success"]);
[DataType(DataType.Password)] public string Password { get; set; } = ""; RedirectManager.RedirectTo("/");
} }
}
private sealed class InputModel {
[DataType(DataType.Password)]
public string Password { get; set; } = "";
}
}

View file

@ -9,132 +9,129 @@
@inject IdentityUserAccessor UserAccessor @inject IdentityUserAccessor UserAccessor
@inject IUserStore<ApplicationUser> UserStore @inject IUserStore<ApplicationUser> UserStore
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject IStringLocalizer<ExternalLogin> Localizer
<PageTitle>Manage your external logins</PageTitle> <PageTitle>@Localizer["Title"]</PageTitle>
<StatusMessage /> <StatusMessage/>
@if (currentLogins?.Count > 0)
{ @if (CurrentLogins?.Count > 0) {
<h3>Registered Logins</h3> <BoardComponent>
<table class="table"> <BoardCardComponent Heading="@Localizer["Title"]">
<tbody> <div class="overflow-x-auto">
@foreach (var login in currentLogins) <table class="table">
{ <thead>
<tr> <tr>
<td>@login.ProviderDisplayName</td> <th>Provider</th>
<td> <th></th>
@if (showRemoveButton) </tr>
{ </thead>
<form @formname="@($"remove-login-{login.LoginProvider}")" @onsubmit="OnSubmitAsync" method="post"> <tbody>
<AntiforgeryToken /> @foreach (var login in CurrentLogins) {
<div> <tr>
<input type="hidden" name="@nameof(LoginProvider)" value="@login.LoginProvider" /> <td>@login.ProviderDisplayName</td>
<input type="hidden" name="@nameof(ProviderKey)" value="@login.ProviderKey" /> <td>
<button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button> @if (ShowRemoveButton) {
</div> <form @formname="@($"remove-login-{login.LoginProvider}")" @onsubmit="OnSubmitAsync" method="post">
</form> <AntiforgeryToken/>
} <div>
else <input type="hidden" name="@nameof(LoginProvider)" value="@login.LoginProvider"/>
{ <input type="hidden" name="@nameof(ProviderKey)" value="@login.ProviderKey"/>
@: &nbsp; <button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
} </div>
</td> </form>
</tr> } else {
} @: &nbsp;
</tbody> }
</table> </td>
} </tr>
@if (otherLogins?.Count > 0) }
{ </tbody>
<h4>Add another service to log in.</h4> </table>
<hr /> </div>
<form class="form-horizontal" action="Account/Manage/LinkExternalLogin" method="post"> </BoardCardComponent>
<AntiforgeryToken /> @if (OtherLogins?.Count > 0) {
<div> <BoardCardComponent Heading="Add another service to log in.">
<p> <form class="form-horizontal" action="Account/Manage/LinkExternalLogin" method="post">
@foreach (var provider in otherLogins) <AntiforgeryToken/>
{ <div>
<button type="submit" class="btn btn-primary" name="Provider" value="@provider.Name" title="Log in using your @provider.DisplayName account"> <p>
@provider.DisplayName @foreach (var provider in OtherLogins) {
</button> <button type="submit" class="btn btn-primary" name="Provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">
} @provider.DisplayName
</p> </button>
</div> }
</form> </p>
</div>
</form>
</BoardCardComponent>
}
</BoardComponent>
} }
@code { @code {
public const string LinkLoginCallbackAction = "LinkLoginCallback"; public const string LinkLoginCallbackAction = "LinkLoginCallback";
private ApplicationUser user = default!; private ApplicationUser User { get; set; } = default!;
private IList<UserLoginInfo>? currentLogins; private IList<UserLoginInfo>? CurrentLogins { get; set; }
private IList<AuthenticationScheme>? otherLogins; private IList<AuthenticationScheme>? OtherLogins { get; set; }
private bool showRemoveButton; private bool ShowRemoveButton { get; set; }
[CascadingParameter] [CascadingParameter]
private HttpContext HttpContext { get; set; } = default!; private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm] [SupplyParameterFromForm]
private string? LoginProvider { get; set; } private string? LoginProvider { get; set; }
[SupplyParameterFromForm]
[SupplyParameterFromForm]
private string? ProviderKey { get; set; } private string? ProviderKey { get; set; }
[SupplyParameterFromQuery]
[SupplyParameterFromQuery]
private string? Action { get; set; } private string? Action { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync() {
{ User = await UserAccessor.GetRequiredUserAsync(HttpContext);
user = await UserAccessor.GetRequiredUserAsync(HttpContext); CurrentLogins = await UserManager.GetLoginsAsync(User);
currentLogins = await UserManager.GetLoginsAsync(user); OtherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync())
otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()) .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.Where(auth => currentLogins.All(ul => auth.Name != ul.LoginProvider)) .ToList();
.ToList();
string? passwordHash = null; string? passwordHash = null;
if (UserStore is IUserPasswordStore<ApplicationUser> userPasswordStore) if (UserStore is IUserPasswordStore<ApplicationUser> userPasswordStore) {
{ passwordHash = await userPasswordStore.GetPasswordHashAsync(User, HttpContext.RequestAborted);
passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); }
}
showRemoveButton = passwordHash is not null || currentLogins.Count > 1; ShowRemoveButton = passwordHash is not null || CurrentLogins.Count > 1;
if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction) if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction) {
{ await OnGetLinkLoginCallbackAsync();
await OnGetLinkLoginCallbackAsync(); }
} }
}
private async Task OnSubmitAsync() private async Task OnSubmitAsync() {
{ var result = await UserManager.RemoveLoginAsync(User, LoginProvider!, ProviderKey!);
var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); if (!result.Succeeded) {
if (!result.Succeeded) RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not removed.", HttpContext);
{ }
RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not removed.", HttpContext);
}
await SignInManager.RefreshSignInAsync(user); await SignInManager.RefreshSignInAsync(User);
RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.", HttpContext); RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.", HttpContext);
} }
private async Task OnGetLinkLoginCallbackAsync() private async Task OnGetLinkLoginCallbackAsync() {
{ var userId = await UserManager.GetUserIdAsync(User);
var userId = await UserManager.GetUserIdAsync(user); var info = await SignInManager.GetExternalLoginInfoAsync(userId);
var info = await SignInManager.GetExternalLoginInfoAsync(userId); if (info is null) {
if (info is null) RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext);
{ }
RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext);
}
var result = await UserManager.AddLoginAsync(user, info); var result = await UserManager.AddLoginAsync(User, info);
if (!result.Succeeded) if (!result.Succeeded) {
{ RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not added. External logins can only be associated with one account.", HttpContext);
RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not added. External logins can only be associated with one account.", HttpContext); }
}
// Clear the existing external cookie to ensure a clean login process // Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.", HttpContext); RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.", HttpContext);
} }
}
}

View file

@ -8,80 +8,72 @@
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject IdentityUserAccessor UserAccessor @inject IdentityUserAccessor UserAccessor
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject IStringLocalizer<SetPassword> Localizer
<PageTitle>Set password</PageTitle> <PageTitle>@Localizer["Title"]</PageTitle>
<h3>Set your password</h3> <StatusMessage Message="@Message"/>
<StatusMessage Message="@message" /> <BoardComponent>
<p class="text-info"> <BoardCardComponent Heading="@Localizer["Title"]">
You do not have a local username/password for this site. Add a local <Alert CanRemove="false" Type="Alert.MessageType.Information">
account so you can log in without an external login. <p>@Localizer["Message"]</p>
</p> </Alert>
<div class="row"> <EditForm Model="Input" FormName="set-password" OnValidSubmit="OnValidSubmitAsync" method="post">
<div class="col-md-6"> <DataAnnotationsValidator/>
<EditForm Model="Input" FormName="set-password" OnValidSubmit="OnValidSubmitAsync" method="post"> <InputLabelComponent LabelText="@Localizer["NewPassword_Label"]" For="() => Input.NewPassword">
<DataAnnotationsValidator /> <InputText type="password" @bind-Value="Input.NewPassword" class="input input-bordered w-full" autocomplete="new-password"
<ValidationSummary class="text-danger" role="alert" /> required aria-required="true" placeholder="@Localizer["NewPassword_Placeholder"]"/>
<div class="form-floating mb-3"> </InputLabelComponent>
<InputText type="password" @bind-Value="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password." /> <InputLabelComponent LabelText="@Localizer["ConfirmPassword_Label"]" For="() => Input.ConfirmPassword">
<label for="new-password" class="form-label">New password</label> <InputText type="password" @bind-Value="Input.ConfirmPassword" class="input input-bordered w-full" autocomplete="new-password"
<ValidationMessage For="() => Input.NewPassword" class="text-danger" /> required aria-required="true" placeholder="@Localizer["ConfirmPassword_Placeholder"]"/>
</div> </InputLabelComponent>
<div class="form-floating mb-3"> <button type="submit" class="btn btn-primary w-full">
<InputText type="password" @bind-Value="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password." /> @Localizer["ChangePassword_Submit"]
<label for="confirm-password" class="form-label">Confirm password</label> </button>
<ValidationMessage For="() => Input.ConfirmPassword" class="text-danger" /> </EditForm>
</div> </BoardCardComponent>
<button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button> </BoardComponent>
</EditForm>
</div>
</div>
@code { @code {
private string? message; private string? Message { get; set; }
private ApplicationUser user = default!; private ApplicationUser User { get; set; } = default!;
[CascadingParameter] [CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm] [SupplyParameterFromForm] private InputModel Input { get; set; } = new();
private InputModel Input { get; set; } = new();
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync() {
{ User = await UserAccessor.GetRequiredUserAsync(HttpContext);
user = await UserAccessor.GetRequiredUserAsync(HttpContext);
var hasPassword = await UserManager.HasPasswordAsync(user); bool hasPassword = await UserManager.HasPasswordAsync(User);
if (hasPassword) if (hasPassword) {
{ RedirectManager.RedirectTo("Account/Manage/ChangePassword");
RedirectManager.RedirectTo("Account/Manage/ChangePassword"); }
} }
}
private async Task OnValidSubmitAsync() private async Task OnValidSubmitAsync() {
{ var addPasswordResult = await UserManager.AddPasswordAsync(User, Input.NewPassword!);
var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); if (!addPasswordResult.Succeeded) {
if (!addPasswordResult.Succeeded) Message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}";
{ return;
message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}"; }
return;
}
await SignInManager.RefreshSignInAsync(user); await SignInManager.RefreshSignInAsync(User);
RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext); RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext);
} }
private sealed class InputModel private sealed class InputModel {
{ [Required]
[Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)]
[DataType(DataType.Password)] [Display(Name = "New password")]
[Display(Name = "New password")] public string? NewPassword { get; set; }
public string? NewPassword { get; set; }
[DataType(DataType.Password)] [DataType(DataType.Password)]
[Display(Name = "Confirm new password")] [Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string? ConfirmPassword { get; set; } public string? ConfirmPassword { get; set; }
} }
}
}

View file

@ -9,64 +9,59 @@
@inject IEmailSender<ApplicationUser> EmailSender @inject IEmailSender<ApplicationUser> EmailSender
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject IStringLocalizer<RegisterConfirmation> Localizer
<HeadContent> <HeadContent>
<meta name="robots" content="noindex,nofollow"> <meta name="robots" content="noindex,nofollow">
</HeadContent> </HeadContent>
<PageTitle>Register confirmation</PageTitle> <PageTitle>@Localizer["Title"] </PageTitle>
<h1>Register confirmation</h1> <StatusMessage Message="@statusMessage"/>
<StatusMessage Message="@statusMessage" /> <BoardComponent CenterContent="true">
<BoardCardComponent Heading="@Localizer["Title"]">
@if (emailConfirmationLink is not null) <Alert CanRemove="false" Type="Alert.MessageType.Success">
{ @if (emailConfirmationLink is not null) {
<p> <p>
This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender. This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
Normally this would be emailed: <a href="@emailConfirmationLink">Click here to confirm your account</a> Normally this would be emailed: <a href="@emailConfirmationLink">Click here to confirm your account</a>
</p> </p>
} } else {
else <p>@Localizer["Message"]</p>
{ }
<p>Please check your email to confirm your account.</p> </Alert>
} </BoardCardComponent>
</BoardComponent>
@code { @code {
private string? emailConfirmationLink; private string? emailConfirmationLink;
private string? statusMessage; private string? statusMessage;
[CascadingParameter] [CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromQuery] [SupplyParameterFromQuery] private string? Email { get; set; }
private string? Email { get; set; }
[SupplyParameterFromQuery] [SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
private string? ReturnUrl { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync() {
{ if (Email is null) {
if (Email is null) RedirectManager.RedirectTo("");
{ }
RedirectManager.RedirectTo("");
}
var user = await UserManager.FindByEmailAsync(Email); var user = await UserManager.FindByEmailAsync(Email);
if (user is null) if (user is null) {
{ HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; statusMessage = "Error finding user for unspecified email";
statusMessage = "Error finding user for unspecified email"; } else if (EmailSender is IdentityNoOpEmailSender) {
} // Once you add a real email sender, you should remove this code that lets you confirm the account
else if (EmailSender is IdentityNoOpEmailSender) var userId = await UserManager.GetUserIdAsync(user);
{ var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
// Once you add a real email sender, you should remove this code that lets you confirm the account code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var userId = await UserManager.GetUserIdAsync(user); emailConfirmationLink = NavigationManager.GetUriWithQueryParameters(
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); new Dictionary<string, object?> {["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl});
emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( }
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, }
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
} }
}
}

View file

@ -4,40 +4,42 @@
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@inject IStringLocalizer<ExternalLoginPicker> Localizer
@if (externalLogins.Length == 0) @if (externalLogins.Length > 0) {
{ <BoardCardComponent Heading="@Localizer["Title"]">
<div> <form class="form-horizontal" action="Account/PerformExternalLogin" method="post">
<p> <div class="flex flex-col space-y-3">
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article <AntiforgeryToken/>
about setting up this ASP.NET application to support logging in via external services</a>. <input type="hidden" name="ReturnUrl" value="@ReturnUrl"/>
</p> <p>
</div> @foreach (var provider in externalLogins) {
} if (provider.Name is "OpenIdConnect") {
else <button type="submit" class="btn btn-wide btn-primary" name="provider" value="@provider.Name" title="@Localizer["OpenId_Tooltip"]">
{ @Localizer["OpenId_Label"]
<form class="form-horizontal" action="Account/PerformExternalLogin" method="post"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<div> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9" />
<AntiforgeryToken /> </svg>
<input type="hidden" name="ReturnUrl" value="@ReturnUrl" /> </button>
<p> } else {
@foreach (var provider in externalLogins) <button type="submit" class="btn btn-wide btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">
{ @provider.DisplayName
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> </button>
} }
</p> }
</div> </p>
</form> </div>
</form>
</BoardCardComponent>
} }
@code { @code {
private AuthenticationScheme[] externalLogins = []; private AuthenticationScheme[] externalLogins = [];
[SupplyParameterFromQuery] [SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
private string? ReturnUrl { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync() {
{ externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray();
externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); }
}
} }

View file

@ -0,0 +1,7 @@
namespace Wave.Data;
public class OidcConfiguration {
public string Authority { get; set; } = string.Empty;
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty;
}

View file

@ -11,7 +11,7 @@ public class UserClaimsFactory(
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>(userManager, roleManager, options) { : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>(userManager, roleManager, options) {
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user) { protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user) {
var principal = await base.GenerateClaimsAsync(user); var principal = await base.GenerateClaimsAsync(user);
principal.AddClaim(new Claim("Id", user.Id)); // principal.AddClaim(new Claim("Id", user.Id));
principal.AddClaim(new Claim("FullName", user.Name)); principal.AddClaim(new Claim("FullName", user.Name));
return principal; return principal;
} }

View file

@ -5,7 +5,6 @@
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
@ -19,6 +18,7 @@
using Wave.Data; using Wave.Data;
using Wave.Services; using Wave.Services;
using Wave.Utilities; using Wave.Utilities;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
string humanReadableVersion = Assembly.GetEntryAssembly()? string humanReadableVersion = Assembly.GetEntryAssembly()?
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()? .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
@ -94,7 +94,7 @@
.AddPolicy("ArticleEditOrReviewPermissions", p => p.RequireRole("Author", "Reviewer", "Admin")) .AddPolicy("ArticleEditOrReviewPermissions", p => p.RequireRole("Author", "Reviewer", "Admin"))
.AddPolicy("EmailApi", p => p.RequireClaim("EmailApi") .AddPolicy("EmailApi", p => p.RequireClaim("EmailApi")
.AddAuthenticationSchemes(ApiKeyDefaults.AuthenticationScheme)); .AddAuthenticationSchemes(ApiKeyDefaults.AuthenticationScheme));
builder.Services.AddAuthentication(options => { builder.Services.AddAuthentication(options => {
options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
@ -103,6 +103,30 @@
options.Realm = "Wave API"; options.Realm = "Wave API";
}) })
.AddIdentityCookies(); .AddIdentityCookies();
if (builder.Configuration.GetSection("Oidc").Get<OidcConfiguration>() is {} oidc && !string.IsNullOrWhiteSpace(oidc.Authority)) {
builder.Services.AddAuthentication(options => {
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
}).AddOpenIdConnect(options => {
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.Authority = oidc.Authority;
options.ClientId = oidc.ClientId;
options.ClientSecret = oidc.ClientSecret;
options.ResponseType = OpenIdConnectResponseType.Code;
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
options.CallbackPath = new PathString("/signin-oidc");
options.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
options.RemoteSignOutPath = new PathString("/signout-oidc");
});
}
#endregion #endregion
@ -114,7 +138,10 @@
options.UseNpgsql(connectionString)); options.UseNpgsql(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) builder.Services.AddIdentityCore<ApplicationUser>(options => {
options.SignIn.RequireConfirmedAccount = true;
options.ClaimsIdentity.UserIdClaimType = "Id";
})
.AddRoles<IdentityRole>() .AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>() .AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager() .AddSignInManager()

View file

@ -110,4 +110,7 @@
<data name="Success" xml:space="preserve"> <data name="Success" xml:space="preserve">
<value>Vielen Dank fürs Bestätigen ihrer E-Mail Adresse.</value> <value>Vielen Dank fürs Bestätigen ihrer E-Mail Adresse.</value>
</data> </data>
<data name="Login_Label" xml:space="preserve">
<value>Anmelden</value>
</data>
</root> </root>

View file

@ -110,4 +110,7 @@
<data name="Success" xml:space="preserve"> <data name="Success" xml:space="preserve">
<value>Thank you for confirming your email.</value> <value>Thank you for confirming your email.</value>
</data> </data>
<data name="Login_Label" xml:space="preserve">
<value>Log In</value>
</data>
</root> </root>

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Message" xml:space="preserve">
<value>Sie haben sich erfolgreich mit {0} angemeldet.
Bitte geben Sie nun eine E-Mail Addresse für diese Webseite ein.</value>
</data>
<data name="Email_Label" xml:space="preserve">
<value>E-Mail</value>
</data>
<data name="Email_Placeholder" xml:space="preserve">
<value>name@example.de</value>
</data>
<data name="Submit" xml:space="preserve">
<value>Registrieren</value>
</data>
<data name="Title" xml:space="preserve">
<value>Benutzerkonto Erstellen</value>
</data>
</root>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Create Account</value>
</data>
<data name="Message" xml:space="preserve">
<value>You've successfully authenticated with {0}.
Please enter an email address for this site below.</value>
</data>
<data name="Email_Label" xml:space="preserve">
<value>Email</value>
</data>
<data name="Email_Placeholder" xml:space="preserve">
<value>name@example.com</value>
</data>
<data name="Submit" xml:space="preserve">
<value>Register</value>
</data>
</root>

View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Passwort Erstellen</value>
</data>
<data name="Message" xml:space="preserve">
<value>Sie haben momentan kein lokales Benutzerkonto/Passwort für diese Seite. Fügen Sie ein lokales Konto hinzu um sich ohne externen Dienst anmelden zu können.</value>
</data>
<data name="ConfirmPassword_Label" xml:space="preserve">
<value>Neues Passwort wiederholen</value>
</data>
<data name="ConfirmPassword_Placeholder" xml:space="preserve">
<value>Bestätigen Sie ihr neues Passwort</value>
</data>
<data name="NewPassword_Label" xml:space="preserve">
<value>Neues Passwort</value>
</data>
<data name="NewPassword_Placeholder" xml:space="preserve">
<value>Geben Sie ihr neues Passwort ein</value>
</data>
<data name="ChangePassword_Submit" xml:space="preserve">
<value>Passwort Ändern</value>
</data>
</root>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Create Password</value>
</data>
<data name="Message" xml:space="preserve">
<value>You do not have a local username/password for this site. Add a local account, so you can log in without an external login.</value>
</data>
<data name="NewPassword_Label" xml:space="preserve">
<value>New password</value>
</data>
<data name="NewPassword_Placeholder" xml:space="preserve">
<value>Enter your new password</value>
</data>
<data name="ConfirmPassword_Label" xml:space="preserve">
<value>Confirm new password</value>
</data>
<data name="ConfirmPassword_Placeholder" xml:space="preserve">
<value>Confirm your new Password</value>
</data>
<data name="ChangePassword_Submit" xml:space="preserve">
<value>Change Password</value>
</data>
</root>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Registrierung Bestätigen</value>
</data>
<data name="Message" xml:space="preserve">
<value>Bitte öffnen Sie Ihre E-Mails und bestätigen Sie ihr Benutzerkonto. Sie können sich nicht Anmelden bevor Ihr Konto bestätigt wurde.</value>
</data>
</root>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Confirm Registration</value>
</data>
<data name="Message" xml:space="preserve">
<value>Please check your email to confirm your account. You cannot log in before your account has been confirmed.</value>
</data>
</root>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Anmelden über externen Dienst</value>
</data>
<data name="OpenId_Tooltip" xml:space="preserve">
<value>Melden Sie sich über ihren OpenID Connect Dienst an</value>
</data>
</root>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Title" xml:space="preserve">
<value>Log In using external service</value>
</data>
<data name="OpenId_Label" xml:space="preserve">
<value>OpenID Connect</value>
</data>
<data name="OpenId_Tooltip" xml:space="preserve">
<value>Log In using OpenID Connect provider</value>
</data>
</root>

View file

@ -17,6 +17,7 @@
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" /> <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" />
<PackageReference Include="MailKit" Version="4.3.0" /> <PackageReference Include="MailKit" Version="4.3.0" />
<PackageReference Include="Markdig" Version="0.34.0" /> <PackageReference Include="Markdig" Version="0.34.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />

File diff suppressed because one or more lines are too long