import { getPool } from "../../../db/index.js"; import { createHash } from "crypto"; import { getConfig } from "../../../common/config.js"; import axios from "axios"; import sodium from "sodium-native"; async function getEntity(client, userId, shortName) { const sql = ` SELECT JSON_BUILD_OBJECT( 'fedidUrl', e.fedid_url, 'shortName', e.short_name, 'didId', e.did_id, 'controlPrivateKeyB64U', e.control_private_key_b64u, 'recoveryPrivateKeyB64U', e.recovery_private_key_b64u, 'didDoc', e.did_doc ) AS record FROM entity e WHERE LOWER(e.short_name) = LOWER($1) AND e.user_id = $2; ` const res = await client.query(sql, [ shortName, userId, ]); if (res.rows.length > 0 && res.rows[0].record) { return res.rows[0].record; } return null; } async function get(input, userId) { let response = { success: false, error: 'Unknown error', }; const client = await getPool(); try { const existingEntity = await getEntity(client, userId, input.shortName) if (!existingEntity) { response.error = 'entity not found' } else { const data = { didDoc: existingEntity.didDoc, } response = { message: `retrieved: ${input.shortName}`, data, } } } catch(e) { console.error(e) } finally { await client.release(); } return response; } async function getDomains(input, userId) { let response = { success: false, error: 'Unknown error', }; try { const config = getConfig(); const fedidUrl = input.fedidUrl ?? config.defaultFedidUrl; const data = (await axios.get( `${fedidUrl}/api/v2/domains`, )).data.data.domains; response = { message: `retrieved domains`, data, } } catch(e) { console.error(e) } return response; } async function save(client, userId, entity) { const res = await client.query(` INSERT INTO entity ( user_id, fedid_url, short_name, did_id, control_private_key_b64u, recovery_private_key_b64u, did_doc ) VALUES ( $1, $2, $3, $4, $5, $6, $7 ) RETURNING id; `, [ userId, entity.fedidUrl, entity.shortName, entity.didDoc.id, entity.controlPrivateKeyB64U, entity.recoveryPrivateKeyB64U, entity.didDoc, ]); } async function create(input, userId) { let response = { success: false, error: 'Unknown error', }; const client = await getPool(); try { const config = getConfig(); const fedidUrl = input.fedidUrl ?? config.defaultFedidUrl; const domains = (await axios.get( `${fedidUrl}/api/v2/domains`, )).data.data.domains; const shortName = input.shortName.split('@') const domain = shortName[1] if (!domains.includes(domain)) { throw new Error('domain is not available'); } const entity = { shortName: input.shortName, fedidUrl, } // Generate a control key entity.controlPublicKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES); entity.controlPrivateKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES); sodium.crypto_sign_keypair(entity.controlPublicKey, entity.controlPrivateKey); entity.controlPublicKeyB64U = entity.controlPublicKey.toString("base64url"); entity.controlPrivateKeyB64U = entity.controlPrivateKey.toString("base64url"); // Generate a recovery key entity.recoveryPublicKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES); entity.recoveryPrivateKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES); entity.recoveryPrivateKeyB64U = entity.recoveryPrivateKey.toString("base64url"); sodium.crypto_sign_keypair(entity.recoveryPublicKey, entity.recoveryPrivateKey); entity.recoveryHash = createHash("sha256").update(entity.recoveryPublicKey).digest("hex").slice(0, 48); entity.didDoc = (await axios.post( `${fedidUrl}/api/v2/did/create`, { shortName: entity.shortName, control: entity.controlPublicKeyB64U, recoveryHash: entity.recoveryHash, }, )).data.data.didDoc; await save(client, userId, entity); response = { message: `created and saved: ${entity.didDoc.id}`, data: { didDoc: entity.didDoc, }, } } catch(e) { console.error(e); response.error = e.message; } finally { await client.release(); } return response; } export const entity = { getEntity, getDomains, get, create, }