Browse Source

feat(account): add QR code login

pull/1313/head
colin 6 months ago
parent
commit
5649da83bc
  1. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json
  2. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
  3. 22
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs
  4. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictLoginModel.cs
  5. 4
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs
  6. 21
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml
  7. 151
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  8. 108
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.js
  9. 33
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/wwwroot/client-proxies/qrcode-proxy.js
  10. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/zh-Hans.json

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

@ -15,6 +15,8 @@
"DisplayName:EmailVerifyCode": "Mail verification code",
"DisplayName:WeChatCode": "Wechat login code",
"DisplayName:AuthenticatorCode": "Authenticator Code",
"DisplayName:PhoneNumber": "Phone Number",
"DisplayName:Code": "Code",
"TwoFactor": "Two factor authentication",
"TwoFactor:Enabled": "TwoFactor Enabled",
"TwoFactor:Email": "Email",
@ -54,6 +56,7 @@
"SecurityLogs": "Security Logs",
"PasswordLogin": "Login with password",
"PhoneNumberLogin": "Login with phone",
"ScanQrCodeLogin": "Login with scan",
"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."
}

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

@ -15,6 +15,8 @@
"DisplayName:EmailVerifyCode": "邮件验证码",
"DisplayName:WeChatCode": "微信登录凭证",
"DisplayName:AuthenticatorCode": "验证代码",
"DisplayName:PhoneNumber": "手机号码",
"DisplayName:Code": "验证码",
"TwoFactor": "双因素身份验证",
"TwoFactor:Enabled": "启用双因素认证",
"TwoFactor:Email": "邮箱验证",
@ -54,6 +56,7 @@
"SecurityLogs": "安全日志",
"PasswordLogin": "密码登录",
"PhoneNumberLogin": "验证码登录",
"ScanQrCodeLogin": "扫码登录",
"DisplayName:Abp.Account.EnablePhoneNumberLogin": "使用手机验证码进行身份验证",
"Description:Abp.Account.EnablePhoneNumberLogin": "表示服务器是否允许用户使用手机验证码进行身份验证。"
}

22
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs

@ -4,6 +4,7 @@ using IdentityServer4.Services;
using IdentityServer4.Stores;
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using LINGYUN.Abp.Account.Web.Pages.Account;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -18,7 +19,6 @@ using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.AspNetIdentity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
@ -53,7 +53,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
public override async Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
PasswordLoginInput = new PasswordLoginInputModel();
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);
@ -62,7 +62,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
// TODO: Find a proper cancel way.
// ShowCancelButton = true;
LoginInput.UserNameOrEmailAddress = context.LoginHint;
PasswordLoginInput.UserNameOrEmailAddress = context.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey];
@ -75,7 +75,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
if (context?.IdP != null)
{
LoginInput.UserNameOrEmailAddress = context.LoginHint;
PasswordLoginInput.UserNameOrEmailAddress = context.LoginHint;
ExternalProviders = new[] { new ExternalLoginProviderModel { AuthenticationScheme = context.IdP } };
return Page();
}
@ -107,7 +107,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
return Page();
}
public override async Task<IActionResult> OnPostAsync(string action)
public override async Task<IActionResult> OnPostPasswordLogin(string action)
{
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);
if (action == "Cancel")
@ -138,9 +138,9 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
await ReplaceEmailToUsernameOfInputIfNeeds();
var result = await SignInManager.PasswordSignInAsync(
LoginInput.UserNameOrEmailAddress,
LoginInput.Password,
LoginInput.RememberMe,
PasswordLoginInput.UserNameOrEmailAddress,
PasswordLoginInput.Password,
PasswordLoginInput.RememberMe,
true
);
@ -148,7 +148,7 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = result.ToIdentitySecurityLogAction(),
UserName = LoginInput.UserNameOrEmailAddress,
UserName = PasswordLoginInput.UserNameOrEmailAddress,
ClientId = context?.Client?.ClientId
});
@ -173,8 +173,8 @@ namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
}
//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 UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
var user = await UserManager.FindByNameAsync(PasswordLoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(PasswordLoginInput.UserNameOrEmailAddress);
Debug.Assert(user != null, nameof(user) + " != null");
await IdentityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictLoginModel.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Pages.Account;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -9,7 +10,6 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.OpenIddict.Pages.Account
@ -35,7 +35,7 @@ namespace LINGYUN.Abp.Account.Web.OpenIddict.Pages.Account
public async override Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
PasswordLoginInput = new PasswordLoginInputModel();
var request = await OpenIddictRequestHelper.GetFromReturnUrlAsync(ReturnUrl);
if (request?.ClientId != null)
@ -43,7 +43,7 @@ namespace LINGYUN.Abp.Account.Web.OpenIddict.Pages.Account
// TODO: Find a proper cancel way.
// ShowCancelButton = true;
LoginInput.UserNameOrEmailAddress = request.LoginHint;
PasswordLoginInput.UserNameOrEmailAddress = request.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = request.GetParameter(TenantResolverConsts.DefaultTenantKey)?.ToString();

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

@ -1,13 +1,11 @@
using LINGYUN.Abp.Account.Emailing;
using LINGYUN.Abp.Account.Web.Bundling;
using LINGYUN.Abp.Account.Web.Pages.Account;
using LINGYUN.Abp.Account.Web.ProfileManagement;
using LINGYUN.Abp.Identity;
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;
@ -16,7 +14,6 @@ using Volo.Abp.Account.Web.ProfileManagement;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.QRCode;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.Sms;
@ -121,6 +118,7 @@ public class AbpAccountWebModule : AbpModule
.Configure(typeof(Pages.Account.LoginModel).FullName, bundle =>
{
bundle.AddFiles("/client-proxies/account-proxy.js");
bundle.AddFiles("/client-proxies/qrcode-proxy.js");
bundle.AddContributors(typeof(QRCodeScriptContributor));
});
});

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

