committed by
GitHub
43 changed files with 1507 additions and 148 deletions
@ -1,5 +1,6 @@ |
|||
export { useAccountApi } from './useAccountApi'; |
|||
export { useExternalLoginsApi } from './useExternalLoginsApi'; |
|||
export { useLinkUsersApi } from './useLinkUsersApi'; |
|||
export { useMySessionApi } from './useMySessionApi'; |
|||
export { useProfileApi } from './useProfileApi'; |
|||
export { useScanQrCodeApi } from './useScanQrCodeApi'; |
|||
|
|||
@ -0,0 +1,54 @@ |
|||
import type { ListResultDto } from '@abp/core'; |
|||
|
|||
import type { LinkUserDto, UnLinkUserInput } from '../types/link-user'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useLinkUsersApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 查询关联用户列表 |
|||
* @returns { Promise<ListResultDto<LinkUserDto>> } 关联用户列表 |
|||
*/ |
|||
function getListApi(): Promise<ListResultDto<LinkUserDto>> { |
|||
return request<ListResultDto<LinkUserDto>>('api/account/link-users', { |
|||
method: 'GET', |
|||
}); |
|||
} |
|||
/** |
|||
* 生成关联用户Token |
|||
*/ |
|||
function generateLinkTokenApi(): Promise<string> { |
|||
return request(`api/account/link-users/generate-link-token`, { |
|||
method: 'POST', |
|||
}); |
|||
} |
|||
/** |
|||
* 生成关联用户登录Token |
|||
*/ |
|||
function generateLinkLoginTokenApi(): Promise<string> { |
|||
return request(`api/account/link-users/generate-link-login-token`, { |
|||
method: 'POST', |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 取消用户关联 |
|||
* @param input 参数 |
|||
*/ |
|||
function unLinkApi(input: UnLinkUserInput): Promise<void> { |
|||
return request(`api/account/link-users/unlink`, { |
|||
method: 'POST', |
|||
data: input, |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getListApi, |
|||
generateLinkTokenApi, |
|||
generateLinkLoginTokenApi, |
|||
unLinkApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
<script setup lang="ts"> |
|||
import type { VxeGridProps } from '@abp/ui'; |
|||
|
|||
import type { LinkUserDto } from '../types'; |
|||
|
|||
import { h, ref } from 'vue'; |
|||
|
|||
import { confirm, useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { sortby } from '@abp/core'; |
|||
import { useMessage, useVbenVxeGrid } from '@abp/ui'; |
|||
import { |
|||
CheckOutlined, |
|||
CloseOutlined, |
|||
DeleteOutlined, |
|||
LoginOutlined, |
|||
PlusOutlined, |
|||
} from '@ant-design/icons-vue'; |
|||
import { Button } from 'ant-design-vue'; |
|||
|
|||
import { useLinkUsersApi } from '../api/useLinkUsersApi'; |
|||
|
|||
const emit = defineEmits<{ |
|||
(event: 'link', token: string): void; |
|||
(event: 'login', userId: string, tenantId?: string): void; |
|||
}>(); |
|||
|
|||
const message = useMessage(); |
|||
const { getListApi, generateLinkTokenApi, unLinkApi } = useLinkUsersApi(); |
|||
|
|||
const linkUsers = ref<LinkUserDto[]>([]); |
|||
|
|||
const [Modal, modalApi] = useVbenModal({ |
|||
async onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
await onInit(); |
|||
} |
|||
}, |
|||
title: $t('AbpAccount.LinkUser'), |
|||
}); |
|||
const gridOptions: VxeGridProps<LinkUserDto> = { |
|||
columns: [ |
|||
{ |
|||
align: 'left', |
|||
field: 'linkUserName', |
|||
sortable: true, |
|||
title: $t('AbpAccount.DisplayName:LinkUserName'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'directlyLinked', |
|||
slots: { default: 'directlyLinked' }, |
|||
sortable: true, |
|||
title: $t('AbpAccount.DisplayName:DirectlyLinked'), |
|||
width: 150, |
|||
}, |
|||
{ |
|||
field: 'actions', |
|||
fixed: 'right', |
|||
slots: { default: 'actions' }, |
|||
title: $t('AbpUi.Actions'), |
|||
width: 220, |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page, sort }) => { |
|||
let items = sortby(linkUsers.value, sort.field); |
|||
if (sort.order === 'desc') { |
|||
items = items.toReversed(); |
|||
} |
|||
const result = { |
|||
totalCount: linkUsers.value.length, |
|||
items: items.slice( |
|||
(page.currentPage - 1) * page.pageSize, |
|||
page.currentPage * page.pageSize, |
|||
), |
|||
}; |
|||
return new Promise((resolve) => { |
|||
resolve(result); |
|||
}); |
|||
}, |
|||
}, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
sortConfig: { |
|||
remote: true, |
|||
allowBtn: true, |
|||
}, |
|||
toolbarConfig: { |
|||
custom: true, |
|||
export: true, |
|||
zoom: true, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ |
|||
gridOptions, |
|||
}); |
|||
|
|||
async function onInit() { |
|||
const { items } = await getListApi(); |
|||
linkUsers.value = items; |
|||
gridApi.query(); |
|||
} |
|||
|
|||
async function onCreatLink() { |
|||
confirm({ |
|||
async beforeClose(scope) { |
|||
if (scope.isConfirm) { |
|||
const token = await generateLinkTokenApi(); |
|||
emit('link', token); |
|||
} |
|||
}, |
|||
centered: true, |
|||
content: $t('AbpAccount.LinkAccountWillBeCreateWarning'), |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
|
|||
function onLoginByLink(record: LinkUserDto) { |
|||
emit('login', record.linkUserId, record.linkTenantId); |
|||
modalApi.close(); |
|||
} |
|||
|
|||
async function onDeleteLink(record: LinkUserDto) { |
|||
confirm({ |
|||
async beforeClose(scope) { |
|||
if (scope.isConfirm) { |
|||
await unLinkApi({ |
|||
userId: record.linkUserId, |
|||
tenantId: record.linkTenantId, |
|||
}); |
|||
message.success($t('AbpUi.DeletedSuccessfully')); |
|||
await onInit(); |
|||
} |
|||
}, |
|||
centered: true, |
|||
content: $t('AbpAccount.LinkAccountWillBeDeleteWarning', [ |
|||
record.linkUserName, |
|||
]), |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Grid> |
|||
<template #toolbar-tools> |
|||
<Button :icon="h(PlusOutlined)" type="primary" @click="onCreatLink"> |
|||
{{ $t('AbpAccount.LinkUser:New') }} |
|||
</Button> |
|||
</template> |
|||
<template #directlyLinked="{ row }"> |
|||
<div class="flex flex-row justify-center"> |
|||
<CheckOutlined v-if="row.directlyLinked" class="text-green-500" /> |
|||
<CloseOutlined v-else class="text-red-500" /> |
|||
</div> |
|||
</template> |
|||
<template #actions="{ row }"> |
|||
<div class="flex flex-row"> |
|||
<Button |
|||
:icon="h(LoginOutlined)" |
|||
block |
|||
type="link" |
|||
@click="onLoginByLink(row)" |
|||
> |
|||
{{ $t('AbpAccount.LinkUser:Login') }} |
|||
</Button> |
|||
<Button |
|||
:icon="h(DeleteOutlined)" |
|||
block |
|||
danger |
|||
type="link" |
|||
@click="onDeleteLink(row)" |
|||
> |
|||
{{ $t('AbpUi.Delete') }} |
|||
</Button> |
|||
</div> |
|||
</template> |
|||
</Grid> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -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'; |
|||
|
|||
@ -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'; |
|||
|
|||
@ -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 }; |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
|
|||
public abstract class LinkUserBaseDto |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户Id
|
|||
/// </summary>
|
|||
public Guid UserId { get; set; } |
|||
/// <summary>
|
|||
/// 关联租户Id
|
|||
/// </summary>
|
|||
public Guid? TenantId { get; set; } |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
/// <summary>
|
|||
/// 关联用户Dto
|
|||
/// </summary>
|
|||
public class LinkUserDto |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户Id
|
|||
/// </summary>
|
|||
public Guid LinkUserId { get; set; } |
|||
/// <summary>
|
|||
/// 关联用户名
|
|||
/// </summary>
|
|||
public string LinkUserName { get; set; } |
|||
/// <summary>
|
|||
/// 关联租户Id
|
|||
/// </summary>
|
|||
public Guid? LinkTenantId { get; set; } |
|||
/// <summary>
|
|||
/// 关联租户名
|
|||
/// </summary>
|
|||
public string LinkTenantName { get; set; } |
|||
/// <summary>
|
|||
/// 直接关联
|
|||
/// </summary>
|
|||
public bool DirectlyLinked { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
/// <summary>
|
|||
/// 关联用户Dto
|
|||
/// </summary>
|
|||
public class LinkUserInput : LinkUserBaseDto |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户Token
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Token { get; set; } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
/// <summary>
|
|||
/// 取消关联用户Dto
|
|||
/// </summary>
|
|||
public class UnLinkUserInput : LinkUserBaseDto |
|||
{ |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
/// <summary>
|
|||
/// 验证关联用户TokenDto
|
|||
/// </summary>
|
|||
public class VerifyLinkTokenInput : LinkUserBaseDto |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户Token
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Token { get; set; } |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
|
|||
public class VerifyLinkUserDto |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户Id
|
|||
/// </summary>
|
|||
public Guid? LinkUserId { get; set; } |
|||
/// <summary>
|
|||
/// 关联用户名
|
|||
/// </summary>
|
|||
public string LinkUserName { get; set; } |
|||
/// <summary>
|
|||
/// 关联租户Id
|
|||
/// </summary>
|
|||
public Guid? LinkTenantId { get; set; } |
|||
/// <summary>
|
|||
/// 关联租户名
|
|||
/// </summary>
|
|||
public string LinkTenantName { get; set; } |
|||
/// <summary>
|
|||
/// 是否已关联
|
|||
/// </summary>
|
|||
public bool IsLinked { get; set; } |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace LINGYUN.Abp.Account.Dto; |
|||
|
|||
public class VerifyLinkUserInput : LinkUserBaseDto |
|||
{ |
|||
} |
|||
@ -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; |
|||
/// <summary>
|
|||
/// 关联用户应用服务接口
|
|||
/// </summary>
|
|||
public interface IIdentityLinkUserAppService : IApplicationService |
|||
{ |
|||
/// <summary>
|
|||
/// 关联用户
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task LinkAsync(LinkUserInput input); |
|||
/// <summary>
|
|||
/// 取消关联用户
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task UnlinkAsync(UnLinkUserInput input); |
|||
/// <summary>
|
|||
/// 生成关联用户Token
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
Task<string> GenerateLinkTokenAsync(); |
|||
/// <summary>
|
|||
/// 生成关联用户登录Token
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
Task<string> GenerateLinkLoginTokenAsync(); |
|||
/// <summary>
|
|||
/// 获取已关联用户列表
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
Task<ListResultDto<LinkUserDto>> GetListAsync(); |
|||
/// <summary>
|
|||
/// 验证关联用户
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task<VerifyLinkUserDto> VerifyLinkUserAsync(VerifyLinkUserInput input); |
|||
/// <summary>
|
|||
/// 验证关联用户Token
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> VerifyLinkTokenAsync(VerifyLinkTokenInput input); |
|||
/// <summary>
|
|||
/// 验证关联用户登录Token
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input); |
|||
} |
|||
@ -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<string> GenerateLinkLoginTokenAsync() |
|||
{ |
|||
return await IdentityLinkUserManager.GenerateLinkTokenAsync( |
|||
new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), |
|||
LinkUserTokenProviderConsts.LinkUserLoginTokenPurpose); |
|||
} |
|||
|
|||
public async virtual Task<string> GenerateLinkTokenAsync() |
|||
{ |
|||
return await IdentityLinkUserManager.GenerateLinkTokenAsync( |
|||
new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), |
|||
LinkUserTokenProviderConsts.LinkUserTokenPurpose); |
|||
} |
|||
|
|||
public async virtual Task<ListResultDto<LinkUserDto>> GetListAsync() |
|||
{ |
|||
var identityLinkUserDtos = new List<LinkUserDto>(); |
|||
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<LinkUserDto>(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<VerifyLinkUserDto> 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<bool> VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input) |
|||
{ |
|||
return await IdentityLinkUserManager.VerifyLinkTokenAsync( |
|||
new IdentityLinkUserInfo(input.UserId, input.TenantId), |
|||
input.Token, |
|||
LinkUserTokenProviderConsts.LinkUserLoginTokenPurpose); |
|||
} |
|||
|
|||
[AllowAnonymous] |
|||
public async virtual Task<bool> VerifyLinkTokenAsync(VerifyLinkTokenInput input) |
|||
{ |
|||
return await IdentityLinkUserManager.VerifyLinkTokenAsync( |
|||
new IdentityLinkUserInfo(input.UserId, input.TenantId), |
|||
input.Token, |
|||
LinkUserTokenProviderConsts.LinkUserTokenPurpose); |
|||
} |
|||
} |
|||
@ -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<ListResultDto<NameValue>> GetTwoFactorProvidersAsync(GetTwoFactorProvidersInput input) |
|||
{ |
|||
return await AccountAppService.GetTwoFactorProvidersAsync(input); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -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<string> GenerateLinkLoginTokenAsync() |
|||
{ |
|||
return _service.GenerateLinkLoginTokenAsync(); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("generate-link-token")] |
|||
public virtual Task<string> GenerateLinkTokenAsync() |
|||
{ |
|||
return _service.GenerateLinkTokenAsync(); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<ListResultDto<LinkUserDto>> 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<VerifyLinkUserDto> VerifyLinkUserAsync(VerifyLinkUserInput input) |
|||
{ |
|||
return _service.VerifyLinkUserAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("verify-link-login-token")] |
|||
public virtual Task<bool> VerifyLinkLoginTokenAsync(VerifyLinkTokenInput input) |
|||
{ |
|||
return _service.VerifyLinkLoginTokenAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("verify-link-token")] |
|||
public virtual Task<bool> VerifyLinkTokenAsync(VerifyLinkTokenInput input) |
|||
{ |
|||
return _service.VerifyLinkTokenAsync(input); |
|||
} |
|||
} |
|||
@ -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<AccountResource> L |
|||
@inject ICurrentUser CurrentUser |
|||
@inject ICurrentTenant CurrentTenant |
|||
@{ |
|||
PageLayout.Content.Title = L["LinkLogged"].Value; |
|||
} |
|||
|
|||
<div> |
|||
<div class="gap-2 d-grid"> |
|||
<a href="@Url.Content(await Model.GetReturnUrlAsync(Model.ReturnUrl, Model.ReturnUrlHash))" class="mb-3 btn btn-primary"> @L["StayWithCurrentAccount"]</a> |
|||
</div> |
|||
<form method="post" asp-page="/Account/LinkLogged" id="linkUserLoginForm" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash"> |
|||
<input asp-for="LinkTenantId"> |
|||
<input asp-for="LinkUserId"> |
|||
<div class="gap-2 d-grid"> |
|||
<abp-button class="btn-secondary">@L["LogInUsingYourProviderAccount", Model.LinkTenantAndUserName] →</abp-button> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
@ -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<ICurrentPrincipalAccessor>(); |
|||
public IIdentityLinkUserAppService IdentityLinkUserAppService => LazyServiceProvider.LazyGetRequiredService<IIdentityLinkUserAppService>(); |
|||
public IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache => LazyServiceProvider.LazyGetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>(); |
|||
|
|||
public virtual async Task<IActionResult> 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<IActionResult> 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<string> GetReturnUrlAsync(string returnUrl, string returnUrlHash) |
|||
{ |
|||
return base.GetRedirectUrlAsync(returnUrl, returnUrlHash); |
|||
} |
|||
|
|||
protected virtual Task<IActionResult> RedirectToLoginPageAsync() |
|||
{ |
|||
return Task.FromResult<IActionResult>(RedirectToPage("./Login", new |
|||
{ |
|||
ReturnUrl, |
|||
ReturnUrlHash |
|||
})); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using Microsoft.AspNetCore; |
|||
using OpenIddict.Server; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.LinkUser; |
|||
|
|||
public class LinkUserAuthorizationHandler : IOpenIddictServerHandler<ValidateAuthorizationRequestContext> |
|||
{ |
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>() |
|||
.UseScopedHandler<LinkUserAuthorizationHandler>() |
|||
.SetOrder(2000) |
|||
.SetType(OpenIddictServerHandlerType.Custom) |
|||
.Build(); |
|||
|
|||
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) |
|||
{ |
|||
var request = context.Transaction.GetHttpRequest(); |
|||
if (request == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var linkUserId = request.Query["LinkUserId"].FirstOrDefault(); |
|||
var linkToken = request.Query["LinkToken"].FirstOrDefault(); |
|||
|
|||
if (string.IsNullOrEmpty(linkUserId) || string.IsNullOrEmpty(linkToken)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
context.Transaction.SetProperty("LinkUserId", linkUserId); |
|||
context.Transaction.SetProperty("LinkToken", linkToken); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue