Browse Source

feat(account): Add phone verification code login

- add new localized text
- Use tab to switch between different login forms
pull/1313/head
colin 7 months ago
parent
commit
c9b6328919
  1. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
  2. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
  3. 21
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs
  4. 27
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Microsoft/AspNetCore/Mvc/ModelBinding/ModelStateExtensions.cs
  5. 169
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml
  6. 149
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  7. 54
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.js

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json

@ -51,6 +51,10 @@
"ProfileTab:Authenticator": "Authenticator",
"ProfileTab:SecurityLog": "Security Log",
"PhoneNumberChangedMessage": "Your mobile number has been successfully changed.",
"SecurityLogs": "Security Logs"
"SecurityLogs": "Security Logs",
"PasswordLogin": "Login with password",
"PhoneNumberLogin": "Login with phone",
"DisplayName:Abp.Account.EnablePhoneNumberLogin": "Authenticate with a phone number",
"Description:Abp.Account.EnablePhoneNumberLogin": "Indicates whether the server allows users to use mobile phone verification codes."
}
}

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json

@ -51,6 +51,10 @@
"ProfileTab:Authenticator": "身份验证程序",
"ProfileTab:SecurityLog": "安全日志",
"PhoneNumberChangedMessage": "您的手机号码已成功更改.",
"SecurityLogs": "安全日志"
"SecurityLogs": "安全日志",
"PasswordLogin": "密码登录",
"PhoneNumberLogin": "验证码登录",
"DisplayName:Abp.Account.EnablePhoneNumberLogin": "使用手机验证码进行身份验证",
"Description:Abp.Account.EnablePhoneNumberLogin": "表示服务器是否允许用户使用手机验证码进行身份验证。"
}
}

21
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs

@ -7,6 +7,7 @@ using LINGYUN.Abp.Identity.AspNetCore.QrCode;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using Volo.Abp.Account.Localization;
@ -91,31 +92,37 @@ public class AbpAccountWebModule : AbpModule
options.ScriptBundles
.Configure(typeof(ManageModel).FullName,
configuration =>
bundle =>
{
// Client Proxies
configuration.AddFiles("/client-proxies/account-proxy.js");
bundle.AddFiles("/client-proxies/account-proxy.js");
// Session
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Session/Index.js");
bundle.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Session/Index.js");
// Authenticator
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Authenticator/Index.js");
bundle.AddFiles("/Pages/Account/Components/ProfileManagementGroup/Authenticator/Index.js");
// SecurityLog
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js");
bundle.AddFiles("/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js");
// TwoFactor
configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js");
bundle.AddFiles("/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js");
// QrCode
configuration.AddContributors(typeof(QRCodeScriptContributor));
bundle.AddContributors(typeof(QRCodeScriptContributor));
});
options.ScriptBundles
.Configure(AccountBundles.Scripts.ChangePassword, bundle =>
{
bundle.AddContributors(typeof(ChangePasswordScriptContributor));
});
options.ScriptBundles
.Configure(typeof(Pages.Account.LoginModel).FullName, bundle =>
{
bundle.AddFiles("/client-proxies/account-proxy.js");
bundle.AddContributors(typeof(QRCodeScriptContributor));
});
});
}
}

27
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Microsoft/AspNetCore/Mvc/ModelBinding/ModelStateExtensions.cs

@ -0,0 +1,27 @@
using System.Linq;
namespace Microsoft.AspNetCore.Mvc.ModelBinding;
public static class ModelStateExtensions
{
public static void RemoveModelErrors(this ModelStateDictionary modelState, string modelName)
{
var keys = modelState.Keys
.Where(k => k.StartsWith(modelName + ".") || k == modelName)
.ToList();
foreach (var key in keys)
{
modelState.Remove(key);
}
}
public static bool IsValidForModel(this ModelStateDictionary modelState, string modelName)
{
modelState.RemoveModelErrors(modelName);
return modelState.Keys
.Where(k => k.StartsWith(modelName + ".") || k == modelName)
.All(key => modelState[key].ValidationState == ModelValidationState.Valid);
}
}

169
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml

