// Apple configuration profile (.mobileconfig) for one-click Mail setup on // macOS/iOS — the Apple answer to autodiscovery (Apple Mail ignores RFC 6186 // SRV records, and "Microsoft Exchange" needs EWS/EAS which Stalwart doesn't // speak). The profile carries server settings + the address but NO password: // profiles are plaintext XML, so Apple prompts for the password on install. // // 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). import { getUserSession } from 'nuxt-oidc-auth/runtime/server/utils/session.js' import { randomUUID } from 'node:crypto' const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ function xmlEscape(s: string): string { return s .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') } export default defineEventHandler(async (event) => { const session = await getUserSession(event).catch(() => null) if (!(session as { accessToken?: string } | null)?.accessToken) { throw createError({ statusCode: 401, statusMessage: 'Not signed in' }) } const q = getQuery(event) const email = String(q.email ?? '').trim().toLowerCase() const name = String(q.name ?? '').trim() || email if (!EMAIL_RE.test(email)) { throw createError({ statusCode: 400, statusMessage: 'Invalid email' }) } const mailHost = new URL(useRuntimeConfig().public.mailUrl as string).host // The regex above guarantees an @, but noUncheckedIndexedAccess doesn't know. const localPart = email.split('@')[0] ?? email const accountUuid = randomUUID() const profileUuid = randomUUID() const e = xmlEscape(email) const n = xmlEscape(name) const h = xmlEscape(mailHost) const profile = ` PayloadContent EmailAccountDescriptiondezky mail (${e}) EmailAccountName${n} EmailAccountTypeEmailTypeIMAP EmailAddress${e} IncomingMailServerAuthenticationEmailAuthPassword IncomingMailServerHostName${h} IncomingMailServerPortNumber993 IncomingMailServerUseSSL IncomingMailServerUsername${e} OutgoingMailServerAuthenticationEmailAuthPassword OutgoingMailServerHostName${h} OutgoingMailServerPortNumber465 OutgoingMailServerUseSSL OutgoingMailServerUsername${e} OutgoingPasswordSameAsIncomingPassword PreventAppSheet PreventMove SMIMEEnabled PayloadDescriptionConfigures the ${e} mail account. PayloadDisplayNamedezky mail PayloadIdentifiereu.dezky.mail.${xmlEscape(localPart)} PayloadTypecom.apple.mail.managed PayloadUUID${accountUuid} PayloadVersion1 PayloadDescriptionSets up ${e} in Apple Mail. You'll be asked for the mailbox password during installation. PayloadDisplayNamedezky mail — ${e} PayloadIdentifiereu.dezky.profile.${xmlEscape(localPart)} PayloadOrganizationdezky PayloadRemovalDisallowed PayloadTypeConfiguration PayloadUUID${profileUuid} PayloadVersion1 ` setHeader(event, 'Content-Type', 'application/x-apple-aspen-config') setHeader( event, 'Content-Disposition', `attachment; filename="dezky-mail-${localPart.replace(/[^a-z0-9.-]/gi, '_')}.mobileconfig"`, ) return profile })