146 lines
2.9 KiB
JavaScript
146 lines
2.9 KiB
JavaScript
import { ENVIRONMENT_DEV } from "../protocol/index.mjs";
|
|
|
|
export const CLOUD_API_SERVICE_ID = "hwlab-cloud-api";
|
|
|
|
export const AUDIT_FIELD_NAMES = Object.freeze([
|
|
"requestId",
|
|
"actor",
|
|
"source",
|
|
"operation",
|
|
"target",
|
|
"result",
|
|
"timestamp"
|
|
]);
|
|
|
|
export function createAuditRecord(input = {}) {
|
|
const record = {
|
|
requestId: asString(input.requestId, "req_unassigned"),
|
|
actor: normalizeActor(input.actor),
|
|
source: normalizeSource(input.source, input.traceId),
|
|
operation: asString(input.operation, "cloud_api.request"),
|
|
target: normalizeTarget(input.target),
|
|
result: asString(input.result, "unknown"),
|
|
timestamp: asString(input.timestamp, new Date().toISOString())
|
|
};
|
|
|
|
assertAuditFields(record);
|
|
return record;
|
|
}
|
|
|
|
export function deriveActorFromMeta(meta = {}) {
|
|
return {
|
|
type: meta.actorType || "system",
|
|
id: meta.actorId || `system_${CLOUD_API_SERVICE_ID}`
|
|
};
|
|
}
|
|
|
|
export function assertAuditFields(record) {
|
|
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
throw new Error("audit record must be an object");
|
|
}
|
|
|
|
for (const field of AUDIT_FIELD_NAMES) {
|
|
if (!Object.hasOwn(record, field)) {
|
|
throw new Error(`audit.${field} is required`);
|
|
}
|
|
}
|
|
|
|
if (Number.isNaN(Date.parse(record.timestamp))) {
|
|
throw new Error("audit.timestamp must be an RFC 3339 timestamp");
|
|
}
|
|
}
|
|
|
|
function normalizeActor(actor) {
|
|
if (!actor) {
|
|
return {
|
|
type: "system",
|
|
id: `system_${CLOUD_API_SERVICE_ID}`
|
|
};
|
|
}
|
|
|
|
if (typeof actor === "string") {
|
|
return {
|
|
type: "user",
|
|
id: actor
|
|
};
|
|
}
|
|
|
|
if (typeof actor === "object" && !Array.isArray(actor)) {
|
|
return {
|
|
type: asString(actor.type, "user"),
|
|
id: asString(actor.id, `system_${CLOUD_API_SERVICE_ID}`)
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: "system",
|
|
id: `system_${CLOUD_API_SERVICE_ID}`
|
|
};
|
|
}
|
|
|
|
function normalizeSource(source, traceId) {
|
|
const base = {
|
|
serviceId: CLOUD_API_SERVICE_ID,
|
|
environment: ENVIRONMENT_DEV
|
|
};
|
|
|
|
if (traceId) {
|
|
base.traceId = traceId;
|
|
}
|
|
|
|
if (!source) {
|
|
return base;
|
|
}
|
|
|
|
if (typeof source === "string") {
|
|
return {
|
|
...base,
|
|
serviceId: source
|
|
};
|
|
}
|
|
|
|
if (typeof source === "object" && !Array.isArray(source)) {
|
|
return {
|
|
...base,
|
|
...source
|
|
};
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
function normalizeTarget(target) {
|
|
if (!target) {
|
|
return {
|
|
type: "cloud_api",
|
|
id: CLOUD_API_SERVICE_ID
|
|
};
|
|
}
|
|
|
|
if (typeof target === "string") {
|
|
return {
|
|
type: "resource",
|
|
id: target
|
|
};
|
|
}
|
|
|
|
if (typeof target === "object" && !Array.isArray(target)) {
|
|
return {
|
|
type: asString(target.type, "resource"),
|
|
id: asString(target.id, "target_unspecified")
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: "cloud_api",
|
|
id: CLOUD_API_SERVICE_ID
|
|
};
|
|
}
|
|
|
|
function asString(value, fallback) {
|
|
if (value === undefined || value === null || value === "") {
|
|
return fallback;
|
|
}
|
|
return String(value);
|
|
}
|