Files
bewcloud/routes/_middleware.tsx
Bruno Bernardino 781df673dc Add CardDav and CalDav servers (#80)
* Add CardDav and CalDav servers

This implements the servers, but not the clients (yet). The implementation is essentially a proxy to Radicale (as a container in `docker-compose.yml`), with certain security assurances.

If you're upgrading, basically you'll need to create a new `data-radicale` directory, and everything else should just work.

This will also release v2.3.0 with those enabled by default. Tested with Thunderbird and Apple Calendar + Contacts.

To disable these, simply add the new config details and comment out or don't add the new `radicale` service from `docker-compose.yml`.

Related to #56
2025-07-20 10:35:32 +01:00

89 lines
3.2 KiB
TypeScript

import { FreshContext } from 'fresh/server.ts';
import { FreshContextState } from '/lib/types.ts';
import { getDataFromRequest } from '/lib/auth.ts';
export const handler = [
async function handleCors(request: Request, context: FreshContext<FreshContextState>) {
const path = new URL(request.url).pathname;
if (
request.method == 'OPTIONS' && path !== '/dav' && !path.startsWith('/dav/') && path !== '/carddav' &&
!path.startsWith('/carddav/') && path !== '/caldav' && !path.startsWith('/caldav/')
) {
const response = new Response(null, {
status: 204,
});
const origin = request.headers.get('Origin') || '*';
const headers = response.headers;
headers.set('Access-Control-Allow-Origin', origin);
headers.set('Access-Control-Allow-Credentials', 'true');
headers.set('Access-Control-Allow-Methods', 'POST, OPTIONS, GET, PUT, DELETE');
headers.set(
'Access-Control-Allow-Headers',
'Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With',
);
return response;
}
const origin = request.headers.get('Origin') || '*';
const response = await context.next();
const headers = response.headers;
headers.set('Access-Control-Allow-Origin', origin);
headers.set('Access-Control-Allow-Credentials', 'true');
headers.set(
'Access-Control-Allow-Headers',
'Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With',
);
headers.set(
'Access-Control-Allow-Methods',
'POST, OPTIONS, GET, PUT, DELETE',
);
headers.set(
'Content-Security-Policy',
"default-src 'self'; child-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'",
);
headers.set('X-Frame-Options', 'DENY');
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
return response;
},
async function handleContextState(request: Request, context: FreshContext<FreshContextState>) {
const { user, session } = (await getDataFromRequest(request)) || {};
if (user) {
context.state.user = user;
}
if (session) {
context.state.session = session;
}
const response = await context.next();
return response;
},
async function handleLogging(request: Request, context: FreshContext<FreshContextState>) {
const response = await context.next();
console.info(`${new Date().toISOString()} - [${response.status}] ${request.method} ${request.url}`);
// NOTE: Uncomment when debugging WebDav/CardDav/CalDav stuff
// if (request.url.includes('/dav') || request.url.includes('/carddav') || request.url.includes('/caldav')) {
// console.info(`Request`, request.headers);
// try {
// console.info((await request.clone().text()) || '<No Body>');
// } catch (_error) {
// console.info('<No Body>');
// }
// console.info(`Response`, response.headers);
// console.info(`Status`, response.status);
// }
return response;
},
];