Files
m4 357ac45281 feat(desktop): DB-T2 强制登录门 + 注册/找回 + U-5 机器-用户锁(§21)
- 登录门:非 signed_in 全屏盖 app,登录/注册/找回三页,只盖 UI
  不断机器通道(U-1);status 通道异常落登录页不砖(U-3)
- U-5:登录成功当场校验本机 installation ∈ 会话用户列表,不符即
  销毁会话并提示绑定者邮箱;绑定状态损坏 fail-closed
- relay 客户端补注册/找回四通道 + installations/binding-owner 查询;
  IPC 预期失败全部结构化 {ok:false, code} 返回
- i18n userAccount 48 键 × 6 语言;electron +11 / ui +7 测试全绿

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-19 13:12:49 +08:00

67 lines
1.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* machine-binding.ts — 本机机器绑定(mercury-relay 插件 state.json)的只读视图。
*
* U-5 机器-用户锁的事实来源:插件绑定后把 ``relay_binding`` 写进
* ``<HERMES_HOME|~/.hermes>/mercury-relay/state.json``。desktop 只取
* ``site`` 与 ``agent_id``(= relay installation id)两个字段做所有权
* 校验;``host_refresh_token`` 等凭据字段读完即弃,绝不落日志。
*
* 无 electron import:状态文件原文由 main.ts 注入。文件缺失 = 未绑定
* (返回 null);文件存在但损坏 = fail-closed 抛 MachineBindingError
* (锁态无法判定时不能默许放行)。
*/
import { canonicalizeRelaySite } from './relay-account'
export interface MachineBinding {
site: string
installationId: string
}
export class MachineBindingError extends Error {
readonly code = 'binding_state_invalid'
constructor() {
super('binding_state_invalid')
}
}
export function parseMachineBinding(stateText: string): MachineBinding | null {
let parsed: any
try {
parsed = JSON.parse(stateText)
} catch {
throw new MachineBindingError()
}
const record = parsed?.relay_binding
if (record === null || record === undefined) {return null}
try {
const site = canonicalizeRelaySite(record.site)
const installationId = typeof record.agent_id === 'string' ? record.agent_id : ''
if (!installationId) {throw new MachineBindingError()}
return { site, installationId }
} catch (error) {
if (error instanceof MachineBindingError) {throw error}
throw new MachineBindingError()
}
}
/** 读状态文件;ENOENT 视为未绑定。 */
export function loadMachineBinding(readStateText: () => string): MachineBinding | null {
let text: string
try {
text = readStateText()
} catch {
return null
}
return parseMachineBinding(text)
}