generated from Nodarx/template
refactor: use split socket location class
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -150,12 +150,11 @@ class ConnectCommand extends Command
|
|||||||
// ── Build service object ────────────────────────────────────────────
|
// ── Build service object ────────────────────────────────────────────
|
||||||
|
|
||||||
$location = new ServiceLocation(
|
$location = new ServiceLocation(
|
||||||
host: $host,
|
inboundHost: $host,
|
||||||
port: $port > 0 ? $port : 993,
|
inboundPort: $port > 0 ? $port : 993,
|
||||||
encryption: $encryption,
|
inboundEncryption: $encryption,
|
||||||
verifyPeer: !$noVerify,
|
inboundVerifyPeer: !$noVerify,
|
||||||
verifyPeerName: !$noVerify,
|
inboundVerifyHost: !$noVerify,
|
||||||
allowSelfSigned: $noVerify,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$identity = (new ServiceIdentityBasic())->jsonDeserialize([
|
$identity = (new ServiceIdentityBasic())->jsonDeserialize([
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class DisconnectCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$label = $service->getLabel() ?? $serviceId;
|
$label = $service->getLabel() ?? $serviceId;
|
||||||
$host = $service->getLocation()?->getHost() ?? 'unknown';
|
$host = $service->getLocation()?->getInboundHost() ?? 'unknown';
|
||||||
|
|
||||||
$io->title('Disconnect IMAP Service');
|
$io->title('Disconnect IMAP Service');
|
||||||
$io->definitionList(
|
$io->definitionList(
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class DiscoverCommand extends Command
|
|||||||
/** @var array<string, ServiceLocation> $choiceMap */
|
/** @var array<string, ServiceLocation> $choiceMap */
|
||||||
$choiceMap = [];
|
$choiceMap = [];
|
||||||
foreach ($candidates as $c) {
|
foreach ($candidates as $c) {
|
||||||
$label = sprintf('%s : %d [%s]', $c->getHost(), $c->getPort(), $encLabel($c->getEncryption()));
|
$label = sprintf('%s : %d [%s]', $c->getInboundHost(), $c->getInboundPort(), $encLabel($c->getInboundEncryption()));
|
||||||
$choiceMap[$label] = $c;
|
$choiceMap[$label] = $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +170,9 @@ class DiscoverCommand extends Command
|
|||||||
|
|
||||||
$io->success('Server selected:');
|
$io->success('Server selected:');
|
||||||
$io->definitionList(
|
$io->definitionList(
|
||||||
['Host' => $location->getHost()],
|
['Host' => $location->getInboundHost()],
|
||||||
['Port' => (string) $location->getPort()],
|
['Port' => (string) $location->getInboundPort()],
|
||||||
['Encryption' => $encLabel($location->getEncryption())],
|
['Encryption' => $encLabel($location->getInboundEncryption())],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$save) {
|
if (!$save) {
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ class TestCommand extends Command
|
|||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf('%s://%s:%d', $location->getEncryption(), $location->getHost(), $location->getPort());
|
return sprintf('%s://%s:%d', $location->getInboundEncryption(), $location->getInboundHost(), $location->getInboundPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatSender(Message $message): string
|
private function formatSender(Message $message): string
|
||||||
|
|||||||
@@ -11,22 +11,29 @@ namespace KTXM\ProviderImap\Providers;
|
|||||||
|
|
||||||
use KTXM\ProviderImap\Client\ConnectionConfig;
|
use KTXM\ProviderImap\Client\ConnectionConfig;
|
||||||
use KTXM\ProviderImap\Client\ConnectionSecurity;
|
use KTXM\ProviderImap\Client\ConnectionSecurity;
|
||||||
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
|
use KTXM\ProviderImap\Smtp\ConnectionConfig as SmtpConnectionConfig;
|
||||||
|
use KTXM\ProviderImap\Smtp\ConnectionSecurity as SmtpConnectionSecurity;
|
||||||
|
use KTXF\Resource\Provider\ResourceServiceLocationSocketSplit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMAP Service Location
|
* IMAP/SMTP Service Location
|
||||||
*
|
*
|
||||||
* Connection details for an IMAP server (host / port / encryption).
|
* Split socket location: the inbound side describes the IMAP server, the
|
||||||
|
* outbound side describes the SMTP submission server.
|
||||||
*/
|
*/
|
||||||
class ServiceLocation implements ResourceServiceLocationInterface
|
class ServiceLocation implements ResourceServiceLocationSocketSplit
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $host = '',
|
private string $inboundHost = '',
|
||||||
private int $port = 993,
|
private int $inboundPort = 993,
|
||||||
private string $encryption = 'ssl', // ssl | tls | starttls | none
|
private string $inboundEncryption = 'ssl', // none | ssl | tls | starttls
|
||||||
private bool $verifyPeer = true,
|
private bool $inboundVerifyPeer = true,
|
||||||
private bool $verifyPeerName = true,
|
private bool $inboundVerifyHost = true,
|
||||||
private bool $allowSelfSigned = false,
|
private string $outboundHost = '',
|
||||||
|
private int $outboundPort = 587,
|
||||||
|
private string $outboundEncryption = 'starttls', // none | ssl | tls | starttls
|
||||||
|
private bool $outboundVerifyPeer = true,
|
||||||
|
private bool $outboundVerifyHost = true,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// ── Serialisation ────────────────────────────────────────────────────────
|
// ── Serialisation ────────────────────────────────────────────────────────
|
||||||
@@ -43,15 +50,19 @@ class ServiceLocation implements ResourceServiceLocationInterface
|
|||||||
|
|
||||||
public function jsonSerialize(): array
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return array_filter([
|
return [
|
||||||
'type' => self::TYPE_URI,
|
'type' => self::TYPE_SOCKET_SPLIT,
|
||||||
'host' => $this->host,
|
'inboundHost' => $this->inboundHost,
|
||||||
'port' => $this->port,
|
'inboundPort' => $this->inboundPort,
|
||||||
'encryption' => $this->encryption,
|
'inboundEncryption' => $this->inboundEncryption,
|
||||||
'verifyPeer' => $this->verifyPeer,
|
'inboundVerifyPeer' => $this->inboundVerifyPeer,
|
||||||
'verifyPeerName' => $this->verifyPeerName,
|
'inboundVerifyHost' => $this->inboundVerifyHost,
|
||||||
'allowSelfSigned' => $this->allowSelfSigned,
|
'outboundHost' => $this->outboundHost,
|
||||||
], fn($v) => $v !== null && $v !== '');
|
'outboundPort' => $this->outboundPort,
|
||||||
|
'outboundEncryption' => $this->outboundEncryption,
|
||||||
|
'outboundVerifyPeer' => $this->outboundVerifyPeer,
|
||||||
|
'outboundVerifyHost' => $this->outboundVerifyHost,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonDeserialize(array|string $data): static
|
public function jsonDeserialize(array|string $data): static
|
||||||
@@ -60,70 +71,119 @@ class ServiceLocation implements ResourceServiceLocationInterface
|
|||||||
$data = json_decode($data, true);
|
$data = json_decode($data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->host = $data['host'] ?? '';
|
$this->inboundHost = $data['inboundHost'] ?? '';
|
||||||
$this->port = (int)($data['port'] ?? 993);
|
$this->inboundPort = (int) ($data['inboundPort'] ?? 993);
|
||||||
$this->encryption = $data['encryption'] ?? 'ssl';
|
$this->inboundEncryption = $data['inboundEncryption'] ?? 'ssl';
|
||||||
$this->verifyPeer = $data['verifyPeer'] ?? true;
|
$this->inboundVerifyPeer = $data['inboundVerifyPeer'] ?? true;
|
||||||
$this->verifyPeerName = $data['verifyPeerName'] ?? true;
|
$this->inboundVerifyHost = $data['inboundVerifyHost'] ?? true;
|
||||||
$this->allowSelfSigned = $data['allowSelfSigned'] ?? false;
|
|
||||||
|
$this->outboundHost = $data['outboundHost'] ?? '';
|
||||||
|
$this->outboundPort = (int) ($data['outboundPort'] ?? 587);
|
||||||
|
$this->outboundEncryption = $data['outboundEncryption'] ?? 'starttls';
|
||||||
|
$this->outboundVerifyPeer = $data['outboundVerifyPeer'] ?? true;
|
||||||
|
$this->outboundVerifyHost = $data['outboundVerifyHost'] ?? true;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ResourceServiceLocationInterface ─────────────────────────────────────
|
// ── ResourceServiceLocationSocketSplit ───────────────────────────────────
|
||||||
|
|
||||||
public function type(): string
|
public function type(): string
|
||||||
{
|
{
|
||||||
return self::TYPE_URI;
|
return self::TYPE_SOCKET_SPLIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function location(): string
|
public function locationInbound(): string
|
||||||
{
|
{
|
||||||
return $this->encryption . '://' . $this->host . ':' . $this->port;
|
return $this->inboundEncryption . '://' . $this->inboundHost . ':' . $this->inboundPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Accessors ────────────────────────────────────────────────────────────
|
public function locationOutbound(): string
|
||||||
|
{
|
||||||
|
return $this->outboundEncryption . '://' . $this->outboundHost . ':' . $this->outboundPort;
|
||||||
|
}
|
||||||
|
|
||||||
public function getHost(): string { return $this->host; }
|
public function getInboundHost(): string { return $this->inboundHost; }
|
||||||
public function setHost(string $v): void { $this->host = $v; }
|
public function setInboundHost(string $value): void { $this->inboundHost = $value; }
|
||||||
|
|
||||||
public function getPort(): int { return $this->port; }
|
public function getOutboundHost(): string { return $this->outboundHost; }
|
||||||
public function setPort(int $v): void { $this->port = $v; }
|
public function setOutboundHost(string $value): void { $this->outboundHost = $value; }
|
||||||
|
|
||||||
public function getEncryption(): string { return $this->encryption; }
|
public function getInboundPort(): int { return $this->inboundPort; }
|
||||||
public function setEncryption(string $v): void { $this->encryption = $v; }
|
public function setInboundPort(int $value): void { $this->inboundPort = $value; }
|
||||||
|
|
||||||
public function getVerifyPeer(): bool { return $this->verifyPeer; }
|
public function getOutboundPort(): int { return $this->outboundPort; }
|
||||||
public function setVerifyPeer(bool $v): void { $this->verifyPeer = $v; }
|
public function setOutboundPort(int $value): void { $this->outboundPort = $value; }
|
||||||
|
|
||||||
public function getVerifyPeerName(): bool { return $this->verifyPeerName; }
|
public function getInboundEncryption(): string { return $this->inboundEncryption; }
|
||||||
public function setVerifyPeerName(bool $v): void { $this->verifyPeerName = $v; }
|
public function setInboundEncryption(string $value): void { $this->inboundEncryption = $value; }
|
||||||
|
|
||||||
public function getAllowSelfSigned(): bool { return $this->allowSelfSigned; }
|
public function getOutboundEncryption(): string { return $this->outboundEncryption; }
|
||||||
public function setAllowSelfSigned(bool $v): void { $this->allowSelfSigned = $v; }
|
public function setOutboundEncryption(string $value): void { $this->outboundEncryption = $value; }
|
||||||
|
|
||||||
|
public function getInboundVerifyPeer(): bool { return $this->inboundVerifyPeer; }
|
||||||
|
public function setInboundVerifyPeer(bool $value): void { $this->inboundVerifyPeer = $value; }
|
||||||
|
|
||||||
|
public function getInboundVerifyHost(): bool { return $this->inboundVerifyHost; }
|
||||||
|
public function setInboundVerifyHost(bool $value): void { $this->inboundVerifyHost = $value; }
|
||||||
|
|
||||||
|
public function getOutboundVerifyPeer(): bool { return $this->outboundVerifyPeer; }
|
||||||
|
public function setOutboundVerifyPeer(bool $value): void { $this->outboundVerifyPeer = $value; }
|
||||||
|
|
||||||
|
public function getOutboundVerifyHost(): bool { return $this->outboundVerifyHost; }
|
||||||
|
public function setOutboundVerifyHost(bool $value): void { $this->outboundVerifyHost = $value; }
|
||||||
|
|
||||||
// ── Client helpers ───────────────────────────────────────────────────────
|
// ── Client helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a standalone IMAP client ConnectionConfig from this location.
|
* Build an IMAP ConnectionConfig from the inbound side.
|
||||||
*/
|
*/
|
||||||
public function toConnectionConfig(?string $username = null, ?string $password = null): ConnectionConfig
|
public function toConnectionConfig(?string $username = null, ?string $password = null): ConnectionConfig
|
||||||
{
|
{
|
||||||
$security = match ($this->encryption) {
|
return new ConnectionConfig(
|
||||||
|
host: $this->inboundHost,
|
||||||
|
port: $this->inboundPort,
|
||||||
|
security: $this->imapSecurity($this->inboundEncryption),
|
||||||
|
username: $username,
|
||||||
|
password: $password,
|
||||||
|
verifyPeer: $this->inboundVerifyPeer,
|
||||||
|
verifyPeerName: $this->inboundVerifyHost,
|
||||||
|
allowSelfSigned: !$this->inboundVerifyPeer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an SMTP submission ConnectionConfig from the outbound side.
|
||||||
|
*/
|
||||||
|
public function toSmtpConnectionConfig(?string $username = null, ?string $password = null): SmtpConnectionConfig
|
||||||
|
{
|
||||||
|
return new SmtpConnectionConfig(
|
||||||
|
host: $this->outboundHost,
|
||||||
|
port: $this->outboundPort,
|
||||||
|
security: $this->smtpSecurity($this->outboundEncryption),
|
||||||
|
username: $username,
|
||||||
|
password: $password,
|
||||||
|
verifyPeer: $this->outboundVerifyPeer,
|
||||||
|
verifyPeerName: $this->outboundVerifyHost,
|
||||||
|
allowSelfSigned: !$this->outboundVerifyPeer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function imapSecurity(string $encryption): ConnectionSecurity
|
||||||
|
{
|
||||||
|
return match ($encryption) {
|
||||||
'ssl', 'tls' => ConnectionSecurity::Tls,
|
'ssl', 'tls' => ConnectionSecurity::Tls,
|
||||||
'starttls' => ConnectionSecurity::StartTls,
|
'starttls' => ConnectionSecurity::StartTls,
|
||||||
default => ConnectionSecurity::Plain,
|
default => ConnectionSecurity::Plain,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return new ConnectionConfig(
|
private function smtpSecurity(string $encryption): SmtpConnectionSecurity
|
||||||
host: $this->host,
|
{
|
||||||
port: $this->port,
|
return match ($encryption) {
|
||||||
security: $security,
|
'ssl', 'tls' => SmtpConnectionSecurity::Tls,
|
||||||
username: $username,
|
'starttls' => SmtpConnectionSecurity::StartTls,
|
||||||
password: $password,
|
default => SmtpConnectionSecurity::Plain,
|
||||||
verifyPeer: $this->verifyPeer,
|
};
|
||||||
verifyPeerName: $this->verifyPeerName,
|
|
||||||
allowSelfSigned: $this->allowSelfSigned,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Discovery
|
|||||||
];
|
];
|
||||||
$seen = [];
|
$seen = [];
|
||||||
foreach ($results as $r) {
|
foreach ($results as $r) {
|
||||||
$seen[$r->getHost()] = true;
|
$seen[$r->getInboundHost()] = true;
|
||||||
}
|
}
|
||||||
foreach ($candidates as $host) {
|
foreach ($candidates as $host) {
|
||||||
if (isset($seen[$host])) {
|
if (isset($seen[$host])) {
|
||||||
@@ -203,10 +203,11 @@ class Discovery
|
|||||||
|
|
||||||
$loc = new ServiceLocation();
|
$loc = new ServiceLocation();
|
||||||
$loc->jsonDeserialize([
|
$loc->jsonDeserialize([
|
||||||
'host' => $host,
|
'inboundHost' => $host,
|
||||||
'port' => $port,
|
'inboundPort' => $port,
|
||||||
'encryption' => $encryption,
|
'inboundEncryption' => $encryption,
|
||||||
'verifyPeer' => $verifySSL,
|
'inboundVerifyPeer' => $verifySSL,
|
||||||
|
'inboundVerifyHost' => $verifySSL,
|
||||||
]);
|
]);
|
||||||
return $loc;
|
return $loc;
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable) {
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { LocationSocketSole } from '@KTXM/MailManager/models/location'
|
import { LocationSocketSplit } from '@KTXM/MailManager/models/location'
|
||||||
import { ServiceObject } from '@KTXM/MailManager/models/service'
|
import { ServiceObject } from '@KTXM/MailManager/models/service'
|
||||||
import type {
|
import type {
|
||||||
ServiceLocation,
|
ServiceLocation,
|
||||||
ServiceLocationSocketSole,
|
ServiceLocationSocketSplit,
|
||||||
} from '@KTXM/MailManager/types/service'
|
} from '@KTXM/MailManager/types/service'
|
||||||
import type {
|
import type {
|
||||||
ProviderProtocolPanelProps,
|
ProviderProtocolPanelProps,
|
||||||
ProviderProtocolPanelEmits,
|
ProviderProtocolPanelEmits,
|
||||||
} from '@KTXM/MailManager/types/integration'
|
} from '@KTXM/MailManager/types/integration'
|
||||||
|
|
||||||
type ImapEncryption = 'none' | 'ssl' | 'tls' | 'starttls'
|
type Encryption = 'none' | 'ssl' | 'tls' | 'starttls'
|
||||||
|
|
||||||
const props = defineProps<ProviderProtocolPanelProps>()
|
const props = defineProps<ProviderProtocolPanelProps>()
|
||||||
const emit = defineEmits<ProviderProtocolPanelEmits>()
|
const emit = defineEmits<ProviderProtocolPanelEmits>()
|
||||||
|
|
||||||
const host = ref('')
|
// ── Inbound (IMAP) ──────────────────────────────────────────────────────────
|
||||||
const encryption = ref<ImapEncryption>('ssl')
|
const inboundHost = ref('')
|
||||||
const port = ref('993')
|
const inboundEncryption = ref<Encryption>('ssl')
|
||||||
const verifyPeer = ref(true)
|
const inboundPort = ref('993')
|
||||||
const verifyHost = ref(true)
|
const inboundVerifyPeer = ref(true)
|
||||||
|
const inboundVerifyHost = ref(true)
|
||||||
|
|
||||||
|
// ── Outbound (SMTP) ─────────────────────────────────────────────────────────
|
||||||
|
const outboundHost = ref('')
|
||||||
|
const outboundEncryption = ref<Encryption>('starttls')
|
||||||
|
const outboundPort = ref('587')
|
||||||
|
const outboundVerifyPeer = ref(true)
|
||||||
|
const outboundVerifyHost = ref(true)
|
||||||
|
|
||||||
const encryptionOptions = [
|
const encryptionOptions = [
|
||||||
{ title: 'Implicit TLS (SSL)', value: 'ssl' },
|
{ title: 'Implicit TLS (SSL)', value: 'ssl' },
|
||||||
@@ -36,23 +44,30 @@ const rules = {
|
|||||||
return Number.isInteger(numericValue) && numericValue >= 1 && numericValue <= 65535
|
return Number.isInteger(numericValue) && numericValue >= 1 && numericValue <= 65535
|
||||||
? true
|
? true
|
||||||
: 'Port must be between 1 and 65535'
|
: 'Port must be between 1 and 65535'
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = computed(() => !!host.value && rules.port(port.value) === true)
|
const inboundValid = computed(() => !!inboundHost.value && rules.port(inboundPort.value) === true)
|
||||||
|
const outboundValid = computed(() => !!outboundHost.value && rules.port(outboundPort.value) === true)
|
||||||
|
const isValid = computed(() => inboundValid.value && outboundValid.value)
|
||||||
|
|
||||||
const currentLocation = computed((): ServiceLocationSocketSole | null => {
|
const currentLocation = computed((): ServiceLocationSocketSplit | null => {
|
||||||
if (!isValid.value) {
|
if (!isValid.value) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SOCKET_SOLE',
|
type: 'SOCKET_SPLIT',
|
||||||
host: host.value,
|
inboundHost: inboundHost.value,
|
||||||
port: Number(port.value),
|
inboundPort: Number(inboundPort.value),
|
||||||
encryption: encryption.value,
|
inboundEncryption: inboundEncryption.value,
|
||||||
verifyPeer: verifyPeer.value,
|
inboundVerifyPeer: inboundVerifyPeer.value,
|
||||||
verifyHost: verifyHost.value,
|
inboundVerifyHost: inboundVerifyHost.value,
|
||||||
|
outboundHost: outboundHost.value,
|
||||||
|
outboundPort: Number(outboundPort.value),
|
||||||
|
outboundEncryption: outboundEncryption.value,
|
||||||
|
outboundVerifyPeer: outboundVerifyPeer.value,
|
||||||
|
outboundVerifyHost: outboundVerifyHost.value,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,14 +95,20 @@ watch(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextService.location instanceof LocationSocketSole) {
|
if (nextService.location instanceof LocationSocketSplit) {
|
||||||
nextService.location.host = location.host
|
const target = nextService.location
|
||||||
nextService.location.port = location.port
|
target.inboundHost = location.inboundHost
|
||||||
nextService.location.encryption = location.encryption
|
target.inboundPort = location.inboundPort
|
||||||
nextService.location.verifyPeer = location.verifyPeer ?? true
|
target.inboundEncryption = location.inboundEncryption
|
||||||
nextService.location.verifyHost = location.verifyHost ?? true
|
target.inboundVerifyPeer = location.inboundVerifyPeer ?? true
|
||||||
|
target.inboundVerifyHost = location.inboundVerifyHost ?? true
|
||||||
|
target.outboundHost = location.outboundHost
|
||||||
|
target.outboundPort = location.outboundPort
|
||||||
|
target.outboundEncryption = location.outboundEncryption
|
||||||
|
target.outboundVerifyPeer = location.outboundVerifyPeer ?? true
|
||||||
|
target.outboundVerifyHost = location.outboundVerifyHost ?? true
|
||||||
} else {
|
} else {
|
||||||
nextService.location = LocationSocketSole.fromJson(location)
|
nextService.location = LocationSocketSplit.fromJson(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:service', nextService)
|
emit('update:service', nextService)
|
||||||
@@ -95,25 +116,34 @@ watch(
|
|||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(encryption, (next, previous) => {
|
watch(inboundEncryption, (next, previous) => {
|
||||||
const previousDefault = defaultPortFor(previous)
|
const previousDefault = defaultInboundPort(previous)
|
||||||
if (!port.value || Number(port.value) === previousDefault) {
|
if (!inboundPort.value || Number(inboundPort.value) === previousDefault) {
|
||||||
port.value = String(defaultPortFor(next))
|
inboundPort.value = String(defaultInboundPort(next))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(outboundEncryption, (next, previous) => {
|
||||||
|
const previousDefault = defaultOutboundPort(previous)
|
||||||
|
if (!outboundPort.value || Number(outboundPort.value) === previousDefault) {
|
||||||
|
outboundPort.value = String(defaultOutboundPort(next))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function syncFromLocation(location: ServiceLocation | null) {
|
function syncFromLocation(location: ServiceLocation | null) {
|
||||||
const socketLocation = getSocketLocation(location)
|
const split = location?.type === 'SOCKET_SPLIT' ? location : null
|
||||||
|
|
||||||
host.value = socketLocation?.host ?? ''
|
inboundHost.value = split?.inboundHost ?? ''
|
||||||
encryption.value = socketLocation?.encryption ?? 'ssl'
|
inboundEncryption.value = split?.inboundEncryption ?? 'ssl'
|
||||||
port.value = String(socketLocation?.port ?? defaultPortFor(encryption.value))
|
inboundPort.value = String(split?.inboundPort ?? defaultInboundPort(inboundEncryption.value))
|
||||||
verifyPeer.value = socketLocation?.verifyPeer ?? true
|
inboundVerifyPeer.value = split?.inboundVerifyPeer ?? true
|
||||||
verifyHost.value = socketLocation?.verifyHost ?? true
|
inboundVerifyHost.value = split?.inboundVerifyHost ?? true
|
||||||
}
|
|
||||||
|
|
||||||
function getSocketLocation(location?: ServiceLocation | null): ServiceLocationSocketSole | null {
|
outboundHost.value = split?.outboundHost ?? ''
|
||||||
return location?.type === 'SOCKET_SOLE' ? location : null
|
outboundEncryption.value = split?.outboundEncryption ?? 'starttls'
|
||||||
|
outboundPort.value = String(split?.outboundPort ?? defaultOutboundPort(outboundEncryption.value))
|
||||||
|
outboundVerifyPeer.value = split?.outboundVerifyPeer ?? true
|
||||||
|
outboundVerifyHost.value = split?.outboundVerifyHost ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boolean {
|
function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boolean {
|
||||||
@@ -121,30 +151,51 @@ function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boo
|
|||||||
return a === b
|
return a === b
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.type !== 'SOCKET_SOLE' || b.type !== 'SOCKET_SOLE') {
|
if (a.type !== 'SOCKET_SPLIT' || b.type !== 'SOCKET_SPLIT') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.host === b.host
|
return a.inboundHost === b.inboundHost
|
||||||
&& a.port === b.port
|
&& a.inboundPort === b.inboundPort
|
||||||
&& a.encryption === b.encryption
|
&& a.inboundEncryption === b.inboundEncryption
|
||||||
&& (a.verifyPeer ?? true) === (b.verifyPeer ?? true)
|
&& (a.inboundVerifyPeer ?? true) === (b.inboundVerifyPeer ?? true)
|
||||||
&& (a.verifyHost ?? true) === (b.verifyHost ?? true)
|
&& (a.inboundVerifyHost ?? true) === (b.inboundVerifyHost ?? true)
|
||||||
|
&& a.outboundHost === b.outboundHost
|
||||||
|
&& a.outboundPort === b.outboundPort
|
||||||
|
&& a.outboundEncryption === b.outboundEncryption
|
||||||
|
&& (a.outboundVerifyPeer ?? true) === (b.outboundVerifyPeer ?? true)
|
||||||
|
&& (a.outboundVerifyHost ?? true) === (b.outboundVerifyHost ?? true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultPortFor(nextEncryption: ImapEncryption): number {
|
function defaultInboundPort(encryption: Encryption): number {
|
||||||
return nextEncryption === 'ssl' || nextEncryption === 'tls' ? 993 : 143
|
return encryption === 'ssl' || encryption === 'tls' ? 993 : 143
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultOutboundPort(encryption: Encryption): number {
|
||||||
|
if (encryption === 'ssl') {
|
||||||
|
return 465
|
||||||
|
}
|
||||||
|
if (encryption === 'none') {
|
||||||
|
return 25
|
||||||
|
}
|
||||||
|
return 587
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="imap-protocol-panel">
|
<div class="imap-protocol-panel">
|
||||||
<h3 class="text-h6 mb-4">IMAP Connection Settings</h3>
|
<h3 class="text-h6 mb-4">Connection Settings</h3>
|
||||||
<p class="text-body-2 mb-6">Configure the server address, transport security, and certificate verification for your IMAP mailbox.</p>
|
<p class="text-body-2 mb-6">Configure the incoming (IMAP) and outgoing (SMTP) servers, transport security, and certificate verification for this mailbox.</p>
|
||||||
|
|
||||||
|
<!-- ── Incoming (IMAP) ─────────────────────────────────────────────────── -->
|
||||||
|
<div class="d-flex align-center mb-4">
|
||||||
|
<v-icon start color="primary">mdi-email-arrow-left</v-icon>
|
||||||
|
<span class="text-subtitle-1 font-weight-medium">Incoming mail (IMAP)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="host"
|
v-model="inboundHost"
|
||||||
label="Server Host"
|
label="IMAP Server Host"
|
||||||
hint="For example: imap.example.com"
|
hint="For example: imap.example.com"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -157,7 +208,7 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<v-select
|
<v-select
|
||||||
v-model="encryption"
|
v-model="inboundEncryption"
|
||||||
:items="encryptionOptions"
|
:items="encryptionOptions"
|
||||||
label="Security"
|
label="Security"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -166,7 +217,7 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="port"
|
v-model="inboundPort"
|
||||||
label="Port"
|
label="Port"
|
||||||
hint="Defaults to 993 for TLS/SSL and 143 for plain or STARTTLS"
|
hint="Defaults to 993 for TLS/SSL and 143 for plain or STARTTLS"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
@@ -179,7 +230,7 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
:rules="[rules.required, rules.port]"
|
:rules="[rules.required, rules.port]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-expansion-panels class="mt-4">
|
<v-expansion-panels class="mb-6">
|
||||||
<v-expansion-panel>
|
<v-expansion-panel>
|
||||||
<v-expansion-panel-title>
|
<v-expansion-panel-title>
|
||||||
<v-icon start>mdi-cog</v-icon>
|
<v-icon start>mdi-cog</v-icon>
|
||||||
@@ -187,7 +238,7 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
</v-expansion-panel-title>
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="verifyPeer"
|
v-model="inboundVerifyPeer"
|
||||||
label="Verify TLS certificate"
|
label="Verify TLS certificate"
|
||||||
color="primary"
|
color="primary"
|
||||||
hint="Disable only for trusted internal or test environments"
|
hint="Disable only for trusted internal or test environments"
|
||||||
@@ -196,7 +247,7 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="verifyHost"
|
v-model="inboundVerifyHost"
|
||||||
label="Verify certificate hostname"
|
label="Verify certificate hostname"
|
||||||
color="primary"
|
color="primary"
|
||||||
hint="Checks that the certificate matches the IMAP host"
|
hint="Checks that the certificate matches the IMAP host"
|
||||||
@@ -207,9 +258,78 @@ function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
|
|
||||||
<v-alert type="info" variant="tonal">
|
<v-divider class="mb-6" />
|
||||||
STARTTLS is accepted for compatibility, but the current IMAP client transport does not perform STARTTLS negotiation. Prefer TLS on port 993 when available.
|
|
||||||
</v-alert>
|
<!-- ── Outgoing (SMTP) ─────────────────────────────────────────────────── -->
|
||||||
|
<div class="d-flex align-center mb-4">
|
||||||
|
<v-icon start color="primary">mdi-email-arrow-right</v-icon>
|
||||||
|
<span class="text-subtitle-1 font-weight-medium">Outgoing mail (SMTP)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="outboundHost"
|
||||||
|
label="SMTP Server Host"
|
||||||
|
hint="For example: smtp.example.com"
|
||||||
|
persistent-hint
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-server-network"
|
||||||
|
class="mb-4"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:rules="[rules.required]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-model="outboundEncryption"
|
||||||
|
:items="encryptionOptions"
|
||||||
|
label="Security"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-shield-lock"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="outboundPort"
|
||||||
|
label="Port"
|
||||||
|
hint="Defaults to 465 for implicit TLS, 587 for STARTTLS, and 25 for plain"
|
||||||
|
persistent-hint
|
||||||
|
variant="outlined"
|
||||||
|
prepend-inner-icon="mdi-numeric"
|
||||||
|
class="mb-4"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
:rules="[rules.required, rules.port]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel>
|
||||||
|
<v-expansion-panel-title>
|
||||||
|
<v-icon start>mdi-cog</v-icon>
|
||||||
|
Security Options
|
||||||
|
</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<v-switch
|
||||||
|
v-model="outboundVerifyPeer"
|
||||||
|
label="Verify TLS certificate"
|
||||||
|
color="primary"
|
||||||
|
hint="Disable only for trusted internal or test environments"
|
||||||
|
persistent-hint
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-switch
|
||||||
|
v-model="outboundVerifyHost"
|
||||||
|
label="Verify certificate hostname"
|
||||||
|
color="primary"
|
||||||
|
hint="Checks that the certificate matches the SMTP host"
|
||||||
|
persistent-hint
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user