Files
ladill-servers/app/Services/Mail/MailboxUserProvisioner.php
T
isaaccladandCursor b6c8ac343f Extract Ladill Servers as standalone app at servers.ladill.com.
VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 19:18:30 +00:00

114 lines
3.5 KiB
PHP

<?php
namespace App\Services\Mail;
use App\Models\Mailbox;
use App\Models\User;
use Illuminate\Support\Str;
/**
* Every mailbox holder gets a Ladill account (users.email = mailbox address).
* The Ladill login password matches the mailbox password so holders can sign in
* to account.ladill.com with the same credentials they use for webmail/IMAP.
*/
class MailboxUserProvisioner
{
public function __construct(private MailboxWebmailVault $vault) {}
public function ensureForMailbox(Mailbox $mailbox, ?string $password = null): User
{
$user = $this->ensureForAddress((string) $mailbox->address, (string) $mailbox->display_name, $password);
$this->syncHolderPassword($mailbox, $password);
return $user;
}
public function ensureForAddress(string $address, string $displayName = '', ?string $password = null): User
{
$email = strtolower(trim($address));
if ($email === '' || ! str_contains($email, '@')) {
throw new \InvalidArgumentException('Invalid mailbox address.');
}
$attributes = [
'name' => ($name = trim($displayName) ?: Str::headline(Str::before($email, '@'))),
'first_name' => Str::before($name, ' '),
'last_name' => trim(Str::after($name, ' ')) ?: '',
'password' => $password ?? Str::random(40),
];
$user = User::firstOrCreate(['email' => $email], $attributes);
if (! $user->wasRecentlyCreated && $password !== null && $password !== '') {
$user->password = $password;
$user->save();
}
if ($user->email_verified_at === null) {
$user->forceFill(['email_verified_at' => now()])->save();
}
return $user;
}
/**
* Keep the Ladill account password aligned with the mailbox password.
* Uses the plaintext when available; otherwise copies the stored bcrypt hash.
*/
public function syncHolderPassword(Mailbox $mailbox, ?string $plaintextPassword = null): void
{
$email = strtolower(trim((string) $mailbox->address));
if ($email === '' || ! str_contains($email, '@')) {
return;
}
$user = User::query()->where('email', $email)->first();
if (! $user) {
return;
}
if ($plaintextPassword !== null && $plaintextPassword !== '') {
$user->password = $plaintextPassword;
$user->save();
$this->vault->store($mailbox, $plaintextPassword);
return;
}
$hash = (string) $mailbox->password_hash;
if ($hash !== '') {
$this->assignPasswordHash($user, $hash);
}
}
public function provisionSilently(string $address, string $displayName = '', ?string $password = null): ?User
{
try {
return $this->ensureForAddress($address, $displayName, $password);
} catch (\Throwable $e) {
report($e);
return null;
}
}
public function provisionMailboxSilently(Mailbox $mailbox, ?string $password = null): ?User
{
try {
return $this->ensureForMailbox($mailbox, $password);
} catch (\Throwable $e) {
report($e);
return null;
}
}
private function assignPasswordHash(User $user, string $passwordHash): void
{
$user->getConnection()
->table($user->getTable())
->where($user->getKeyName(), $user->getKey())
->update(['password' => $passwordHash]);
}
}