feat(runtime): add production control-plane foundation
This commit is contained in:
+39
-2
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
||||
import { GATEWAY_AUTH_HEADER } from './runtime-config.js';
|
||||
import type { Writable } from 'node:stream';
|
||||
import Fastify, {
|
||||
type FastifyInstance,
|
||||
@@ -12,6 +13,7 @@ export const API_DEFAULT_PORT = 8790 as const;
|
||||
const LEGACY_PORT = 8788;
|
||||
|
||||
export const REDACT_PATHS = Object.freeze([
|
||||
`req.headers.${GATEWAY_AUTH_HEADER}`,
|
||||
'req.headers.authorization',
|
||||
'req.headers.cookie',
|
||||
'req.headers.x-confirmation-token',
|
||||
@@ -26,11 +28,24 @@ export interface ReadinessResult {
|
||||
}
|
||||
|
||||
export interface BuildAppOptions {
|
||||
readonly gatewayToken?: string;
|
||||
readonly readiness?: () => ReadinessResult | Promise<ReadinessResult>;
|
||||
readonly registerRoutes?: (app: FastifyInstance) => void | Promise<void>;
|
||||
readonly logger?: false | { readonly stream?: Writable };
|
||||
}
|
||||
|
||||
export function secureTokenEqual(
|
||||
supplied: string,
|
||||
expected: string,
|
||||
compare: (
|
||||
actual: NodeJS.ArrayBufferView,
|
||||
expected: NodeJS.ArrayBufferView,
|
||||
) => boolean = timingSafeEqual,
|
||||
): boolean {
|
||||
const digest = (value: string): Buffer => createHash('sha256').update(value, 'utf8').digest();
|
||||
return compare(digest(supplied), digest(expected));
|
||||
}
|
||||
|
||||
export interface ListenEnvironment {
|
||||
readonly API_HOST?: string;
|
||||
readonly API_PORT?: string;
|
||||
@@ -171,10 +186,32 @@ export function buildApp(options: BuildAppOptions = {}): FastifyInstance {
|
||||
},
|
||||
};
|
||||
const app = Fastify(fastifyOptions);
|
||||
const readiness = options.readiness ?? (() => ({ ready: true, checks: { bootstrap: 'ready' } }));
|
||||
const readiness =
|
||||
options.readiness ?? (() => ({ ready: false, checks: { bootstrap: 'unconfigured' } }));
|
||||
|
||||
app.addHook('onRequest', async (request, reply) => {
|
||||
reply.header('X-Request-Id', request.id);
|
||||
if (options.gatewayToken !== undefined) {
|
||||
const supplied = request.headers[GATEWAY_AUTH_HEADER];
|
||||
const authenticated = secureTokenEqual(
|
||||
typeof supplied === 'string' ? supplied : '',
|
||||
options.gatewayToken,
|
||||
);
|
||||
if (!authenticated) {
|
||||
return reply
|
||||
.code(401)
|
||||
.type('application/problem+json')
|
||||
.send(
|
||||
sendProblem(
|
||||
request,
|
||||
401,
|
||||
'Unauthorized',
|
||||
'GATEWAY_AUTH_REQUIRED',
|
||||
'Trusted gateway authentication is required.',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
const host = request.headers.host;
|
||||
if (!host || !isLoopbackAuthority(host)) {
|
||||
return reply
|
||||
|
||||
Reference in New Issue
Block a user