Files
projectsend/app/Modules/Platform/Mail/MailOAuthBroker.php
T
denkfabrik-li 933eaa2ba4 Send mail through Microsoft Graph as an admin-connected mailbox
Adds "Microsoft 365 (OAuth)" to the Email settings provider dropdown.
Selecting it swaps the SMTP form for an app registration (client id,
secret, optional tenant) and a "Connect mailbox" flow: the admin signs
into the mailbox the installation should send as, and outgoing email
goes through Graph sendMail as that mailbox — no password, no app
password, no SMTP AUTH, which Microsoft is winding down.

Delegated flow on purpose: it needs no admin consent and works for
work/school and personal accounts alike. Its one weakness — a grant
can die silently behind a password reset or a Conditional Access
change — is answered by a daily scheduled refresh that keeps the
token alive and, on a dead grant, warns the settings admins once
in-app and on the settings page instead of letting mail stop quietly.

Tokens and the client secret live encrypted in their own row and are
read fresh at send time, never through the boot-config cache. The
stored SMTP transport survives a provider switch untouched.
2026-08-23 22:46:24 +02:00

48 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Platform\Mail;
/**
* One OAuth mail provider's token machinery: building the consent URL,
* turning the returned code into tokens, and keeping those tokens fresh.
*
* Deliberately not Socialite: a mail connection needs raw tokens with a
* send scope, not a user identity, and Socialite's user() call would
* drag in a userinfo permission (User.Read on Graph) that sending mail
* does not need. Implementations write their results straight onto the
* MailOAuthConnection row and save it.
*/
interface MailOAuthBroker
{
/** The provider consent URL the admin's browser is sent to. */
public function authorizeUrl(MailOAuthConnection $connection, string $state, string $redirectUri): string;
/**
* Exchange the callback's authorization code for tokens and record
* them, along with the connected mailbox's address, on the connection.
*
* @throws MailOAuthException
*/
public function exchange(MailOAuthConnection $connection, string $code, string $redirectUri): void;
/**
* Refresh the access token (rotating the refresh token when the
* provider hands back a new one) and record the outcome — including
* `last_error` on failure, so the settings page and the scheduled
* health check read one source of truth.
*
* @throws MailOAuthException
*/
public function refresh(MailOAuthConnection $connection): void;
/**
* An access token currently valid for at least a small safety margin,
* refreshing first when needed — what transports call at send time.
*
* @throws MailOAuthException
*/
public function freshAccessToken(MailOAuthConnection $connection): string;
}