feat(api): serve well-known discovery with etag and 304 (#2349)

This commit is contained in:
Hampus
2026-09-02 17:24:11 +02:00
committed by GitHub
parent 5719dfe8a3
commit 4f48e04cad
4 changed files with 74 additions and 0 deletions
@@ -36,6 +36,20 @@ export function configureMiddleware(routes: HonoApp, options: MiddlewarePipeline
const resolvedHeader = resolveClientIpHeaderName(clientIpHeaderName);
routes.use('/webhooks/:webhook_id/:token', cors({origins: '*'}));
routes.use('/webhooks/:webhook_id/:token/messages/:message_id', cors({origins: '*'}));
routes.use(
'/.well-known/fluxer',
cors({
origins: '*',
methods: ['GET', 'HEAD', 'OPTIONS'],
allowedHeaders: [
HttpHeaders.ACCEPT,
HttpHeaders.CONTENT_TYPE,
HttpHeaders.IF_MODIFIED_SINCE,
HttpHeaders.IF_NONE_MATCH,
],
exposedHeaders: [HttpHeaders.ETAG, HttpHeaders.LAST_MODIFIED],
}),
);
applyMiddlewareStack(routes, {
requestId: {},
cors: {origins: corsOrigins, exposedHeaders: [HttpHeaders.X_FLUXER_VERSION]},
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createHash} from 'node:crypto';
import {entityTagMatches} from '../utils/EntityTag';
export interface DiscoveryValidators {
etag: string;
lastModified: Date;
}
export function nextDiscoveryValidators(document: unknown, previous: DiscoveryValidators | null): DiscoveryValidators {
const etag = `"${createHash('sha256').update(JSON.stringify(document)).digest('hex')}"`;
if (previous !== null && previous.etag === etag) {
return previous;
}
return {etag, lastModified: new Date(Math.floor(Date.now() / 1000) * 1000)};
}
export function isDiscoveryNotModified(
validators: DiscoveryValidators,
ifNoneMatch: string | undefined,
ifModifiedSince: string | undefined,
): boolean {
if (ifNoneMatch !== undefined) {
return entityTagMatches(ifNoneMatch, validators.etag);
}
if (ifModifiedSince === undefined) {
return false;
}
const since = Date.parse(ifModifiedSince);
return !Number.isNaN(since) && validators.lastModified.getTime() <= since;
}
@@ -13,8 +13,11 @@ import {RateLimitMiddleware} from '../middleware/RateLimitMiddleware';
import {OpenAPI} from '../middleware/ResponseTypeMiddleware';
import {RateLimitConfigs} from '../RateLimitConfig';
import type {HonoEnv} from '../types/HonoEnv';
import {type DiscoveryValidators, isDiscoveryNotModified, nextDiscoveryValidators} from './DiscoveryValidators';
import type {InstanceCaptchaEffectiveConfig} from './InstanceConfigRepository';
let discoveryValidators: DiscoveryValidators | null = null;
function buildDiscoveryStaticInput(
gifService: GifService | undefined,
appPublic: InstanceAppPublic,
@@ -131,6 +134,18 @@ export function InstanceController(app: Hono<HonoEnv>) {
limits,
},
);
discoveryValidators = nextDiscoveryValidators(response, discoveryValidators);
ctx.header('ETag', discoveryValidators.etag);
ctx.header('Last-Modified', discoveryValidators.lastModified.toUTCString());
if (
isDiscoveryNotModified(
discoveryValidators,
ctx.req.header('If-None-Match'),
ctx.req.header('If-Modified-Since'),
)
) {
return ctx.body(null, 304);
}
return ctx.json(response);
},
);
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
function stripWeakness(entityTag: string): string {
return entityTag.startsWith('W/') ? entityTag.slice(2) : entityTag;
}
export function entityTagMatches(ifNoneMatch: string, etag: string): boolean {
const target = stripWeakness(etag.trim());
return ifNoneMatch
.split(',')
.map((candidate) => candidate.trim())
.some((candidate) => candidate === '*' || stripWeakness(candidate) === target);
}