@ -28,9 +28,11 @@
<div class="account-module-form">
@if (Model.EnableLocalLogin)
{
<abp-tabs>
<abp-tab title="@L["PasswordLogin"].Value" active="Model.LoginType == LoginType.Password">
<abp-tabs id="LoginFormTabs">
<abp-tab title="@L["PasswordLogin"].Value" active="Model.LoginType == LoginType.Password" header-name="PasswordLogin">
<form method="post" class="mt-4" asp-page-handler="PasswordLogin">
<input asp-for="PasswordLoginInput.ReturnUrl" />
<input asp-for="PasswordLoginInput.ReturnUrlHash" />
<div class="mb-3">
<label asp-for="PasswordLoginInput.UserNameOrEmailAddress" class="form-label"></label>
<input asp-for="PasswordLoginInput.UserNameOrEmailAddress" class="form-control" />
@ -69,8 +71,10 @@
</div>
</form>
</abp-tab>
<abp-tab title="@L["PhoneNumberLogin"].Value" active="Model.LoginType == LoginType.PhoneNumber">
<abp-tab title="@L["PhoneNumberLogin"].Value" active="Model.LoginType == LoginType.PhoneNumber" header-name="PhoneNumberLogin">
<form method="post" class="mt-4" asp-page-handler="PhoneNumberLogin" id="PhoneNumberForm">
<input asp-for="PhoneLoginInput.ReturnUrl" />
<input asp-for="PhoneLoginInput.ReturnUrlHash" />
<div class="mb-3">
<label asp-for="PhoneLoginInput.PhoneNumber" class="form-label"></label>
<input asp-for="PhoneLoginInput.PhoneNumber" class="form-control" id="PhoneNumberInput" />
@ -99,6 +103,17 @@
</div>
</form>
</abp-tab>
<abp-tab title="@L["ScanQrCodeLogin"].Value" active="Model.LoginType == LoginType.QrCode" header-name="QrCodeLogin">
<form method="post" class="mt-4" asp-page-handler="QrCodeLogin" id="QrCodeForm">
<input asp-for="QrCodeLoginInput.ReturnUrl" />
<input asp-for="QrCodeLoginInput.ReturnUrlHash" />
<input asp-for="QrCodeLoginInput.Key" id="QrCodeKey" />
<div class="gap-2" style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
<div id="QrCode"></div>
<div id="QrCodeStatus"></div>
</div>
</form>
</abp-tab>
</abp-tabs>
}

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

