KI-AGENT: Defekte Mailkonto-Verschlüsselung abfangen
This commit is contained in:
@@ -23,27 +23,62 @@ export default async function emailAsUserRoutes(server: FastifyInstance) {
|
||||
|
||||
const encryptedValue = (value: unknown) => value ? decrypt(value as any) : null
|
||||
|
||||
const accountResponse = (row: any) => ({
|
||||
const accountResponse = (row: any) => {
|
||||
const invalidEncryptedFields: string[] = []
|
||||
const safeEncryptedValue = (value: unknown, field: string) => {
|
||||
if (!value) return null
|
||||
try {
|
||||
return encryptedValue(value)
|
||||
} catch {
|
||||
invalidEncryptedFields.push(field)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const email = safeEncryptedValue(row.emailEncrypted, "email")
|
||||
const smtpHost = safeEncryptedValue(row.smtpHostEncrypted, "smtpHost")
|
||||
const imapHost = safeEncryptedValue(row.imapHostEncrypted, "imapHost")
|
||||
safeEncryptedValue(row.passwordEncrypted, "password")
|
||||
|
||||
if (invalidEncryptedFields.length) {
|
||||
server.log.warn({
|
||||
accountId: row.id,
|
||||
invalidEncryptedFields,
|
||||
}, "E-Mail-Kontodaten können mit dem aktuellen ENCRYPTION_KEY nicht entschlüsselt werden")
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
userId: row.userId,
|
||||
tenantId: row.tenantId,
|
||||
type: row.type,
|
||||
email: encryptedValue(row.emailEncrypted),
|
||||
smtpHost: encryptedValue(row.smtpHostEncrypted),
|
||||
email,
|
||||
displayName: email || `Mailkonto ${String(row.id).slice(0, 8)} muss repariert werden`,
|
||||
smtpHost,
|
||||
smtpPort: row.smtpPort ? Number(row.smtpPort) : null,
|
||||
smtpSsl: row.smtpSsl,
|
||||
imapHost: encryptedValue(row.imapHostEncrypted),
|
||||
imapHost,
|
||||
imapPort: row.imapPort ? Number(row.imapPort) : null,
|
||||
imapSsl: row.imapSsl,
|
||||
hasPassword: Boolean(row.passwordEncrypted),
|
||||
})
|
||||
credentialsReadable: invalidEncryptedFields.length === 0,
|
||||
invalidEncryptedFields,
|
||||
}
|
||||
}
|
||||
|
||||
const accountCredentials = (row: any) => ({
|
||||
...accountResponse(row),
|
||||
const accountCredentials = (row: any) => {
|
||||
const account = accountResponse(row)
|
||||
if (!account.credentialsReadable) {
|
||||
throw new Error("Die verschlüsselten Kontodaten sind nicht lesbar. Bitte das E-Mail-Konto in den Einstellungen vollständig neu speichern.")
|
||||
}
|
||||
|
||||
return {
|
||||
...account,
|
||||
password: encryptedValue(row.passwordEncrypted),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const bodyValue = (body: any, camelKey: string, snakeKey: string) => body[camelKey] ?? body[snakeKey]
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { de as deLocale } from "date-fns/locale"
|
||||
type EmailAccount = {
|
||||
id: string
|
||||
email: string
|
||||
displayName?: string
|
||||
credentialsReadable?: boolean
|
||||
imapHost?: string | null
|
||||
hasPassword?: boolean
|
||||
}
|
||||
@@ -657,7 +659,7 @@ onMounted(loadAccounts)
|
||||
v-else-if="accounts.length"
|
||||
v-model="selectedAccountId"
|
||||
:items="accounts"
|
||||
label-key="email"
|
||||
label-key="displayName"
|
||||
value-key="id"
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
@@ -302,7 +302,7 @@ const sendEmail = async () => {
|
||||
>
|
||||
<USelectMenu
|
||||
:items="emailAccounts"
|
||||
label-key="email"
|
||||
label-key="displayName"
|
||||
value-key="id"
|
||||
v-model="emailData.account"
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ const toast = useToast()
|
||||
const mode = route.params.mode
|
||||
const isCreate = computed(() => mode === "create")
|
||||
const loading = ref(false)
|
||||
const credentialsReadable = ref(true)
|
||||
|
||||
const itemInfo = ref({
|
||||
email: "",
|
||||
@@ -20,6 +21,7 @@ const itemInfo = ref({
|
||||
const setup = async () => {
|
||||
if(!isCreate.value) {
|
||||
const account = await useNuxtApp().$api(`/api/email/accounts/${route.params.id}`)
|
||||
credentialsReadable.value = account.credentialsReadable !== false
|
||||
itemInfo.value = {
|
||||
...itemInfo.value,
|
||||
...account,
|
||||
@@ -36,6 +38,13 @@ const payload = () => ({
|
||||
password: itemInfo.value.password || undefined,
|
||||
})
|
||||
|
||||
const canSave = computed(() => Boolean(
|
||||
itemInfo.value.email
|
||||
&& itemInfo.value.imapHost
|
||||
&& itemInfo.value.smtpHost
|
||||
&& (isCreate.value || credentialsReadable.value || itemInfo.value.password)
|
||||
))
|
||||
|
||||
const createAccount = async () => {
|
||||
loading.value = true
|
||||
const res = await useNuxtApp().$api(`/api/email/accounts`, {
|
||||
@@ -123,7 +132,7 @@ const syncAccount = async () => {
|
||||
v-if="!isCreate"
|
||||
icon="i-heroicons-check"
|
||||
:loading="loading"
|
||||
:disabled="!itemInfo.email || !itemInfo.imapHost || !itemInfo.smtpHost"
|
||||
:disabled="!canSave"
|
||||
@click="saveAccount"
|
||||
>
|
||||
Speichern
|
||||
@@ -132,6 +141,16 @@ const syncAccount = async () => {
|
||||
</UDashboardNavbar>
|
||||
|
||||
<div class="mx-auto max-w-4xl p-4">
|
||||
<UAlert
|
||||
v-if="!isCreate && !credentialsReadable"
|
||||
class="mb-4"
|
||||
color="error"
|
||||
variant="soft"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
title="Zugangsdaten müssen repariert werden"
|
||||
description="Die Daten wurden mit einem anderen oder nicht mehr verfügbaren Verschlüsselungsschlüssel gespeichert. Trage E-Mail-Adresse, Passwort, IMAP-Host und SMTP-Host vollständig neu ein und speichere das Konto."
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-if="!isCreate"
|
||||
class="mb-4"
|
||||
@@ -139,7 +158,7 @@ const syncAccount = async () => {
|
||||
variant="soft"
|
||||
icon="i-heroicons-lock-closed"
|
||||
title="Passwort bleibt geschützt"
|
||||
description="Das gespeicherte Passwort wird nicht angezeigt. Trage nur dann ein neues Passwort ein, wenn du es ändern möchtest."
|
||||
:description="credentialsReadable ? 'Das gespeicherte Passwort wird nicht angezeigt. Trage nur dann ein neues Passwort ein, wenn du es ändern möchtest.' : 'Das bisherige Passwort kann nicht entschlüsselt werden und muss neu eingetragen werden.'"
|
||||
/>
|
||||
|
||||
<UForm class="grid gap-6">
|
||||
@@ -158,11 +177,11 @@ const syncAccount = async () => {
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UFormField :label="isCreate ? 'Passwort' : 'Neues Passwort'">
|
||||
<UFormField :label="isCreate || !credentialsReadable ? 'Passwort' : 'Neues Passwort'">
|
||||
<UInput
|
||||
v-model="itemInfo.password"
|
||||
type="password"
|
||||
:placeholder="isCreate ? '' : 'Unverändert lassen'"
|
||||
:placeholder="isCreate || !credentialsReadable ? '' : 'Unverändert lassen'"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ setupPage()
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-envelope" class="size-5 text-primary" />
|
||||
<p class="truncate font-medium">
|
||||
{{ account.email }}
|
||||
{{ account.displayName || account.email }}
|
||||
</p>
|
||||
<UBadge
|
||||
size="xs"
|
||||
@@ -88,6 +88,9 @@ setupPage()
|
||||
<span>SMTP: {{ account.smtpHost || "nicht gesetzt" }}:{{ account.smtpPort || "-" }}</span>
|
||||
<span>{{ account.hasPassword ? "Passwort hinterlegt" : "Passwort fehlt" }}</span>
|
||||
</div>
|
||||
<p v-if="account.credentialsReadable === false" class="mt-2 text-sm text-error">
|
||||
Die verschlüsselten Zugangsdaten sind nicht lesbar. Öffne das Konto und trage alle Zugangsdaten neu ein.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
@@ -96,6 +99,7 @@ setupPage()
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
:loading="syncingAccount === account.id"
|
||||
:disabled="account.credentialsReadable === false"
|
||||
@click.stop="syncAccount(account)"
|
||||
>
|
||||
Synchronisieren
|
||||
|
||||
Reference in New Issue
Block a user