feat: enhance CORS handling with public API support and session management

This commit is contained in:
Rotimi Best
2026-05-19 10:24:18 +01:00
parent 917a71ccce
commit 35e780b39d
5 changed files with 67 additions and 15 deletions
+14 -13
View File
@@ -1,13 +1,12 @@
import 'dotenv/config';
import { API_SERVER_URL } from '@api/constants';
import { resolveTrustedBrowserOriginForCors } from '@api/utils/origins';
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 { cors } from 'hono/cors';
import { isPublicApiPath, 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,19 +41,21 @@ export const app = new Hono()
.use('*', logger())
.use('*', prettyJSON())
.use('*', secureHeaders())
.use(
'*',
cors({
origin: (origin) => resolveTrustedBrowserOriginForCors(origin),
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'Cio-org-id'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600,
credentials: true
})
)
.use('*', async (c, next) => {
if (isPublicApiPath(c.req.path)) return next();
return sessionCors(c, next);
})
.use('*', rateLimiter)
.use('*', async (c, next) => {
if (isPublicApiPath(c.req.path)) {
c.set('user', null);
c.set('session', null);
c.set('orgRoles', {});
return next();
}
let session: Awaited<ReturnType<typeof auth.api.getSession>> | null = null;
try {
+36
View File
@@ -0,0 +1,36 @@
import { cors } from 'hono/cors';
import { resolveTrustedBrowserOriginForCors } from '@api/utils/origins';
const sharedCorsOptions = {
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'Cio-org-id'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600
};
/**
* Bearer-token routes meant to be called from any third-party origin (no cookies).
* `*` is safe here because `credentials` is off — the browser will reject a Set-Cookie roundtrip.
*/
export const publicApiCors = cors({
...sharedCorsOptions,
origin: '*',
credentials: false
});
/**
* Cookie/session routes (dashboard RPC, Better Auth, org dashboards).
* Allowed origins: configured `TRUSTED_ORIGINS`, `*.classroomio.com`, verified custom domains.
*/
export const sessionCors = cors({
...sharedCorsOptions,
origin: (origin) => resolveTrustedBrowserOriginForCors(origin),
credentials: true
});
/** Path prefix considered "public REST API" for CORS / session bypass. */
export const PUBLIC_API_PATH_PREFIX = '/public-api/';
export function isPublicApiPath(path: string): boolean {
return path.startsWith(PUBLIC_API_PATH_PREFIX);
}
+5 -1
View File
@@ -1,5 +1,9 @@
import { Hono } from '@api/utils/hono';
import { publicApiCors } from '@api/middlewares/cors';
import { v1AudienceRouter } from './audience';
import { v1CoursesRouter } from './courses';
export const v1Router = new Hono().route('/audience', v1AudienceRouter).route('/courses', v1CoursesRouter);
export const v1Router = new Hono()
.use('*', publicApiCors)
.route('/audience', v1AudienceRouter)
.route('/courses', v1CoursesRouter);