Browse Source

feat: 增加二次认证登陆实现.

pull/1081/head
colin 1 year ago
parent
commit
898e740114
  1. 2
      apps/vben5/apps/app-antd/.env.development
  2. 47
      apps/vben5/apps/app-antd/src/adapter/form.ts
  3. 5
      apps/vben5/apps/app-antd/src/adapter/request/index.ts
  4. 15
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  5. 15
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  6. 39
      apps/vben5/apps/app-antd/src/views/_core/authentication/login.vue
  7. 206
      apps/vben5/apps/app-antd/src/views/_core/authentication/two-factor-login.vue
  8. 3
      apps/vben5/packages/@abp/account/package.json
  9. 1
      apps/vben5/packages/@abp/account/src/api/index.ts
  10. 63
      apps/vben5/packages/@abp/account/src/api/useAccountApi.ts
  11. 28
      apps/vben5/packages/@abp/account/src/api/useProfileApi.ts
  12. 3
      apps/vben5/packages/@abp/account/src/api/useTokenApi.ts
  13. 93
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  14. 6
      apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue
  15. 60
      apps/vben5/packages/@abp/account/src/components/components/EmailConfirmModal.vue
  16. 62
      apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue
  17. 22
      apps/vben5/packages/@abp/account/src/types/account.ts
  18. 1
      apps/vben5/packages/@abp/account/src/types/index.ts
  19. 12
      apps/vben5/packages/@abp/account/src/types/profile.ts
  20. 16
      apps/vben5/packages/@abp/account/src/types/token.ts
  21. 3
      apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue
  22. 1
      apps/vben5/packages/@abp/settings/src/types/settings.ts

2
apps/vben5/apps/app-antd/.env.development

@ -10,7 +10,7 @@ VITE_GLOB_API_URL=/
VITE_NITRO_MOCK=true
# 是否打开 devtools,true 为打开,false 为关闭
VITE_DEVTOOLS=false
VITE_DEVTOOLS=true
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true

47
apps/vben5/apps/app-antd/src/adapter/form.ts

@ -0,0 +1,47 @@
import type {
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };

5
apps/vben5/apps/app-antd/src/adapter/request/index.ts

