scope-probe

tcmts.dev.devenv.scope-probe · #2ijze3lb · mjs file

// With a kept tcm sign-in (its DPoP key and access token), try writes the token's scope allows and doesn't.
import { createHash, createPrivateKey, randomUUID, sign } from 'node:crypto'
import fs from 'node:fs'
const handle = process.argv[2] ?? 'alice.test'
const s = Object.values(JSON.parse(fs.readFileSync(new URL('./config/tcm/atproto.json', import.meta.url), 'utf8')).sessions).find((x) => x.handle === handle)
const key = createPrivateKey({ key: s.dpopKey, format: 'jwk' })
const b64 = (x) => Buffer.from(typeof x === 'string' ? x : JSON.stringify(x)).toString('base64url')
let nonce
const proof = (method, url) => {
  const { d, ...jwk } = s.dpopKey
  const head = b64({ typ: 'dpop+jwt', alg: 'ES256', jwk })
  const body = b64({ htm: method, htu: url, iat: Math.floor(Date.now() / 1000), jti: randomUUID(), ath: createHash('sha256').update(s.accessToken).digest('base64url'), ...(nonce ? { nonce } : {}) })
  return `${head}.${body}.${sign('sha256', Buffer.from(`${head}.${body}`), { key, dsaEncoding: 'ieee-p1363' }).toString('base64url')}`
}
const call = async (collection, rkey) => {
  const url = `${s.pds}/xrpc/com.atproto.repo.putRecord`
  for (let i = 0; i < 2; i++) {
    const r = await fetch(url, { method: 'POST', headers: { authorization: `DPoP ${s.accessToken}`, dpop: proof('POST', url), 'content-type': 'application/json' }, body: JSON.stringify({ repo: s.did, collection, rkey, record: { $type: collection, text: 'probe', createdAt: new Date().toISOString() } }) })
    if (r.headers.get('dpop-nonce') && r.status === 401 && !nonce) { nonce = r.headers.get('dpop-nonce'); continue }
    return `${r.status} ${(await r.text()).slice(0, 160)}`
  }
}
console.log('app.bsky.feed.post:', await call('app.bsky.feed.post', 'probe1'))
console.log('codes.tcm.star (not in the CLI\'s scope):', await call('codes.tcm.star', 'probe2'))
console.log('codes.tcm.review (in its scope):', await call('codes.tcm.review', 'probe3'))

used by