@@ -57,6 +57,12 @@ export async function getAutopass() {
|
||||
store = new Corestore(path.join(dir, 'store'))
|
||||
pass = new Autopass(store)
|
||||
await pass.ready()
|
||||
// Restore invite meta so list/delete work after process restart
|
||||
try {
|
||||
await restoreLastInviteMeta(pass)
|
||||
} catch (err) {
|
||||
log.debug('invite meta restore skipped', { error: err.message })
|
||||
}
|
||||
log.info('AutoPass vault ready', { dir })
|
||||
return pass
|
||||
})()
|
||||
@@ -105,9 +111,10 @@ export async function createConnectionInvite(opts = {}) {
|
||||
|
||||
await vault.add(PKG_KEY, packageValue)
|
||||
|
||||
// AutoPass single active invite — readOnly so invitees cannot rewrite the vault
|
||||
const readOnly = role === Roles.viewer
|
||||
const invite = await vault.createInvite({ readOnly })
|
||||
// Always readOnly: invitees only need to read peardock:pkg (public key + capability).
|
||||
// Writable invites hang in autopass@3 when the pairer awaits deleteInvite / writable.
|
||||
// Role elevation is entirely via the HMAC capability, not Autopass write rights.
|
||||
const invite = await vault.createInvite({ readOnly: true })
|
||||
|
||||
lastInviteMeta = {
|
||||
invite,
|
||||
@@ -194,4 +201,71 @@ export function isAutopassReady() {
|
||||
return Boolean(pass)
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort warm at server boot so BlindPairing is listening before clients redeem.
|
||||
* Safe to call multiple times; failures are non-fatal.
|
||||
*/
|
||||
export async function warmAutopass() {
|
||||
try {
|
||||
await getAutopass()
|
||||
return true
|
||||
} catch (err) {
|
||||
log.warn('AutoPass warm failed (invites unavailable until first create)', {
|
||||
error: err.message,
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('autopass')} vault
|
||||
*/
|
||||
async function restoreLastInviteMeta(vault) {
|
||||
if (lastInviteMeta) return
|
||||
// Autopass stores at most one active invite in the view
|
||||
let existing = null
|
||||
try {
|
||||
existing = await vault.base?.view?.findOne?.('@autopass/invite', {})
|
||||
} catch {
|
||||
existing = null
|
||||
}
|
||||
if (!existing) return
|
||||
|
||||
let jti = null
|
||||
let role = Roles.operator
|
||||
let expiresAt = new Date(Date.now() + 72 * 3600 * 1000).toISOString()
|
||||
try {
|
||||
const rec = await vault.get(PKG_KEY)
|
||||
if (rec?.value) {
|
||||
const pkg = typeof rec.value === 'string' ? JSON.parse(rec.value) : rec.value
|
||||
jti = pkg.jti || null
|
||||
role = pkg.role || role
|
||||
expiresAt = pkg.expiresAt || expiresAt
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Invite string is not re-derived easily without z32 of existing.invite buffer
|
||||
let inviteStr = null
|
||||
try {
|
||||
const z32 = (await import('z32')).default
|
||||
if (existing.invite) inviteStr = z32.encode(existing.invite)
|
||||
} catch {
|
||||
inviteStr = null
|
||||
}
|
||||
if (!inviteStr) return
|
||||
|
||||
lastInviteMeta = {
|
||||
invite: inviteStr,
|
||||
role,
|
||||
createdAt: new Date().toISOString(),
|
||||
expiresAt,
|
||||
jti: jti || 'unknown',
|
||||
maxUses: 1,
|
||||
note: 'restored-after-restart',
|
||||
}
|
||||
log.info('Restored active AutoPass invite metadata after restart')
|
||||
}
|
||||
|
||||
export { PKG_KEY }
|
||||
|
||||
Reference in New Issue
Block a user