@ -1,3 +1,4 @@
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
@ -105,6 +106,10 @@ export function initRequestClient() {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
// 当前mock接口返回的错误字段是 error 或者 message
const responseData = error?.response?.data ?? {};
if (responseData?.error_description) {
message.error($t(`abp.oauth.${responseData.error_description}`) || msg);
return;
}
const errorMessage = responseData?.error ?? responseData?.message ?? '';
// 如果没有错误信息,则会根据状态码进行提示
message.error(errorMessage || msg);

15
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -1,5 +1,19 @@
{
"title": "Abp Framework",
"oauth": {
"Invalid username or password!": "Invalid username or password!",
"Invalid authenticator code!": "Invalid authenticator code!",
"The specified refresh token is no longer valid.": "The session has expired. Please log in again!",
"RequiresTwoFactor": "Requires Two Factor",
"twoFactor": {
"title": "Two Factor",
"authenticator": "Authenticator",
"emailAddress": "Email Address",
"phoneNumber": "Phone Number",
"getCode": "Get Code",
"code": "Code"
}
},
"manage": {
"title": "Manage",
"identity": {
@ -38,6 +52,7 @@
},
"security": {
"title": "Security Settings",
"unSet": "Not Set",
"verified": "Verified",
"unVerified": "Not Verified",
"email": "Email",

15
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -1,5 +1,19 @@
{
"title": "Abp框架",
"oauth": {
"Invalid username or password!": "用户名或密码错误!",
"Invalid authenticator code!": "无效的验证器代码!",
"The specified refresh token is no longer valid.": "会话已过期,请重新登陆!",
"RequiresTwoFactor": "需要二次认证",
"twoFactor": {
"title": "二次认证",
"authenticator": "验证方式",
"emailAddress": "电子邮件",
"phoneNumber": "手机号码",
"getCode": "获取验证码",
"code": "验证码"
}
},
"manage": {
"title": "管理",
"identity": {
@ -38,6 +52,7 @@
},
"security": {
"title": "安全设置",
"unSet": "未设置",
"verified": "已验证",
"unVerified": "未验证",
"email": "电子邮件",

39
apps/vben5/apps/app-antd/src/views/_core/authentication/login.vue

@ -1,13 +1,17 @@
<script lang="ts" setup>
import type { TwoFactorError } from '@abp/account';
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed } from 'vue';
import { AuthenticationLogin, z } from '@vben/common-ui';
import { AuthenticationLogin, useVbenModal, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAuthStore } from '#/store';
import TwoFactorLogin from './two-factor-login.vue';
defineOptions({ name: 'Login' });
const authStore = useAuthStore();
@ -34,12 +38,35 @@ const formSchema = computed((): VbenFormSchema[] => {
},
];
});
const [TwoFactorModal, twoFactorModalApi] = useVbenModal({
connectedComponent: TwoFactorLogin,
});
async function onLogin(params: Recordable<any>) {
try {
await authStore.authLogin(params);
} catch (error) {
onTwoFactorError(params, error);
}
}
function onTwoFactorError(params: Recordable<any>, error: any) {
const tfaError = error as TwoFactorError;
if (tfaError.twoFactorToken) {
twoFactorModalApi.setData({
...tfaError,
...params,
});
twoFactorModalApi.open();
}
}
</script>
<template>
<AuthenticationLogin
:form-schema="formSchema"
:loading="authStore.loginLoading"
@submit="authStore.authLogin"
/>
<div>
<AuthenticationLogin
:form-schema="formSchema"
:loading="authStore.loginLoading"
@submit="onLogin"
/>
<TwoFactorModal />
</div>
</template>

206
apps/vben5/apps/app-antd/src/views/_core/authentication/two-factor-login.vue

@ -0,0 +1,206 @@
<script setup lang="ts">
import type { TwoFactorProvider } from '@abp/account';
import type { FormExpose } from 'ant-design-vue/es/form/Form';
import { computed, ref, toValue, useTemplateRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAccountApi } from '@abp/account';
import { Button, Form, Input, Select } from 'ant-design-vue';
import { useAuthStore } from '#/store/auth';
const FormItem = Form.Item;
interface TwoFactorState {
username: string;
password: string;
twoFactorToken: string;
userId: string;
twoFactorProvider: string;
code: string;
email: string;
phoneNumber: string;
}
const formModel = ref({} as TwoFactorState);
const form = useTemplateRef<FormExpose>('formRef');
const twoFactorProviders = ref<TwoFactorProvider[]>([]);
const sendValidCodeLoading = ref(false);
const sendValidCodeInternal = ref(0);
const getSendValidCodeLoading = computed(() => {
return sendValidCodeInternal.value > 0;
});
const getSendValidCodeTitle = computed(() => {
if (sendValidCodeInternal.value > 0) {
return `${sendValidCodeInternal.value}`;
}
return $t('abp.oauth.twoFactor.getCode');
});
const authStore = useAuthStore();
const {
getTwoFactorProvidersApi,
sendEmailSigninCodeApi,
sendPhoneSigninCodeApi,
} = useAccountApi();
const [Modal, modalApi] = useVbenModal({
title: $t('abp.oauth.twoFactor.title'),
fullscreenButton: false,
closeOnClickModal: false,
closeOnPressEscape: false,
onConfirm: onLogin,
async onOpenChange(isOpen) {
if (isOpen) {
const state = modalApi.getData<TwoFactorState>();
formModel.value = state;
const { items } = await getTwoFactorProvidersApi({
userId: state.userId,
});
twoFactorProviders.value = items;
}
},
});
/** 二次认证登陆 */
async function onLogin() {
modalApi.setState({
confirmLoading: true,
closable: false,
});
try {
await form.value?.validate();
const model = toValue(formModel);
await authStore.authLogin({
username: model.username,
password: model.password,
TwoFactorProvider: model.twoFactorProvider,
TwoFactorCode: model.code,
});
} finally {
modalApi.setState({
confirmLoading: false,
closable: true,
});
}
}
/** 发送邮件验证代码 */
async function onSendEmail() {
await form.value?.validateFields('email');
await onSendCode(() =>
sendEmailSigninCodeApi({
emailAddress: formModel.value.email,
}),
);
}
/** 发送短信验证代码 */
async function onSendSms() {
await form.value?.validateFields('phoneNumber');
await onSendCode(() =>
sendPhoneSigninCodeApi({
phoneNumber: formModel.value.phoneNumber,
}),
);
}
/** 发送验证代码 */
async function onSendCode(api: () => Promise<void>) {
try {
sendValidCodeLoading.value = true;
sendValidCodeInternal.value = 60;
await api();
setInterval(() => {
if (sendValidCodeInternal.value <= 0) {
return;
}
sendValidCodeInternal.value -= 1;
}, 1000);
} catch {
sendValidCodeInternal.value = 0;
} finally {
sendValidCodeLoading.value = false;
}
}
</script>
<template>
<Modal>
<Form
ref="formRef"
:label-col="{ span: 6 }"
:model="formModel"
:wrapper-col="{ span: 18 }"
>
<!-- 选择验证器 -->
<FormItem
:label="$t('abp.oauth.twoFactor.authenticator')"
name="twoFactorProvider"
required
>
<Select
v-model:value="formModel.twoFactorProvider"
:field-names="{ label: 'name', value: 'value' }"
:options="twoFactorProviders"
allow-clear
/>
</FormItem>
<!-- 邮件验证 -->
<template v-if="formModel.twoFactorProvider === 'Email'">
<FormItem
:label="$t('abp.oauth.twoFactor.emailAddress')"
name="email"
required
>
<Input
v-model:value="formModel.email"
autocomplete="off"
type="email"
/>
</FormItem>
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required>
<div class="flex flex-row gap-4">
<Input v-model:value="formModel.code" autocomplete="off" />
<Button
:disabled="getSendValidCodeLoading"
:loading="sendValidCodeLoading"
@click="onSendEmail"
>
{{ getSendValidCodeTitle }}
</Button>
</div>
</FormItem>
</template>
<!-- 手机号码验证 -->
<template v-if="formModel.twoFactorProvider === 'Phone'">
<FormItem
:label="$t('abp.oauth.twoFactor.phoneNumber')"
name="phoneNumber"
required
>
<Input v-model:value="formModel.phoneNumber" autocomplete="off" />
</FormItem>
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required>
<div class="flex flex-row gap-4">
<Input v-model:value="formModel.code" autocomplete="off" />
<Button
:disabled="getSendValidCodeLoading"
:loading="sendValidCodeLoading"
@click="onSendSms"
>
{{ getSendValidCodeTitle }}
</Button>
</div>
</FormItem>
</template>
<!-- 身份验证程序验证 -->
<template v-else-if="formModel.twoFactorProvider === 'Authenticator'">
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required>
<Input v-model:value="formModel.code" autocomplete="off" />
</FormItem>
</template>
</Form>
</Modal>
</template>
<style scoped></style>

3
apps/vben5/packages/@abp/account/package.json

@ -34,6 +34,7 @@
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"ant-design-vue": "catalog:",
"vue": "catalog:*"
"vue": "catalog:*",
"vue-router": "catalog:"
}
}

1
apps/vben5/packages/@abp/account/src/api/index.ts

@ -1,3 +1,4 @@
export { useAccountApi } from './useAccountApi';
export { useProfileApi } from './useProfileApi';
export { useTokenApi } from './useTokenApi';
export { useUserInfoApi } from './useUserInfoApi';

63
apps/vben5/packages/@abp/account/src/api/useAccountApi.ts

@ -0,0 +1,63 @@
import type { ListResultDto } from '@abp/core';
import type {
GetTwoFactorProvidersInput,
SendEmailSigninCodeDto,
SendPhoneSigninCodeDto,
TwoFactorProvider,
} from '../types/account';
import { useRequest } from '@abp/request';
export function useAccountApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
*/
function getTwoFactorProvidersApi(
input: GetTwoFactorProvidersInput,
): Promise<ListResultDto<TwoFactorProvider>> {
return request<ListResultDto<TwoFactorProvider>>(
'/api/account/two-factor-providers',
{
method: 'GET',
params: input,
},
);
}
/**
*
* @param input
*/
function sendEmailSigninCodeApi(
input: SendEmailSigninCodeDto,
): Promise<void> {
return request('/api/account/email/send-signin-code', {
data: input,
method: 'POST',
});
}
/**
*
* @param input
*/
function sendPhoneSigninCodeApi(
input: SendPhoneSigninCodeDto,
): Promise<void> {
return request('/api/account/phone/send-signin-code', {
data: input,
method: 'POST',
});
}
return {
cancel,
getTwoFactorProvidersApi,
sendEmailSigninCodeApi,
sendPhoneSigninCodeApi,
};
}

28
apps/vben5/packages/@abp/account/src/api/useProfileApi.ts

@ -2,7 +2,9 @@ import type {
AuthenticatorDto,
AuthenticatorRecoveryCodeDto,
ChangePasswordInput,
ConfirmEmailInput,
ProfileDto,
SendEmailConfirmCodeDto,
TwoFactorEnabledDto,
UpdateProfileDto,
VerifyAuthenticatorCodeInput,
@ -103,14 +105,40 @@ export function useProfileApi() {
});
}
/**
*
* @param input
*/
function sendEmailConfirmLinkApi(
input: SendEmailConfirmCodeDto,
): Promise<void> {
return request('/api/account/my-profile/send-email-confirm-link', {
data: input,
method: 'POST',
});
}
/**
*
* @param input
*/
function confirmEmailApi(input: ConfirmEmailInput) {
return request('/api/account/my-profile/confirm-email', {
data: input,
method: 'PUT',
});
}
return {
cancel,
changePasswordApi,
changeTwoFactorEnabledApi,
confirmEmailApi,
getApi,
getAuthenticatorApi,
getTwoFactorEnabledApi,
resetAuthenticatorApi,
sendEmailConfirmLinkApi,
updateApi,
verifyAuthenticatorCodeApi,
};

