@liujitcn/kratos-uni-app-core 0.0.28 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,8 @@
28
28
  "core.crypto.public_key_invalid": "公钥格式无效",
29
29
  "core.crypto.random_failed": "随机数生成失败",
30
30
  "core.crypto.unsupported": "当前环境不支持密码加密",
31
+ "core.password.verify_title": "验证密码",
32
+ "core.password.verify_label": "当前密码",
31
33
  "core.file.upload_failed": "上传失败",
32
34
  "core.home.account_capability": "账户能力",
33
35
  "core.home.account_capability_description": "个人资料、应用设置与智能助手入口已保留",
@@ -58,6 +60,23 @@
58
60
  "core.login.captcha_write_failed": "验证码图片写入失败",
59
61
  "core.login.default_sub_title": "欢迎使用本应用",
60
62
  "core.login.login_success": "登录成功",
63
+ "core.login.mfa_title": "多因素认证",
64
+ "core.login.mfa_code": "请输入动态口令",
65
+ "core.login.mfa_recovery_code": "恢复码(可选)",
66
+ "core.login.mfa_setup_title": "绑定多因素认证",
67
+ "core.login.mfa_copy_uri": "复制绑定地址",
68
+ "core.login.mfa_copy_success": "绑定地址已复制",
69
+ "core.login.mfa_recovery_codes_copied": "恢复码已复制",
70
+ "core.login.mfa_recovery_codes_title": "保存 MFA 恢复码",
71
+ "core.login.mfa_recovery_codes_warning": "每个恢复码只能使用一次,此弹窗关闭后不会再次显示。",
72
+ "core.login.mfa_copy_recovery_codes": "复制恢复码",
73
+ "core.login.mfa_recovery_codes_confirm": "我已保存",
74
+ "core.login.mfa_setup_success": "多因素认证绑定成功,请重新登录。",
75
+ "core.login.mfa_webauthn_action": "使用 Passkey",
76
+ "core.login.mfa_webauthn_setup_unsupported": "当前平台不支持 WebAuthn,请使用 TOTP 或恢复码。",
77
+ "core.login.mfa_webauthn_setup_invalid": "WebAuthn 注册未返回有效凭据。",
78
+ "core.login.mfa_webauthn_login_unsupported": "当前平台不支持 WebAuthn,请使用恢复码。",
79
+ "core.login.mfa_webauthn_login_invalid": "WebAuthn 登录未返回有效凭据。",
61
80
  "core.login.password": "请输入密码",
62
81
  "core.login.privacy": "《隐私协议》",
63
82
  "core.login.protocol_missing": "服务条款和隐私协议未配置",
@@ -71,6 +90,7 @@
71
90
  "core.login.wechat_bind_tip": "该微信尚未绑定,请登录已有账号完成绑定",
72
91
  "core.login.wechat_failed": "微信登录失败,请稍后重试",
73
92
  "core.navigation.home": "首页",
93
+ "core.navigation.message": "消息",
74
94
  "core.navigation.error.access": "菜单访问模式无效:{access}",
75
95
  "core.navigation.error.child_tab": "只有移动端二级菜单可以作为 tab:{name}",
76
96
  "core.navigation.error.cycle": "移动端菜单存在循环父级:{name}",
@@ -92,6 +112,22 @@
92
112
  "core.protocol.not_configured": "{title}未配置,暂时无法查看",
93
113
  "core.protocol.privacy": "隐私协议",
94
114
  "core.protocol.service": "服务条款",
115
+ "core.settings.authorization": "授权管理",
116
+ "core.settings.contact": "联系我们",
117
+ "core.settings.feedback": "问题反馈",
118
+ "core.settings.logout_confirm": "是否退出登录?",
119
+ "core.settings.logout_failed": "退出登录失败",
120
+ "core.settings.mfa_bind_success": "多因素认证绑定成功",
121
+ "core.settings.mfa_disable_action": "禁用",
122
+ "core.settings.mfa_disable_factor_required": "请输入动态口令或恢复码。",
123
+ "core.settings.mfa_disable_success": "多因素认证已禁用",
124
+ "core.settings.mfa_disable_title": "禁用多因素认证",
125
+ "core.settings.mfa_disabled": "未启用",
126
+ "core.settings.mfa_enabled_method": "已启用({method})",
127
+ "core.settings.mfa_method_totp": "TOTP",
128
+ "core.settings.mfa_method_webauthn": "WebAuthn",
129
+ "core.settings.mfa_title": "多因素认证",
130
+ "core.settings.title": "设置",
95
131
  "core.status.CONFIG_ERROR": "导航配置无效",
