Map external user auths to users via custom fields
This commit is contained in:
parent
98c0d276b0
commit
bf9ca2fc2d
13 changed files with 404 additions and 50 deletions
|
|
@ -80,7 +80,10 @@ namespace LANCommander.Server.Services.Models
|
|||
public class AuthenticationProvider
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public AuthenticationProviderType Type { get; set; } = AuthenticationProviderType.OAuth2;
|
||||
public string Color { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public string Documentation { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
|
|
@ -90,18 +93,6 @@ namespace LANCommander.Server.Services.Models
|
|||
public string UserInfoEndpoint { get; set; }
|
||||
public IEnumerable<string> Scopes { get; set; } = new List<string>();
|
||||
public IEnumerable<ClaimMapping> ClaimMappings { get; set; } = new List<ClaimMapping>();
|
||||
|
||||
public string GetSlug()
|
||||
{
|
||||
var slug = Name.ToLower();
|
||||
|
||||
slug = Regex.Replace(slug, @"[^a-z0-9\s-]", "");
|
||||
slug = Regex.Replace(slug, @"\s+", " ").Trim();
|
||||
slug = slug.Substring(0, slug.Length <= 45 ? slug.Length : 45).Trim();
|
||||
slug = Regex.Replace(slug, @"\s", "-");
|
||||
|
||||
return slug;
|
||||
}
|
||||
}
|
||||
|
||||
public class ClaimMapping
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
@using LANCommander.Server.Models
|
||||
@using LANCommander.Server.Services
|
||||
@using LANCommander.Server.Services.Models
|
||||
@model LoginModel
|
||||
@model LANCommander.Server.Areas.Identity.Pages.Account.LoginModel
|
||||
@{ Layout = "/UI/Views/Shared/_LayoutBasic.cshtml"; }
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Log in";
|
||||
|
||||
var providers = await HttpContext.GetExternalProvidersAsync();
|
||||
var providers = HttpContext.GetExternalProviders();
|
||||
}
|
||||
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||
|
|
@ -118,11 +118,11 @@
|
|||
@foreach (var provider in providers)
|
||||
{
|
||||
<form method="post">
|
||||
<input type="hidden" name="Provider" value="@provider.Name" />
|
||||
<input type="hidden" name="Provider" value="@provider.Slug" />
|
||||
<input type="hidden" name="ReturnUrl" value="@Model.ReturnUrl" />
|
||||
|
||||
<button class="ant-btn ant-btn-primary ant-btn-block" type="submit">
|
||||
<span>Sign in using @provider.DisplayName</span>
|
||||
<span>Sign in using @provider.Name</span>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
|
|
|
|||
19
LANCommander.Server/Controllers/AccountController.cs
Normal file
19
LANCommander.Server/Controllers/AccountController.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using LANCommander.Server.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Server.Controllers;
|
||||
|
||||
public class AccountController : BaseController
|
||||
{
|
||||
public AccountController(ILogger<AccountController> logger) : base(logger)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("/SignInOAuth")]
|
||||
public async Task<IActionResult> SignInOAuth()
|
||||
{
|
||||
var authenticationProviders = await AuthenticationService.GetAuthenticationProviderTemplatesAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using LANCommander.Server.Data.Models;
|
||||
using LANCommander.Server.Services;
|
||||
using LANCommander.Server.Services.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using AuthenticationService = LANCommander.Server.Services.AuthenticationService;
|
||||
|
||||
namespace LANCommander.Server.Extensions;
|
||||
|
||||
|
|
@ -9,9 +16,10 @@ public static class AuthenticationBuilderExtensions
|
|||
{
|
||||
public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder authBuilder, AuthenticationProvider authenticationProvider)
|
||||
{
|
||||
var slug = authenticationProvider.GetSlug();
|
||||
if (String.IsNullOrWhiteSpace(authenticationProvider.Slug))
|
||||
return authBuilder;
|
||||
|
||||
return authBuilder.AddOpenIdConnect(slug, authenticationProvider.Name, options =>
|
||||
return authBuilder.AddOpenIdConnect(authenticationProvider.Slug, authenticationProvider.Name, options =>
|
||||
{
|
||||
options.ClientId = authenticationProvider.ClientId;
|
||||
options.ClientSecret = authenticationProvider.ClientSecret;
|
||||
|
|
@ -30,8 +38,8 @@ public static class AuthenticationBuilderExtensions
|
|||
};
|
||||
|
||||
// Callbacks for middleware to properly correlate
|
||||
options.CallbackPath = new PathString($"/signin-oidc-{slug}");
|
||||
options.SignedOutCallbackPath = new PathString($"/signout-oidc-{slug}");
|
||||
options.CallbackPath = new PathString($"/SignInOIDC");
|
||||
options.SignedOutCallbackPath = new PathString($"/SignOutOIDC");
|
||||
|
||||
foreach (var scope in authenticationProvider.Scopes)
|
||||
{
|
||||
|
|
@ -49,13 +57,14 @@ public static class AuthenticationBuilderExtensions
|
|||
public static AuthenticationBuilder AddOAuth(this AuthenticationBuilder authBuilder,
|
||||
AuthenticationProvider authenticationProvider)
|
||||
{
|
||||
var slug = authenticationProvider.GetSlug();
|
||||
if (String.IsNullOrWhiteSpace(authenticationProvider.Slug))
|
||||
return authBuilder;
|
||||
|
||||
return authBuilder.AddOAuth(slug, authenticationProvider.Name, options =>
|
||||
return authBuilder.AddOAuth(authenticationProvider.Slug, authenticationProvider.Name, options =>
|
||||
{
|
||||
options.ClientId = authenticationProvider.ClientId;
|
||||
options.ClientSecret = authenticationProvider.ClientSecret;
|
||||
options.CallbackPath = new PathString($"/signin-oauth-{slug}");
|
||||
options.CallbackPath = new PathString($"/SignInOAuth");
|
||||
|
||||
options.AuthorizationEndpoint = authenticationProvider.AuthorizationEndpoint;
|
||||
options.TokenEndpoint = authenticationProvider.TokenEndpoint;
|
||||
|
|
@ -74,26 +83,35 @@ public static class AuthenticationBuilderExtensions
|
|||
options.ClaimActions.MapJsonKey(claimMapping.Name, claimMapping.Value);
|
||||
}
|
||||
|
||||
options.Events.OnTicketReceived = async context =>
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
// Retrieve user information
|
||||
/*options.Events.OnCreatingTicket = async context =>
|
||||
options.Events.OnCreatingTicket = async context =>
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
|
||||
|
||||
var response = await context.Backchannel.SendAsync(request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HttpRequestException($"An error occurred while retrieving the user profile: {response.StatusCode}");
|
||||
}
|
||||
|
||||
var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||
context.RunClaimActions(user.RootElement);
|
||||
};*/
|
||||
var oauthUser = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||
|
||||
context.Identity.AddClaim(new Claim("Provider", authenticationProvider.Name));
|
||||
|
||||
context.RunClaimActions(oauthUser.RootElement);
|
||||
|
||||
var signInManager = context.HttpContext.RequestServices.GetService<SignInManager<User>>();
|
||||
var userService = context.HttpContext.RequestServices.GetService<UserService>();
|
||||
var userCustomFieldService = context.HttpContext.RequestServices.GetService<UserCustomFieldService>();
|
||||
|
||||
var customField = await userCustomFieldService.FirstOrDefaultAsync(cf => cf.Name == $"ExternalId/{authenticationProvider.Slug}" && cf.Value == context.Identity.Name);
|
||||
var user = await userService.GetAsync(customField.UserId.GetValueOrDefault());
|
||||
|
||||
await signInManager.SignInAsync(user, true);
|
||||
|
||||
context.Response.Redirect("/");
|
||||
await context.Response.CompleteAsync();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,21 @@
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
using LANCommander.Server.Services;
|
||||
using LANCommander.Server.Services.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace LANCommander.Server.Extensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
public static async Task<AuthenticationScheme[]> GetExternalProvidersAsync(this HttpContext context)
|
||||
public static IEnumerable<AuthenticationProvider> GetExternalProviders(this HttpContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var schemes = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
|
||||
|
||||
return (from scheme in await schemes.GetAllSchemesAsync()
|
||||
where !string.IsNullOrEmpty(scheme.DisplayName)
|
||||
select scheme).ToArray();
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
return settings.Authentication.AuthenticationProviders;
|
||||
}
|
||||
|
||||
public static async Task<bool> IsProviderSupportedAsync(this HttpContext context, string provider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
return (from scheme in await context.GetExternalProvidersAsync()
|
||||
where string.Equals(scheme.Name, provider, StringComparison.OrdinalIgnoreCase)
|
||||
select scheme).Any();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.5.6" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.2" />
|
||||
<PackageReference Include="CaseConverter" Version="2.0.1" />
|
||||
<PackageReference Include="CoreRCON" Version="5.0.5" />
|
||||
<PackageReference Include="craftersmine.SteamGridDB.Net" Version="1.1.7" />
|
||||
<PackageReference Include="Crc32.NET" Version="1.2.0" />
|
||||
|
|
|
|||
20
LANCommander.Server/Models/LoginModel.cs
Normal file
20
LANCommander.Server/Models/LoginModel.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LANCommander.Server.Models;
|
||||
|
||||
public class LoginModel
|
||||
{
|
||||
[Required]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "User Name")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
// public string ReturnUrl { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
146
LANCommander.Server/UI/Pages/Account/Login.cshtml
Normal file
146
LANCommander.Server/UI/Pages/Account/Login.cshtml
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
@page "/Login"
|
||||
@using LANCommander.Server.Extensions
|
||||
@using LANCommander.Server.Models
|
||||
@using LANCommander.Server.Services
|
||||
@using LANCommander.Server.Services.Models
|
||||
@model LANCommander.Server.UI.Pages.Account.LoginModel
|
||||
@{ Layout = "/UI/Views/Shared/_LayoutBasic.cshtml"; }
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Log in";
|
||||
|
||||
var providers = HttpContext.GetExternalProviders();
|
||||
}
|
||||
|
||||
<div class="ant-row ant-row-middle ant-row-space-around" style="min-height: 100vh; margin-top: -24px;">
|
||||
<div class="ant-col ant-col-xs-24 ant-col-md-10">
|
||||
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
@switch (SettingService.GetSettings().Theme)
|
||||
{
|
||||
case LANCommanderTheme.Light:
|
||||
<img src="~/static/logo.svg" />
|
||||
break;
|
||||
|
||||
case LANCommanderTheme.Dark:
|
||||
<img src="~/static/logo-dark.svg" />
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
@foreach (var error in ModelState.SelectMany(x => x.Value.Errors))
|
||||
{
|
||||
<div data-show="true" class="ant-alert ant-alert-error ant-alert-no-icon" style="margin-bottom: 16px">
|
||||
<div class="ant-alert-content">
|
||||
<div class="ant-alert-message">@error.ErrorMessage</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="ant-card ant-card-bordered">
|
||||
<div class="ant-card-head">
|
||||
<div class="ant-card-head-wrapper">
|
||||
<div class="ant-card-head-title">Login</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="account" method="post" class="ant-card-body" autocomplete="off">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="ant-form ant-form-vertical">
|
||||
<div class="ant-form-item">
|
||||
<div class="ant-form-item-row ant-row">
|
||||
<div class="ant-form-item-label ant-col">
|
||||
<label asp-for="Model.Username" class="form-label"></label>
|
||||
</div>
|
||||
|
||||
<div class="ant-form-item-control ant-col">
|
||||
<div class="ant-form-item-control-input">
|
||||
<div class="ant-form-item-control-input-content">
|
||||
<input asp-for="Model.Username" class="ant-input" autocomplete="username" aria-required="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ant-form-item">
|
||||
<div class="ant-form-item-row ant-row">
|
||||
<div class="ant-form-item-label ant-col">
|
||||
<label asp-for="Model.Password" class="form-label"></label>
|
||||
</div>
|
||||
|
||||
<div class="ant-form-item-control ant-col">
|
||||
<div class="ant-form-item-control-input">
|
||||
<div class="ant-form-item-control-input-content">
|
||||
<input asp-for="Model.Password" class="ant-input" autocomplete="current-password" aria-required="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ant-form-item">
|
||||
<div class="ant-form-item-row ant-row">
|
||||
<div class="ant-form-item-control ant-col">
|
||||
<div class="ant-form-item-control-input">
|
||||
<div class="ant-form-item-control-input-content">
|
||||
<label class="ant-checkbox-wrapper">
|
||||
<span class="ant-checkbox">
|
||||
<input class="ant-checkbox-input" asp-for="Model.RememberMe"/>
|
||||
<span class="ant-checkbox-inner"></span>
|
||||
</span>
|
||||
<span>
|
||||
@Html.DisplayNameFor(m => m.Model.RememberMe)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ant-form-item" style="margin-bottom: 0;">
|
||||
<div class="ant-form-item-row ant-row">
|
||||
<button id="login-submit" type="submit" class="ant-btn ant-btn-primary ant-btn-block">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if (providers != null && providers.Count() > 0)
|
||||
{
|
||||
<span class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-center">
|
||||
<span class="ant-divider-inner-text">
|
||||
Or
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@foreach (var provider in providers)
|
||||
{
|
||||
<form method="post">
|
||||
<input type="hidden" name="Provider" value="@provider.Slug" />
|
||||
<input type="hidden" name="ReturnUrl" value="@Model.ReturnUrl" />
|
||||
|
||||
<button class="ant-btn ant-btn-primary ant-btn-block" type="submit">
|
||||
@if (!String.IsNullOrWhiteSpace(provider.Icon))
|
||||
{
|
||||
<span class="anticon" role="img">
|
||||
<svg class="bi" width="32" height="32" fill="currentColor">
|
||||
<use xlink:href="bootstrap-icons.svg#@provider.Icon"/>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
|
||||
<span>Sign in using @provider.Name</span>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
<div style="text-align: center; margin-top: 16px;">
|
||||
Don't have account yet? <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl" tabindex="-1">Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
150
LANCommander.Server/UI/Pages/Account/Login.cshtml.cs
Normal file
150
LANCommander.Server/UI/Pages/Account/Login.cshtml.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using LANCommander.Server.Data.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LANCommander.Server.Services;
|
||||
using LANCommander.Server.Extensions;
|
||||
using LANCommander.Server.Data;
|
||||
|
||||
namespace LANCommander.Server.UI.Pages.Account
|
||||
{
|
||||
public class LoginModel : PageModel
|
||||
{
|
||||
private readonly SignInManager<User> SignInManager;
|
||||
private readonly UserService UserService;
|
||||
private readonly RoleService RoleService;
|
||||
private readonly ILogger<LoginModel> Logger;
|
||||
|
||||
public LoginModel(
|
||||
SignInManager<User> signInManager,
|
||||
UserService userService,
|
||||
RoleService roleService,
|
||||
ILogger<LoginModel> logger)
|
||||
{
|
||||
SignInManager = signInManager;
|
||||
UserService = userService;
|
||||
RoleService = roleService;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
||||
/// directly from your code. This API may change or be removed in future releases.
|
||||
/// </summary>
|
||||
[BindProperty]
|
||||
public Models.LoginModel Model { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
||||
/// directly from your code. This API may change or be removed in future releases.
|
||||
/// </summary>
|
||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
||||
/// directly from your code. This API may change or be removed in future releases.
|
||||
/// </summary>
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
||||
/// directly from your code. This API may change or be removed in future releases.
|
||||
/// </summary>
|
||||
[TempData]
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, ErrorMessage);
|
||||
}
|
||||
|
||||
returnUrl ??= Url.Content("~/");
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
ExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
|
||||
ReturnUrl = returnUrl;
|
||||
|
||||
if (DatabaseContext.Provider == Data.Enums.DatabaseProvider.Unknown)
|
||||
return Redirect("/FirstTimeSetup");
|
||||
|
||||
var administrators = await RoleService.GetUsersAsync(RoleService.AdministratorRoleName);
|
||||
|
||||
if (administrators == null || !administrators.Any())
|
||||
return Redirect("/FirstTimeSetup");
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null, string provider = null)
|
||||
{
|
||||
returnUrl ??= Url.Content("~/");
|
||||
|
||||
ExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(provider) && await HttpContext.IsProviderSupportedAsync(provider))
|
||||
{
|
||||
return Challenge(new AuthenticationProperties { RedirectUri = returnUrl }, provider);
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
if (settings.Authentication.RequireApproval)
|
||||
{
|
||||
var user = await UserService.GetAsync(Model.Username);
|
||||
|
||||
if (user != null && !user.Approved && !(await UserService.IsInRoleAsync(user.UserName, RoleService.AdministratorRoleName)))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Your account must be approved by an administrator.");
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
// This doesn't count login failures towards account lockout
|
||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||
var result = await SignInManager.PasswordSignInAsync(Model.Username, Model.Password, Model.RememberMe, lockoutOnFailure: false);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
Logger.LogInformation("User logged in.");
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Model.RememberMe });
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
Logger.LogWarning("User account locked out.");
|
||||
return RedirectToPage("./Lockout");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
6
LANCommander.Server/UI/Pages/Account/_ViewImports.cshtml
Normal file
6
LANCommander.Server/UI/Pages/Account/_ViewImports.cshtml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@using Microsoft.AspNetCore.Identity
|
||||
@using LANCommander.Server.Areas.Identity
|
||||
@using LANCommander.Server.Areas.Identity.Pages
|
||||
@using LANCommander.Server.Data.Models
|
||||
@using LANCommander.Server.UI.Components
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
@page "/Settings/Authentication"
|
||||
@using CaseConverter
|
||||
@using LANCommander.Server.Models;
|
||||
@using LANCommander.Server.UI.Pages.Settings.Authentication.Components
|
||||
@inject IMessageService MessageService
|
||||
|
|
@ -60,6 +61,12 @@
|
|||
{
|
||||
try
|
||||
{
|
||||
foreach (var authenticationProvider in Settings.Authentication.AuthenticationProviders)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(authenticationProvider.Slug))
|
||||
authenticationProvider.Slug = authenticationProvider.Name.ToPascalCase();
|
||||
}
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
MessageService.Success("Settings saved!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using Microsoft.JSInterop
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DetailedErrors": true
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue