feat(mail): CalDAV/CardDAV exposed + in the Apple profile
ci / changes (push) Successful in 4s
ci / tc_booking (push) Has been skipped
ci / tc_operator (push) Has been skipped
ci / tc_website (push) Has been skipped
ci / tc_platform_api (push) Successful in 23s
ci / tc_portal (push) Successful in 26s
ci / build_booking (push) Has been skipped
ci / build_operator (push) Has been skipped
ci / test_platform_api (push) Successful in 33s
ci / build_portal (push) Successful in 43s
ci / build_platform_api (push) Successful in 16s
ci / deploy (push) Successful in 43s

DAV was internal-only (the node's :443 is Traefik's). New mail-dav
Ingress routes /.well-known/caldav, /.well-known/carddav and /dav on
mail.dezky.eu through to Stalwart — with the HTTPS-redirect middleware
(safe for DAV's GET/PROPFIND; kept OFF the autodiscover Ingress whose
POSTs don't survive redirects). The _caldavs/_carddavs SRV records are
now legitimate, so the Domains page surfaces them, and the Apple
.mobileconfig gains CalDAV + CardDAV payloads: one install sets up Mail,
Calendar and Contacts on Mac/iPhone. Stalwart's STALWART_PUBLIC_URL is
set to https://mail.dezky.eu on the host (discovery documents).
This commit is contained in:
Ronni Baslund
2026-06-11 08:23:15 +02:00
parent 716d854b3d
commit 77898c5027
3 changed files with 72 additions and 9 deletions
+32 -3
View File
@@ -6,8 +6,9 @@
//
// Session-gated like every portal API. The mail host comes from runtime
// config (public.mailUrl), so dev (.local) and prod (.eu) generate correct
// profiles from one build. CalDAV/CardDAV payloads join once DAV is
// reachable from outside (the node's :443 belongs to Traefik today).
// profiles from one build. Includes CalDAV + CardDAV payloads (Traefik
// routes /dav + the well-knowns on the mail host through to Stalwart), so
// one install configures Mail, Calendar and Contacts.
import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js'
import { randomUUID } from 'node:crypto'
@@ -39,6 +40,8 @@ export default defineEventHandler(async (event) => {
// The regex above guarantees an @, but noUncheckedIndexedAccess doesn't know.
const localPart = email.split('@')[0] ?? email
const accountUuid = randomUUID()
const caldavUuid = randomUUID()
const carddavUuid = randomUUID()
const profileUuid = randomUUID()
const e = xmlEscape(email)
const n = xmlEscape(name)
@@ -76,8 +79,34 @@ export default defineEventHandler(async (event) => {
<key>PayloadUUID</key><string>${accountUuid}</string>
<key>PayloadVersion</key><integer>1</integer>
</dict>
<dict>
<key>CalDAVAccountDescription</key><string>dezky calendar (${e})</string>
<key>CalDAVHostName</key><string>${h}</string>
<key>CalDAVPort</key><integer>443</integer>
<key>CalDAVUseSSL</key><true/>
<key>CalDAVUsername</key><string>${e}</string>
<key>PayloadDescription</key><string>Configures the ${e} calendar account.</string>
<key>PayloadDisplayName</key><string>dezky calendar</string>
<key>PayloadIdentifier</key><string>eu.dezky.caldav.${xmlEscape(localPart)}</string>
<key>PayloadType</key><string>com.apple.caldav.account</string>
<key>PayloadUUID</key><string>${caldavUuid}</string>
<key>PayloadVersion</key><integer>1</integer>
</dict>
<dict>
<key>CardDAVAccountDescription</key><string>dezky contacts (${e})</string>
<key>CardDAVHostName</key><string>${h}</string>
<key>CardDAVPort</key><integer>443</integer>
<key>CardDAVUseSSL</key><true/>
<key>CardDAVUsername</key><string>${e}</string>
<key>PayloadDescription</key><string>Configures the ${e} contacts account.</string>
<key>PayloadDisplayName</key><string>dezky contacts</string>
<key>PayloadIdentifier</key><string>eu.dezky.carddav.${xmlEscape(localPart)}</string>
<key>PayloadType</key><string>com.apple.carddav.account</string>
<key>PayloadUUID</key><string>${carddavUuid}</string>
<key>PayloadVersion</key><integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key><string>Sets up ${e} in Apple Mail. You'll be asked for the mailbox password during installation.</string>
<key>PayloadDescription</key><string>Sets up ${e} in Apple Mail, Calendar and Contacts. You'll be asked for the mailbox password during installation.</string>
<key>PayloadDisplayName</key><string>dezky mail — ${e}</string>
<key>PayloadIdentifier</key><string>eu.dezky.profile.${xmlEscape(localPart)}</string>
<key>PayloadOrganization</key><string>dezky</string>