magic-link
@noy-db/on-magic-link·pnpm add @noy-db/on-magic-linkOne-shot viewer session via a single-use URL.
What it does
createMagicLinkToken(vault, options) mints a MagicLinkToken — a ULID
token, the target vault, an expiresAt, and a role that's always
'viewer'. deriveMagicLinkKEK(serverSecret, token, vault) HKDF-derives
the same viewer KEK on both server (at grant time) and client (at unlock
time) from a server-held secret and the token — the ULID is hashed into
the HKDF salt so it can't be trivially inverted, and the vault id is
bound into the info field so a token for vault A can't unlock vault B.
buildMagicLinkKeyring assembles the resulting UnlockedKeyring, always
viewer-scoped. Single-use enforcement (marking a token consumed) is the
server's job — isMagicLinkValid only checks expiresAt. A newer
delegation bridge (issueMagicLinkDelegation / claimMagicLinkDelegation)
layers scoped, tier-bound grants — whole-collection or single-record — on
top of the same primitive.
Usage
import { createMagicLinkToken, deriveMagicLinkKEK } from '@noy-db/on-magic-link'
// Server: mint and send
const link = createMagicLinkToken('acme', { ttlMs: 7 * 24 * 60 * 60 * 1000 })
await emailViewer(`https://app.example.com/view?t=${link.token}`)
// Server + client both derive the same KEK from the token
const kek = await deriveMagicLinkKEK(serverSecret, link.token, 'acme')