434 lines
12 KiB
JavaScript
434 lines
12 KiB
JavaScript
import { getPool } from "../../db/index.js";
|
|
|
|
function isUuid(uuid) {
|
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid)) return true;
|
|
return false;
|
|
}
|
|
|
|
function getAuditErrors(audit) {
|
|
if (typeof audit !== 'object') {
|
|
return 'invalid audit object format';
|
|
}
|
|
|
|
if (audit.version != 1) {
|
|
return 'audit version invalid';
|
|
}
|
|
|
|
if (audit.hashType != 'SHA256') {
|
|
return 'audit hash type invalid';
|
|
}
|
|
|
|
if (!audit.digest) {
|
|
return 'audit digest should exist';
|
|
}
|
|
|
|
if (isNaN(audit.created)) {
|
|
return 'audit created type invalid';
|
|
}
|
|
|
|
if (!audit.eventId && !audit.agreementId) {
|
|
return 'eventId or agreementId should exist';
|
|
}
|
|
|
|
if (audit.eventId && !isUuid(audit.eventId)) {
|
|
return 'eventId invalid';
|
|
}
|
|
|
|
if (audit.agreementId && !isUuid(audit.agreementId)) {
|
|
return 'agreementId invalid';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function processAudit(data) {
|
|
const client = await getPool();
|
|
try {
|
|
let type = data.audit.eventId ? 'event' : 'agreement';
|
|
const digest = data.audit.digest;
|
|
let id = data.audit.eventId ? data.audit.eventId : data.audit.agreementId;
|
|
await client.query(`BEGIN`);
|
|
let res = await client.query(`
|
|
SELECT 1
|
|
FROM audit a
|
|
WHERE a.${type}_id = $1
|
|
AND digest = $2
|
|
LIMIT 1;
|
|
`, [
|
|
id,
|
|
digest,
|
|
]);
|
|
if (res.rows.length > 0) {
|
|
throw new Error('audit record with id and digest exists');
|
|
}
|
|
res = await client.query(`
|
|
INSERT INTO audit (
|
|
version,
|
|
event_id,
|
|
agreement_id,
|
|
hash_type,
|
|
digest,
|
|
created
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6
|
|
) RETURNING audit_id;
|
|
`, [
|
|
data.audit.version,
|
|
data.audit.eventId,
|
|
data.audit.agreementId,
|
|
data.audit.hashType,
|
|
data.audit.digest,
|
|
data.audit.created,
|
|
]);
|
|
for (const signature of data.signatures) {
|
|
await client.query(`
|
|
INSERT INTO audit_signature (
|
|
audit_id,
|
|
version,
|
|
id,
|
|
signedOn,
|
|
type,
|
|
jws
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6
|
|
);
|
|
`, [
|
|
res.rows[0].audit_id,
|
|
signature.version,
|
|
signature.id,
|
|
signature.signedOn,
|
|
signature.type,
|
|
signature.jws,
|
|
]);
|
|
}
|
|
if (data.meta) {
|
|
for await (const [key, value] of Object.entries(data.meta)) {
|
|
await client.query(`
|
|
INSERT INTO audit_meta (
|
|
audit_id,
|
|
key,
|
|
value
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
);
|
|
`, [
|
|
res.rows[0].audit_id,
|
|
key,
|
|
value,
|
|
]);
|
|
}
|
|
}
|
|
await client.query('COMMIT');
|
|
} catch(e) {
|
|
console.error(e)
|
|
} finally {
|
|
await client.release();
|
|
}
|
|
}
|
|
|
|
async function getAuditsByAgreementId(client, id, offset, limit) {
|
|
let ret = await client.query(`
|
|
WITH origAudits AS (
|
|
SELECT
|
|
audit_id,
|
|
version,
|
|
agreement_id,
|
|
hash_type,
|
|
digest,
|
|
created
|
|
FROM audit
|
|
WHERE agreement_id = $1
|
|
ORDER BY created DESC
|
|
OFFSET $2
|
|
LIMIT $3
|
|
),
|
|
audits AS (
|
|
SELECT
|
|
a.*,
|
|
JSON_AGG(json_build_object(
|
|
'version', s.version,
|
|
'id', s.id,
|
|
'signedOn', s.signedOn,
|
|
'type', s.type,
|
|
'jws', s.jws
|
|
)) AS signatures
|
|
FROM
|
|
origAudits a,
|
|
audit_signature s
|
|
WHERE s.audit_id = a.audit_id
|
|
GROUP BY
|
|
a.audit_id,
|
|
a.version,
|
|
a.agreement_id,
|
|
a.hash_type,
|
|
a.digest,
|
|
a.created
|
|
)
|
|
SELECT
|
|
JSON_AGG(json_build_object(
|
|
'audit', json_build_object(
|
|
'version', a.version,
|
|
'agreementId', a.agreement_id,
|
|
'hashType', a.hash_type,
|
|
'digest', a.digest,
|
|
'created', a.created
|
|
),
|
|
'signatures', a.signatures
|
|
)) AS records
|
|
FROM audits a
|
|
`, [
|
|
id,
|
|
offset,
|
|
limit,
|
|
]);
|
|
return ret;
|
|
}
|
|
|
|
async function getAuditsByEventId(client, id, offset, limit) {
|
|
let ret = await client.query(`
|
|
WITH origAudits AS (
|
|
SELECT
|
|
audit_id,
|
|
version,
|
|
event_id,
|
|
hash_type,
|
|
digest,
|
|
created
|
|
FROM audit
|
|
WHERE event_id = $1
|
|
ORDER BY created DESC
|
|
OFFSET $2
|
|
LIMIT $3
|
|
),
|
|
audits AS (
|
|
SELECT
|
|
a.*,
|
|
JSON_AGG(json_build_object(
|
|
'version', s.version,
|
|
'id', s.id,
|
|
'signedOn', s.signedOn,
|
|
'type', s.type,
|
|
'jws', s.jws
|
|
)) AS signatures
|
|
FROM
|
|
origAudits a,
|
|
audit_signature s
|
|
WHERE s.audit_id = a.audit_id
|
|
GROUP BY
|
|
a.audit_id,
|
|
a.version,
|
|
a.event_id,
|
|
a.hash_type,
|
|
a.digest,
|
|
a.created
|
|
)
|
|
SELECT
|
|
JSON_AGG(json_build_object(
|
|
'audit', json_build_object(
|
|
'version', a.version,
|
|
'eventId', a.event_id,
|
|
'hashType', a.hash_type,
|
|
'digest', a.digest,
|
|
'created', a.created
|
|
),
|
|
'signatures', a.signatures
|
|
)) AS records
|
|
FROM audits a
|
|
`, [
|
|
id,
|
|
offset,
|
|
limit,
|
|
]);
|
|
return ret;
|
|
}
|
|
|
|
async function getAuditsByMeta(client, meta, offset, limit) {
|
|
const fields = [offset, limit];
|
|
let count = fields.length + 1;
|
|
let whereInVals = ``
|
|
for await (const [key, value] of Object.entries(meta)) {
|
|
if (whereInVals != ``)
|
|
whereInVals = ` AND `
|
|
whereInVals += `(key = $${count++} AND value = $${count++})`;
|
|
fields.push(key);
|
|
fields.push(value);
|
|
}
|
|
let ret = await client.query(`
|
|
WITH origAudits AS (
|
|
SELECT
|
|
audit_id,
|
|
version,
|
|
event_id,
|
|
hash_type,
|
|
digest,
|
|
created
|
|
FROM audit
|
|
WHERE audit_id IN (
|
|
SELECT audit_id
|
|
FROM audit_meta
|
|
WHERE ${whereInVals}
|
|
)
|
|
ORDER BY created DESC
|
|
OFFSET $1
|
|
LIMIT $2
|
|
),
|
|
audits AS (
|
|
SELECT
|
|
a.*,
|
|
JSON_AGG(json_build_object(
|
|
'version', s.version,
|
|
'id', s.id,
|
|
'signedOn', s.signedOn,
|
|
'type', s.type,
|
|
'jws', s.jws
|
|
)) AS signatures
|
|
FROM
|
|
origAudits a,
|
|
audit_signature s
|
|
WHERE s.audit_id = a.audit_id
|
|
GROUP BY
|
|
a.audit_id,
|
|
a.version,
|
|
a.event_id,
|
|
a.hash_type,
|
|
a.digest,
|
|
a.created
|
|
)
|
|
SELECT
|
|
JSON_AGG(json_build_object(
|
|
'audit', json_build_object(
|
|
'version', a.version,
|
|
'eventId', a.event_id,
|
|
'hashType', a.hash_type,
|
|
'digest', a.digest,
|
|
'created', a.created
|
|
),
|
|
'signatures', a.signatures
|
|
)) AS records
|
|
FROM audits a
|
|
`, fields);
|
|
return ret;
|
|
}
|
|
|
|
async function getAudits(eventId, agreementId, meta, offset) {
|
|
let ret;
|
|
let res;
|
|
const limit = 100;
|
|
const client = await getPool();
|
|
try {
|
|
if (eventId) {
|
|
res = await getAuditsByEventId(client, eventId, offset, limit);
|
|
} else if (agreementId) {
|
|
res = await getAuditsByAgreementId(client, agreementId, offset, limit);
|
|
} else {
|
|
res = await getAuditsByMeta(client, meta, offset, limit);
|
|
}
|
|
if (res.rows.length > 0 && res.rows[0].records) {
|
|
ret = {
|
|
auditRecords: res.rows[0].records
|
|
};
|
|
if (ret.auditRecords.length === limit) {
|
|
ret.nextPageToken = Buffer.from(`${offset + limit}`).toString("base64");
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
} finally {
|
|
await client.release();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
async function put(input) {
|
|
let res = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
try {
|
|
const { audit, meta } = input;
|
|
const error = getAuditErrors(audit);
|
|
if (error) {
|
|
res.error = error;
|
|
console.log(`${prefix} - Bad Audit: ${error}`);
|
|
} else {
|
|
const id = audit.eventId ? audit.eventId : audit.agreementId;
|
|
if (audit.eventId) {
|
|
res.message = `event: ${audit.eventId}`;
|
|
} else {
|
|
res.message = `agreement: ${audit.agreementId}`;
|
|
}
|
|
await processAudit(input);
|
|
res.success = true;
|
|
delete res.error;
|
|
}
|
|
} catch (e) {
|
|
if (e.message != 'audit record with id and digest exists')
|
|
console.error(e);
|
|
res.error = e.message;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
async function get(input) {
|
|
let res = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
try {
|
|
let error;
|
|
let id;
|
|
let type;
|
|
if (input.eventId) {
|
|
id = input.eventId;
|
|
type = 'event';
|
|
error = isUuid(input.eventId) ? null : 'eventId invalid';
|
|
} else if (input.agreementId) {
|
|
id = input.agreementId;
|
|
type = 'agreement';
|
|
error = isUuid(input.agreementId) ? null : 'agreementId invalid';
|
|
} else if (input.meta) {
|
|
id = `<meta key/value>`;
|
|
type = 'meta';
|
|
} else {
|
|
error = 'eventId, agreementId, or meta required';
|
|
}
|
|
if (error) {
|
|
res.error = error;
|
|
res.message = `bad request: ${error}`;
|
|
} else {
|
|
res.message = `requested ${type}: ${id}`;
|
|
const offset = input.pageToken ? parseInt(Buffer.from(`${input.pageToken}`, "base64").toString('ascii')) : 0;
|
|
let ret = await getAudits(input.eventId, input.agreementId, input.meta, offset);
|
|
if (ret) {
|
|
res.data = ret;
|
|
res.success = true;
|
|
delete res.error;
|
|
} else {
|
|
res.error = "not found"
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e.message != 'audit record with id and digest exists')
|
|
console.error(e);
|
|
res.error = e.message;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
export const archive = {
|
|
put,
|
|
get,
|
|
}
|