accounts
Version:
Tempo Accounts SDK
158 lines • 5.89 kB
JavaScript
import { createClient, http } from 'viem';
import { tempo, tempoDevnet, tempoModerato } from 'viem/chains';
import * as z from 'zod/mini';
import * as CliAuth from '../../CliAuth.js';
import { from } from '../../Handler.js';
/**
* Instantiates a generic device-code handler for access-key bootstrap.
*
* Exposes 4 endpoints:
* - `GET /auth/pkce/pending/:code`
* - `POST /auth/pkce/code`
* - `POST /auth/pkce/poll/:code`
* - `POST /auth/pkce`
*
* @param {codeAuth.Options} options - Options.
* @returns {Handler} Request handler.
*/
export function codeAuth(options = {}) {
const { chains = [tempo, tempoModerato, tempoDevnet], now, path = '/auth/pkce', maxBodyBytes = 16_384, policy, random, rateLimit = CliAuth.RateLimit.memory({ max: 120, windowMs: 60_000 }), rateLimitKey = getRateLimitKey, store = CliAuth.Store.memory(), transports = {}, ttlMs, ...rest } = options;
const [defaultChain] = chains;
const clients = new Map();
for (const chain of chains) {
const transport = transports[chain.id] ?? http();
clients.set(chain.id, createClient({ chain, transport }));
}
function getClient(chainId) {
const id = Number(chainId ?? defaultChain.id);
const client = clients.get(id);
if (!client)
throw new Error(`Chain ${id} not configured`);
return client;
}
const router = from(rest);
async function checkRateLimit(request) {
if (rateLimit === false)
return undefined;
const { success } = await rateLimit.limit({ key: rateLimitKey(request), request });
if (success)
return undefined;
return Response.json({ error: 'Rate limit exceeded.' }, { status: 429 });
}
router.get(`${path}/pending/:code`, async (c) => {
const limited = await checkRateLimit(c.req.raw);
if (limited)
return limited;
try {
const code = c.req.param('code');
const result = await CliAuth.pending({
code,
...(now ? { now } : {}),
store,
});
return Response.json(z.encode(CliAuth.pendingResponse, result));
}
catch (error) {
const status = error instanceof CliAuth.PendingError ? error.status : 400;
return Response.json({ error: error.message }, { status });
}
});
router.post(`${path}/code`, async (c) => {
const limited = await checkRateLimit(c.req.raw);
if (limited)
return limited;
try {
const request = z.decode(CliAuth.createRequest, await readJson(c.req.raw, maxBodyBytes));
const chainId = request.chainId ?? defaultChain.id;
getClient(chainId);
const result = await CliAuth.createDeviceCode({
chainId,
...(now ? { now } : {}),
...(policy ? { policy } : {}),
...(random ? { random } : {}),
request,
store,
...(typeof ttlMs !== 'undefined' ? { ttlMs } : {}),
});
return Response.json(z.encode(CliAuth.createResponse, result));
}
catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
});
router.post(`${path}/poll/:code`, async (c) => {
const limited = await checkRateLimit(c.req.raw);
if (limited)
return limited;
try {
const request = z.decode(CliAuth.pollRequest, await readJson(c.req.raw, maxBodyBytes));
const code = c.req.param('code');
const result = await CliAuth.poll({
code,
...(now ? { now } : {}),
request,
store,
});
return Response.json(z.encode(CliAuth.pollResponse, result));
}
catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
});
router.post(path, async (c) => {
const limited = await checkRateLimit(c.req.raw);
if (limited)
return limited;
try {
const request = z.decode(CliAuth.authorizeRequest, await readJson(c.req.raw, maxBodyBytes));
const result = await CliAuth.authorize({
client: getClient(request.keyAuthorization.chainId),
...(now ? { now } : {}),
request,
store,
});
return Response.json(z.encode(CliAuth.authorizeResponse, result));
}
catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
});
return router;
}
async function readJson(request, maxBodyBytes) {
const length = request.headers.get('content-length');
if (length && Number(length) > maxBodyBytes)
throw new Error('Request body is too large.');
if (!request.body)
return JSON.parse('');
const reader = request.body.getReader();
const chunks = [];
let size = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done)
break;
size += value.byteLength;
if (size > maxBodyBytes) {
await reader.cancel().catch(() => undefined);
throw new Error('Request body is too large.');
}
chunks.push(value);
}
}
finally {
reader.releaseLock();
}
const bytes = new Uint8Array(size);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.byteLength;
}
return JSON.parse(new TextDecoder().decode(bytes));
}
function getRateLimitKey(request) {
return request.headers.get('cf-connecting-ip') ?? 'unknown';
}
//# sourceMappingURL=codeAuth.js.map