75 lines
2.3 KiB
JavaScript
75 lines
2.3 KiB
JavaScript
import crypto from 'node:crypto';
|
|
import bcrypt from 'bcrypt';
|
|
import { pool } from '../pool.js';
|
|
import { recordAudit } from './audit_stub.js';
|
|
|
|
const FIELDS = ['slug','name','kind','model','persona_path','capabilities','scopes'];
|
|
|
|
export async function create(input, actor) {
|
|
const cols = [], vals = [], ph = [];
|
|
let i = 1;
|
|
for (const f of FIELDS) {
|
|
if (input[f] !== undefined) { cols.push(f); vals.push(input[f]); ph.push(`$${i++}`); }
|
|
}
|
|
const { rows: [r] } = await pool.query(
|
|
`INSERT INTO agents(${cols.join(',')}) VALUES(${ph.join(',')}) RETURNING *`,
|
|
vals
|
|
);
|
|
await recordAudit(actor, 'create', 'agent', r.id, null, r);
|
|
return r;
|
|
}
|
|
|
|
export async function getById(id) {
|
|
const { rows: [r] } = await pool.query(`SELECT * FROM agents WHERE id=$1`, [id]);
|
|
return r;
|
|
}
|
|
|
|
export async function getBySlug(slug) {
|
|
const { rows: [r] } = await pool.query(`SELECT * FROM agents WHERE slug=$1`, [slug]);
|
|
return r;
|
|
}
|
|
|
|
export async function list() {
|
|
const { rows } = await pool.query(`SELECT * FROM agents ORDER BY name`);
|
|
return rows;
|
|
}
|
|
|
|
export async function setCapabilities(id, capabilities, scopes) {
|
|
const { rows: [r] } = await pool.query(
|
|
`UPDATE agents SET capabilities=$1, scopes=$2 WHERE id=$3 RETURNING *`,
|
|
[capabilities, scopes || {}, id]
|
|
);
|
|
return r;
|
|
}
|
|
|
|
export async function createToken(agent_id, label) {
|
|
const plaintext = 'vk_' + crypto.randomBytes(32).toString('base64url');
|
|
const token_hash = await bcrypt.hash(plaintext, 12);
|
|
const { rows: [t] } = await pool.query(
|
|
`INSERT INTO agent_tokens(agent_id, label, token_hash) VALUES($1,$2,$3) RETURNING id`,
|
|
[agent_id, label || null, token_hash]
|
|
);
|
|
return { token: plaintext, id: t.id };
|
|
}
|
|
|
|
export async function verifyToken(plaintext) {
|
|
if (!plaintext?.startsWith('vk_')) return null;
|
|
const { rows } = await pool.query(
|
|
`SELECT t.id, t.token_hash, t.agent_id, a.*
|
|
FROM agent_tokens t JOIN agents a ON a.id = t.agent_id
|
|
WHERE t.revoked_at IS NULL`
|
|
);
|
|
for (const row of rows) {
|
|
if (await bcrypt.compare(plaintext, row.token_hash)) {
|
|
await pool.query(`UPDATE agent_tokens SET last_used=now() WHERE id=$1`, [row.id]);
|
|
const { token_hash, ...agent } = row;
|
|
return agent;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function revokeToken(token_id) {
|
|
await pool.query(`UPDATE agent_tokens SET revoked_at=now() WHERE id=$1`, [token_id]);
|
|
}
|