@ -1,18 +1,21 @@
@page
@using LINGYUN.Abp.Account.Web.Pages.Account
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings
@using Volo.Abp.Account.Web.Pages.Account;
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Abp.AspNetCore.Mvc.UI.Theming;
@using Volo.Abp.Identity;
@using Volo.Abp.Settings
@using Volo.Abp.Settings;
@model LINGYUN.Abp.Account.Web.Pages.Account.LoginModel
@inject IHtmlLocalizer<AccountResource> L
@inject IThemeManager ThemeManager
@inject IPageLayout PageLayout
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@{
Layout = ThemeManager.CurrentTheme.GetAccountLayout();
PageLayout.Content.Title = L["Login"].Value;
}
@section scripts
@ -22,84 +25,102 @@
</abp-script-bundle>
}
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (Model.EnableLocalLogin)
{
<form method="post" class="mt-4">
<div class="mb-3">
<label asp-for="LoginInput.UserNameOrEmailAddress" class="form-label"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="account-module-form">
@if (Model.EnableLocalLogin)
{
<abp-tabs>
<abp-tab title="@L["PasswordLogin"].Value" active="Model.LoginType == LoginType.Password">
<form method="post" class="mt-4" asp-page-handler="PasswordLogin">
<div class="mb-3">
<label asp-for="PasswordLoginInput.UserNameOrEmailAddress" class="form-label"></label>
<input asp-for="PasswordLoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="PasswordLoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="LoginInput.Password" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="LoginInput.Password" />
<button class="btn btn-secondary" type="button" id="PasswordVisibilityButton"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
<div class="mb-3">
<label asp-for="PasswordLoginInput.Password" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="PasswordLoginInput.Password" />
<button class="btn btn-secondary" type="button" id="PasswordVisibilityButton"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="PasswordLoginInput.Password"></span>
</div>
<span asp-validation-for="LoginInput.Password"></span>
</div>
<abp-row>
<abp-column>
<abp-input asp-for="LoginInput.RememberMe" class="mb-4" />
</abp-column>
<abp-column class="text-end">
<a href="@Url.Page("./ForgotPassword", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["ForgotPassword"]</a>
</abp-column>
</abp-row>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<div class="d-grid gap-2">
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-lg mt-3">@L["Login"]</abp-button>
@if (Model.ShowCancelButton)
<abp-row>
<abp-column>
<abp-input asp-for="PasswordLoginInput.RememberMe" class="mb-4" />
</abp-column>
<abp-column class="text-end">
<a href="@Url.Page("./ForgotPassword", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })">@L["ForgotPassword"]</a>
</abp-column>
</abp-row>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-lg mt-3">@L["Cancel"]</abp-button>
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })" class="text-decoration-none">@L["Register"]</a>
</strong>
}
</div>
</form>
}
<div class="d-grid gap-2">
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-lg mt-3">@L["Login"]</abp-button>
@if (Model.ShowCancelButton)
{
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-lg mt-3">@L["Cancel"]</abp-button>
}
</div>
</form>
</abp-tab>
<abp-tab title="@L["PhoneNumberLogin"].Value" active="Model.LoginType == LoginType.PhoneNumber">
<form method="post" class="mt-4" asp-page-handler="PhoneNumberLogin" id="PhoneNumberForm">
<div class="mb-3">
<label asp-for="PhoneLoginInput.PhoneNumber" class="form-label"></label>
<input asp-for="PhoneLoginInput.PhoneNumber" class="form-control" id="PhoneNumberInput" />
<span asp-validation-for="PhoneLoginInput.PhoneNumber" class="text-danger"></span>
</div>
@if (Model.VisibleExternalProviders.Any())
{
<div class="mt-2">
<h5>@L["OrLoginWith"]</h5>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.VisibleExternalProviders)
{
@* <button
type="submit"
class="btn btn-primary m-1"
name="provider"
value="@provider.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", provider.DisplayName]"
>
@if (provider.Icon != null)
{
<i class="@provider.Icon"></i>
}
<span>@provider.DisplayName</span>
</button> *@
@await Component.InvokeAsync(provider.ComponentType, provider);
}
<div class="mb-3">
<label asp-for="PhoneLoginInput.Code" class="form-label"></label>
<div class="input-group">
<input asp-for="PhoneLoginInput.Code" class="form-control" />
<button class="btn btn-secondary" type="button" id="SendVerifyCodeButton">@L["SendVerifyCode"]</button>
</div>
<span asp-validation-for="PhoneLoginInput.Code" class="text-danger"></span>
</div>
<abp-row>
<abp-column>
<abp-input asp-for="PhoneLoginInput.RememberMe" class="mb-4" />
</abp-column>
</abp-row>
<div class="d-grid gap-2">
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-lg mt-3">@L["Login"]</abp-button>
@if (Model.ShowCancelButton)
{
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-lg mt-3">@L["Cancel"]</abp-button>
}
</div>
</form>
</div>
}
</abp-tab>
</abp-tabs>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="mt-2">
<h5>@L["OrLoginWith"]</h5>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.VisibleExternalProviders)
{
@await Component.InvokeAsync(provider.ComponentType, provider)
;
}
</form>
</div>
}
</div>
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
</div>

