feat(mail): 邮箱登录换取会话 token,JMAP 调用不再传输明文密码

- 登录调 /API/Mail/Login 换取 token(密码仅一次传输)

- JmapClient 改持 token;登出吊销 token
This commit is contained in:
TermiNexus committed 2026-08-08 15:12:17 +08:00
1 parent 600ca27cbb
commit 86cd82024f
3 files changed
+22 -10

No files matched your search

+14 -2
View File
@@ -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<void> => post<void>('/API/Mail/ChangePassword', { NewPassword }),
/** JMAP 收发代理(后端统一转发,避免浏览器跨域) */
jmap: (data: { Password: string; MethodCalls: Array<[string, Record<string, unknown>, string]>; AccountId?: string }): Promise<JmapProxyResult> =>
/** 邮箱登录:校验密码并换取会话 token(密码仅此一次传输,后端存内存) */
login: (Password: string): Promise<MailLoginResult> => post<MailLoginResult>('/API/Mail/Login', { Password }),
/** 邮箱登出:吊销会话 token */
logout: (Token: string): Promise<void> => post<void>('/API/Mail/Logout', { Token }),
/** JMAP 收发代理(携带会话 token,不再传输明文密码) */
jmap: (data: { Token: string; MethodCalls: Array<[string, Record<string, unknown>, string]>; AccountId?: string }): Promise<JmapProxyResult> =>
post<JmapProxyResult>('/API/Mail/Jmap', data),
/** 管理员:全部邮箱列表 */
+5 -5
View File
@@ -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, unknown>, string]>): Promise<JmapCallResult> {
const result = await mailApi.jmap({
Password: this.password,
Token: this.token,
MethodCalls: methodCalls,
AccountId: this.accountId || undefined,
})
+3 -3
View File
@@ -30,7 +30,6 @@ const applyForm = ref({ MailboxName: '', Password: '', ConfirmPassword: '' })
// JMAP 会话
const client = ref<JmapClient | null>(null)
const jmapPassword = ref('')
const loginVisible = ref(false)
const loginForm = ref({ Password: '' })
const connecting = ref(false)
@@ -107,10 +106,11 @@ async function submitLogin(): Promise<void> {
}
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()