@ -1,6 +1,8 @@
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -24,7 +26,6 @@ using Volo.Abp.Reflection;
using Volo.Abp.Security.Claims;
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;
@ -50,6 +51,9 @@ public class LoginModel : AccountPageModel
[BindProperty(Name = "PhoneLoginInput")]
public PhoneLoginInputModel PhoneLoginInput { get; set; }
[BindProperty(Name = "QrCodeLoginInput")]
public QrCodeLoginInputModel QrCodeLoginInput { get; set; }
public bool EnableLocalLogin { get; set; }
public bool ShowCancelButton { get; set; }
@ -60,6 +64,7 @@ public class LoginModel : AccountPageModel
public IEnumerable<ExternalLoginProviderModel> VisibleExternalProviders => ExternalProviders.Where(x => !x.DisplayName.IsNullOrWhiteSpace());
protected IIdentityUserRepository UserRepository => LazyServiceProvider.LazyGetRequiredService<IIdentityUserRepository>();
protected IQrCodeLoginProvider QrCodeLoginProvider => LazyServiceProvider.LazyGetRequiredService<IQrCodeLoginProvider>();
protected IExternalProviderService ExternalProviderService { get; }
protected IAuthenticationSchemeProvider SchemeProvider { get; }
@ -82,8 +87,21 @@ public class LoginModel : AccountPageModel
public virtual async Task<IActionResult> OnGetAsync()
{
LoginType = LoginType.Password;
PhoneLoginInput = new PhoneLoginInputModel();
PasswordLoginInput = new PasswordLoginInputModel();
PhoneLoginInput = new PhoneLoginInputModel
{
ReturnUrl = ReturnUrl,
ReturnUrlHash = ReturnUrlHash,
};
QrCodeLoginInput = new QrCodeLoginInputModel
{
ReturnUrl = ReturnUrl,
ReturnUrlHash = ReturnUrlHash,
};
PasswordLoginInput = new PasswordLoginInputModel
{
ReturnUrl = ReturnUrl,
ReturnUrlHash = ReturnUrlHash,
};
ExternalProviders = await GetExternalProviders();
@ -108,6 +126,7 @@ public class LoginModel : AccountPageModel
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
ModelState.RemoveModelErrors(nameof(PhoneLoginInput));
ModelState.RemoveModelErrors(nameof(QrCodeLoginInput));
if (!TryValidateModel(PasswordLoginInput, nameof(PasswordLoginInput)))
{
return Page();
@ -159,7 +178,7 @@ public class LoginModel : AccountPageModel
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
return await RedirectSafelyAsync(PasswordLoginInput.ReturnUrl, PasswordLoginInput.ReturnUrlHash);
}
public async virtual Task<IActionResult> OnPostPhoneNumberLogin(string action)
@ -172,6 +191,7 @@ public class LoginModel : AccountPageModel
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
ModelState.RemoveModelErrors(nameof(QrCodeLoginInput));
ModelState.RemoveModelErrors(nameof(PasswordLoginInput));
if (!TryValidateModel(PhoneLoginInput, nameof(PhoneLoginInput)))
{
@ -193,9 +213,7 @@ public class LoginModel : AccountPageModel
return Page();
}
await SignInManager.SignInAsync(
user,
PasswordLoginInput.RememberMe);
await SignInManager.SignInAsync(user, PhoneLoginInput.RememberMe);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
@ -207,7 +225,90 @@ public class LoginModel : AccountPageModel
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
return await RedirectSafelyAsync(PhoneLoginInput.ReturnUrl, PhoneLoginInput.ReturnUrlHash);
}
protected virtual void SetTenantCookies(Guid? tenantId = null)
{
if (tenantId.HasValue)
{
Response.Cookies.Append(
"__tenant",
tenantId.ToString(),
new CookieOptions
{
Path = "/",
HttpOnly = false,
IsEssential = true,
Expires = DateTimeOffset.Now.AddYears(10)
}
);
}
else
{
Response.Cookies.Delete("__tenant");
}
}
public async virtual Task<IActionResult> OnPostQrCodeLogin(string action)
{
LoginType = LoginType.QrCode;
await CheckLocalLoginAsync();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
ModelState.RemoveModelErrors(nameof(PhoneLoginInput));
ModelState.RemoveModelErrors(nameof(PasswordLoginInput));
if (!TryValidateModel(QrCodeLoginInput, nameof(QrCodeLoginInput)))
{
return Page();
}
var qrCodeInfo = await QrCodeLoginProvider.GetCodeAsync(QrCodeLoginInput.Key);
// 二维码扫描后用户Id不为空
if (qrCodeInfo == null || qrCodeInfo.Token.IsNullOrWhiteSpace() == true)
{
Alerts.Danger(L["QrCode:Invalid"]);
return Page();
}
SetTenantCookies(qrCodeInfo.TenantId);
using (CurrentTenant.Change(qrCodeInfo.TenantId))
{
var user = await UserManager.FindByIdAsync(qrCodeInfo.UserId);
if (user == null)
{
// TODO: 用户验证无效?
Alerts.Danger(L["QrCode:Invalid"]);
return Page();
}
if (!await UserManager.VerifyUserTokenAsync(user, QrCodeLoginProviderConsts.Name, QrCodeLoginProviderConsts.Purpose, qrCodeInfo.Token))
{
Alerts.Danger(L["QrCode:Invalid"]);
return Page();
}
// TODO: 记住登录
await SignInManager.SignInAsync(user, true);
await QrCodeLoginProvider.RemoveAsync(QrCodeLoginInput.Key);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = QrCodeLoginProviderConsts.Purpose,
Action = IdentitySecurityLogActionConsts.LoginSucceeded,
UserName = user.UserName
});
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(QrCodeLoginInput.ReturnUrl, QrCodeLoginInput.ReturnUrlHash);
}
}
public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
@ -331,8 +432,8 @@ public class LoginModel : AccountPageModel
// ÖØ¶¨ÏòË«ÒòËØÈÏÖ¤Ò³Ãæ
return Task.FromResult<IActionResult>(RedirectToPage("SendCode", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
returnUrl = PasswordLoginInput.ReturnUrl,
returnUrlHash = PasswordLoginInput.ReturnUrlHash,
rememberMe = PasswordLoginInput.RememberMe
}));
}
@ -340,8 +441,8 @@ public class LoginModel : AccountPageModel
protected virtual async Task<IdentityUser> GetIdentityUserAsync(string userNameOrEmailAddress)
{
return await UserManager.FindByNameAsync(PasswordLoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(PasswordLoginInput.UserNameOrEmailAddress);
return await UserManager.FindByNameAsync(userNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(userNameOrEmailAddress);
}
protected async virtual Task<List<ExternalLoginProviderModel>> GetExternalProviders()
@ -435,8 +536,8 @@ public class LoginModel : AccountPageModel
return RedirectToPage("ChangePassword", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
returnUrl = PasswordLoginInput.ReturnUrl,
returnUrlHash = PasswordLoginInput.ReturnUrlHash,
rememberMe = PasswordLoginInput.RememberMe
});
}
@ -452,7 +553,18 @@ public class LoginModel : AccountPageModel
}
}
public class PhoneLoginInputModel
public abstract class LoginInputModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; }
}
public class PhoneLoginInputModel : LoginInputModel
{
[Phone]
[Required]
@ -461,13 +573,12 @@ public class PhoneLoginInputModel
[Required]
[StringLength(6)]
[Display(Name = "VerifyCode")]
public string Code { get; set; }
public bool RememberMe { get; set; }
}
public class PasswordLoginInputModel
public class PasswordLoginInputModel : LoginInputModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
@ -482,6 +593,12 @@ public class PasswordLoginInputModel
public bool RememberMe { get; set; }
}
public class QrCodeLoginInputModel : LoginInputModel
{
[HiddenInput]
public string Key { get; set; }
}
public enum LoginType
{
Password = 0,

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

@ -1,9 +1,15 @@
$(function () {
let timer;
let countDown = 0;
var l = abp.localization.getResource('AbpAccount');
let checkQrCodeTimer;
let isQrCodeInitialized = false;
var qrCodeService = labp.account.qrCodeLogin;
let sendSmsCodeTimer;
let sendSmsCodeCountDown = 0;
var authService = labp.account.account;
var l = abp.localization.getResource('AbpAccount');
var il = abp.localization.getResource('AbpIdentity');
$("#SendVerifyCodeButton").click(function (e) {
const button = $(this);
e.preventDefault();
@ -15,20 +21,24 @@ $(function () {
var input = $('#PhoneNumberForm').serializeFormToObject();
abp.ui.setBusy({ busy: true });
authService.sendPhoneSigninCode({
phoneNumber: input.phoneLoginInput.phoneNumber,
}).then(function () {
countDown = 60;
timer = setInterval(function () {
abp.ui.clearBusy();
sendSmsCodeCountDown = 60;
sendSmsCodeTimer = setInterval(function () {
button.prop('disabled', true);
button.text(`${countDown}`);
if (countDown === 0) {
clearInterval(timer);
button.text(`${sendSmsCodeCountDown}`);
if (sendSmsCodeCountDown === 0) {
clearInterval(sendSmsCodeTimer);
button.prop('disabled', false);
button.text(l('SendVerifyCode'));
}
countDown--;
sendSmsCodeCountDown--;
}, 1000);
}).catch(function () {
abp.ui.clearBusy();
});
});
@ -51,4 +61,84 @@ $(function () {
icon.toggleClass("fa-eye-slash").toggleClass("fa-eye");
}
});
$('#LoginFormTabs').on('shown.bs.tab', function (e) {
const tabName = e.target.name;
if (tabName === 'QrCodeLogin') {
initQrCode();
} else {
releaseQrCodeTimer();
}
// 切换tab移除错误提示
$('#AbpPageAlerts').remove();
});
function initQrCode() {
if (isQrCodeInitialized) {
return;
}
if (checkQrCodeTimer) {
clearInterval(checkQrCodeTimer);
checkQrCodeTimer = undefined;
}
abp.ui.setBusy({ busy: true });
qrCodeService.generate().then(function (result) {
abp.ui.clearBusy();
$('#QrCodeKey').val(result.key);
const qrCodeUrl = 'QRCODE_LOGIN:' + result.key;
$('#QrCode').empty();
new QRCode(document.getElementById("QrCode"), {
text: qrCodeUrl,
width: 150,
height: 150
});
$('#QrCodeStatus').text(il('QrCode:NotScaned'));
checkQrCodeTimer = setInterval(function () {
checkQrCode(result.key);
}, 5000);
}).catch(function () {
abp.ui.clearBusy();
});
isQrCodeInitialized = true;
}
function checkQrCode(key) {
qrCodeService.check(key, {
abpHandleError: false
}).then(function (result) {
switch (result.status) {
case 10:
releaseQrCodeTimer();
$('#QrCodeForm').submit();
break;
case 5:
$('#QrCodeStatus').text(il('QrCode:Scaned'));
// TODO: 替换用户头像?
if (result.picture) {
$('#QrCode').html('<img src="' + result.picture + '" alt="User Avatar" style="width: 150px; height: 150px; border-radius: 50%;">');
}
break;
case 0:
$('#QrCodeStatus').text(il('QrCode:NotScaned'));
break;
case -1:
$('#QrCodeStatus').text(il('QrCode:Invalid'));
releaseQrCodeTimer();
initQrCode();
break;
}
}).catch(function () {
console.warn('Check for QR code errors');
releaseQrCodeTimer();
});
}
function releaseQrCodeTimer() {
if (checkQrCodeTimer) {
clearInterval(checkQrCodeTimer);
checkQrCodeTimer = undefined;
isQrCodeInitialized = false;
}
}
});

33
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/wwwroot/client-proxies/qrcode-proxy.js

@ -0,0 +1,33 @@
/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */
// module account
(function(){
// controller labp.account.qrCodeLogin
(function(){
abp.utils.createNamespace(window, 'labp.account.qrCodeLogin');
labp.account.qrCodeLogin.generate = function(ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/account/qrcode/generate',
type: 'POST',
dataType: null,
}, ajaxParams));
};
labp.account.qrCodeLogin.check = function(key, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/account/qrcode/' + key + '/check',
type: 'GET'
}, ajaxParams));
};
})();
})();

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/zh-Hans.json

@ -2,7 +2,7 @@
"culture": "zh-Hans",
"texts": {
"QrCode:Invalid": "二维码已失效!",
"QrCode:NotScaned": "扫描二维码!",
"QrCode:NotScaned": "扫描二维码!",
"QrCode:Scaned": "请确认二维码."
}
}
Loading…
Cancel
Save