96
132
  "core.status.forbidden": "暂无访问权限",
97
133
  "core.status.NOT_FOUND": "页面不存在",
@@ -100,5 +136,9 @@
100
136
  "core.status.loading": "正在加载",
101
137
  "core.webview.embed_blocked": "当前 H5 页面可能被目标站点限制嵌入",
102
138
  "core.webview.invalid_url": "缺少有效链接地址",
103
- "core.webview.open_failed": "链接无法打开"
139
+ "core.webview.open_failed": "链接无法打开",
140
+ "common.value.top_level": "顶级节点",
141
+ "common.resource.default": "{resource}",
142
+ "common.action.delete_resource": "删除{resource}",
143
+ "common.action.set_field_status": "设置{field}"
104
144
  }
@@ -28,6 +28,8 @@
28
28
  "core.crypto.public_key_invalid": "公鑰格式無效",
29
29
  "core.crypto.random_failed": "隨機數生成失敗",
30
30
  "core.crypto.unsupported": "當前環境不支援密碼加密",
31
+ "core.password.verify_title": "驗證密碼",
32
+ "core.password.verify_label": "當前密碼",
31
33
  "core.file.upload_failed": "上傳失敗",
32
34
  "core.home.account_capability": "賬戶能力",
33
35
  "core.home.account_capability_description": "個人資料、應用設定與智慧助手入口已保留",
@@ -58,6 +60,23 @@
58
60
  "core.login.captcha_write_failed": "驗證碼圖片寫入失敗",
59
61
  "core.login.default_sub_title": "歡迎使用本應用",
60
62
  "core.login.login_success": "登入成功",
63
+ "core.login.mfa_title": "多因素認證",
64
+ "core.login.mfa_code": "請輸入動態密碼",
65
+ "core.login.mfa_recovery_code": "恢復碼(可選)",
66
+ "core.login.mfa_setup_title": "綁定多因素認證",
67
+ "core.login.mfa_copy_uri": "複製綁定地址",
68
+ "core.login.mfa_copy_success": "綁定地址已複製",
69
+ "core.login.mfa_recovery_codes_copied": "恢復碼已複製",
70
+ "core.login.mfa_recovery_codes_title": "儲存 MFA 恢復碼",
71
+ "core.login.mfa_recovery_codes_warning": "每個恢復碼只能使用一次,關閉此視窗後不會再次顯示。",
72
+ "core.login.mfa_copy_recovery_codes": "複製恢復碼",
73
+ "core.login.mfa_recovery_codes_confirm": "我已儲存",
74
+ "core.login.mfa_setup_success": "多因素認證綁定成功,請重新登入。",
75
+ "core.login.mfa_webauthn_action": "使用 Passkey",
76
+ "core.login.mfa_webauthn_setup_unsupported": "目前平台不支援 WebAuthn,請使用 TOTP 或恢復碼。",
77
+ "core.login.mfa_webauthn_setup_invalid": "WebAuthn 註冊未回傳有效憑證。",
78
+ "core.login.mfa_webauthn_login_unsupported": "目前平台不支援 WebAuthn,請使用恢復碼。",
79
+ "core.login.mfa_webauthn_login_invalid": "WebAuthn 登入未回傳有效憑證。",
61
80
  "core.login.password": "請輸入密碼",
62
81
  "core.login.privacy": "《隱私協議》",
63
82
  "core.login.protocol_missing": "服務條款和隱私協議未配置",
@@ -71,6 +90,7 @@
71
90
  "core.login.wechat_bind_tip": "該微信尚未繫結,請登入已有賬號完成繫結",
72
91
  "core.login.wechat_failed": "微信登入失敗,請稍後重試",
73
92
  "core.navigation.home": "首頁",
93
+ "core.navigation.message": "消息",
74
94
  "core.navigation.error.access": "選單訪問模式無效:{access}",
75
95
  "core.navigation.error.child_tab": "只有移動端二級選單可以作為 tab:{name}",
76
96
  "core.navigation.error.cycle": "移動端選單存在迴圈父級:{name}",
