Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/lib/server/osoh-state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"monitoredSites": [
{
"siteId": "sentinel-demo",
"siteSlug": "sentinel-demo",
"displayName": "Sentinel Demo",
"classification": "external_monitored_surface",
"status": "active_monitored_surface",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
],
"monitoredSurfaceTokens": [
{
"tokenId": "token-sentinel-demo",
"siteId": "sentinel-demo",
"tokenHash": "f17bed16f9e3d3f1d9f4f81b19bf5f7f62e8c31c6f6b2c1e6d9d2a1a5f6b4c3d",
"scope": "ingest",
"revoked": false,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
],
"monitoringEvents": [],
"auditTrail": []
}
19 changes: 18 additions & 1 deletion src/lib/server/osoh-store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';

export type IsoUtcTimestamp = string;

export type OsohMonitoredSurfaceClass = 'external_monitored_surface';
Expand All @@ -18,7 +21,9 @@ export type OsohAuditAction =
| 'site_updated'
| 'site_disabled'
| 'token_created'
| 'token_revoked';
| 'token_revoked'
| 'ingest_auth_failed'
| 'ingest_event_accepted';

export interface OsohMonitoredSiteRecord {
siteId: string;
Expand Down Expand Up @@ -69,9 +74,21 @@ export interface OsohStoreState {
auditTrail: OsohAuditTrailRecord[];
}

const statePath = resolve(process.cwd(), 'src/lib/server/osoh-state.json');

export const createEmptyOsohStoreState = (): OsohStoreState => ({
monitoredSites: [],
monitoredSurfaceTokens: [],
monitoringEvents: [],
auditTrail: [],
});

export const loadOsohStoreState = async (): Promise<OsohStoreState> => {
const raw = await readFile(statePath, 'utf8');
const parsed = JSON.parse(raw) as OsohStoreState;
return parsed;
};

export const saveOsohStoreState = async (state: OsohStoreState): Promise<void> => {
await writeFile(statePath, JSON.stringify(state, null, 2) + "`n", 'utf8');
};
96 changes: 55 additions & 41 deletions src/pages/api/v1/monitoring/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type {
OsohStoreState,
} from '../../../../../lib/server/osoh-store';
import {
createEmptyOsohStoreState,
loadOsohStoreState,
saveOsohStoreState,
} from '../../../../../lib/server/osoh-store';
import {
resolveIngestAuth,
Expand All @@ -17,7 +18,7 @@ import {

export const prerender = false;

type RouteStage = 'auth' | 'validation' | 'accepted';
type RouteStage = 'auth' | 'validation' | 'storage' | 'accepted';

type RouteErrorReason =
| 'missing_token'
Expand All @@ -32,7 +33,8 @@ type RouteErrorReason =
| 'missing_occurred_at'
| 'invalid_occurred_at'
| 'missing_payload_body'
| 'invalid_payload_body';
| 'invalid_payload_body'
| 'state_write_failed';

const json = (body: unknown, status: number): Response =>
new Response(JSON.stringify(body), {
Expand All @@ -45,43 +47,20 @@ const json = (body: unknown, status: number): Response =>

const nowIso = (): IsoUtcTimestamp => new Date().toISOString();

const loadOsohStoreState = async (): Promise<OsohStoreState> => {
return createEmptyOsohStoreState();
};

const appendMonitoringEvent = async (
const appendMonitoringEvent = (
state: OsohStoreState,
event: OsohMonitoringEventRecord,
): Promise<void> => {
): void => {
state.monitoringEvents.push(event);
};

const appendAuditTrailRecord = async (
const appendAuditTrailRecord = (
state: OsohStoreState,
record: OsohAuditTrailRecord,
): Promise<void> => {
): void => {
state.auditTrail.push(record);
};

const auditAuthFailure = async (
state: OsohStoreState,
reason: Extract<
RouteErrorReason,
'missing_token' | 'unknown_token' | 'revoked_token' | 'inactive_site' | 'invalid_scope'
>,
): Promise<void> => {
await appendAuditTrailRecord(state, {
auditId: randomUUID(),
siteId: 'unknown',
action: 'site_updated',
occurredAt: nowIso(),
details: {
category: 'ingest_auth_failure',
reason,
},
});
};

export const POST: APIRoute = async ({ request }) => {
const state = await loadOsohStoreState();

Expand All @@ -91,13 +70,35 @@ export const POST: APIRoute = async ({ request }) => {
);

if (!authResult.ok) {
await auditAuthFailure(state, authResult.reason);
appendAuditTrailRecord(state, {
auditId: randomUUID(),
siteId: 'unknown',
action: 'ingest_auth_failed',
occurredAt: nowIso(),
details: {
category: 'ingest_auth_failure',
reason: authResult.reason,
},
});

try {
await saveOsohStoreState(state);
} catch {
return json(
{
ok: false,
stage: 'storage',
reason: 'state_write_failed',
},
500,
);
}

return json(
{
ok: false,
stage: 'auth' satisfies RouteStage,
reason: authResult.reason satisfies RouteErrorReason,
stage: 'auth',
reason: authResult.reason,
},
401,
);
Expand All @@ -111,8 +112,8 @@ export const POST: APIRoute = async ({ request }) => {
return json(
{
ok: false,
stage: 'validation' satisfies RouteStage,
reason: 'invalid_json' satisfies RouteErrorReason,
stage: 'validation',
reason: 'invalid_json',
},
400,
);
Expand All @@ -124,8 +125,8 @@ export const POST: APIRoute = async ({ request }) => {
return json(
{
ok: false,
stage: 'validation' satisfies RouteStage,
reason: validationResult.reason satisfies RouteErrorReason,
stage: 'validation',
reason: validationResult.reason,
},
400,
);
Expand All @@ -134,7 +135,7 @@ export const POST: APIRoute = async ({ request }) => {
const receivedAt = nowIso();
const eventId = randomUUID();

await appendMonitoringEvent(state, {
appendMonitoringEvent(state, {
eventId,
siteId: authResult.siteId,
eventType: validationResult.normalized.eventType,
Expand All @@ -143,10 +144,10 @@ export const POST: APIRoute = async ({ request }) => {
payload: validationResult.normalized.payload,
});

await appendAuditTrailRecord(state, {
appendAuditTrailRecord(state, {
auditId: randomUUID(),
siteId: authResult.siteId,
action: 'site_updated',
action: 'ingest_event_accepted',
occurredAt: receivedAt,
details: {
category: 'ingest_event_accepted',
Expand All @@ -155,10 +156,23 @@ export const POST: APIRoute = async ({ request }) => {
},
});

try {
await saveOsohStoreState(state);
} catch {
return json(
{
ok: false,
stage: 'storage',
reason: 'state_write_failed',
},
500,
);
}

return json(
{
ok: true,
stage: 'accepted' satisfies RouteStage,
stage: 'accepted',
eventId,
siteId: authResult.siteId,
receivedAt,
Expand Down
Loading