149
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -3,10 +3,12 @@ using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
@ -15,7 +17,7 @@ using Volo.Abp;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Reflection;
@ -24,10 +26,10 @@ using Volo.Abp.Settings;
using Volo.Abp.Validation;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using IIdentityUserRepository = LINGYUN.Abp.Identity.IIdentityUserRepository;
namespace LINGYUN.Abp.Account.Web.Pages.Account;
//[ExposeServices(typeof(Volo.Abp.Account.Web.Pages.Account.LoginModel))]
public class LoginModel : AccountPageModel
{
[HiddenInput]
@ -38,8 +40,15 @@ public class LoginModel : AccountPageModel
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; }
[BindProperty]
public LoginInputModel LoginInput { get; set; }
[HiddenInput]
[BindProperty(SupportsGet = true)]
public LoginType LoginType { get; set; }
[BindProperty(Name = "PasswordLoginInput")]
public PasswordLoginInputModel PasswordLoginInput { get; set; }
[BindProperty(Name = "PhoneLoginInput")]
public PhoneLoginInputModel PhoneLoginInput { get; set; }
public bool EnableLocalLogin { get; set; }
@ -50,6 +59,7 @@ public class LoginModel : AccountPageModel
public IEnumerable<ExternalLoginProviderModel> ExternalProviders { get; set; }
public IEnumerable<ExternalLoginProviderModel> VisibleExternalProviders => ExternalProviders.Where(x => !x.DisplayName.IsNullOrWhiteSpace());
protected IIdentityUserRepository UserRepository => LazyServiceProvider.LazyGetRequiredService<IIdentityUserRepository>();
protected IExternalProviderService ExternalProviderService { get; }
protected IAuthenticationSchemeProvider SchemeProvider { get; }
@ -71,7 +81,9 @@ public class LoginModel : AccountPageModel
public virtual async Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
LoginType = LoginType.Password;
PhoneLoginInput = new PhoneLoginInputModel();
PasswordLoginInput = new PasswordLoginInputModel();
ExternalProviders = await GetExternalProviders();
@ -85,24 +97,30 @@ public class LoginModel : AccountPageModel
return Page();
}
public async virtual Task<IActionResult> OnPostAsync(string action)
public async virtual Task<IActionResult> OnPostPasswordLogin(string action)
{
await CheckLocalLoginAsync();
LoginType = LoginType.Password;
ValidateModel();
await CheckLocalLoginAsync();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
ModelState.RemoveModelErrors(nameof(PhoneLoginInput));
if (!TryValidateModel(PasswordLoginInput, nameof(PasswordLoginInput)))
{
return Page();
}
await ReplaceEmailToUsernameOfInputIfNeeds();
await IdentityOptions.SetAsync();
var result = await SignInManager.PasswordSignInAsync(
LoginInput.UserNameOrEmailAddress,
LoginInput.Password,
LoginInput.RememberMe,
PasswordLoginInput.UserNameOrEmailAddress,
PasswordLoginInput.Password,
PasswordLoginInput.RememberMe,
true
);
@ -110,7 +128,7 @@ public class LoginModel : AccountPageModel
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = result.ToIdentitySecurityLogAction(),
UserName = LoginInput.UserNameOrEmailAddress
UserName = PasswordLoginInput.UserNameOrEmailAddress
});
if (result.RequiresTwoFactor)
@ -134,7 +152,7 @@ public class LoginModel : AccountPageModel
}
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
var user = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress);
var user = await GetIdentityUserAsync(PasswordLoginInput.UserNameOrEmailAddress);
Debug.Assert(user != null, nameof(user) + " != null");
@ -144,6 +162,54 @@ public class LoginModel : AccountPageModel
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public async virtual Task<IActionResult> OnPostPhoneNumberLogin(string action)
{
LoginType = LoginType.PhoneNumber;
await CheckLocalLoginAsync();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
ModelState.RemoveModelErrors(nameof(PasswordLoginInput));
if (!TryValidateModel(PhoneLoginInput, nameof(PhoneLoginInput)))
{
return Page();
}
var user = await UserRepository.FindByPhoneNumberAsync(PhoneLoginInput.PhoneNumber);
if (user == null)
{
Logger.LogInformation("the user phone number is not registed!");
Alerts.Danger(L["InvalidPhoneNumber"]);
return Page();
}
var result = await UserManager.VerifyTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider, PhoneLoginInput.Code);
if (!result)
{
Alerts.Danger(L["InvalidVerifyCode"]);
return Page();
}
await SignInManager.SignInAsync(
user,
PasswordLoginInput.RememberMe);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityTwoFactor,
Action = IdentitySecurityLogActionConsts.LoginSucceeded,
UserName = user.UserName
});
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash });
@ -267,15 +333,15 @@ public class LoginModel : AccountPageModel
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
rememberMe = PasswordLoginInput.RememberMe
}));
}
protected virtual async Task<IdentityUser> GetIdentityUserAsync(string userNameOrEmailAddress)
{
return await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
return await UserManager.FindByNameAsync(PasswordLoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(PasswordLoginInput.UserNameOrEmailAddress);
}
protected async virtual Task<List<ExternalLoginProviderModel>> GetExternalProviders()
@ -316,24 +382,24 @@ public class LoginModel : AccountPageModel
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds()
{
if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress))
if (!ValidationHelper.IsValidEmailAddress(PasswordLoginInput.UserNameOrEmailAddress))
{
return;
}
var userByUsername = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress);
var userByUsername = await UserManager.FindByNameAsync(PasswordLoginInput.UserNameOrEmailAddress);
if (userByUsername != null)
{
return;
}
var userByEmail = await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
var userByEmail = await UserManager.FindByEmailAsync(PasswordLoginInput.UserNameOrEmailAddress);
if (userByEmail == null)
{
return;
}
LoginInput.UserNameOrEmailAddress = userByEmail.UserName;
PasswordLoginInput.UserNameOrEmailAddress = userByEmail.UserName;
}
protected virtual async Task CheckLocalLoginAsync()
@ -352,8 +418,8 @@ public class LoginModel : AccountPageModel
protected async virtual Task<IActionResult> HandleUserNotAllowed()
{
var notAllowedUser = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress);
if (await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password))
var notAllowedUser = await GetIdentityUserAsync(PasswordLoginInput.UserNameOrEmailAddress);
if (await UserManager.CheckPasswordAsync(notAllowedUser, PasswordLoginInput.Password))
{
// Óû§±ØÐëÐÞ¸ÄÃÜÂë
if (notAllowedUser.ShouldChangePasswordOnNextLogin || await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser))
@ -371,7 +437,7 @@ public class LoginModel : AccountPageModel
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
rememberMe = PasswordLoginInput.RememberMe
});
}
}
@ -385,3 +451,40 @@ public class LoginModel : AccountPageModel
return Task.FromResult<IActionResult>(Page());
}
}
public class PhoneLoginInputModel
{
[Phone]
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
public string PhoneNumber { get; set; }
[Required]
[StringLength(6)]
[Display(Name = "VerifyCode")]
public string Code { get; set; }
public bool RememberMe { get; set; }
}
public class PasswordLoginInputModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string UserNameOrEmailAddress { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[DataType(DataType.Password)]
[DisableAuditing]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
public enum LoginType
{
Password = 0,
PhoneNumber = 1,
QrCode = 2
}

54
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.js

@ -0,0 +1,54 @@
$(function () {
let timer;
let countDown = 0;
var l = abp.localization.getResource('AbpAccount');
var authService = labp.account.account;
$("#SendVerifyCodeButton").click(function (e) {
const button = $(this);
e.preventDefault();
var isValid = $('#PhoneNumberForm').validate().element('#PhoneNumberInput');
if (!isValid) {
return false;
}
var input = $('#PhoneNumberForm').serializeFormToObject();
authService.sendPhoneSigninCode({
phoneNumber: input.phoneLoginInput.phoneNumber,
}).then(function () {
countDown = 60;
timer = setInterval(function () {
button.prop('disabled', true);
button.text(`${countDown}`);
if (countDown === 0) {
clearInterval(timer);
button.prop('disabled', false);
button.text(l('SendVerifyCode'));
}
countDown--;
}, 1000);
});
});
$("#PasswordVisibilityButton").click(function (e) {
let button = $(this);
let passwordInput = button.parent().find("input");
if (!passwordInput) {
return;
}
if (passwordInput.attr("type") === "password") {
passwordInput.attr("type", "text");
}
else {
passwordInput.attr("type", "password");
}
let icon = button.find("i");
if (icon) {
icon.toggleClass("fa-eye-slash").toggleClass("fa-eye");
}
});
});
Loading…
Cancel
Save