chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
/**
* On-disk store for OAuth sessions, one JSON file per identifier under
* ~/.config/atproto-git. The file holds the token pair and the private DPoP
* key they are bound to, so it is written owner-readable only.
*
* The git credential store cannot hold these: it stores flat
* username/password strings, and a DPoP-bound token is useless without its
* key.
*/
import {
mkdirSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
/**
* @param {Record<string, string|undefined>} env
* @returns {string}
*/
export function sessionDir(env) {
if (env.ATPROTO_GIT_CONFIG_DIR) return env.ATPROTO_GIT_CONFIG_DIR;
const base = env.XDG_CONFIG_HOME || join(homedir(), '.config');
return join(base, 'atproto-git');
}
/**
* A filesystem-safe name for an identifier. Handles and DIDs share a charset
* that only needs `:` mapped, but escape defensively.
* @param {string} identifier
* @returns {string}
*/
function fileName(identifier) {
return `${identifier.replace(/[^A-Za-z0-9._-]/g, '_')}.json`;
}
/**
* @param {Record<string, string|undefined>} env
* @param {string} identifier - handle or DID the session was created for
* @returns {import('./oauth.js').OauthSession|null}
*/
export function loadSession(env, identifier) {
try {
const raw = readFileSync(
join(sessionDir(env), fileName(identifier)),
'utf8',
);
return JSON.parse(raw);
} catch {
return null;
}
}
/**
* Find a session by the DID it authenticated, for callers that know the
* account but not the identifier it was logged in under.
* @param {Record<string, string|undefined>} env
* @param {string} did
* @returns {import('./oauth.js').OauthSession|null}
*/
export function findSessionByDid(env, did) {
try {
for (const entry of readdirSync(sessionDir(env))) {
if (!entry.endsWith('.json')) continue;
try {
const session = JSON.parse(
readFileSync(join(sessionDir(env), entry), 'utf8'),
);
if (session?.did === did) return session;
} catch {
// A malformed file hides one session, not all of them.
}
}
} catch {
// No directory means no sessions.
}
return null;
}
/**
* @param {Record<string, string|undefined>} env
* @param {string} identifier
* @param {import('./oauth.js').OauthSession} session
*/
export function saveSession(env, identifier, session) {
const dir = sessionDir(env);
mkdirSync(dir, { recursive: true, mode: 0o700 });
writeFileSync(join(dir, fileName(identifier)), JSON.stringify(session), {
mode: 0o600,
});
}
/**
* @param {Record<string, string|undefined>} env
* @param {string} identifier
*/
export function deleteSession(env, identifier) {
rmSync(join(sessionDir(env), fileName(identifier)), { force: true });
}