@@ -92,6 +112,22 @@
92
112
  "core.protocol.not_configured": "{title}未配置,暫時無法檢視",
93
113
  "core.protocol.privacy": "隱私協議",
94
114
  "core.protocol.service": "服務條款",
115
+ "core.settings.authorization": "授權管理",
116
+ "core.settings.contact": "聯絡我們",
117
+ "core.settings.feedback": "問題反饋",
118
+ "core.settings.logout_confirm": "是否退出登入?",
119
+ "core.settings.logout_failed": "退出登入失敗",
120
+ "core.settings.mfa_bind_success": "多因素認證綁定成功",
121
+ "core.settings.mfa_disable_action": "停用",
122
+ "core.settings.mfa_disable_factor_required": "請輸入動態密碼或恢復碼。",
123
+ "core.settings.mfa_disable_success": "多因素認證已停用",
124
+ "core.settings.mfa_disable_title": "停用多因素認證",
125
+ "core.settings.mfa_disabled": "未啟用",
126
+ "core.settings.mfa_enabled_method": "已啟用({method})",
127
+ "core.settings.mfa_method_totp": "TOTP",
128
+ "core.settings.mfa_method_webauthn": "WebAuthn",
129
+ "core.settings.mfa_title": "多因素認證",
130
+ "core.settings.title": "設定",
95
131
  "core.status.CONFIG_ERROR": "導航配置無效",
96
132
  "core.status.forbidden": "暫無訪問許可權",
97
133
  "core.status.NOT_FOUND": "頁面不存在",
@@ -100,5 +136,9 @@
100
136
  "core.status.loading": "正在載入",
101
137
  "core.webview.embed_blocked": "當前 H5 頁面可能被目標站點限制嵌入",
102
138
  "core.webview.invalid_url": "缺少有效連結地址",
103
- "core.webview.open_failed": "連結無法開啟"
139
+ "core.webview.open_failed": "連結無法開啟",
140
+ "common.value.top_level": "頂級節點",
141
+ "common.resource.default": "{resource}",
142
+ "common.action.delete_resource": "刪除{resource}",
143
+ "common.action.set_field_status": "設定{field}"
104
144
  }
package/src/navigation.ts CHANGED
@@ -59,6 +59,18 @@ const createDefaultMenus = (): AppMenu[] => [
59
59
  icon: 'HOME_DEFAULT',
60
60
  selectedIcon: 'HOME_SELECTED',
61
61
  },
