Last active
February 21, 2025 19:02
-
-
Save FaserF/77948f1a70f4631946dad1c5d631b15d to your computer and use it in GitHub Desktop.
Cloudflare Worker to send Mail from contact form via Microsoft Graph Exchange Online, Update SharePoint Lists and add Outlook Contacts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// IMPORTANT!!! CHANGE THE FOLLOWING VARIABLES BEFORE USING: | |
// mainDomain, clientId, clientSecret, tenantId, siteId, listId, cloudflare_TurnstileTSecret, companyName, allowedDomains | |
addEventListener('fetch', event => { | |
event.respondWith(handleRequest(event.request)); | |
}) | |
async function validateTurnstileToken(tokenCf, ip, cloudflare_TurnstileTSecret) { | |
const formDataT = new FormData(); | |
formDataT.append("secret", cloudflare_TurnstileTSecret); | |
formDataT.append("response", tokenCf); | |
formDataT.append("remoteip", ip); | |
const url = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; | |
const response = await fetch(url, { | |
method: "POST", | |
body: formDataT, | |
}); | |
const data = await response.json(); | |
return data.success; | |
} | |
async function handleRequest(request) { | |
const referer = request.headers.get('Referer'); | |
if (!referer) { | |
return new Response('Missing Referer header. Calling this domain directly or from localhost is not allowed.', { status: 403 }); | |
} | |
const domain = new URL(referer).hostname; | |
// Main Variables | |
const mainDomain = 'mydomain.de'; | |
const cloudflare_TurnstileTSecret = "1x0000000000000000000000000000000AA"; | |
const clientId = '00000000-0000-0000-c000-000000000000'; | |
const clientSecret = 'clientSecret'; | |
const tenantId = '00000000-0000-0000-c000-000000000000'; | |
const siteId = 'tenantname.sharepoint.com,00000000-0000-0000-c000-000000000000,00000000-0000-0000-c000-000000000000'; | |
const listId = '00000000-0000-0000-c000-000000000000'; | |
const listIdRegistrations = '00000000-0000-0000-c000-000000000000'; | |
const companyName = 'MyCompanyName'; | |
const maxFileSizeMB = 149 | |
// Zulässige Domains (Hauptdomains und Subdomains) | |
const allowedDomains = [ | |
mainDomain, | |
`*.${mainDomain}`, | |
'dash.cloudflare.com' | |
]; | |
const isAllowed = allowedDomains.some(allowedDomain => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`)); | |
if (!isAllowed) { | |
return new Response(`Forbidden domain ${domain}`, { status: 403 }); | |
} | |
// Ermitteln der bevorzugten Sprache | |
const userLanguage = request.headers.get('Accept-Language')?.split(',')[0]; | |
// Standardtext, falls keine spezifische Sprache gefunden wird | |
const responseMessage = userLanguage && userLanguage.startsWith('de') ? { | |
generalTitle: "Datenübermittlung", | |
successMessage: `Vielen Dank für Ihre Anfrage. Die Formulardaten wurde erfolgreich übermittelt. Wir werden uns zeitnah bei Ihnen melden. Ihr ${companyName} Team`, | |
errorTitleToken: "Fehler beim Abrufen des Tokens", | |
errorTitleMailSend: "Fehler bei der E-Mail Übermittlung", | |
errorTitleGraphPermission: "Fehlende Backend Graph Berechtigung", | |
errorMaxAttachmentSize: `Die Gesamtgröße der Anhänge überschreitet das erlaubte Limit von ${maxFileSizeMB} MB. Bitte erneut versuchen mit einem kleineren Anhang.`, | |
errorMessageServerAuthentification: "Leider gab es ein Problem bei der Authentifizierung des Servers. Bitte versuchen Sie es später erneut.", | |
errorMessageServerFields: "Es wurden nicht alle notwendigen Formulardaten gefunden.", | |
errorMessageContactIT: `Der Fehler ist nicht durch Ihre Eingabe entstanden. Bitte versuchen Sie es später erneut. Um dieses Problem zu beheben, wenden Sie sich bitte an die ${companyName} IT-Abteilung (it@${mainDomain}).`, | |
technicalInfoTitle: "Technische Informationen:", | |
errorDetails: "Fehlermeldung:", | |
redirectFromDomain: "Ursprüngliche Übermittlung von Domain:", | |
redirectInformation1: "Sie werden in Kürze zur ", | |
redirectInformation2: "weitergeleitet." | |
} : { | |
generalTitle: "data transmission", | |
successMessage: `Thank you for your inquiry. The form data was sent successfully. We will get back to you shortly. Your ${companyName} team.`, | |
errorTitleToken: "Error Fetching Token", | |
errorTitleMailSend: "Error Sending Mail", | |
errorTitleGraphPermission: "Missing backend graph permission", | |
errorMaxAttachmentSize: `The total size of attachments exceeds the allowed limit of ${maxFileSizeMB} MB. Please try again with a smaller attachment.`, | |
errorMessageServerAuthentification: "There was an issue with authenticating the server. Please try again later.", | |
errorMessageServerFields: "Not all necessary form data was found.", | |
errorMessageContactIT: `The error was not caused by your input. Please try again later. To resolve this issue, please contact the ${companyName} IT department (it@${mainDomain}).`, | |
technicalInfoTitle: "Technical Information:", | |
errorDetails: "Error Message:", | |
redirectFromDomain: "Original submission of domain:", | |
redirectInformation1: "You will be redirected to the ", | |
redirectInformation2: "shortly." | |
}; | |
if (request.method === 'GET' || request.method === 'POST') { | |
let token = ""; | |
try { | |
token = await getMicrosoftGraphToken(clientId, clientSecret, tenantId); | |
} catch (error) { | |
console.error("Error while receiving Graph authentication token:", error); | |
// Technische Details für die Fehlermeldung einfügen | |
const errorMessage = error.message || "Unbekannter Fehler"; | |
const errorDetails = error.stack || "Keine weiteren Details verfügbar."; | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorTitleToken}</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}:</strong> ${errorMessage}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
<p>Please check the authentification secret at <a href="https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Credentials/appId/${clientId}">Entra</a>. You may need to replace the app id in the url.</p> | |
<h3>Stacktrace:</h3> | |
<pre>${errorDetails}</pre> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
// Berechtigungen prüfen | |
let permissions = await getPermissionsFromToken(token); | |
let errorBody = ''; | |
try { | |
if (request.method === 'POST') { | |
const formData = await request.formData(); | |
const tokenCf = formData.get("cf-turnstile-response"); | |
const ip = request.headers.get("CF-Connecting-IP"); | |
if (!tokenCf) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>Inavlid Anti-Bot Challenge</h1> | |
<p>Unfortunatly the Anti-Bot Challenge failed. Please try it again.</p> | |
<p>Missing Turnstile token</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 400, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
const isValid = await validateTurnstileToken(tokenCf, ip, cloudflare_TurnstileTSecret); | |
if (!isValid) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>Inavlid Anti-Bot Challenge</h1> | |
<p>Unfortunatly the Anti-Bot Challenge failed. Please try it again.</p> | |
<p>Invalid Turnstile token</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 403, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
if (!permissions.includes('Mail.Send')) { | |
return new Response("Missing Graph permission: Mail.Send", { status: 403 }); | |
} | |
// Alle Datei-Felder automatisch erkennen und leere Dateien ignorieren | |
const files = []; | |
formData.forEach((value, key) => { | |
if (value instanceof File && value.name && value.size > 0) { | |
files.push({ key, file: value }); | |
} | |
}); | |
// Graph allows max 150MB as file size. Cloudflare Workers allows 100? MB in the Free plan | |
const maxSize = maxFileSizeMB * 1024 * 1024; // calculate MB to bytes | |
if (files.length > 0) { | |
let totalSize = 0; | |
for (const { file } of files) { | |
const buffer = await file.arrayBuffer(); | |
totalSize += buffer.byteLength; | |
} | |
if (totalSize > maxSize) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${companyName} ${responseMessage.errorMessageServerFields}</h1> | |
<p>${responseMessage.errorMaxAttachmentSize}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://meldung.${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 400, | |
headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
}); | |
} | |
} | |
const visitorCountry = request.headers.get('CF-IPCountry') || 'Unknown'; | |
const requiredFields = ['firstname', 'lastname', 'mail', 'description']; | |
// Arrays für erkannte und fehlende Felder | |
let missingFields = []; | |
let recognizedFields = []; | |
const newAddress = formData.get('newaddress'); | |
// Alle Formulardaten durchlaufen und überprüfen | |
formData.forEach((value, key) => { | |
if (requiredFields.includes(key)) { | |
if (value) { | |
recognizedFields.push(key); | |
} else { | |
missingFields.push(key); | |
} | |
} else { | |
if (key === 'address' && value === 'Andere Immobilie') { | |
const newAddress = formData.get('newaddress'); | |
if (newAddress) { | |
// "address" mit "newaddress" überschreiben und "newaddress" löschen | |
formData.set('address', newAddress); | |
formData.delete('newaddress'); | |
} | |
} else if (key === 'cf-turnstile-response') { | |
formData.delete('cf-turnstile-response'); | |
} else if (value) { | |
recognizedFields.push(key); | |
} | |
} | |
}); | |
if (newAddress) { | |
let permissionGranted = permissions.includes('Sites.ReadWrite.All') || | |
permissions.includes('Sites.FullControl.All'); | |
if (!permissionGranted) { | |
errorBody = ` | |
<br><br><br> | |
<h1>${responseMessage.errorTitleGraphPermission}</h1> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}</strong> Missing Graph API permission "Sites.ReadWrite.All"</p> | |
<p><strong>Detected Graph API permissions:</strong> ${permissions.join(', ')}</p> | |
<p>Please check the permission at <a href="https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/${clientId}">Entra</a>. You may need to replace the app id in the url.</p> | |
`; | |
} else { | |
// Aufruf der Funktion, um den Wert in SharePoint zu speichern | |
const itemData = { | |
Title: newAddress | |
}; | |
try { | |
await addItemToSharePointList(token, itemData, siteId, listId); | |
} catch (error) { | |
errorBody = ` | |
<br><br><br> | |
<h1>${responseMessage.technicalInfoTitle}</h1> | |
<p><strong>${responseMessage.errorDetails}</strong> Issue while adding estate to form list on SharePoint</p> | |
<p>${error.message}</p> | |
<pre>${error.stack}</pre> | |
`; | |
console.error("Error during SharePoint item addition:", error); | |
console.error("Stack trace:", error.stack); | |
} | |
} | |
} | |
// Falls Pflichtfelder fehlen, Fehlerantwort zurückgeben | |
if (missingFields.length > 0) { | |
const missingFieldsMessage = `Missing required fields: ${missingFields.join(', ')}`; | |
const recognizedFieldsMessage = `Recognized fields: ${recognizedFields.join(', ')}`; | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorMessageServerFields}</h1> | |
<p><strong>${responseMessage.errorDetails}</strong> ${missingFieldsMessage}</p> | |
<p>${recognizedFieldsMessage}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 400, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
let emailData; | |
// Icons für bekannte Felder | |
const fieldIcons = { | |
address: "📍", | |
firstname: "👤", | |
lastname: "👤", | |
mail: "📧", | |
phone: "📞", | |
description: "📝" | |
}; | |
// Mapping von internen Feldnamen auf verständlichere Bezeichnungen | |
const fieldLabels = { | |
firstname: "Vorname", | |
lastname: "Nachname", | |
mail: "E-Mail", | |
phone: "Telefon", | |
address: "Adresse", | |
description: "Nachricht" | |
}; | |
if (new URL(request.url).pathname === '/register') { | |
try { | |
let permissionGranted = permissions.includes('Contacts.ReadWrite') | |
if (!permissionGranted) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorTitleMailSend}</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h1>${responseMessage.errorTitleGraphPermission}</h1> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}</strong> Missing Graph API permission "Contacts.ReadWrite"</p> | |
<p><strong>Detected Graph API permissions:</strong> ${permissions.join(', ')}</p> | |
<p>Please check the permission at <a href="https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/${clientId}">Entra</a>. You may need to replace the app id in the url.</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} else { | |
function parseAddress(address) { | |
if (!address) return { street: "", houseNumber: "", zipCode: "", city: "" }; | |
// Regex zum Extrahieren: Straße, Hausnummer, PLZ, Stadt | |
const addressRegex = /^(.+?)\s(\d+[a-zA-Z]?),\s(\d{5})\s(.+)$/; | |
const match = address.match(addressRegex); | |
if (!match) { | |
console.error("Invalid address format:", address); | |
return { street: address || "", houseNumber: "", zipCode: "", city: "" }; | |
} | |
return { | |
street: match[1] || "", | |
houseNumber: match[2] || "", | |
zipCode: match[3] || "", | |
city: match[4] || "" | |
}; | |
} | |
// Sicherstellen, dass formData existiert und die Werte nicht undefined sind | |
const addressData = parseAddress(formData.get('address') || ""); | |
const contact = { | |
firstName: formData.get('firstname') || "", | |
lastName: formData.get('lastname') || "", | |
email: formData.get('mail') || "", | |
phone: formData.get('phone') || "", | |
street: addressData.street, | |
houseNumber: addressData.houseNumber, | |
zipCode: addressData.zipCode, | |
city: addressData.city, | |
country: "Germany" | |
}; | |
const contactlist = "Benutzerregistrierung" | |
try { | |
await addContactToOutlook(token, contact, mainDomain, contactlist); | |
} catch (error) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorTitleMailSend}</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h1>${responseMessage.errorTitleGraphPermission}</h1> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}</strong> Issue while adding contact Exchange Online</p> | |
<p>${error.message}</p> | |
<pre>${error.stack}</pre> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
contact.Title = new Date().toLocaleString(); | |
await addItemToSharePointList(token, contact, siteId, listIdRegistrations); | |
} | |
// E-Mail-Body zusammenstellen | |
let emailBody = `Neue Registrierung von ${formData.get('mail')} via https://${domain}\n`; | |
emailBody += `Dies dient nur zur Information. Die Daten wurden automatisch als Kontakt angelegt und die Immobilie in einer SharePoint Liste eingetragen.\n\n`; | |
emailBody += `━━━━━━━━━━━━━━━━━━━\n`; | |
recognizedFields.forEach(field => { | |
const value = formData.get(field); | |
const icon = fieldIcons[field] || "🔹"; | |
const label = fieldLabels[field] || field; | |
emailBody += `${icon} ${label}: ${value}\n`; | |
}); | |
emailBody += `━━━━━━━━━━━━━━━━━━━\n`; | |
if (visitorCountry !== "DE") { | |
emailBody += `\n\n⚠️ Der Aufrufer hat seine Anfrage nicht aus Deutschland gestellt, bitte aufpassen! Erkanntes Land: ${visitorCountry}\n`; | |
} | |
// Falls es Anhänge gibt, diese Base64-kodieren | |
let attachments = []; | |
if (files.length > 0) { | |
attachments = await Promise.all(files.map(async ({ file }) => { | |
const fileContent = await file.arrayBuffer(); | |
return { | |
"@odata.type": "#microsoft.graph.fileAttachment", | |
contentBytes: bufferToBase64(fileContent), | |
name: file.name, | |
contentType: file.type | |
}; | |
})); | |
} | |
// E-Mail-Daten setzen | |
const address = formData.get('address'); | |
emailData = { | |
subject: `Neue Registierung von ${formData.get('mail')}`, | |
body: emailBody, | |
from: `info@${mainDomain}`, | |
to: `register@${mainDomain}`, | |
replyTo: formData.get('mail')?.trim() || `info@${mainDomain}`, | |
attachments: attachments.length > 0 ? attachments : null | |
}; | |
} catch (error) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorDetails} Register</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}:</strong> ${error.message}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
} else { | |
try { | |
// E-Mail-Body zusammenstellen | |
let emailBody = `Neue Kontaktaufnahme von ${formData.get('mail')} via https://${domain}\n\n`; | |
emailBody += `━━━━━━━━━━━━━━━━━━━\n`; | |
recognizedFields.forEach(field => { | |
const value = formData.get(field); | |
const icon = fieldIcons[field] || "🔹"; | |
const label = fieldLabels[field] || field; | |
emailBody += `${icon} ${label}: ${value}\n`; | |
}); | |
emailBody += `━━━━━━━━━━━━━━━━━━━\n`; | |
if (visitorCountry !== "DE") { | |
emailBody += `\n\n⚠️ Der Aufrufer hat seine Anfrage nicht aus Deutschland gestellt, bitte aufpassen! Erkanntes Land: ${visitorCountry}\n`; | |
} | |
// Falls es Anhänge gibt, diese Base64-kodieren | |
let attachments = []; | |
if (files.length > 0) { | |
attachments = await Promise.all(files.map(async ({ file }) => { | |
const fileContent = await file.arrayBuffer(); | |
return { | |
"@odata.type": "#microsoft.graph.fileAttachment", | |
contentBytes: bufferToBase64(fileContent), | |
name: file.name, | |
contentType: file.type | |
}; | |
})); | |
} | |
// E-Mail-Daten setzen | |
const address = formData.get('address'); | |
emailData = { | |
subject: address ? `Neue Meldung für ${address}` : `Neue Kontaktaufnahme von ${formData.get('mail')}`, | |
body: emailBody, | |
from: `info@${mainDomain}`, | |
to: address ? `meldung@${mainDomain}` : `kontakt@${mainDomain}`, | |
replyTo: formData.get('mail')?.trim() || `info@${mainDomain}`, | |
attachments: attachments.length > 0 ? attachments : null | |
}; | |
} catch (error) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorDetails} Mailbody</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}:</strong> ${error.message}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
} | |
try { | |
const response = await sendEmailAndRespond(emailData, mainDomain, token, companyName, responseMessage, errorBody); | |
return response; | |
} catch (error) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorDetails} Mailsend</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}:</strong> ${error.message}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
} else if (request.method === 'GET' && new URL(request.url).pathname === '/get-properties') { | |
// Überprüfen, ob die Berechtigung "Sites.Read.All" im Token enthalten ist | |
let permissionGranted = permissions.includes('Sites.Read.All') || | |
permissions.includes('Sites.ReadWrite.All') || | |
permissions.includes('Sites.FullControl.All'); | |
if (!permissionGranted) { | |
// Fehlerseite mit Titel, Icon und technischem Abschnitt bei fehlender Berechtigung | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorTitleGraphPermission}</h1> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}</strong> Missing Graph API permission "Sites.Read.All"</p> | |
<p><strong>Detected Graph API permissions:</strong> ${permissions.join(', ')}</p> | |
<p>Please check the permission at <a href="https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/${clientId}">Entra</a>. You may need to replace the app id in the url.</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 403, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
try { | |
const properties = await getSharePointList(token, siteId, listId); | |
return new Response(JSON.stringify(properties), { | |
status: 200, | |
headers: { 'Content-Type': 'application/json' } | |
}); | |
} catch (error) { | |
return new Response(JSON.stringify({ error: error.message }), { | |
status: 500, | |
headers: { 'Content-Type': 'application/json' } | |
}); | |
} | |
} | |
} catch (error) { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorDetails} POST</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}:</strong> ${error.message}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 500, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
} | |
if (domain === 'dash.cloudflare.com') { | |
return new Response(`Cannot progress data from ${domain}, you have to call this worker from a website with a POST message.`, { status: 405 }); | |
} else { | |
const htmlResponse = ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorMessageServerFields}</h1> | |
<p><strong>${responseMessage.errorDetails}</strong> Invalid request method</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p><strong>${responseMessage.redirectFromDomain}</strong> ${domain}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
</body> | |
</html> | |
`; | |
return new Response(htmlResponse, { | |
status: 405, | |
headers: { | |
'Content-Type': 'text/html; charset=utf-8', | |
} | |
}); | |
} | |
} | |
async function getMicrosoftGraphToken(clientId, clientSecret, tenantId) { | |
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; | |
const body = new URLSearchParams({ | |
client_id: clientId, | |
client_secret: clientSecret, | |
grant_type: 'client_credentials', | |
scope: 'https://graph.microsoft.com/.default', | |
}); | |
const tokenResponse = await fetch(tokenUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/x-www-form-urlencoded', | |
}, | |
body: body, | |
}); | |
const tokenData = await tokenResponse.json(); | |
console.log("Token Response:", tokenData); | |
if (!tokenData.access_token) { | |
throw new Error(`Error while recieving Graph authentification token: ${tokenData.error_description || JSON.stringify(tokenData)}`); | |
} | |
return tokenData.access_token; | |
} | |
async function sendEmailAndRespond(emailData, mainDomain, token, companyName, responseMessage, errorBody = "") { | |
const maxRetries = 1; | |
let attempt = 0; | |
let emailResponse; | |
while (attempt <= maxRetries) { | |
try { | |
emailResponse = await fetch(`https://graph.microsoft.com/v1.0/users/info@${mainDomain}/sendMail`, { | |
method: 'POST', | |
headers: { | |
'Authorization': `Bearer ${token}`, | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
message: { | |
subject: emailData.subject, | |
body: { contentType: 'Text', content: emailData.body }, | |
from: { emailAddress: { address: emailData.from } }, | |
toRecipients: [{ emailAddress: { address: emailData.to } }], | |
replyTo: emailData.replyTo ? [{ emailAddress: { address: emailData.replyTo } }] : undefined, | |
attachments: emailData.attachments || [] | |
} | |
}) | |
}); | |
if (emailResponse.ok) { | |
// Erfolgreiche E-Mail-Übertragung | |
return new Response(generateHtmlResponse( | |
200, mainDomain, companyName, responseMessage.generalTitle, | |
responseMessage.successMessage, responseMessage.redirectInformation1, | |
responseMessage.redirectInformation2, errorBody | |
), { | |
status: 200, | |
headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
}); | |
} | |
if (attempt < maxRetries) { | |
console.warn(`Email attempt ${attempt + 1} failed. Retrying in 15 seconds...`); | |
await new Promise(resolve => setTimeout(resolve, 15000)); // 15 Sekunden warten | |
} | |
attempt++; | |
} catch (error) { | |
console.error("Error sending email:", error); | |
return new Response(generateErrorHtml( | |
500, mainDomain, companyName, responseMessage, { status: 500, statusText: error.message }, errorBody | |
), { | |
status: 500, | |
headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
}); | |
} | |
} | |
// Falls alle Versuche fehlschlagen, endgültige Fehlerseite zurückgeben | |
return new Response(generateErrorHtml( | |
500, mainDomain, companyName, responseMessage, emailResponse, errorBody | |
), { | |
status: 500, | |
headers: { 'Content-Type': 'text/html; charset=utf-8' } | |
}); | |
} | |
function generateHtmlResponse(status, mainDomain, companyName, title, message, redirectInfo1, redirectInfo2, errorBody) { | |
return ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="15;url=https://${mainDomain}"> | |
<title>${companyName} ${title}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${companyName} ${title}</h1> | |
<p>${message}</p> | |
<p>${redirectInfo1} <a href="https://${mainDomain}">Homepage</a> ${redirectInfo2}</p> | |
${errorBody} | |
</body> | |
</html> | |
`; | |
} | |
function generateErrorHtml(status, mainDomain, companyName, responseMessage, emailResponse, errorBody) { | |
return ` | |
<html> | |
<head> | |
<meta http-equiv="refresh" content="60;url=https://${mainDomain}"> | |
<title>${companyName} ${responseMessage.generalTitle}</title> | |
<link rel="icon" type="image/png" href="https://${mainDomain}/assets/img/favicon/favicon-48x48.png" sizes="48x48" /> | |
<link rel="icon" type="image/svg+xml" href="https://${mainDomain}/assets/img/favicon/favicon.svg" /> | |
</head> | |
<body> | |
<h1>${responseMessage.errorTitleMailSend}</h1> | |
<p>${responseMessage.errorMessageServerAuthentification}</p> | |
<p>${responseMessage.errorMessageContactIT}</p> | |
<p>${responseMessage.redirectInformation1} <a href="https://${mainDomain}">Homepage</a> ${responseMessage.redirectInformation2}</p> | |
<h2>${responseMessage.technicalInfoTitle}</h2> | |
<p><strong>${responseMessage.errorDetails}</strong> Error during sending mail.</p> | |
<h3>Response from Graph API:</h3> | |
<p><strong>Status Code:</strong> ${emailResponse.status}</p> | |
<p><strong>Status Text:</strong> ${emailResponse.statusText}</p> | |
<h3>Error Message:</h3> | |
<pre>${emailResponse.status === 500 ? "Internal Server Error" : emailResponse.statusText}</pre> | |
<h3>Request Details:</h3> | |
<p><strong>Method:</strong> POST</p> | |
<p><strong>URL:</strong> https://graph.microsoft.com/v1.0/users/info@${mainDomain}/sendMail</p> | |
${errorBody} | |
</body> | |
</html> | |
`; | |
} | |
async function getSharePointList(token, siteId, listId) { | |
const response = await fetch(`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items?expand=fields`, { | |
method: 'GET', | |
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } | |
}); | |
if (!response.ok) { | |
throw new Error(`Failed to fetch SharePoint list: ${await response.text()}`); | |
} | |
const data = await response.json(); | |
return data.value.map(item => item.fields.Title); | |
} | |
async function addItemToSharePointList(token, itemData, siteId, listId) { | |
const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/columns`; | |
const headers = { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json", | |
"Accept": "application/json" | |
}; | |
// Schritt 1: Liste der Spalten abrufen | |
const getColumns = async () => { | |
const response = await fetch(url, { | |
method: 'GET', | |
headers: headers | |
}); | |
const responseData = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error fetching columns: ${response.statusText} - Details: ${JSON.stringify(responseData, null, 2)}`); | |
} | |
return responseData.value; // Gibt die Liste der existierenden Spalten zurück | |
}; | |
// Schritt 2: Hinzufügen von neuen Feldern, falls notwendig | |
const addMissingFields = async (columns, itemData) => { | |
for (const [key, value] of Object.entries(itemData)) { | |
const fieldExists = columns.some(column => column.name === key); | |
if (!fieldExists) { | |
console.log(`Field "${key}" does not exist, adding...`); | |
// Füge das Feld hinzu (hier muss ein passender API-Aufruf gemacht werden, um das Feld hinzuzufügen) | |
const addFieldUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/columns`; | |
const addFieldBody = { | |
"displayName": key, | |
"name": key, | |
"text": {} // Hier könntest du je nach Feldtyp spezifische Konfigurationen machen | |
}; | |
const addFieldResponse = await fetch(addFieldUrl, { | |
method: 'POST', | |
headers: headers, | |
body: JSON.stringify(addFieldBody) | |
}); | |
const addFieldResponseData = await addFieldResponse.json(); | |
if (!addFieldResponse.ok) { | |
throw new Error(`Error adding field "${key}": ${addFieldResponse.statusText} - Details: ${JSON.stringify(addFieldResponseData, null, 2)}`); | |
} | |
console.log(`Field "${key}" added successfully.`); | |
} | |
} | |
}; | |
// Schritt 3: Überprüfen, ob der Eintrag bereits existiert | |
const checkIfItemExists = async (itemData) => { | |
const getItemsUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items?$expand=fields&$select=fields`; | |
const response = await fetch(getItemsUrl, { | |
method: 'GET', | |
headers: headers | |
}); | |
const responseData = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error fetching items: ${response.statusText} - Details: ${JSON.stringify(responseData, null, 2)}`); | |
} | |
// Funktion, um zu prüfen, ob der Wert ein Datum oder eine Uhrzeit ist | |
const isDateOrTime = (value) => { | |
// RegEx zum Erkennen von Datums-/Uhrzeitformaten | |
const dateTimePattern = /(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)|(\d{2}\/\d{2}\/\d{4})|(\d{2}:\d{2}(:\d{2})?)/; | |
return dateTimePattern.test(value); | |
}; | |
// Vergleich der Felder mit Normalisierung | |
const normalizeValue = (value) => { | |
if (typeof value === 'string') { | |
return value.trim().toLowerCase(); | |
} | |
return value; | |
}; | |
// Überprüfen, ob ein Eintrag mit denselben Werten bereits existiert | |
const existingItem = responseData.value.find(item => { | |
let isDuplicate = true; // Defaultmäßig als Duplikat ansehen | |
// Durch alle Felder in itemData iterieren und vergleichen | |
for (const [key, value] of Object.entries(itemData)) { | |
// Wenn das Feld Datum oder Uhrzeit enthält, überspringen | |
if (isDateOrTime(value)) { | |
continue; // Ignoriere den Vergleich für dieses Feld | |
} | |
// Check ob der Wert im bestehenden Item existiert | |
if (item.fields && item.fields[key] !== undefined) { | |
const itemValue = item.fields[key]; | |
// Normalisierung sowohl des existierenden Wertes als auch des Eingabewertes | |
const normalizedItemValue = normalizeValue(itemValue); | |
const normalizedInputValue = normalizeValue(value); | |
// Wenn Werte nicht übereinstimmen, markiere es als nicht Duplikat | |
if (normalizedItemValue !== normalizedInputValue) { | |
isDuplicate = false; | |
break; // Keine weiteren Felder mehr überprüfen | |
} | |
} | |
} | |
return isDuplicate; | |
}); | |
return existingItem; | |
}; | |
try { | |
// Hole die existierenden Spalten der Liste | |
const columns = await getColumns(); | |
// Überprüfen und Felder hinzufügen, wenn sie fehlen | |
await addMissingFields(columns, itemData); | |
// Überprüfen, ob das Item bereits existiert | |
const existingItem = await checkIfItemExists(itemData); | |
if (existingItem) { | |
console.log("Item already exists in the list, skipping addition."); | |
return existingItem; // Item existiert bereits, zurückgeben | |
} | |
// Schritt 4: Item zur Liste hinzufügen | |
const addItemUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items`; | |
const requestBody = { | |
fields: itemData | |
}; | |
console.log("Request Body:", JSON.stringify(requestBody, null, 2)); | |
const response = await fetch(addItemUrl, { | |
method: 'POST', | |
headers: headers, | |
body: JSON.stringify(requestBody) | |
}); | |
const responseData = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error adding item to SharePoint list: ${response.statusText} - Details: ${JSON.stringify(responseData, null, 2)}`); | |
} | |
console.log("Item successfully added to SharePoint List:", responseData); | |
return responseData; | |
} catch (error) { | |
console.error("Error during the API call:", error); | |
console.error("Stack trace:", error.stack); | |
throw new Error(`Error adding item to SharePoint list: ${error.message}`); | |
} | |
} | |
async function addContactToOutlook(token, contact, mainDomain, contactlist) { | |
// URL für die Benutzer-Kontakte | |
const userContactsUrl = `https://graph.microsoft.com/v1.0/users/info@${mainDomain}/contacts`; | |
// URL für Benutzer-Kontaktlisten | |
const contactListsUrl = `https://graph.microsoft.com/v1.0/users/info@${mainDomain}/contactFolders`; | |
// 1. Hole alle Kontaktlisten des Benutzers | |
const existingListsResponse = await fetch(contactListsUrl, { | |
method: 'GET', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
} | |
}); | |
if (!existingListsResponse.ok) { | |
throw new Error(`Failed to fetch existing contact lists: ${await existingListsResponse.text()}`); | |
} | |
const existingListsData = await existingListsResponse.json(); | |
let contactListId = null; | |
// 2. Prüfe, ob eine benutzerdefinierte Kontaktliste existiert (wird aber nicht mehr verwendet, nur noch die Existenzprüfung für den Kontakt) | |
const existingList = existingListsData.value.find(list => list.displayName === contactlist); | |
// Falls die benutzerdefinierte Liste nicht existiert, erstelle eine neue | |
if (!existingList) { | |
const createListResponse = await fetch(contactListsUrl, { | |
method: 'POST', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify({ | |
displayName: contactlist | |
}) | |
}); | |
if (!createListResponse.ok) { | |
throw new Error(`Failed to create contact list: ${await createListResponse.text()}`); | |
} | |
const createListData = await createListResponse.json(); | |
contactListId = createListData.id; | |
console.log(`Created new contact list: ${createListData.displayName}`); | |
} else { | |
contactListId = existingList.id; | |
console.log(`Using existing contact list: ${existingList.displayName}`); | |
} | |
// 3. Durchsuche **alle** Kontaktlisten (einschließlich Standardlisten) nach dem Kontakt | |
let contactExists = false; | |
let existingContactId = null; | |
let contactExistsDefault = false; | |
let existingContactDefaultId = null; | |
for (const list of existingListsData.value) { | |
const existingContactsResponse = await fetch(`${contactListsUrl}/${list.id}/contacts`, { | |
method: 'GET', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
} | |
}); | |
if (!existingContactsResponse.ok) { | |
throw new Error(`Failed to fetch existing contacts from list ${list.displayName}: ${await existingContactsResponse.text()}`); | |
} | |
const existingContactsData = await existingContactsResponse.json(); | |
const existingContact = existingContactsData.value.find(c => | |
c.emailAddresses && c.emailAddresses.some(e => e.address.toLowerCase() === contact.email.toLowerCase()) | |
); | |
if (existingContact) { | |
contactExists = true; | |
existingContactId = existingContact.id; | |
console.log(`Contact with email ${contact.email} already exists in list ${list.displayName}. Updating fields.`); | |
break; | |
} | |
} | |
if (!contactExists) { | |
const existingContactsResponse = await fetch(`${userContactsUrl}`, { | |
method: 'GET', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
} | |
}); | |
if (!existingContactsResponse.ok) { | |
throw new Error(`Failed to fetch existing contacts from default list: ${await existingContactsResponse.text()}`); | |
} | |
const existingContactsData = await existingContactsResponse.json(); | |
const existingContact = existingContactsData.value.find(c => | |
c.emailAddresses && c.emailAddresses.some(e => e.address.toLowerCase() === contact.email.toLowerCase()) | |
); | |
if (existingContact) { | |
contactExistsDefault = true; | |
existingContactDefaultId = existingContact.id; | |
console.log(`Contact with email ${contact.email} already exists in default contact list. Updating fields.`); | |
} | |
} | |
// Falls der Kontakt existiert, aktualisiere ihn | |
if (contactExists && existingContactId) { | |
const updatedContactData = { | |
givenName: contact.firstName || "", | |
surname: contact.lastName || "", | |
emailAddresses: [{ address: contact.email, name: `${contact.firstName} ${contact.lastName}` }], | |
businessPhones: contact.phone ? [contact.phone] : [], | |
homeAddress: { | |
street: `${contact.street} ${contact.houseNumber}`.trim(), | |
city: contact.city || "", | |
postalCode: contact.zipCode || "", | |
countryOrRegion: contact.country || "" | |
} | |
}; | |
// Aktualisiere den Kontakt | |
const updateContactResponse = await fetch(`${contactListsUrl}/${existingContactId}/contacts/${existingContactId}`, { | |
method: 'PATCH', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify(updatedContactData) | |
}); | |
if (!updateContactResponse.ok) { | |
throw new Error(`Failed to update contact: ${await updateContactResponse.text()}`); | |
} | |
const updatedContact = await updateContactResponse.json(); | |
console.log("Contact updated successfully:", updatedContact); | |
return updatedContact; | |
} | |
// Falls der Kontakt existiert in der Standardkontaktliste, aktualisiere ihn | |
if (contactExistsDefault && existingContactDefaultId) { | |
const updatedContactData = { | |
givenName: contact.firstName || "", | |
surname: contact.lastName || "", | |
emailAddresses: [{ address: contact.email, name: `${contact.firstName} ${contact.lastName}` }], | |
businessPhones: contact.phone ? [contact.phone] : [], | |
homeAddress: { | |
street: `${contact.street} ${contact.houseNumber}`.trim(), | |
city: contact.city || "", | |
postalCode: contact.zipCode || "", | |
countryOrRegion: contact.country || "" | |
} | |
}; | |
const updateContactResponse = await fetch(`${userContactsUrl}/${existingContactDefaultId}`, { | |
method: 'PATCH', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify(updatedContactData) | |
}); | |
if (!updateContactResponse.ok) { | |
throw new Error(`Failed to update contact in default list: ${await updateContactResponse.text()}`); | |
} | |
const updatedContact = await updateContactResponse.json(); | |
console.log("Contact updated in default list successfully:", updatedContact); | |
return updatedContact; | |
} | |
// Falls der Kontakt nicht existiert, einen neuen Kontakt erstellen | |
const contactData = { | |
givenName: contact.firstName || "", | |
surname: contact.lastName || "", | |
emailAddresses: [{ address: contact.email, name: `${contact.firstName} ${contact.lastName}` }], | |
businessPhones: contact.phone ? [contact.phone] : [], | |
homeAddress: { | |
street: `${contact.street} ${contact.houseNumber}`.trim(), | |
city: contact.city || "", | |
postalCode: contact.zipCode || "", | |
countryOrRegion: contact.country || "" | |
} | |
}; | |
// 4. Kontakt in der gewünschten Kontaktliste erstellen | |
const createContactResponse = await fetch(`${contactListsUrl}/${contactListId}/contacts`, { | |
method: 'POST', | |
headers: { | |
"Authorization": `Bearer ${token}`, | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify(contactData) | |
}); | |
if (!createContactResponse.ok) { | |
throw new Error(`Failed to add contact: ${await createContactResponse.text()}`); | |
} | |
const responseData = await createContactResponse.json(); | |
console.log("Contact added successfully to the contact list:", responseData); | |
return responseData; | |
} | |
function decodeJWT(token) { | |
// Teile des JWT-Tokens (Header, Payload, Signature) durch den Punkt trennen | |
const parts = token.split('.'); | |
if (parts.length !== 3) { | |
throw new Error('Invalid JWT token'); | |
} | |
// Der Payload-Teil des JWT-Tokens (zweiter Teil) decodiert von Base64Url | |
const payload = parts[1]; | |
const decodedPayload = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); | |
return decodedPayload; | |
} | |
async function getPermissionsFromToken(token) { | |
try { | |
const decodedToken = decodeJWT(token); | |
// Die "roles" und "permissions" befinden sich im Payload des Tokens | |
console.log("Decoded Token:", decodedToken); | |
const permissions = decodedToken.roles || []; | |
return permissions; | |
} catch (error) { | |
console.error('Error decoding JWT token:', error); | |
return []; | |
} | |
} | |
// Funktion zur Umwandlung eines ArrayBuffer in Base64 | |
function bufferToBase64(buffer) { | |
let binary = ''; | |
let bytes = new Uint8Array(buffer); | |
for (let i = 0; i < bytes.byteLength; i++) { | |
binary += String.fromCharCode(bytes[i]); | |
} | |
return btoa(binary); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment