From a5fad6155827a1b23f3fbaeeee6370ceac10197f Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 Jun 2026 13:14:31 +0800 Subject: [PATCH 1/2] feat(account): Add support for logging in with link accounts --- .../vben5/apps/app-antd/src/layouts/basic.vue | 42 +++- .../app-antd/src/locales/langs/en-US/abp.json | 1 + .../app-antd/src/locales/langs/zh-CN/abp.json | 1 + apps/vben5/apps/app-antd/src/store/auth.ts | 39 +++- .../src/views/_core/authentication/login.vue | 14 +- .../packages/@abp/account/src/api/index.ts | 1 + .../@abp/account/src/api/useLinkUsersApi.ts | 54 +++++ .../account/src/components/UserLinkModal.vue | 192 ++++++++++++++++ .../@abp/account/src/components/index.ts | 1 + .../@abp/account/src/hooks/useOAuthService.ts | 16 +- .../packages/@abp/account/src/types/index.ts | 2 + .../@abp/account/src/types/link-user.ts | 14 ++ .../packages/@abp/account/src/types/token.ts | 10 + .../packages/@abp/account/src/utils/auth.ts | 25 ++- .../AbpAccountApplicationContractsModule.cs | 18 +- .../Abp/Account/Dto/LinkUserBaseDto.cs | 15 ++ .../LINGYUN/Abp/Account/Dto/LinkUserDto.cs | 29 +++ .../LINGYUN/Abp/Account/Dto/LinkUserInput.cs | 15 ++ .../Abp/Account/Dto/UnLinkUserInput.cs | 7 + .../Abp/Account/Dto/VerifyLinkTokenInput.cs | 14 ++ .../Abp/Account/Dto/VerifyLinkUserDto.cs | 27 +++ .../Abp/Account/Dto/VerifyLinkUserInput.cs | 5 + .../Account/IIdentityLinkUserAppService.cs | 57 +++++ .../Abp/Account/IMyProfileAppService.cs | 1 - .../Account/Localization/Resources/en.json | 13 +- .../Localization/Resources/zh-Hans.json | 13 +- .../Abp/Account/IdentityLinkUserAppService.cs | 170 ++++++++++++++ .../LINGYUN/Abp/Account/AccountController.cs | 140 ++++++------ .../Abp/Account/IdentityLinkUserController.cs | 70 ++++++ .../Controllers/AuthorizeController.cs | 20 +- .../Pages/Account/SelectAccount.cshtml.cs | 2 +- .../Pages/Account/LinkLogged.cshtml | 26 +++ .../Pages/Account/LinkLogged.cshtml.cs | 106 +++++++++ .../Pages/Account/Login.cshtml | 28 ++- .../Pages/Account/Login.cshtml.cs | 208 +++++++++++++++--- .../Pages/Account/Register.cshtml.cs | 97 +++++++- .../Pages/Account/SendCode.cshtml.cs | 20 +- .../Pages/Account/VerifyCode.cshtml.cs | 83 +++++++ .../Abp/Identity/IdentityErrorCodes.cs | 4 + .../LINGYUN/Abp/Identity/Localization/en.json | 1 + .../Abp/Identity/Localization/zh-Hans.json | 1 + .../LinkUser/AbpOpenIddictLinkUserModule.cs | 1 + .../LinkUser/LinkUserAuthorizationHandler.cs | 37 ++++ 43 files changed, 1492 insertions(+), 148 deletions(-) create mode 100644 apps/vben5/packages/@abp/account/src/api/useLinkUsersApi.ts create mode 100644 apps/vben5/packages/@abp/account/src/components/UserLinkModal.vue create mode 100644 apps/vben5/packages/@abp/account/src/types/link-user.ts create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserBaseDto.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserDto.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserInput.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/UnLinkUserInput.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkTokenInput.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserDto.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserInput.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IIdentityLinkUserAppService.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IdentityLinkUserAppService.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/IdentityLinkUserController.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml.cs create mode 100644 aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/LinkUserAuthorizationHandler.cs diff --git a/apps/vben5/apps/app-antd/src/layouts/basic.vue b/apps/vben5/apps/app-antd/src/layouts/basic.vue index c2682b198..755598abb 100644 --- a/apps/vben5/apps/app-antd/src/layouts/basic.vue +++ b/apps/vben5/apps/app-antd/src/layouts/basic.vue @@ -1,10 +1,10 @@ + + + + diff --git a/apps/vben5/packages/@abp/account/src/components/index.ts b/apps/vben5/packages/@abp/account/src/components/index.ts index 807e2f91a..3bbd032e1 100644 --- a/apps/vben5/packages/@abp/account/src/components/index.ts +++ b/apps/vben5/packages/@abp/account/src/components/index.ts @@ -1,3 +1,4 @@ export { default as MyProfile } from './MyProfile.vue'; export { default as MySetting } from './MySetting.vue'; export { default as QrCodeLogin } from './QrCodeLogin.vue'; +export { default as UserLinkModal } from './UserLinkModal.vue'; diff --git a/apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts b/apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts index 92e064aeb..d5e87dd90 100644 --- a/apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts +++ b/apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts @@ -1,4 +1,7 @@ +import type { SigninRedirectArgs, SignoutRedirectArgs } from 'oidc-client-ts'; + import type { + LinkUserTokenRequest, PasswordTokenRequestModel, PhoneNumberTokenRequest, QrCodeTokenRequest, @@ -7,8 +10,12 @@ import type { import { userManager } from '../utils/auth'; export function useOAuthService() { - async function login() { - return userManager.signinRedirect(); + async function login(args?: SigninRedirectArgs) { + return userManager.signinRedirect(args); + } + + async function loginByLinkUser(input: LinkUserTokenRequest) { + return userManager.signinLinkUser(input); } async function loginByPassword(input: PasswordTokenRequestModel) { @@ -23,8 +30,8 @@ export function useOAuthService() { return userManager.signinQrCode(input); } - async function logout() { - return userManager.signoutRedirect(); + async function logout(args?: SignoutRedirectArgs) { + return userManager.signoutRedirect(args); } async function revokeTokens() { @@ -55,6 +62,7 @@ export function useOAuthService() { return { login, + loginByLinkUser, loginByPassword, loginBySmsCode, loginByQrCode, diff --git a/apps/vben5/packages/@abp/account/src/types/index.ts b/apps/vben5/packages/@abp/account/src/types/index.ts index 0f182a66b..dae3e2f1b 100644 --- a/apps/vben5/packages/@abp/account/src/types/index.ts +++ b/apps/vben5/packages/@abp/account/src/types/index.ts @@ -1,6 +1,8 @@ export * from './account'; export * from './bind'; export * from './external-logins'; +export * from './link-user'; export * from './profile'; export * from './token'; export * from './user'; +export type { SigninRedirectArgs } from 'oidc-client-ts'; diff --git a/apps/vben5/packages/@abp/account/src/types/link-user.ts b/apps/vben5/packages/@abp/account/src/types/link-user.ts new file mode 100644 index 000000000..c80c605f4 --- /dev/null +++ b/apps/vben5/packages/@abp/account/src/types/link-user.ts @@ -0,0 +1,14 @@ +interface LinkUserDto { + directlyLinked: boolean; + linkTenantId?: string; + linkTenantName?: string; + linkUserId: string; + linkUserName: string; +} + +interface UnLinkUserInput { + tenantId?: string; + userId: string; +} + +export type { LinkUserDto, UnLinkUserInput }; diff --git a/apps/vben5/packages/@abp/account/src/types/token.ts b/apps/vben5/packages/@abp/account/src/types/token.ts index 653b5cde8..cf06a2e11 100644 --- a/apps/vben5/packages/@abp/account/src/types/token.ts +++ b/apps/vben5/packages/@abp/account/src/types/token.ts @@ -38,6 +38,15 @@ interface PasswordTokenRequestModel { /** 用户名 */ username: string; } +/** 关联用户授权请求数据模型 */ +interface LinkUserTokenRequest { + /** 当前用户访问令牌 */ + accessToken?: string; + /** 关联用户租户Id */ + linkTenantId?: string; + /** 关联用户Id */ + linkUserId: string; +} /** 令牌撤销请求数据类型 */ interface RevokeTokenRequest { /** 令牌 */ @@ -92,6 +101,7 @@ interface ShouldChangePasswordError extends OAuthError { } export type { + LinkUserTokenRequest, OAuthError, OAuthTokenRefreshModel, OAuthTokenResult, diff --git a/apps/vben5/packages/@abp/account/src/utils/auth.ts b/apps/vben5/packages/@abp/account/src/utils/auth.ts index bc15f2775..b32ca9ae9 100644 --- a/apps/vben5/packages/@abp/account/src/utils/auth.ts +++ b/apps/vben5/packages/@abp/account/src/utils/auth.ts @@ -1,6 +1,7 @@ import type { Logger, UserManagerSettings } from 'oidc-client-ts'; import type { + LinkUserTokenRequest, PasswordTokenRequestModel, PhoneNumberTokenRequest, QrCodeTokenRequest, @@ -71,6 +72,28 @@ class AbpUserManager extends UserManager { params.set('userId', model.userId); } } + async signinLinkUser(params: LinkUserTokenRequest) { + const logger = this._logger.create('signinLinkUser'); + const body = new URLSearchParams({ + LinkUserId: params.linkUserId, + grant_type: 'link_user', + client_id: this.settings.client_id, + }); + if (params.accessToken) { + body.set('access_token', params.accessToken); + } + if (params.linkTenantId) { + body.set('LinkTenantId', params.linkTenantId); + } + const client_secret = this.settings.client_secret; + if (client_secret) { + body.set('client_secret', client_secret); + } + this._writeUserId(body, params); + this._writeTenantId(body, params); + this._writeTwoFactorToken(body, params); + return await this._fetchUser(logger, body); + } async signinQrCode(params: QrCodeTokenRequest) { const logger = this._logger.create('signinQrCode'); const client_secret = this.settings.client_secret; @@ -89,7 +112,6 @@ class AbpUserManager extends UserManager { this._writeTwoFactorToken(body, params); return await this._fetchUser(logger, body); } - override async signinResourceOwnerCredentials( params: PasswordTokenRequestModel, ) { @@ -111,7 +133,6 @@ class AbpUserManager extends UserManager { this._writeChangePasswordToken(body, params); return await this._fetchUser(logger, body); } - async signinSmsCode(params: PhoneNumberTokenRequest) { const logger = this._logger.create('signinSmsCode'); const client_secret = this.settings.client_secret; diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs index d78983c51..0302277b5 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/AbpAccountApplicationContractsModule.cs @@ -1,14 +1,14 @@ using Volo.Abp.Account.Localization; using Volo.Abp.Localization; -using Volo.Abp.Modularity; +using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; -namespace LINGYUN.Abp.Account; - -[DependsOn( - typeof(Volo.Abp.Account.AbpAccountApplicationContractsModule))] -public class AbpAccountApplicationContractsModule : AbpModule -{ +namespace LINGYUN.Abp.Account; + +[DependsOn( + typeof(Volo.Abp.Account.AbpAccountApplicationContractsModule))] +public class AbpAccountApplicationContractsModule : AbpModule +{ public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -22,5 +22,5 @@ public class AbpAccountApplicationContractsModule : AbpModule .Get() .AddVirtualJson("/LINGYUN/Abp/Account/Localization/Resources"); }); - } -} + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserBaseDto.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserBaseDto.cs new file mode 100644 index 000000000..5038aefb3 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserBaseDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace LINGYUN.Abp.Account.Dto; + +public abstract class LinkUserBaseDto +{ + /// + /// 关联用户Id + /// + public Guid UserId { get; set; } + /// + /// 关联租户Id + /// + public Guid? TenantId { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserDto.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserDto.cs new file mode 100644 index 000000000..32707b87b --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserDto.cs @@ -0,0 +1,29 @@ +using System; + +namespace LINGYUN.Abp.Account.Dto; +/// +/// 关联用户Dto +/// +public class LinkUserDto +{ + /// + /// 关联用户Id + /// + public Guid LinkUserId { get; set; } + /// + /// 关联用户名 + /// + public string LinkUserName { get; set; } + /// + /// 关联租户Id + /// + public Guid? LinkTenantId { get; set; } + /// + /// 关联租户名 + /// + public string LinkTenantName { get; set; } + /// + /// 直接关联 + /// + public bool DirectlyLinked { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserInput.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserInput.cs new file mode 100644 index 000000000..37c75dfb2 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/LinkUserInput.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.Account.Dto; +/// +/// 关联用户Dto +/// +public class LinkUserInput : LinkUserBaseDto +{ + /// + /// 关联用户Token + /// + [Required] + public string Token { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/UnLinkUserInput.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/UnLinkUserInput.cs new file mode 100644 index 000000000..8423f4aac --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/UnLinkUserInput.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.Account.Dto; +/// +/// 取消关联用户Dto +/// +public class UnLinkUserInput : LinkUserBaseDto +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkTokenInput.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkTokenInput.cs new file mode 100644 index 000000000..512c16f03 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkTokenInput.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.Account.Dto; +/// +/// 验证关联用户TokenDto +/// +public class VerifyLinkTokenInput : LinkUserBaseDto +{ + /// + /// 关联用户Token + /// + [Required] + public string Token { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserDto.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserDto.cs new file mode 100644 index 000000000..23642f5c9 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserDto.cs @@ -0,0 +1,27 @@ +using System; + +namespace LINGYUN.Abp.Account.Dto; + +public class VerifyLinkUserDto +{ + /// + /// 关联用户Id + /// + public Guid? LinkUserId { get; set; } + /// + /// 关联用户名 + /// + public string LinkUserName { get; set; } + /// + /// 关联租户Id + /// + public Guid? LinkTenantId { get; set; } + /// + /// 关联租户名 + /// + public string LinkTenantName { get; set; } + /// + /// 是否已关联 + /// + public bool IsLinked { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserInput.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserInput.cs new file mode 100644 index 000000000..d406c8734 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/VerifyLinkUserInput.cs @@ -0,0 +1,5 @@ +namespace LINGYUN.Abp.Account.Dto; + +public class VerifyLinkUserInput : LinkUserBaseDto +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IIdentityLinkUserAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IIdentityLinkUserAppService.cs new file mode 100644 index 000000000..ed1dfbbd6 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IIdentityLinkUserAppService.cs @@ -0,0 +1,57 @@ +using LINGYUN.Abp.Account.Dto; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.Account; +/// +/// 关联用户应用服务接口 +/// +public interface IIdentityLinkUserAppService : IApplicationService +{ + /// + /// 关联用户 + /// + /// + /// + Task LinkAsync(LinkUserInput input); + /// + /// 取消关联用户 + /// + /// + /// + Task UnlinkAsync(UnLinkUserInput input); + /// + /// 生成关联用户Token + /// + /// + Task GenerateLinkTokenAsync(); + /// + /// 生成关联用户登录Token + /// + /// + Task GenerateLinkLoginTokenAsync(); + /// + /// 获取已关联用户列表 + /// + /// + Task> GetListAsync(); + /// + /// 验证关联用户 + /// + /// + /// + Task VerifyLinkUserAsync(VerifyLinkUserInput input); + /// + /// 验证关联用户Token + /// + /// + /// + Task VerifyLinkTokenAsync(VerifyLinkTokenInput input); + /// + /// 验证关联用户登录Token + /// + /// + /// + Task VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IMyProfileAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IMyProfileAppService.cs index 05f39b352..9a9ec4c1f 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IMyProfileAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IMyProfileAppService.cs @@ -1,5 +1,4 @@ using LINGYUN.Abp.Identity; -using System; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json index daf6584b7..c9d9c61fc 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/en.json @@ -17,6 +17,8 @@ "DisplayName:AuthenticatorCode": "Authenticator Code", "DisplayName:PhoneNumber": "Phone Number", "DisplayName:Code": "Code", + "DisplayName:LinkUserName": "User Name", + "DisplayName:DirectlyLinked": "Directly Linked", "TwoFactor": "Two factor authentication", "TwoFactor:Enabled": "TwoFactor Enabled", "TwoFactor:Email": "Email", @@ -67,6 +69,15 @@ "CancelBindWarningMessage": "After unbinding, you will not be able to use an external identity. If you log in through this method, your session will be logged out!", "EmailConfirm": "Email Confirm", "AvatarChanged": "Avatar Changed", - "ClickToValidation": "Click To Validation" + "ClickToValidation": "Click To Validation", + "LinkUser": "Link User", + "LinkUser:New": "New Link User", + "LinkUser:Login": "Log in with this account", + "LinkAccountWarning": "Please note that you are linking to other accounts, Click here to cancel the link login!", + "LinkAccountWillBeCreateWarning": "You will log out from your current account and then log in with another account. After completing the login process, the two accounts will be linked together.", + "LinkAccountWillBeDeleteWarning": "Are you sure you want to delete the associated account \"{0}\"?", + "LinkLogged": "Accounts are linked", + "StayWithCurrentAccount": "Stay with the current account", + "Settings:Identity.Account": "Account" } } \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json index 7325b8892..64de06182 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json @@ -17,6 +17,8 @@ "DisplayName:AuthenticatorCode": "验证代码", "DisplayName:PhoneNumber": "手机号码", "DisplayName:Code": "验证码", + "DisplayName:LinkUserName": "用户名", + "DisplayName:DirectlyLinked": "直接关联", "TwoFactor": "双因素身份验证", "TwoFactor:Enabled": "启用双因素认证", "TwoFactor:Email": "邮箱验证", @@ -67,6 +69,15 @@ "CancelBindWarningMessage": "解除绑定后将无法使用外部身份,如果你通过此方式登录,你的会话将被注销!", "EmailConfirm": "确认邮件", "AvatarChanged": "变更头像", - "ClickToValidation": "点击去验证" + "ClickToValidation": "点击去验证", + "LinkUser": "关联账户", + "LinkUser:New": "新关联账户", + "LinkUser:Login": "以该账号登录", + "LinkAccountWarning": "请注意,你正在关联到其他帐户,单击此处取消链接关联!", + "LinkAccountWillBeCreateWarning": "您将从当前账户退出,然后用另一个账户登录.完成登录后,两个账户将被关联起来.", + "LinkAccountWillBeDeleteWarning": "您确定要删除关联帐户\"{0}\"吗?", + "LinkLogged": "帐户已经关联", + "StayWithCurrentAccount": "继续使用当前帐户", + "Settings:Identity.Account": "用户账户" } } \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IdentityLinkUserAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IdentityLinkUserAppService.cs new file mode 100644 index 000000000..34bc88cad --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/IdentityLinkUserAppService.cs @@ -0,0 +1,170 @@ +using LINGYUN.Abp.Account.Dto; +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Users; + +namespace LINGYUN.Abp.Account; + +[Authorize] +public class IdentityLinkUserAppService : AccountApplicationServiceBase, IIdentityLinkUserAppService +{ + protected IdentityLinkUserManager IdentityLinkUserManager { get; } + + protected ITenantStore TenantStore { get; } + + public IdentityLinkUserAppService( + IdentityLinkUserManager identityLinkUserManager, + ITenantStore tenantStore) + { + IdentityLinkUserManager = identityLinkUserManager; + TenantStore = tenantStore; + } + + public async virtual Task GenerateLinkLoginTokenAsync() + { + return await IdentityLinkUserManager.GenerateLinkTokenAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + LinkUserTokenProviderConsts.LinkUserLoginTokenPurpose); + } + + public async virtual Task GenerateLinkTokenAsync() + { + return await IdentityLinkUserManager.GenerateLinkTokenAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + LinkUserTokenProviderConsts.LinkUserTokenPurpose); + } + + public async virtual Task> GetListAsync() + { + var identityLinkUserDtos = new List(); + var identityLinkUserInfo = new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id); + var identityLinkUsers = await IdentityLinkUserManager.GetListAsync(identityLinkUserInfo, includeIndirect: true); + + var currentUserId = CurrentUser.GetId(); + var currentTenantId = CurrentTenant.Id; + + var allActiveTenants = (await TenantStore.GetListAsync()).Where(x => x.IsActive); + var allIdentityLinkUserDtos = identityLinkUsers + .SelectMany(x => new[] + { + new LinkUserDto + { + LinkTenantId = x.TargetTenantId, + LinkUserId = x.TargetUserId, + DirectlyLinked = (x.SourceTenantId == currentTenantId && x.SourceUserId == currentUserId) + || (x.TargetTenantId == currentTenantId && x.TargetUserId == currentUserId) + }, + new LinkUserDto + { + LinkTenantId = x.SourceTenantId, + LinkUserId = x.SourceUserId, + DirectlyLinked = (x.SourceTenantId == currentTenantId && x.SourceUserId == currentUserId) + || (x.TargetTenantId == currentTenantId && x.TargetUserId == currentUserId) + } + }) + .Where(x => x.LinkTenantId != currentTenantId || x.LinkUserId != currentUserId) + .GroupBy(x => new { x.LinkTenantId, x.LinkUserId }) + .Select(g => g.FirstOrDefault(x => x.DirectlyLinked) ?? g.First()) + .ToList(); + + foreach (var identityLinkUserDto in allIdentityLinkUserDtos) + { + var linkTenant = allActiveTenants.FirstOrDefault(x => x.Id == identityLinkUserDto.LinkTenantId); + using (CurrentTenant.Change(linkTenant?.Id, linkTenant?.Name)) + { + var linkUser = await UserManager.FindByIdAsync(identityLinkUserDto.LinkUserId.ToString()); + if (linkUser != null) + { + identityLinkUserDto.LinkUserName = linkUser.UserName; + identityLinkUserDtos.Add( + new LinkUserDto + { + LinkTenantId = linkTenant?.Id, + LinkTenantName = linkTenant?.Name, + LinkUserId = linkUser.Id, + LinkUserName = linkUser.UserName, + DirectlyLinked = identityLinkUserDto.DirectlyLinked, + }); + } + } + } + + return new ListResultDto(identityLinkUserDtos); + } + + public async virtual Task LinkAsync(LinkUserInput input) + { + var identityLinkUserInfo = new IdentityLinkUserInfo(input.UserId, input.TenantId); + if (await IdentityLinkUserManager.VerifyLinkTokenAsync(identityLinkUserInfo, input.Token, LinkUserTokenProviderConsts.LinkUserTokenPurpose)) + { + await IdentityLinkUserManager.LinkAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + identityLinkUserInfo); + return; + } + + throw new BusinessException( + LINGYUN.Abp.Identity.IdentityErrorCodes.LinkUserTokenInValid, + "Link user token is invalid!"); + } + + public async virtual Task UnlinkAsync(UnLinkUserInput input) + { + await IdentityLinkUserManager.UnlinkAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + new IdentityLinkUserInfo(input.UserId, input.TenantId)); + } + + public async virtual Task VerifyLinkUserAsync(VerifyLinkUserInput input) + { + if (await IdentityLinkUserManager.IsLinkedAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + new IdentityLinkUserInfo(input.UserId, input.TenantId), + includeIndirect: true)) + { + using (CurrentTenant.Change(input.TenantId)) + { + TenantConfiguration tenant = null; + if (input.TenantId.HasValue) + { + tenant = await TenantStore.FindAsync(input.TenantId.Value); + } + var user = await UserManager.FindByIdAsync(input.UserId.ToString()); + + return new VerifyLinkUserDto + { + LinkTenantId = tenant?.Id, + LinkTenantName = tenant?.Name, + LinkUserId = user?.Id, + LinkUserName = user?.UserName, + IsLinked = user != null + }; + } + } + return new VerifyLinkUserDto { IsLinked = false }; + } + + [AllowAnonymous] + public async virtual Task VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input) + { + return await IdentityLinkUserManager.VerifyLinkTokenAsync( + new IdentityLinkUserInfo(input.UserId, input.TenantId), + input.Token, + LinkUserTokenProviderConsts.LinkUserLoginTokenPurpose); + } + + [AllowAnonymous] + public async virtual Task VerifyLinkTokenAsync(VerifyLinkTokenInput input) + { + return await IdentityLinkUserManager.VerifyLinkTokenAsync( + new IdentityLinkUserInfo(input.UserId, input.TenantId), + input.Token, + LinkUserTokenProviderConsts.LinkUserTokenPurpose); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs index 39b222546..2aab510c0 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs @@ -1,77 +1,77 @@ -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Account; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Account; using Volo.Abp.Application.Dtos; -using Volo.Abp.AspNetCore.Mvc; - -namespace LINGYUN.Abp.Account; - -[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] -[Area("account")] -[Route("api/account")] -public class AccountController : AbpControllerBase, IAccountAppService -{ - protected IAccountAppService AccountAppService { get; } - public AccountController( - IAccountAppService accountAppService) - { - AccountAppService = accountAppService; - } - - [HttpPost] - [Route("wechat/register")] - public async virtual Task RegisterAsync(WeChatRegisterDto input) - { - await AccountAppService.RegisterAsync(input); - } - - [HttpPost] - [Route("phone/register")] - public async virtual Task RegisterAsync(PhoneRegisterDto input) - { - await AccountAppService.RegisterAsync(input); - } - - [HttpPut] - [Route("phone/reset-password")] - public async virtual Task ResetPasswordAsync(PhoneResetPasswordDto input) - { - await AccountAppService.ResetPasswordAsync(input); - } - - [HttpPost] - [Route("phone/send-signin-code")] - public async virtual Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input) - { - await AccountAppService.SendPhoneSigninCodeAsync(input); - } - - [HttpPost] - [Route("email/send-signin-code")] +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.Account; + +[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] +[Area("account")] +[Route("api/account")] +public class AccountController : AbpControllerBase, IAccountAppService +{ + protected IAccountAppService AccountAppService { get; } + public AccountController( + IAccountAppService accountAppService) + { + AccountAppService = accountAppService; + } + + [HttpPost] + [Route("wechat/register")] + public async virtual Task RegisterAsync(WeChatRegisterDto input) + { + await AccountAppService.RegisterAsync(input); + } + + [HttpPost] + [Route("phone/register")] + public async virtual Task RegisterAsync(PhoneRegisterDto input) + { + await AccountAppService.RegisterAsync(input); + } + + [HttpPut] + [Route("phone/reset-password")] + public async virtual Task ResetPasswordAsync(PhoneResetPasswordDto input) + { + await AccountAppService.ResetPasswordAsync(input); + } + + [HttpPost] + [Route("phone/send-signin-code")] + public async virtual Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input) + { + await AccountAppService.SendPhoneSigninCodeAsync(input); + } + + [HttpPost] + [Route("email/send-signin-code")] public async virtual Task SendEmailSigninCodeAsync(SendEmailSigninCodeDto input) { await AccountAppService.SendEmailSigninCodeAsync(input); - } - - [HttpPost] - [Route("phone/send-register-code")] - public async virtual Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input) - { - await AccountAppService.SendPhoneRegisterCodeAsync(input); - } - - [HttpPost] - [Route("phone/send-password-reset-code")] - public async virtual Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input) - { - await AccountAppService.SendPhoneResetPasswordCodeAsync(input); - } - - [HttpGet] - [Route("two-factor-providers")] + } + + [HttpPost] + [Route("phone/send-register-code")] + public async virtual Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input) + { + await AccountAppService.SendPhoneRegisterCodeAsync(input); + } + + [HttpPost] + [Route("phone/send-password-reset-code")] + public async virtual Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input) + { + await AccountAppService.SendPhoneResetPasswordCodeAsync(input); + } + + [HttpGet] + [Route("two-factor-providers")] public async virtual Task> GetTwoFactorProvidersAsync(GetTwoFactorProvidersInput input) { return await AccountAppService.GetTwoFactorProvidersAsync(input); - } -} + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/IdentityLinkUserController.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/IdentityLinkUserController.cs new file mode 100644 index 000000000..9f05614e5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/IdentityLinkUserController.cs @@ -0,0 +1,70 @@ +using LINGYUN.Abp.Account.Dto; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Account; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.Account; + +[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] +[Area("account")] +[Route("api/account/link-users")] +public class IdentityLinkUserController(IIdentityLinkUserAppService _service) : AbpControllerBase, IIdentityLinkUserAppService +{ + [HttpPost] + [Route("generate-link-login-token")] + public virtual Task GenerateLinkLoginTokenAsync() + { + return _service.GenerateLinkLoginTokenAsync(); + } + + [HttpPost] + [Route("generate-link-token")] + public virtual Task GenerateLinkTokenAsync() + { + return _service.GenerateLinkTokenAsync(); + } + + [HttpGet] + public virtual Task> GetListAsync() + { + return _service.GetListAsync(); + } + + [HttpPost] + [Route("link")] + public virtual Task LinkAsync(LinkUserInput input) + { + return _service.LinkAsync(input); + } + + [HttpPost] + [Route("unlink")] + public virtual Task UnlinkAsync(UnLinkUserInput input) + { + return _service.UnlinkAsync(input); + } + + [HttpPost] + [Route("verify-link-user")] + public virtual Task VerifyLinkUserAsync(VerifyLinkUserInput input) + { + return _service.VerifyLinkUserAsync(input); + } + + [HttpPost] + [Route("verify-link-login-token")] + public virtual Task VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input) + { + return _service.VerifyLinkLoginTokenAsync(input); + } + + [HttpPost] + [Route("verify-link-token")] + public virtual Task VerifyLinkTokenAsync(VerifyLinkTokenInput input) + { + return _service.VerifyLinkTokenAsync(input); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Controllers/AuthorizeController.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Controllers/AuthorizeController.cs index 903bf8f9b..d18dfbe92 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Controllers/AuthorizeController.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Controllers/AuthorizeController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using System; @@ -46,9 +47,26 @@ public class AuthorizeController : Volo.Abp.OpenIddict.Controllers.AuthorizeCont // // For scenarios where the default authentication handler configured in the ASP.NET Core // authentication options shouldn't be used, a specific scheme can be specified here. + if (request.HasPromptValue(OpenIddictConstants.PromptValues.Login)) + { + var prompt = string.Join(" ", request.GetPromptValues().Remove(OpenIddictConstants.PromptValues.Login)); + + var parameters = Request.HasFormContentType ? + Request.Form.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList() : + Request.Query.Where(parameter => parameter.Key != OpenIddictConstants.Parameters.Prompt).ToList(); + + parameters.Add(KeyValuePair.Create(OpenIddictConstants.Parameters.Prompt, new StringValues(prompt))); + + return Challenge( + authenticationSchemes: IdentityConstants.ApplicationScheme, + properties: new AuthenticationProperties() + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) + }); + } var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); if (result is not { Succeeded: true } || - ((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 || + ((request.MaxAge is 0 || (request.MaxAge is not null && result.Properties?.IssuedUtc is not null && TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) && TempData["IgnoreAuthenticationChallenge"] is null or false)) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/SelectAccount.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/SelectAccount.cshtml.cs index 43a8490cd..7340a7219 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/SelectAccount.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/SelectAccount.cshtml.cs @@ -233,7 +233,7 @@ public class SelectAccountModel : AccountPageModel if (RedirectUri.StartsWith("/")) { - int queryIndex = RedirectUri.IndexOf('?'); + var queryIndex = RedirectUri.IndexOf('?'); if (queryIndex >= 0) { queryString = RedirectUri.Substring(queryIndex + 1); diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml new file mode 100644 index 000000000..943d86377 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization; +@using Volo.Abp.Account.Localization; +@using Volo.Abp.MultiTenancy; +@using Volo.Abp.Users; +@model LINGYUN.Abp.Account.Web.Pages.Account.LinkLoggedModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +@inject IHtmlLocalizer L +@inject ICurrentUser CurrentUser +@inject ICurrentTenant CurrentTenant +@{ + PageLayout.Content.Title = L["LinkLogged"].Value; +} + +
+ +
+ + +
+ @L["LogInUsingYourProviderAccount", Model.LinkTenantAndUserName] → +
+
+
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml.cs new file mode 100644 index 000000000..47a5649ec --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/LinkLogged.cshtml.cs @@ -0,0 +1,106 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; + +namespace LINGYUN.Abp.Account.Web.Pages.Account; + +[Authorize] +public class LinkLoggedModel : AccountPageModel +{ + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid LinkUserId { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid? LinkTenantId { get; set; } + + public string LinkTenantAndUserName { get; set; } + + protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyServiceProvider.LazyGetRequiredService(); + public IIdentityLinkUserAppService IdentityLinkUserAppService => LazyServiceProvider.LazyGetRequiredService(); + public IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache => LazyServiceProvider.LazyGetRequiredService(); + + public virtual async Task OnGetAsync() + { + var validLinkUser = await IdentityLinkUserAppService.VerifyLinkUserAsync( + new Dto.VerifyLinkUserInput + { + UserId = LinkUserId, + TenantId = LinkTenantId, + }); + if (!validLinkUser.IsLinked) + { + return await RedirectToLoginPageAsync(); + } + LinkTenantAndUserName = !validLinkUser.LinkTenantName.IsNullOrWhiteSpace() + ? $"{validLinkUser.LinkTenantName}\\{validLinkUser.LinkUserName}" + : validLinkUser.LinkUserName; + + return Page(); + } + + public async virtual Task OnPostAsync() + { + if (LinkUserId == CurrentUser.Id && LinkTenantId == CurrentTenant.Id) + { + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); + } + + using (CurrentTenant.Change(LinkTenantId)) + { + var sourceUser = await UserManager.GetByIdAsync(LinkUserId); + using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(sourceUser))) + { + var validLinkUser = await IdentityLinkUserAppService.VerifyLinkUserAsync( + new Dto.VerifyLinkUserInput + { + UserId = LinkUserId, + TenantId = LinkTenantId, + }); + if (validLinkUser.IsLinked) + { + var isPersistent = (await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme))?.Properties?.IsPersistent ?? false; + await SignInManager.SignOutAsync(); + using (CurrentTenant.Change(LinkTenantId)) + { + var targetUser = await UserManager.GetByIdAsync(LinkUserId); + await SignInManager.SignInAsync(targetUser, isPersistent); + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(targetUser.Id, targetUser.TenantId); + } + + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); + } + } + } + + Alerts.Warning(L["Volo.Abp.Identity:InvalidToken"]); + return Page(); + } + + public virtual Task GetReturnUrlAsync(string returnUrl, string returnUrlHash) + { + return base.GetRedirectUrlAsync(returnUrl, returnUrlHash); + } + + protected virtual Task RedirectToLoginPageAsync() + { + return Task.FromResult(RedirectToPage("./Login", new + { + ReturnUrl, + ReturnUrlHash + })); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml index 0cd0de615..dac94f917 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml @@ -24,13 +24,23 @@ }