103 lines
3.0 KiB
TypeScript
103 lines
3.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { logger } from '@/lib/logger';
|
|
import { getStalwartCredentials } from '@/lib/stalwart/credentials';
|
|
|
|
interface DiscoveryAccountRequest {
|
|
key: string;
|
|
candidates: string[];
|
|
}
|
|
|
|
interface DiscoveryResult {
|
|
url: string | null;
|
|
resolvedAccount: string | null;
|
|
}
|
|
|
|
function buildPublicUrl(serverUrl: string, path: string): string {
|
|
return new URL(path, serverUrl).toString();
|
|
}
|
|
|
|
async function probeCalendarHome(serverUrl: string, authHeader: string, accountName: string): Promise<string | null> {
|
|
const targetUrl = buildPublicUrl(serverUrl, `/dav/cal/${encodeURIComponent(accountName)}`);
|
|
const response = await fetch(targetUrl, {
|
|
method: 'PROPFIND',
|
|
headers: {
|
|
Authorization: authHeader,
|
|
Depth: '0',
|
|
'Content-Type': 'application/xml; charset=utf-8',
|
|
},
|
|
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
<D:propfind xmlns:D="DAV:">
|
|
<D:prop>
|
|
<D:resourcetype/>
|
|
<D:displayname/>
|
|
</D:prop>
|
|
</D:propfind>`,
|
|
redirect: 'manual',
|
|
});
|
|
|
|
if (response.status === 207) {
|
|
return targetUrl;
|
|
}
|
|
|
|
if (response.status >= 300 && response.status < 400) {
|
|
const location = response.headers.get('Location');
|
|
if (location) {
|
|
return new URL(location, targetUrl).toString();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const creds = await getStalwartCredentials(request);
|
|
if (!creds) {
|
|
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json().catch(() => ({}));
|
|
const accounts = Array.isArray(body.accounts) ? body.accounts as DiscoveryAccountRequest[] : [];
|
|
const wellKnownUrl = buildPublicUrl(creds.serverUrl, '/.well-known/caldav');
|
|
const discovered: Record<string, DiscoveryResult> = {};
|
|
|
|
for (const account of accounts) {
|
|
if (!account?.key) continue;
|
|
|
|
const candidates = Array.from(new Set(
|
|
(account.candidates || [])
|
|
.map((candidate) => candidate?.trim())
|
|
.filter((candidate): candidate is string => Boolean(candidate))
|
|
));
|
|
|
|
let url: string | null = null;
|
|
let resolvedAccount: string | null = null;
|
|
|
|
for (const candidate of candidates) {
|
|
try {
|
|
url = await probeCalendarHome(creds.serverUrl, creds.authHeader, candidate);
|
|
if (url) {
|
|
resolvedAccount = candidate;
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
logger.warn('CalDAV discovery probe failed', {
|
|
accountKey: account.key,
|
|
candidate,
|
|
error: error instanceof Error ? error.message : 'Unknown',
|
|
});
|
|
}
|
|
}
|
|
|
|
discovered[account.key] = { url, resolvedAccount };
|
|
}
|
|
|
|
return NextResponse.json({
|
|
wellKnownUrl,
|
|
accounts: discovered,
|
|
});
|
|
} catch (error) {
|
|
logger.error('CalDAV discovery failed', { error: error instanceof Error ? error.message : 'Unknown' });
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
} |