chadtmiller.com/knotless
Git hosted in your atproto account, on any stock PDS, fast enough for agents.
git clone knotless://chadtmiller.com/knotlessneeds the knotless git helper. set it up
// Stands in for the user's browser on the reference PDS's authorization
// page: signs in, approves (or rejects) the request, and follows the
// redirect back to the client's loopback listener.
//
// node harness/authorize.mjs <handle> <password> [--reject] [--ca <cert>]
// <authorize-url>
//
// `knotless login` and `knotless delegate` run it as KNOTLESS_BROWSER, with
// the URL last. With --ca, it reaches the harness's gateway hostname as
// pds.mjs does: at 127.0.0.1, trusting that certificate. It speaks plain
// node:http(s), as fetch sets the sec-fetch-* headers itself.
import { request as http } from 'node:http'
import { request as https } from 'node:https'
import { readFileSync } from 'node:fs'
const args = process.argv.slice(2)
const reject = args.includes('--reject')
const at = args.indexOf('--ca')
const ca = at === -1 ? undefined : readFileSync(args.splice(at, 2)[1])
const [username, password, url] = args.filter((a) => a !== '--reject')
const authorize = new URL(url)
const origin = authorize.origin
const api = `${origin}/@atproto/oauth-provider/~api`
const jar = new Map()
const cookies = () =>
[...jar].map(([name, value]) => `${name}=${value}`).join('; ')
// The gateway hostname, as pds.mjs resolves it.
const local = (_hostname, options, callback) =>
options?.all
? callback(null, [{ address: '127.0.0.1', family: 4 }])
: callback(null, '127.0.0.1', 4)
// One request: its status, headers and body.
const send = (url, { method = 'GET', headers = {}, body } = {}) =>
new Promise((done, fail) => {
const secure = new URL(url).protocol === 'https:'
const tls = secure ? { ca, lookup: local } : {}
const req = (secure ? https : http)(url, { method, headers, ...tls }, (res) => {
const chunks = []
res.on('data', (c) => chunks.push(c))
res.on('end', () =>
done({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks).toString() }),
)
})
req.on('error', fail)
req.end(body)
})
const keep = (res) => {
for (const cookie of res.headers['set-cookie'] ?? []) {
const [pair] = cookie.split(';')
const at = pair.indexOf('=')
jar.set(pair.slice(0, at), pair.slice(at + 1))
}
}
const page = await send(authorize, {
headers: {
'sec-fetch-mode': 'navigate',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'cross-site',
},
})
keep(page)
// A request the server refuses goes straight back to the client.
if (page.headers.location) {
await send(page.headers.location)
process.exit(0)
}
if (page.status !== 200) throw new Error(`authorize page: ${page.status} ${page.body}`)
const call = async (endpoint, body) => {
const res = await send(`${api}${endpoint}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'sec-fetch-mode': 'same-origin',
'sec-fetch-site': 'same-origin',
origin,
referer: authorize.href,
'x-csrf-token': jar.get('csrf-token'),
cookie: cookies(),
},
body: JSON.stringify(body),
})
keep(res)
if (res.status !== 200) throw new Error(`${endpoint}: ${res.status} ${res.body}`)
return JSON.parse(res.body)
}
let done
if (reject) {
done = await call('/reject', {})
} else {
const { account } = await call('/sign-in', {
locale: 'en',
username,
password,
remember: true,
})
done = await call('/consent', { did: account.did })
}
const back = await send(done.url, {
headers: {
'sec-fetch-mode': 'navigate',
'sec-fetch-dest': 'document',
'sec-fetch-site': 'same-origin',
referer: authorize.href,
cookie: cookies(),
},
})
const location = back.headers.location
if (!location) throw new Error(`redirect: ${back.status} ${back.body}`)
await send(location)