From 86cd82024fb333e8e8d834bc2eab3a3e37b1fc48 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 8 Aug 2026 15:12:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(mail):=20=E9=82=AE=E7=AE=B1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=8D=A2=E5=8F=96=E4=BC=9A=E8=AF=9D=20token=EF=BC=8CJ?= =?UTF-8?q?MAP=20=E8=B0=83=E7=94=A8=E4=B8=8D=E5=86=8D=E4=BC=A0=E8=BE=93?= =?UTF-8?q?=E6=98=8E=E6=96=87=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 登录调 /API/Mail/Login 换取 token(密码仅一次传输) - JmapClient 改持 token;登出吊销 token --- src/api/mail.ts | 16 ++++++++++++++-- src/utils/jmap.ts | 10 +++++----- src/views/mail/Mailbox.vue | 6 +++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/api/mail.ts b/src/api/mail.ts index 915304e..4678ec4 100644 --- a/src/api/mail.ts +++ b/src/api/mail.ts @@ -40,6 +40,12 @@ export interface JmapProxyResult { } } +/** 邮箱登录结果 */ +export interface MailLoginResult { + Token: string + MailboxName: string +} + /** 邮箱 API 客户端 */ export const mailApi = { /** 获取 JMAP 连接配置 */ @@ -55,8 +61,14 @@ export const mailApi = { /** 修改邮箱密码 */ changePassword: (NewPassword: string): Promise => post('/API/Mail/ChangePassword', { NewPassword }), - /** JMAP 收发代理(后端统一转发,避免浏览器跨域) */ - jmap: (data: { Password: string; MethodCalls: Array<[string, Record, string]>; AccountId?: string }): Promise => + /** 邮箱登录:校验密码并换取会话 token(密码仅此一次传输,后端存内存) */ + login: (Password: string): Promise => post('/API/Mail/Login', { Password }), + + /** 邮箱登出:吊销会话 token */ + logout: (Token: string): Promise => post('/API/Mail/Logout', { Token }), + + /** JMAP 收发代理(携带会话 token,不再传输明文密码) */ + jmap: (data: { Token: string; MethodCalls: Array<[string, Record, string]>; AccountId?: string }): Promise => post('/API/Mail/Jmap', data), /** 管理员:全部邮箱列表 */ diff --git a/src/utils/jmap.ts b/src/utils/jmap.ts index 5f3b917..c97debb 100644 --- a/src/utils/jmap.ts +++ b/src/utils/jmap.ts @@ -49,12 +49,12 @@ export interface JmapCallResult { export class JmapClient { readonly mailboxName: string - readonly password: string + readonly token: string private accountId = '' - constructor(mailboxName: string, password: string) { + constructor(mailboxName: string, token: string) { this.mailboxName = mailboxName - this.password = password + this.token = token } /** 登录验证:通过一次 Mailbox/get 触发后端发现账户 ID */ @@ -62,10 +62,10 @@ export class JmapClient { await this.request([['Mailbox/get', {}, 'c0']]) } - /** 批量调用 JMAP 方法(经后端代理) */ + /** 批量调用 JMAP 方法(经后端代理,携带会话 token,密码不落前端) */ async request(methodCalls: Array<[string, Record, string]>): Promise { const result = await mailApi.jmap({ - Password: this.password, + Token: this.token, MethodCalls: methodCalls, AccountId: this.accountId || undefined, }) diff --git a/src/views/mail/Mailbox.vue b/src/views/mail/Mailbox.vue index 4773f14..fbde942 100644 --- a/src/views/mail/Mailbox.vue +++ b/src/views/mail/Mailbox.vue @@ -30,7 +30,6 @@ const applyForm = ref({ MailboxName: '', Password: '', ConfirmPassword: '' }) // JMAP 会话 const client = ref(null) -const jmapPassword = ref('') const loginVisible = ref(false) const loginForm = ref({ Password: '' }) const connecting = ref(false) @@ -107,10 +106,11 @@ async function submitLogin(): Promise { } connecting.value = true try { - const c = new JmapClient(myAddress.value, loginForm.value.Password) + // 后端校验密码并签发会话 token(密码仅此一次传输,后端存内存;后续 JMAP 调用只带 token) + const result = await mailApi.login(loginForm.value.Password) + const c = new JmapClient(result.MailboxName, result.Token) await c.discover() client.value = c - jmapPassword.value = loginForm.value.Password loginVisible.value = false loginForm.value.Password = '' await loadMailboxes()