62
+ {
63
+ id: 9990904,
64
+ parentId: APP_MENU_ROOT_ID,
65
+ name: 'AppMessage',
66
+ path: 'app/message',
67
+ viewKey: 'MESSAGE_INBOX',
68
+ title: t('core.navigation.message'),
69
+ access: 'AUTHENTICATED',
70
+ inTabBar: true,
71
+ icon: 'MESSAGE_DEFAULT',
72
+ selectedIcon: 'MESSAGE_SELECTED',
73
+ },
62
74
  {
63
75
  id: 99909,
64
76
  parentId: APP_MENU_ROOT_ID,
@@ -107,7 +119,7 @@ const createDefaultMenus = (): AppMenu[] => [
107
119
  name: 'AppSettings',
108
120
  path: 'app/settings',
109
121
  viewKey: 'SETTINGS',
110
- title: t('system.settings.title'),
122
+ title: t('core.settings.title'),
111
123
  access: 'AUTHENTICATED',
112
124
  inTabBar: false,
113
125
  },
@@ -135,6 +147,7 @@ const createDefaultMenus = (): AppMenu[] => [
135
147
 
136
148
  const menus = ref<AppMenu[]>([])
137
149
  const ready = ref(false)
150
+ const appMenuBadges = ref<Record<string, number>>({})
138
151
  let tabNavigationTarget: string | undefined
139
152
  let adapter: AppNavigationAdapter = {
140
153
  list: () => defBaseMenuService.ListBaseMenu(),
@@ -272,6 +285,39 @@ function navigateTabRouteInStack(
272
285
  })
273
286
  }
274
287
 
288
+ /** 按稳定 viewKey 跳转,并将动作参数作为查询参数传递。 */
289
+ export function navigateAppView(viewKey: string, params: Record<string, string> = {}): void {
290
+ const menu = menus.value.find((item) => item.viewKey === viewKey)
291
+ if (!menu) {
292
+ launchAppStatus('NOT_FOUND')
293
+ return
294
+ }
295
+ const remaining = { ...params }
296
+ const path = menu.path.replace(/:([A-Za-z0-9_]+)/g, (placeholder, key: string) => {
297
+ if (!(key in remaining)) return placeholder
298
+ const value = remaining[key]
299
+ delete remaining[key]
300
+ return encodeURIComponent(value)
301
+ })
302
+ const query = Object.entries(remaining)
303
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
304
+ .join('&')
305
+ navigateAppRoute(`${path}${query ? `?${query}` : ''}`)
306
+ }
307
+
308
+ /** 设置移动端菜单 badge 数量,供业务模块注册未读状态。 */
309
+ export function setAppMenuBadge(viewKey: string, count: number): void {
310
+ const next = { ...appMenuBadges.value }
311
+ if (count > 0) next[viewKey] = Math.min(Math.floor(count), 99)
312
+ else delete next[viewKey]
313
+ appMenuBadges.value = next
314
+ }
315
+
316
+ /** 读取移动端菜单 badge 数量。 */
317
+ export function useAppMenuBadge(viewKey: string) {
318
+ return computed(() => appMenuBadges.value[viewKey] ?? 0)
319
+ }
320
+
275
321
  /** 获取导航响应式状态。 */
276
322
  export function useAppNavigation() {
277
323
  const menuTree = computed<AppMenuNode[]>(() => buildMenuTree(menus.value, APP_MENU_ROOT_ID))
@@ -20,6 +20,25 @@ export enum PasswordCryptoScene {
20
20
  PASSWORD_CRYPTO_SCENE_RESET_BASE_USER_PASSWORD = 3,
21
21
  /** PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD - 个人修改密码加密场景。 */
22
22
  PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD = 4,
23
+ /** PASSWORD_CRYPTO_SCENE_MFA - 多因素认证敏感操作场景。 */
24
+ PASSWORD_CRYPTO_SCENE_MFA = 5,
25
+ }
26
+
27
+ /**
28
+ * LoginStatus 表示登录请求当前所处的认证阶段。
29
+ * 只有 AUTHENTICATED 或 PASSWORD_CHANGE_REQUIRED 才会携带正式访问令牌,MFA 状态必须继续完成挑战。
30
+ */
31
+ export enum LoginStatus {
32
+ /** LOGIN_STATUS_UNSPECIFIED - 未指定登录状态。 */
33
+ LOGIN_STATUS_UNSPECIFIED = 0,
34
+ /** LOGIN_STATUS_AUTHENTICATED - 已完成认证并签发令牌。 */
35
+ LOGIN_STATUS_AUTHENTICATED = 1,
36
+ /** LOGIN_STATUS_MFA_REQUIRED - 需要继续校验多因素认证。 */
37
+ LOGIN_STATUS_MFA_REQUIRED = 2,
38
+ /** LOGIN_STATUS_MFA_ENROLLMENT_REQUIRED - 必须先完成多因素认证绑定。 */
39
+ LOGIN_STATUS_MFA_ENROLLMENT_REQUIRED = 3,
40
+ /** LOGIN_STATUS_PASSWORD_CHANGE_REQUIRED - 下次操作前必须修改临时密码。 */
41
+ LOGIN_STATUS_PASSWORD_CHANGE_REQUIRED = 4,
23
42
  }
24
43
 
25
44
  /** 验证码获取条件 */
@@ -80,8 +99,8 @@ export interface PasswordPublicKeyResponse {
80
99
 
81
100
  /** 刷新令牌请求参数 */
82
101
  export interface RefreshTokenRequest {
83
- /** 更新令牌,用来获取下一次的访问令牌,必选项。 */
84
- refresh_token: string;
102
+ /** 更新令牌;未填写时由服务端从HttpOnly Cookie读取。 */
103
+ refresh_token?: string | undefined;
85
104
  }
86
105
 
87
106
  /** 刷新令牌响应 */
@@ -122,6 +141,18 @@ export interface LoginResponse {
122
141
  token_type: string;
123
142
  /** 令牌有效时间,单位为秒。如果访问令牌过期,服务器应回复授予访问令牌的持续时间。如果省略该参数,必须其他方式设置过期时间。 */
124
143
  expires_in: number;
144
+ /** 登录认证状态 */
145
+ status: LoginStatus;
146
+ /** 多因素认证挑战ID */
147
+ mfa_challenge_id: string;
148
+ /** 多因素认证绑定临时票据 */
149
+ mfa_setup_ticket: string;
150
+ /** 多因素认证挑战或绑定票据有效时间,单位秒 */
151
+ mfa_expires_in: number;
152
+ /** 多因素认证方式:totp、webauthn */
153
+ mfa_method: string;
154
+ /** WebAuthn认证选项JSON */
155
+ mfa_webauthn_options_json: string;
125
156
  }
126
157
 
127
158
  /** Base登录公共服务 */
@@ -0,0 +1,180 @@
1
+ // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-ts_proto v2.11.8
4
+ // protoc unknown
5
+ // source: base/v1/mfa.proto
6
+
7
+ /* eslint-disable */
8
+ import type { PasswordCrypto } from "../../common/v1/types";
9
+ import type { Empty } from "../../google/protobuf/empty";
10
+ import type { LoginResponse } from "./login";
11
+
12
+ /** 多因素认证校验请求。 */
13
+ export interface VerifyMfaRequest {
14
+ /** 多因素认证挑战ID */
15
+ challenge_id: string;
16
+ /** TOTP动态口令 */
17
+ code: string;
18
+ /** 一次性恢复码 */
19
+ recovery_code: string;
20
+ /** WebAuthn认证响应JSON */
21
+ webauthn_response_json: string;
22
+ }
23
+
24
+ /** 登录强制绑定多因素认证请求。 */
25
+ export interface BeginMfaEnrollmentRequest {
26
+ /** 强制绑定临时票据 */
27
+ setup_ticket: string;
28
+ }
29
+
30
+ /** 登录强制绑定多因素认证响应。 */
31
+ export interface BeginMfaEnrollmentResponse {
32
+ /** 绑定临时票据 */
33
+ setup_ticket: string;
34
+ /** Authenticator应用使用的otpauth地址 */
35
+ otpauth_uri: string;
36
+ /** 绑定临时票据有效时间,单位秒 */
37
+ expires_in: number;
38
+ /** 多因素认证方式:totp、webauthn */
39
+ method: string;
40
+ /** WebAuthn注册选项JSON */
41
+ webauthn_options_json: string;
42
+ }
43
+
44
+ /** 登录强制绑定多因素认证确认请求。 */
45
+ export interface ConfirmMfaEnrollmentRequest {
46
+ /** 绑定临时票据 */
47
+ setup_ticket: string;
48
+ /** TOTP动态口令 */
49
+ code: string;
50
+ /** WebAuthn注册响应JSON */
51
+ webauthn_response_json: string;
52
+ }
53
+
54
+ /** 登录强制绑定多因素认证确认响应。 */
55
+ export interface ConfirmMfaEnrollmentResponse {
56
+ /** 是否启用成功 */
57
+ enabled: boolean;
58
+ /** 一次性恢复码,仅本次返回 */
59
+ recovery_codes: string[];
60
+ }
61
+
62
+ /** 查询当前用户多因素认证状态请求。 */
63
+ export interface GetMfaStatusRequest {
64
+ }
65
+
66
+ /** 多因素认证状态响应。 */
67
+ export interface MfaStatusResponse {
68
+ /** 是否已启用多因素认证 */
69
+ enabled: boolean;
70
+ /** 全局多因素认证策略 */
71
+ policy: string;
72
+ /** 认证方式 */
73
+ method: string;
74
+ }
75
+
76
+ /** 开始绑定多因素认证请求。 */
77
+ export interface BeginMfaSetupRequest {
78
+ /** 当前登录密码 */
79
+ password:
80
+ | PasswordCrypto
81
+ | undefined;
82
+ /** 强制绑定场景下的临时票据 */
83
+ setup_ticket: string;
84
+ }
85
+
86
+ /** 开始绑定多因素认证响应。 */
87
+ export interface BeginMfaSetupResponse {
88
+ /** 绑定临时票据 */
89
+ setup_ticket: string;
90
+ /** Authenticator 应用使用的 otpauth 地址 */
91
+ otpauth_uri: string;
92
+ /** 绑定临时票据有效时间,单位秒 */
93
+ expires_in: number;
94
+ /** 多因素认证方式:totp、webauthn */
95
+ method: string;
96
+ /** WebAuthn 注册选项 JSON */
97
+ webauthn_options_json: string;
98
+ }
99
+
100
+ /** 确认绑定多因素认证请求。 */
101
+ export interface ConfirmMfaSetupRequest {
102
+ /** 绑定临时票据 */
103
+ setup_ticket: string;
104
+ /** TOTP 动态口令 */
105
+ code: string;
106
+ /** WebAuthn 注册响应 JSON */
107
+ webauthn_response_json: string;
108
+ }
109
+
110
+ /** 确认绑定多因素认证响应。 */
111
+ export interface ConfirmMfaSetupResponse {
112
+ /** 是否启用成功 */
113
+ enabled: boolean;
114
+ /** 一次性恢复码,仅本次返回 */
115
+ recovery_codes: string[];
116
+ }
117
+
118
+ /** 开始禁用 WebAuthn 多因素认证请求。 */
119
+ export interface BeginMfaDisableRequest {
120
+ }
121
+
122
+ /** 开始禁用 WebAuthn 多因素认证响应。 */
123
+ export interface BeginMfaDisableResponse {
124
+ /** WebAuthn 禁用挑战 ID */
125
+ challenge_id: string;
126
+ /** WebAuthn 禁用挑战有效时间,单位秒 */
127
+ expires_in: number;
128
+ /** WebAuthn 验证选项 JSON */
129
+ webauthn_options_json: string;
130
+ }
131
+
132
+ /** 禁用多因素认证请求。 */
133
+ export interface DisableMfaRequest {
134
+ /** 当前登录密码 */
135
+ password:
136
+ | PasswordCrypto
137
+ | undefined;
138
+ /** TOTP 动态口令 */
139
+ code: string;
140
+ /** WebAuthn 禁用挑战 ID */
141
+ webauthn_challenge_id: string;
142
+ /** WebAuthn 验证响应 JSON */
143
+ webauthn_response_json: string;
144
+ /** 一次性恢复码 */
145
+ recovery_code: string;
146
+ }
147
+
148
+ /** 重新生成恢复码请求。 */
149
+ export interface RegenerateMfaRecoveryCodesRequest {
150
+ /** TOTP 动态口令 */
151
+ code: string;
152
+ }
153
+
154
+ /** 恢复码响应。 */
155
+ export interface RecoveryCodesResponse {
156
+ /** 一次性恢复码,仅本次返回 */
157
+ recovery_codes: string[];
158
+ }
159
+
160
+ /** Base 多因素认证公共服务。 */
161
+ export interface MfaService {
162
+ /** 校验多因素认证 */
163
+ VerifyMfa(request: VerifyMfaRequest): Promise<LoginResponse>;
164
+ /** 开始登录强制绑定配置的 MFA 方式 */
165
+ BeginMfaEnrollment(request: BeginMfaEnrollmentRequest): Promise<BeginMfaEnrollmentResponse>;
166
+ /** 确认登录强制绑定配置的 MFA 方式 */
167
+ ConfirmMfaEnrollment(request: ConfirmMfaEnrollmentRequest): Promise<ConfirmMfaEnrollmentResponse>;
168
+ /** 查询当前用户多因素认证状态。 */
169
+ GetMfaStatus(request: GetMfaStatusRequest): Promise<MfaStatusResponse>;
170
+ /** 开始绑定当前用户的多因素认证方式。 */
171
+ BeginMfaSetup(request: BeginMfaSetupRequest): Promise<BeginMfaSetupResponse>;
172
+ /** 确认绑定当前用户的多因素认证方式。 */
173
+ ConfirmMfaSetup(request: ConfirmMfaSetupRequest): Promise<ConfirmMfaSetupResponse>;
174
+ /** 开始禁用 WebAuthn 多因素认证的 Passkey 验证。 */
175
+ BeginMfaDisable(request: BeginMfaDisableRequest): Promise<BeginMfaDisableResponse>;
176
+ /** 禁用当前用户的多因素认证。 */
177
+ DisableMfa(request: DisableMfaRequest): Promise<Empty>;
178
+ /** 重新生成当前用户的多因素认证恢复码。 */
179
+ RegenerateMfaRecoveryCodes(request: RegenerateMfaRecoveryCodesRequest): Promise<RecoveryCodesResponse>;
180
+ }
@@ -7,6 +7,7 @@
7
7
  /* eslint-disable */
8
8
  import type { PasswordCrypto } from "../../common/v1/types";
9
9
  import type { Empty } from "../../google/protobuf/empty";
10
+ import type { LoginStatus } from "./login";
10
11
 
11
12
  /** 个人中心三方账号绑定列表查询条件 */
12
13
  export interface ListOauthBindingRequest {
@@ -96,6 +97,18 @@ export interface CreateOauthSessionResponse {
96
97
  expires_in: number;
97
98
  /** 是否需要绑定已有账号。 */
98
99
  binding_required: boolean;
100
+ /** 登录认证状态 */
101
+ status: LoginStatus;
102
+ /** 多因素认证挑战ID */
103
+ mfa_challenge_id: string;
104
+ /** 多因素认证绑定临时票据 */
105
+ mfa_setup_ticket: string;
106
+ /** 多因素认证挑战或绑定票据有效时间,单位秒 */
107
+ mfa_expires_in: number;
108
+ /** 多因素认证方式:totp、webauthn */
109
+ mfa_method: string;
110
+ /** WebAuthn认证选项JSON */
111
+ mfa_webauthn_options_json: string;
99
112
  }
100
113
 
101
114
  /** 三方登录回调请求 */
@@ -132,6 +145,18 @@ export interface ExchangeOauthTicketResponse {
132
145
  token_type: string;
133
146
  /** 令牌有效时间,单位为秒。 */
134
147
  expires_in: number;
148
+ /** 登录认证状态 */
149
+ status: LoginStatus;
150
+ /** 多因素认证挑战ID */
151
+ mfa_challenge_id: string;
152
+ /** 多因素认证绑定临时票据 */
153
+ mfa_setup_ticket: string;
154
+ /** 多因素认证挑战或绑定票据有效时间,单位秒 */
155
+ mfa_expires_in: number;
156
+ /** 多因素认证方式:totp、webauthn */
157
+ mfa_method: string;
158
+ /** WebAuthn认证选项JSON */
159
+ mfa_webauthn_options_json: string;
135
160
  }
136
161
 
137
162
  /** 个人中心三方账号绑定回调请求 */
@@ -5,6 +5,8 @@ import type {
5
5
  CreateOauthSessionResponse,
6
6
  } from '../../rpc/base/v1/oauth'
7
7
  import type { LoginRequest, LoginResponse } from '../../rpc/base/v1/login'
8
+ import type { VerifyMfaRequest } from '../../rpc/base/v1/mfa'
9
+ import { defMfaService } from '../../api/base/mfa'
8
10
  import { defAuthService } from '../../api/system/auth'
9
11
  import { defLoginService } from '../../api/base/login'
10
12
  import { defOauthService } from '../../api/base/oauth'
@@ -82,12 +84,13 @@ export const useUserStore = defineStore(
82
84
  * @returns
83
85
  */
84
86
  function login(request: LoginRequest) {
85
- return new Promise<void>((resolve, reject) => {
87
+ return new Promise<LoginResponse>((resolve, reject) => {
86
88
  defLoginService
87
89
  .Login(request)
88
90
  .then(async (data) => {
89
- await applyLoginToken(data)
90
- resolve()
91
+ if (data.status === 1 || data.status === 0 || data.status === 4)
92
+ await applyLoginToken(data)
93
+ resolve(data)
91
94
  })
92
95
  .catch((error) => {
93
96
  reject(error)
@@ -95,6 +98,19 @@ export const useUserStore = defineStore(
95
98
  })
96
99
  }
97
100
 
101
+ /** 校验登录阶段的多因素认证并保存正式令牌。 */
102
+ function verifyMfa(request: VerifyMfaRequest) {
103
+ return new Promise<LoginResponse>((resolve, reject) => {
104
+ defMfaService
105
+ .VerifyMfa(request)
106
+ .then(async (data) => {
107
+ await applyLoginToken(data)
108
+ resolve(data)
109
+ })
110
+ .catch((error) => reject(error))
111
+ })
112
+ }
113
+
98
114
  /**
99
115
  * 创建三方登录会话
100
116
  *
@@ -110,7 +126,8 @@ export const useUserStore = defineStore(
110
126
  resolve(data)
111
127
  return
112
128
  }
113
- await applyLoginToken(data)
129
+ if (data.status === 1 || data.status === 0 || data.status === 4)
130
+ await applyLoginToken(data)
114
131
  resolve(data)
115
132
  })
116
133
  .catch((error) => {
@@ -121,12 +138,13 @@ export const useUserStore = defineStore(
121
138
 
122
139
  /** 绑定已有账号并创建三方登录会话。 */
123
140
  function bindOauthSession(request: BindOauthSessionRequest) {
124
- return new Promise<void>((resolve, reject) => {
141
+ return new Promise<CreateOauthSessionResponse>((resolve, reject) => {
125
142
  defOauthService
126
143
  .BindOauthSession(request)
127
144
  .then(async (data) => {
128
- await applyLoginToken(data)
129
- resolve()
145
+ if (data.status === 1 || data.status === 0 || data.status === 4)
146
+ await applyLoginToken(data)
147
+ resolve(data)
130
148
  })
131
149
  .catch((error) => {
132
150
  reject(error)
@@ -239,6 +257,7 @@ export const useUserStore = defineStore(
239
257
  isAuthenticated,
240
258
  getUserProfile,
241
259
  login,
260
+ verifyMfa,
242
261
  createOauthSession,
243
262
  bindOauthSession,
244
263
  logout,
@@ -0,0 +1,33 @@
1
+ /** 通过当前平台能力复制文本,并在 H5 非安全上下文中降级到选区复制。 */
2
+ export async function copyText(value: string): Promise<void> {
3
+ if (!value) throw new Error('clipboard value is empty')
4
+
5
+ // #ifdef H5
6
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
7
+ try {
8
+ await navigator.clipboard.writeText(value)
9
+ return
10
+ } catch {
11
+ // 局域网 HTTP 等非安全上下文继续使用同步选区复制。
12
+ }
13
+ }
14
+ const textarea = document.createElement('textarea')
15
+ textarea.value = value
16
+ textarea.setAttribute('readonly', '')
17
+ textarea.style.position = 'fixed'
18
+ textarea.style.opacity = '0'
19
+ textarea.style.pointerEvents = 'none'
20
+ document.body.appendChild(textarea)
21
+ textarea.select()
22
+ const copied = document.execCommand('copy')
23
+ document.body.removeChild(textarea)
24
+ if (!copied) throw new Error('clipboard copy failed')
25
+ return undefined
26
+ // #endif
27
+
28
+ // #ifndef H5
29
+ await new Promise<void>((resolve, reject) => {
30
+ uni.setClipboardData({ data: value, success: () => resolve(), fail: reject })
31
+ })
32
+ // #endif
33
+ }
package/src/utils/http.ts CHANGED
@@ -22,6 +22,11 @@ const apiBasePath = import.meta.env.VITE_APP_BASE_API || '/api'
22
22
  const apiTargetUrl = import.meta.env.VITE_APP_API_URL || ''
23
23
  const normalizedApiBasePath = apiBasePath.startsWith('/') ? apiBasePath : `/${apiBasePath}`
24
24
 
25
+ let sourceClient = 'uni-h5'
26
+ // #ifdef MP-WEIXIN
27
+ sourceClient = 'uni-weapp'
28
+ // #endif
29
+
25
30
  /**
26
31
  * H5 开发环境优先走同源代理,避免浏览器直接请求后端产生跨域。
27
32
  * 其它平台继续使用显式配置的后端地址。
@@ -87,10 +92,10 @@ const httpInterceptor = {
87
92
  }
88
93
  // 2. 请求超时,普通请求默认 10s;流式等长连接可自行传入更长超时。
89
94
  options.timeout = options.timeout || 10000
90
- // 3. 添加小程序端请求头标识
95
+ // 3. 添加当前客户端平台标识
91
96
  options.header = {
92
97
  ...options.header,
93
- 'source-client': 'miniapp',
98
+ 'source-client': sourceClient,
94
99
  ...getLocaleRequestHeaders(),
95
100
  }
96
101
  // 4. 添加 token 请求头标识
@@ -330,7 +335,7 @@ async function refreshAccessToken() {
330
335
  method: 'POST',
331
336
  data: { refresh_token: refreshToken },
332
337
  header: {
333
- 'source-client': 'miniapp',
338
+ 'source-client': sourceClient,
334
339
  ...getLocaleRequestHeaders(),
335
340
  },
336
341
  success: resolve,