login-sessions.mjs 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940
  1. import { createHash, randomUUID } from 'node:crypto'
  2. import { mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises'
  3. import { join } from 'node:path'
  4. /** Native-only vault. Passwords are never retained; each origin owns one revocable session. */
  5. export class DesktopLoginSessions {
  6. constructor({ home, encrypt, decrypt }) {
  7. this.root = join(home, 'desktop-login-sessions')
  8. this.encrypt = encrypt; this.decrypt = decrypt
  9. this.pending = Promise.resolve()
  10. }
  11. path(origin) { return join(this.root, createHash('sha256').update(new URL(origin).origin).digest('hex') + '.enc') }
  12. serial(run) {
  13. const next = this.pending.then(run)
  14. this.pending = next.catch(() => {})
  15. return next
  16. }
  17. read(origin) {
  18. return this.serial(async () => {
  19. let bytes
  20. try { bytes = await readFile(this.path(origin)) } catch (error) { if (error.code === 'ENOENT') return; throw error }
  21. const value = JSON.parse(await this.decrypt(bytes))
  22. if (value.origin !== new URL(origin).origin || value.cookie?.name !== 'hermes_yaoyao_session' || typeof value.cookie.value !== 'string' || !value.cookie.value)
  23. throw new Error('保存的登录授权无效,请重新登录')
  24. return value
  25. })
  26. }
  27. save(origin, cookies) {
  28. return this.serial(async () => {
  29. const cookie = cookies.find(value => value.name === 'hermes_yaoyao_session' && value.value)
  30. if (!cookie) throw new Error('服务器没有返回登录授权')
  31. const bytes = await this.encrypt(JSON.stringify({ origin: new URL(origin).origin, cookie }))
  32. await mkdir(this.root, { recursive: true, mode: 0o700 })
  33. const path = this.path(origin), temporary = path + '.' + randomUUID()
  34. try { await writeFile(temporary, bytes, { mode: 0o600, flag: 'wx' }); await rename(temporary, path) }
  35. finally { await rm(temporary, { force: true }) }
  36. })
  37. }
  38. forget(origin) { return this.serial(() => rm(this.path(origin), { force: true })) }
  39. }