refactor: rename and enhance CORS path handling for public API and widgets

This commit is contained in:
Rotimi Best
2026-05-19 11:23:07 +01:00
parent 274acad987
commit 8a07d4d839
4 changed files with 18 additions and 10 deletions
+3 -3
View File
@@ -6,7 +6,7 @@ import { Hono } from '@api/utils/hono';
import { accountRouter } from '@api/routes/account';
import { auth } from '@cio/db/auth';
import { communityRouter } from '@api/routes/community';
import { isPublicApiPath, sessionCors } from '@api/middlewares/cors';
import { isPublicCorsPath, sessionCors } from '@api/middlewares/cors';
import { courseRouter } from '@api/routes/course';
import { dashAnalyticsRouter } from '@api/routes/dash';
import { domainRouter } from '@api/routes/domain/domain';
@@ -42,13 +42,13 @@ export const app = new Hono()
.use('*', prettyJSON())
.use('*', secureHeaders())
.use('*', async (c, next) => {
if (isPublicApiPath(c.req.path)) return next();
if (isPublicCorsPath(c.req.path)) return next();
return sessionCors(c, next);
})
.use('*', rateLimiter)
.use('*', async (c, next) => {
if (isPublicApiPath(c.req.path)) {
if (isPublicCorsPath(c.req.path)) {
c.set('user', null);
c.set('session', null);
c.set('orgRoles', {});
+11 -4
View File
@@ -28,9 +28,16 @@ export const sessionCors = cors({
credentials: true
});
/** Path prefix considered "public REST API" for CORS / session bypass. */
export const PUBLIC_API_PATH_PREFIX = '/public-api/';
/**
* Path prefixes that must accept requests from any third-party origin (no cookies):
*
* - `/public-api/`: Bearer-token REST API (automation keys).
* - `/widgets/`: Anonymous payload endpoint hit by the course-widget embed script on customer sites.
*
* Requests under these prefixes use `publicApiCors` and skip the Better Auth session lookup.
*/
export const PUBLIC_CORS_PATH_PREFIXES = ['/public-api/', '/widgets/'] as const;
export function isPublicApiPath(path: string): boolean {
return path.startsWith(PUBLIC_API_PATH_PREFIX);
export function isPublicCorsPath(path: string): boolean {
return PUBLIC_CORS_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));
}
+2 -1
View File
@@ -5,6 +5,7 @@ import { ZWidgetPublicKeyParams } from '@cio/utils/validation/widget';
import { zValidator } from '@hono/zod-validator';
import { createRateLimiter } from '@api/middlewares/rate-limiter';
import { DEFAULT_WINDOW_MS } from '@api/constants/rate-limiter';
import { publicApiCors } from '@api/middlewares/cors';
const widgetPayloadIpLimiter = createRateLimiter({
maxRequests: 100,
@@ -19,7 +20,7 @@ const widgetPayloadKeyLimiter = createRateLimiter({
keyGenerator: (c) => `widget_payload_key:${c.req.param('publicKey')}`
});
export const publicWidgetsRouter = new Hono().get(
export const publicWidgetsRouter = new Hono().use('*', publicApiCors).get(
'/:publicKey/payload',
zValidator('param', ZWidgetPublicKeyParams),
widgetPayloadIpLimiter,
@@ -251,8 +251,8 @@
<div class="mx-auto flex w-full items-center justify-between gap-20 px-4 py-1 lg:w-[90%] lg:px-0">
<a href="/" class="w-[10%]">
<div class="flex w-full items-center">
<img loading="lazy" width="28" height="28" src="/logo-512.png" alt="classroomio logo" class="w-6 md:w-7" />
<h1 class="ml-2 text-xs font-medium md:text-base">ClassroomIO</h1>
<img loading="lazy" width="28" height="28" src="/logo-512.png" alt="classroomio logo" class="w-7" />
<h1 class="ml-2 text-base font-medium">ClassroomIO</h1>
</div>
</a>