refactor: rename and enhance CORS path handling for public API and widgets
This commit is contained in:
+3
-3
@@ -6,7 +6,7 @@ import { Hono } from '@api/utils/hono';
|
|||||||
import { accountRouter } from '@api/routes/account';
|
import { accountRouter } from '@api/routes/account';
|
||||||
import { auth } from '@cio/db/auth';
|
import { auth } from '@cio/db/auth';
|
||||||
import { communityRouter } from '@api/routes/community';
|
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 { courseRouter } from '@api/routes/course';
|
||||||
import { dashAnalyticsRouter } from '@api/routes/dash';
|
import { dashAnalyticsRouter } from '@api/routes/dash';
|
||||||
import { domainRouter } from '@api/routes/domain/domain';
|
import { domainRouter } from '@api/routes/domain/domain';
|
||||||
@@ -42,13 +42,13 @@ export const app = new Hono()
|
|||||||
.use('*', prettyJSON())
|
.use('*', prettyJSON())
|
||||||
.use('*', secureHeaders())
|
.use('*', secureHeaders())
|
||||||
.use('*', async (c, next) => {
|
.use('*', async (c, next) => {
|
||||||
if (isPublicApiPath(c.req.path)) return next();
|
if (isPublicCorsPath(c.req.path)) return next();
|
||||||
|
|
||||||
return sessionCors(c, next);
|
return sessionCors(c, next);
|
||||||
})
|
})
|
||||||
.use('*', rateLimiter)
|
.use('*', rateLimiter)
|
||||||
.use('*', async (c, next) => {
|
.use('*', async (c, next) => {
|
||||||
if (isPublicApiPath(c.req.path)) {
|
if (isPublicCorsPath(c.req.path)) {
|
||||||
c.set('user', null);
|
c.set('user', null);
|
||||||
c.set('session', null);
|
c.set('session', null);
|
||||||
c.set('orgRoles', {});
|
c.set('orgRoles', {});
|
||||||
|
|||||||
@@ -28,9 +28,16 @@ export const sessionCors = cors({
|
|||||||
credentials: true
|
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 {
|
export function isPublicCorsPath(path: string): boolean {
|
||||||
return path.startsWith(PUBLIC_API_PATH_PREFIX);
|
return PUBLIC_CORS_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ZWidgetPublicKeyParams } from '@cio/utils/validation/widget';
|
|||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator';
|
||||||
import { createRateLimiter } from '@api/middlewares/rate-limiter';
|
import { createRateLimiter } from '@api/middlewares/rate-limiter';
|
||||||
import { DEFAULT_WINDOW_MS } from '@api/constants/rate-limiter';
|
import { DEFAULT_WINDOW_MS } from '@api/constants/rate-limiter';
|
||||||
|
import { publicApiCors } from '@api/middlewares/cors';
|
||||||
|
|
||||||
const widgetPayloadIpLimiter = createRateLimiter({
|
const widgetPayloadIpLimiter = createRateLimiter({
|
||||||
maxRequests: 100,
|
maxRequests: 100,
|
||||||
@@ -19,7 +20,7 @@ const widgetPayloadKeyLimiter = createRateLimiter({
|
|||||||
keyGenerator: (c) => `widget_payload_key:${c.req.param('publicKey')}`
|
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',
|
'/:publicKey/payload',
|
||||||
zValidator('param', ZWidgetPublicKeyParams),
|
zValidator('param', ZWidgetPublicKeyParams),
|
||||||
widgetPayloadIpLimiter,
|
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">
|
<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%]">
|
<a href="/" class="w-[10%]">
|
||||||
<div class="flex w-full items-center">
|
<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" />
|
<img loading="lazy" width="28" height="28" src="/logo-512.png" alt="classroomio logo" class="w-7" />
|
||||||
<h1 class="ml-2 text-xs font-medium md:text-base">ClassroomIO</h1>
|
<h1 class="ml-2 text-base font-medium">ClassroomIO</h1>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user