feat(microsoft-integration): enhance email permissions and consent flow for Outlook integration #37

Merged
NiekRabelink merged 1 commits from dev into main 2026-09-10 12:42:53 +00:00
7 changed files with 95 additions and 28 deletions
+1 -1
View File
@@ -516,7 +516,7 @@ require_once __DIR__ . '/includes/header.php';
<div class="flex items-start space-x-2.5"> <div class="flex items-start space-x-2.5">
<span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">3</span> <span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">3</span>
<div> <div>
<strong class="text-slate-900 dark:text-white">API Permissions:</strong> Ga naar <em>API permissions</em> &rarr; <em>Add a permission</em> &rarr; <em>Microsoft Graph</em> &rarr; <strong>Delegated permissions</strong> &rarr; Vink aan: <code class="text-sky-600 dark:text-sky-400">User.Read</code> en <code class="text-sky-600 dark:text-sky-400">Calendars.ReadWrite</code>. <strong class="text-slate-900 dark:text-white">API Permissions:</strong> Ga naar <em>API permissions</em> &rarr; <em>Add a permission</em> &rarr; <em>Microsoft Graph</em> &rarr; <strong>Delegated permissions</strong> &rarr; Vink aan: <code class="text-sky-600 dark:text-sky-400">User.Read</code>, <code class="text-sky-600 dark:text-sky-400">Calendars.ReadWrite</code>, <code class="text-sky-600 dark:text-sky-400">Mail.ReadWrite</code> en <code class="text-sky-600 dark:text-sky-400">Mail.Send</code>.
</div> </div>
</div> </div>
+9 -2
View File
@@ -138,7 +138,14 @@ $saveStmt = $db->prepare("
"); ");
$saveStmt->execute([$user['id'], $accessToken, $refreshToken, json_encode($newSettings)]); $saveStmt->execute([$user['id'], $accessToken, $refreshToken, json_encode($newSettings)]);
// 8. Succesvol terugkeren naar de Persoonlijke Profiel Hub // 8. Succesvol terugkeren naar de gewenste pagina of het Persoonlijk Profiel
header('Location: /profile.php?success=' . urlencode("Microsoft Outlook succesvol gekoppeld als {$accountEmail}!")); $returnTo = $_SESSION['ms_oauth_return_to'] ?? '/profile.php';
unset($_SESSION['ms_oauth_return_to']);
$redirectUrl = ($returnTo === '/mail.php')
? '/mail.php?success=' . urlencode("Microsoft Outlook 365 e-mail & agenda succesvol gekoppeld als {$accountEmail}!")
: '/profile.php?success=' . urlencode("Microsoft Outlook succesvol gekoppeld als {$accountEmail}!");
header('Location: ' . $redirectUrl);
exit; exit;
+10 -1
View File
@@ -43,8 +43,17 @@ $state = generate_oauth_state((int)$user['id']);
$_SESSION['ms_oauth_state'] = $state; $_SESSION['ms_oauth_state'] = $state;
$_SESSION['ms_oauth_user_id'] = (int)$user['id']; $_SESSION['ms_oauth_user_id'] = (int)$user['id'];
// Onthoud eventuele gewenste terugkeerpagina (bijv. /mail.php)
if (!empty($_GET['return_to'])) {
$_SESSION['ms_oauth_return_to'] = trim($_GET['return_to']);
} else {
unset($_SESSION['ms_oauth_return_to']);
}
$forceConsent = !empty($_GET['reconsent']) || !empty($_GET['force']);
// Stuur gebruiker door naar Microsoft login // Stuur gebruiker door naar Microsoft login
$authUrl = microsoft_get_auth_url($clientId, $redirectUri, $tenantId, $state); $authUrl = microsoft_get_auth_url($clientId, $redirectUri, $tenantId, $state, $forceConsent);
header("Location: {$authUrl}"); header("Location: {$authUrl}");
exit; exit;
+21 -11
View File
@@ -10,7 +10,7 @@ require_once __DIR__ . '/../../config/database.php';
const MS_OAUTH_AUTHORIZE_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/authorize'; const MS_OAUTH_AUTHORIZE_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/authorize';
const MS_OAUTH_TOKEN_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/token'; const MS_OAUTH_TOKEN_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/token';
const MS_GRAPH_BASE_URL = 'https://graph.microsoft.com/v1.0'; const MS_GRAPH_BASE_URL = 'https://graph.microsoft.com/v1.0';
const MS_DEFAULT_SCOPES = 'offline_access User.Read Calendars.ReadWrite Mail.ReadWrite Mail.Send'; const MS_DEFAULT_SCOPES = 'offline_access User.Read Calendars.ReadWrite Mail.ReadWrite Mail.Send OnlineMeetings.ReadWrite';
/** /**
* Haalt de centrale Microsoft App Registratie configuratie op (Client ID, Secret, Tenant ID) * Haalt de centrale Microsoft App Registratie configuratie op (Client ID, Secret, Tenant ID)
@@ -125,7 +125,7 @@ function microsoft_get_app_config(): array {
/** /**
* Genereert de Microsoft OAuth2 login URL * Genereert de Microsoft OAuth2 login URL
*/ */
function microsoft_get_auth_url(string $clientId, string $redirectUri, string $tenantId = '', string $state = ''): string { function microsoft_get_auth_url(string $clientId, string $redirectUri, string $tenantId = '', string $state = '', bool $forceConsent = false): string {
$tenant = (!empty($tenantId) && $tenantId !== 'common') ? trim($tenantId) : 'planningsagenda.nl'; $tenant = (!empty($tenantId) && $tenantId !== 'common') ? trim($tenantId) : 'planningsagenda.nl';
$baseUrl = sprintf(MS_OAUTH_AUTHORIZE_URL, urlencode($tenant)); $baseUrl = sprintf(MS_OAUTH_AUTHORIZE_URL, urlencode($tenant));
@@ -136,7 +136,7 @@ function microsoft_get_auth_url(string $clientId, string $redirectUri, string $t
'response_mode' => 'query', 'response_mode' => 'query',
'scope' => MS_DEFAULT_SCOPES, 'scope' => MS_DEFAULT_SCOPES,
'state' => $state, 'state' => $state,
'prompt' => 'select_account', 'prompt' => $forceConsent ? 'consent' : 'select_account',
]; ];
return $baseUrl . '?' . http_build_query($params); return $baseUrl . '?' . http_build_query($params);
@@ -1038,16 +1038,26 @@ function microsoft_fetch_inbox_messages(int $userId, int $limit = 25, bool $unre
]); ]);
if ($res['status'] !== 200 || !isset($res['json']['value'])) { if ($res['status'] !== 200 || !isset($res['json']['value'])) {
error_log("Microsoft fetch inbox error: HTTP {$res['status']} - " . ($res['error'] ?? json_encode($res['json']))); $graphError = $res['json']['error']['message'] ?? ($res['error'] ?? 'HTTP ' . $res['status']);
// Val terug op demo data indien API fout optreedt $graphCode = $res['json']['error']['code'] ?? '';
error_log("Microsoft fetch inbox error: HTTP {$res['status']} [{$graphCode}] - {$graphError}");
$demo = microsoft_get_mail_demo_data(); $demo = microsoft_get_mail_demo_data();
$needsReconsent = ($res['status'] === 403 || stripos($graphError, 'access') !== false || stripos($graphError, 'privilege') !== false || stripos($graphError, 'denied') !== false || stripos($graphError, 'consent') !== false);
return [ return [
'success' => true, 'success' => true,
'is_demo' => true, 'is_demo' => true,
'messages' => array_slice($demo, 0, $limit), 'messages' => array_slice($demo, 0, $limit),
'unread_count' => count(array_filter($demo, function($m) { return empty($m['is_read']); })), 'unread_count' => count(array_filter($demo, function($m) { return empty($m['is_read']); })),
'total_count' => count($demo), 'total_count' => count($demo),
'warning' => 'Kon live e-mails niet ophalen van Microsoft Graph. Demomodus actief.', 'warning' => $needsReconsent
? 'Je Microsoft koppeling mist machtiging voor e-mail (Mail.ReadWrite). Autoriseer opnieuw om je inbox live in te laden.'
: 'Kon live e-mails niet ophalen van Microsoft Graph (' . $graphError . '). Demomodus actief.',
'needs_reconsent' => $needsReconsent,
'graph_error' => $graphError,
'graph_code' => $graphCode,
'http_status' => $res['status'],
]; ];
} }
+42 -8
View File
@@ -16,18 +16,33 @@ $inboxData = microsoft_fetch_inbox_messages($userId, 30);
$messages = $inboxData['messages'] ?? []; $messages = $inboxData['messages'] ?? [];
$isDemo = !empty($inboxData['is_demo']); $isDemo = !empty($inboxData['is_demo']);
$unreadCount = (int)($inboxData['unread_count'] ?? 0); $unreadCount = (int)($inboxData['unread_count'] ?? 0);
$warning = $inboxData['warning'] ?? '';
$needsReconsent = !empty($inboxData['needs_reconsent']);
// Controleer status // Controleer status
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1"); $stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
$stmtInt->execute([$userId]); $stmtInt->execute([$userId]);
$outlookInt = $stmtInt->fetch(); $outlookInt = $stmtInt->fetch();
$isLiveOutlook = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']); $isOutlookConfigured = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']);
$isLiveMail = $isOutlookConfigured && !$isDemo;
$jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); $jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?> ?>
<div class="max-w-7xl mx-auto space-y-6"> <div class="max-w-7xl mx-auto space-y-6">
<?php if (!empty($_GET['success'])): ?>
<div class="p-4 rounded-2xl bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800/60 flex items-center space-x-2.5 text-xs text-emerald-800 dark:text-emerald-200 shadow-2xs">
<i data-lucide="check-circle-2" class="w-4 h-4 text-emerald-600 dark:text-emerald-400 flex-shrink-0"></i>
<span><?= htmlspecialchars($_GET['success']) ?></span>
</div>
<?php elseif (!empty($_GET['error'])): ?>
<div class="p-4 rounded-2xl bg-rose-50 dark:bg-rose-950/40 border border-rose-200 dark:border-rose-800/60 flex items-center space-x-2.5 text-xs text-rose-800 dark:text-rose-200 shadow-2xs">
<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400 flex-shrink-0"></i>
<span><?= htmlspecialchars($_GET['error']) ?></span>
</div>
<?php endif; ?>
<!-- Top Banner: Status & Snelle Acties --> <!-- Top Banner: Status & Snelle Acties -->
<div class="fociz-card p-6 sm:p-7 relative overflow-hidden"> <div class="fociz-card p-6 sm:p-7 relative overflow-hidden">
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4"> <div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
@@ -35,8 +50,10 @@ $jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_Q
<div class="flex items-center space-x-2 text-sky-600 dark:text-sky-400 text-xs font-bold uppercase tracking-wider mb-1"> <div class="flex items-center space-x-2 text-sky-600 dark:text-sky-400 text-xs font-bold uppercase tracking-wider mb-1">
<i data-lucide="mail" class="w-4 h-4"></i> <i data-lucide="mail" class="w-4 h-4"></i>
<span>Microsoft Outlook 365 &bull; Inbox</span> <span>Microsoft Outlook 365 &bull; Inbox</span>
<?php if ($isLiveOutlook): ?> <?php if ($isLiveMail): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30">LIVE VERBONDEN</span> <span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30">LIVE VERBONDEN</span>
<?php elseif ($isOutlookConfigured && $isDemo): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300 ring-1 ring-amber-500/30">E-MAIL MACHTIGING VEREIST</span>
<?php else: ?> <?php else: ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300 ring-1 ring-amber-500/30">DEMO INBOX</span> <span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300 ring-1 ring-amber-500/30">DEMO INBOX</span>
<?php endif; ?> <?php endif; ?>
@@ -56,10 +73,10 @@ $jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_Q
<span>Vernieuwen</span> <span>Vernieuwen</span>
</button> </button>
<?php if (!$isLiveOutlook): ?> <?php if (!$isLiveMail): ?>
<a href="/profile.php" class="flex items-center space-x-1.5 px-4 py-2 rounded-xl bg-gradient-to-r from-sky-600 to-indigo-600 hover:from-sky-500 hover:to-indigo-500 text-white text-xs font-bold transition shadow-sm shadow-sky-600/20"> <a href="/auth/oauth_microsoft.php?reconsent=1&return_to=/mail.php" class="flex items-center space-x-1.5 px-4 py-2 rounded-xl bg-gradient-to-r from-sky-600 to-indigo-600 hover:from-sky-500 hover:to-indigo-500 text-white text-xs font-bold transition shadow-sm shadow-sky-600/20">
<i data-lucide="link-2" class="w-3.5 h-3.5"></i> <i data-lucide="link-2" class="w-3.5 h-3.5"></i>
<span>Live Outlook Koppelen</span> <span><?= $isOutlookConfigured ? 'Machtigingen Bijwerken' : 'Live Outlook Koppelen' ?></span>
</a> </a>
<?php else: ?> <?php else: ?>
<a href="https://outlook.office.com/mail/" target="_blank" rel="noopener noreferrer" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 border border-sky-200 dark:border-sky-800/60 text-xs font-bold transition hover:bg-sky-100"> <a href="https://outlook.office.com/mail/" target="_blank" rel="noopener noreferrer" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 border border-sky-200 dark:border-sky-800/60 text-xs font-bold transition hover:bg-sky-100">
@@ -70,13 +87,30 @@ $jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_Q
</div> </div>
</div> </div>
<?php if (!$isLiveOutlook): ?> <?php if ($isOutlookConfigured && $isDemo): ?>
<div class="mt-5 p-4 rounded-2xl bg-amber-50/90 dark:bg-amber-950/40 border border-amber-300 dark:border-amber-700/60 flex flex-col sm:flex-row sm:items-center justify-between gap-4 text-xs text-amber-900 dark:text-amber-200 shadow-2xs">
<div class="flex items-start sm:items-center space-x-3">
<div class="w-8 h-8 rounded-xl bg-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center flex-shrink-0 font-bold">
<i data-lucide="shield-alert" class="w-4 h-4"></i>
</div>
<div>
<span class="font-bold block text-amber-950 dark:text-amber-100">E-mail permissie ontbreekt (Herautorisatie vereist)</span>
<span class="text-[11px] text-amber-800 dark:text-amber-300">
Je Microsoft account is gekoppeld voor je agenda, maar het huidige token heeft nog geen toestemming voor e-mail (<code class="font-mono bg-amber-100 dark:bg-amber-900/60 px-1 py-0.5 rounded text-[10px]">Mail.ReadWrite</code>). Klik op <strong>Machtigingen Bijwerken</strong> om eenmalig toestemming te verlenen.
</span>
</div>
</div>
<a href="/auth/oauth_microsoft.php?reconsent=1&return_to=/mail.php" class="px-4 py-2 rounded-xl bg-amber-600 hover:bg-amber-500 text-white font-bold text-xs shadow-xs transition whitespace-nowrap flex-shrink-0 text-center cursor-pointer">
Machtigingen Bijwerken &rarr;
</a>
</div>
<?php elseif (!$isOutlookConfigured): ?>
<div class="mt-5 p-3.5 rounded-2xl bg-amber-50/80 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900/40 flex items-center justify-between gap-3 text-xs text-amber-900 dark:text-amber-200"> <div class="mt-5 p-3.5 rounded-2xl bg-amber-50/80 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900/40 flex items-center justify-between gap-3 text-xs text-amber-900 dark:text-amber-200">
<div class="flex items-center space-x-2.5"> <div class="flex items-center space-x-2.5">
<i data-lucide="info" class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0"></i> <i data-lucide="info" class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0"></i>
<span>Je bekijkt momenteel een interactieve demonstratieset. Koppel je zakelijke Microsoft 365 account via Profiel om al je live zakelijke e-mails in te laden.</span> <span>Je bekijkt momenteel een interactieve demonstratieset. Koppel je zakelijke Microsoft 365 account om al je live zakelijke e-mails in te laden.</span>
</div> </div>
<a href="/auth/oauth_microsoft.php" class="font-bold underline hover:text-amber-700 dark:hover:text-amber-100 flex-shrink-0">Nu koppelen &rarr;</a> <a href="/auth/oauth_microsoft.php?return_to=/mail.php" class="font-bold underline hover:text-amber-700 dark:hover:text-amber-100 flex-shrink-0">Nu koppelen &rarr;</a>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
+8 -1
View File
@@ -234,10 +234,12 @@ try {
// 5b. Haal recente e-mails op voor de Outlook E-mail Widget // 5b. Haal recente e-mails op voor de Outlook E-mail Widget
$recentEmails = []; $recentEmails = [];
$unreadEmailCount = 0; $unreadEmailCount = 0;
$mailNeedsReconsent = false;
try { try {
$mailData = microsoft_fetch_inbox_messages($userId, 5); $mailData = microsoft_fetch_inbox_messages($userId, 5);
$recentEmails = $mailData['messages'] ?? []; $recentEmails = $mailData['messages'] ?? [];
$unreadEmailCount = (int)($mailData['unread_count'] ?? 0); $unreadEmailCount = (int)($mailData['unread_count'] ?? 0);
$mailNeedsReconsent = !empty($mailData['needs_reconsent']);
} catch (Throwable $e) { } catch (Throwable $e) {
error_log("Fout bij ophalen recente e-mails in planner: " . $e->getMessage()); error_log("Fout bij ophalen recente e-mails in planner: " . $e->getMessage());
} }
@@ -934,7 +936,12 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<h3 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider"> <h3 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider">
Outlook E-mail Outlook E-mail
</h3> </h3>
<?php if ($unreadEmailCount > 0): ?> <?php if ($mailNeedsReconsent): ?>
<a href="/auth/oauth_microsoft.php?reconsent=1&return_to=/planner.php" class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30 hover:underline flex items-center space-x-1" title="Klik om e-mail toestemming te verlenen bij Microsoft">
<i data-lucide="shield-alert" class="w-3 h-3 text-amber-600"></i>
<span>Machtiging vereist &rarr;</span>
</a>
<?php elseif ($unreadEmailCount > 0): ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-rose-50 dark:bg-rose-500/15 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/30"> <span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-rose-50 dark:bg-rose-500/15 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/30">
<?= $unreadEmailCount ?> ongelezen <?= $unreadEmailCount ?> ongelezen
</span> </span>
+4 -4
View File
@@ -651,7 +651,7 @@ require_once __DIR__ . '/includes/header.php';
</div> </div>
<div> <div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Mijn Microsoft 365 Agenda</h2> <h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Mijn Microsoft 365 (Agenda & E-mail)</h2>
<?php if ($isOutlookConnected): ?> <?php if ($isOutlookConnected): ?>
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/15 border border-teal-200 dark:border-teal-500/30 text-teal-700 dark:text-teal-400 text-[10px] font-semibold flex items-center space-x-1"> <span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/15 border border-teal-200 dark:border-teal-500/30 text-teal-700 dark:text-teal-400 text-[10px] font-semibold flex items-center space-x-1">
<span class="w-1.5 h-1.5 rounded-full bg-teal-500 animate-pulse"></span> <span class="w-1.5 h-1.5 rounded-full bg-teal-500 animate-pulse"></span>
@@ -663,14 +663,14 @@ require_once __DIR__ . '/includes/header.php';
</span> </span>
<?php endif; ?> <?php endif; ?>
</div> </div>
<p class="text-xs text-slate-500 dark:text-slate-400">Synchroniseer uw agenda-afspraken automatisch met uw dagplanning</p> <p class="text-xs text-slate-500 dark:text-slate-400">Synchroniseer uw agenda-afspraken en zakelijke Outlook e-mails met Fociz</p>
</div> </div>
</div> </div>
<?php if ($isOutlookConnected): ?> <?php if ($isOutlookConnected): ?>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<a href="/auth/oauth_microsoft.php" class="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700 text-xs font-medium transition" title="Opnieuw autoriseren bij Microsoft"> <a href="/auth/oauth_microsoft.php?reconsent=1" class="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700 text-xs font-medium transition" title="Machtigingen bijwerken bij Microsoft (inclusief Mail.ReadWrite)">
Opnieuw Verbinden Machtigingen Bijwerken
</a> </a>
<form method="POST" onsubmit="return confirm('Weet u zeker dat u uw Microsoft Outlook agenda wilt ontkoppelen?');"> <form method="POST" onsubmit="return confirm('Weet u zeker dat u uw Microsoft Outlook agenda wilt ontkoppelen?');">
<input type="hidden" name="action" value="disconnect_outlook"> <input type="hidden" name="action" value="disconnect_outlook">