3
apps/vben5/packages/@abp/account/src/api/useTokenApi.ts

@ -28,9 +28,8 @@ export function useTokenApi() {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'password',
password: input.password,
scope: audience,
username: input.username,
...input,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',

93
apps/vben5/packages/@abp/account/src/components/MySetting.vue

@ -2,8 +2,10 @@
import type { ProfileDto, UpdateProfileDto } from '../types/profile';
import type { UserInfo } from '../types/user';
import { computed, onMounted, reactive, ref } from 'vue';
import { computed, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useUserStore } from '@vben/stores';
@ -18,6 +20,7 @@ import SecuritySettings from './components/SecuritySettings.vue';
const { getApi, updateApi } = useProfileApi();
const userStore = useUserStore();
const { query } = useRoute();
const selectedMenuKeys = ref<string[]>(['basic']);
const myProfile = ref({} as ProfileDto);
@ -59,6 +62,20 @@ const getUserInfo = computed((): null | UserInfo => {
uniqueName: userStore.userInfo.username,
};
});
const [EmailConfirmModal, emailConfirmModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/EmailConfirmModal.vue'),
),
});
function onEmailConfirm() {
if (query?.confirmToken) {
emailConfirmModalApi.setData({
email: myProfile.value.email,
...query,
});
emailConfirmModalApi.open();
}
}
async function onGetProfile() {
const profile = await getApi();
myProfile.value = profile;
@ -68,11 +85,12 @@ async function onUpdateProfile(input: UpdateProfileDto) {
centered: true,
content: $t('AbpAccount.PersonalSettingsSaved'),
onOk: async () => {
const profile = await updateApi(input);
await updateApi(input);
message.success(
$t('AbpAccount.PersonalSettingsChangedConfirmationModalTitle'),
);
myProfile.value = profile;
//
window.location.reload();
},
title: $t('AbpUi.AreYouSure'),
});
@ -85,44 +103,45 @@ function onChangePhoneNumber() {
// TODO: onChangePhoneNumber !
console.warn('onChangePhoneNumber 暂时未实现!');
}
function onValidateEmail() {
// TODO: onValidateEmail !
console.warn('onValidateEmail 暂时未实现!');
}
onMounted(onGetProfile);
onMounted(async () => {
await onGetProfile();
onEmailConfirm();
});
</script>
<template>
<Card>
<div class="flex">
<div class="basis-1/6">
<Menu
v-model:selected-keys="selectedMenuKeys"
:items="menuItems"
mode="inline"
/>
</div>
<div class="basis-5/6">
<BasicSettings
v-if="selectedMenuKeys[0] === 'basic'"
:profile="myProfile"
@submit="onUpdateProfile"
/>
<BindSettings v-else-if="selectedMenuKeys[0] === 'bind'" />
<SecuritySettings
v-else-if="selectedMenuKeys[0] === 'security'"
:user-info="getUserInfo"
@change-password="onChangePassword"
@change-phone-number="onChangePhoneNumber"
@validate-email="onValidateEmail"
/>
<NoticeSettings v-else-if="selectedMenuKeys[0] === 'notice'" />
<AuthenticatorSettings
v-else-if="selectedMenuKeys[0] === 'authenticator'"
/>
<div>
<Card>
<div class="flex">
<div class="basis-1/6">
<Menu
v-model:selected-keys="selectedMenuKeys"
:items="menuItems"
mode="inline"
/>
</div>
<div class="basis-5/6">
<BasicSettings
v-if="selectedMenuKeys[0] === 'basic'"
:profile="myProfile"
@submit="onUpdateProfile"
/>
<BindSettings v-else-if="selectedMenuKeys[0] === 'bind'" />
<SecuritySettings
v-else-if="selectedMenuKeys[0] === 'security'"
:user-info="getUserInfo"
@change-password="onChangePassword"
@change-phone-number="onChangePhoneNumber"
/>
<NoticeSettings v-else-if="selectedMenuKeys[0] === 'notice'" />
<AuthenticatorSettings
v-else-if="selectedMenuKeys[0] === 'authenticator'"
/>
</div>
</div>
</div>
</Card>
</Card>
<EmailConfirmModal />
</div>
</template>
<style scoped></style>

6
apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue

@ -78,12 +78,6 @@ watchEffect(() => {
type="email"
/>
</FormItem>
<FormItem
:label="$t('AbpAccount.DisplayName:PhoneNumber')"
name="phoneNumber"
>
<Input v-model:value="formModel.phoneNumber" autocomplete="off" />
</FormItem>
<FormItem
:label="$t('AbpAccount.DisplayName:Surname')"
name="surname"

60
apps/vben5/packages/@abp/account/src/components/components/EmailConfirmModal.vue

@ -0,0 +1,60 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { Form, Input, message } from 'ant-design-vue';
import { useProfileApi } from '../../api/useProfileApi';
const FormItem = Form.Item;
interface State {
confirmToken: string;
email: string;
returnUrl?: string;
userId: string;
}
const formModel = ref({} as State);
const { cancel, confirmEmailApi } = useProfileApi();
const [Modal, modalApi] = useVbenModal({
onClosed: cancel,
onConfirm: onSubmit,
onOpenChange(isOpen) {
if (isOpen) {
const state = modalApi.getData<State>();
formModel.value = state;
}
},
title: $t('AbpAccount.EmailConfirm'),
});
async function onSubmit() {
try {
modalApi.setState({ confirmLoading: true });
await confirmEmailApi({
confirmToken: encodeURIComponent(formModel.value.confirmToken),
});
message.success($t('AbpAccount.YourEmailIsSuccessfullyConfirm'));
modalApi.close();
if (formModel.value.returnUrl) {
window.location.href = formModel.value.returnUrl;
}
} finally {
modalApi.setState({ confirmLoading: false });
}
}
</script>
<template>
<Modal>
<Form :model="formModel">
<FormItem :label="$t('AbpAccount.DisplayName:Email')">
<Input v-model:value="formModel.email" readonly />
</FormItem>
</Form>
</Modal>
</template>
<style scoped></style>

62
apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue

@ -2,7 +2,7 @@
import type { TwoFactorEnabledDto } from '../../types';
import type { UserInfo } from '../../types/user';
import { onMounted, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { $t } from '@vben/locales';
@ -16,15 +16,29 @@ defineProps<{
const emits = defineEmits<{
(event: 'changePassword'): void;
(event: 'changePhoneNumber'): void;
(event: 'validateEmail'): void;
}>();
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
const { changeTwoFactorEnabledApi, getTwoFactorEnabledApi } = useProfileApi();
const {
changeTwoFactorEnabledApi,
getTwoFactorEnabledApi,
sendEmailConfirmLinkApi,
} = useProfileApi();
const twoFactor = ref<TwoFactorEnabledDto>();
const loading = ref(false);
const sendMailInternal = ref(0);
const getSendMailLoading = computed(() => {
return sendMailInternal.value > 0;
});
const getSendMailTitle = computed(() => {
if (sendMailInternal.value > 0) {
return `${sendMailInternal.value} s`;
}
return $t('AbpAccount.ClickToValidation');
});
async function onGet() {
const dto = await getTwoFactorEnabledApi();
twoFactor.value = dto;
@ -39,6 +53,24 @@ async function onTwoFactorChange(enabled: boolean) {
loading.value = false;
}
}
async function onValidateEmail(email: string) {
sendMailInternal.value = 60;
try {
await sendEmailConfirmLinkApi({
appName: 'VueVben5',
email,
returnUrl: window.location.href,
});
setInterval(() => {
if (sendMailInternal.value <= 0) {
return;
}
sendMailInternal.value -= 1;
}, 1000);
} catch {
sendMailInternal.value = 0;
}
}
onMounted(onGet);
</script>
@ -75,12 +107,19 @@ onMounted(onGet);
</template>
<template #description>
{{ userInfo?.phoneNumber }}
<Tag v-if="userInfo?.phoneNumberVerified" color="success">
{{ $t('abp.account.settings.security.verified') }}
</Tag>
<Tag v-else color="warning">
{{ $t('abp.account.settings.security.unVerified') }}
</Tag>
<template v-if="!userInfo?.phoneNumber">
<Tag color="warning">
{{ $t('abp.account.settings.security.unSet') }}
</Tag>
</template>
<template v-else>
<Tag v-if="userInfo?.phoneNumberVerified" color="success">
{{ $t('abp.account.settings.security.verified') }}
</Tag>
<Tag v-else color="warning">
{{ $t('abp.account.settings.security.unVerified') }}
</Tag>
</template>
</template>
</ListItemMeta>
</ListItem>
@ -89,10 +128,11 @@ onMounted(onGet);
<template #extra>
<Button
v-if="userInfo?.email && !userInfo?.emailVerified"
:disabled="getSendMailLoading"
type="link"
@click="emits('validateEmail')"
@click="onValidateEmail(userInfo.email)"
>
{{ $t('AbpAccount.ClickToValidation') }}
{{ getSendMailTitle }}
</Button>
</template>
<ListItemMeta>

22
apps/vben5/packages/@abp/account/src/types/account.ts

@ -0,0 +1,22 @@
import type { NameValue } from '@abp/core';
interface GetTwoFactorProvidersInput {
userId: string;
}
interface SendEmailSigninCodeDto {
emailAddress: string;
}
interface SendPhoneSigninCodeDto {
phoneNumber: string;
}
type TwoFactorProvider = NameValue<string>;
export type {
GetTwoFactorProvidersInput,
SendEmailSigninCodeDto,
SendPhoneSigninCodeDto,
TwoFactorProvider,
};

1
apps/vben5/packages/@abp/account/src/types/index.ts

@ -1,3 +1,4 @@
export * from './account';
export * from './profile';
export * from './token';
export * from './user';

12
apps/vben5/packages/@abp/account/src/types/profile.ts

@ -55,11 +55,23 @@ interface AuthenticatorRecoveryCodeDto {
recoveryCodes: string[];
}
interface SendEmailConfirmCodeDto {
appName: string;
email: string;
returnUrl?: string;
}
interface ConfirmEmailInput {
confirmToken: string;
}
export type {
AuthenticatorDto,
AuthenticatorRecoveryCodeDto,
ChangePasswordInput,
ConfirmEmailInput,
ProfileDto,
SendEmailConfirmCodeDto,
TwoFactorEnabledDto,
UpdateProfileDto,
VerifyAuthenticatorCodeInput,

16
apps/vben5/packages/@abp/account/src/types/token.ts

@ -48,11 +48,27 @@ interface OAuthTokenResult {
token_type: string;
}
interface OAuthError {
/** 错误类型 */
error: string;
/** 错误描述 */
error_description: string;
/** 错误描述链接 */
error_uri?: string;
}
interface TwoFactorError extends OAuthError {
twoFactorToken: string;
userId: string;
}
export type {
OAuthError,
OAuthTokenRefreshModel,
OAuthTokenResult,
PasswordTokenRequest,
PasswordTokenRequestModel,
TokenRequest,
TokenResult,
TwoFactorError,
};

3
apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue

@ -88,6 +88,9 @@ function onDateChange(e: any, setting: SettingDetail) {
}
function onValueChange(setting: SettingDetail) {
if (setting.valueType === ValueType.NoSet) {
return;
}
const index = settingsUpdateInput.value.settings.findIndex(
(s) => s.name === setting.name,
);

1
apps/vben5/packages/@abp/settings/src/types/settings.ts

@ -18,6 +18,7 @@ export enum ValueType {
Array = 4,
Boolean = 2,
Date = 3,
NoSet = -1,
Number = 1,
Object = 10,
Option = 5,

Loading…
Cancel
Save