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>
114 lines
3.5 KiB
PHP
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]);
|
|
}
|
|
}
|