Add SMTP support for Authelia notifications and fix config persistence
SMTP: Add host, port, username, sender, and password fields to the Authelia configuration. Uses modern address format (submission:// for STARTTLS on 587, smtps:// for implicit TLS on 465) with explicit TLS server name. Falls back to filesystem notifier when SMTP is not configured. Fixes: Config changes now persist before attempting service restart, so a restart failure (e.g. bad SMTP credentials) no longer prevents saving. The specific Authelia error is extracted from the journal and shown in the UI. Frontend: SMTP fields added to the Authentication config card. Form no longer continuously resets from server state while user is editing. OIDC client edit UI added (pencil icon).
This commit is contained in:
@@ -11,6 +11,7 @@ import { Switch } from './ui/switch';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||
import { useAuthelia } from '../hooks/useAuthelia';
|
||||
import { domainsApi } from '../services/api';
|
||||
import type { AutheliaStatus } from '../services/api/authelia';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export function AutheliaComponent() {
|
||||
@@ -312,21 +313,37 @@ function ConfigCard({ status, onUpdate, showSuccess, showError }: {
|
||||
showError: (msg: string) => void;
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const { register, handleSubmit, watch, setValue } = useForm({
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const { register, handleSubmit, watch, setValue, reset } = useForm({
|
||||
defaultValues: {
|
||||
domain: status?.domain ?? '',
|
||||
defaultPolicy: status?.defaultPolicy || 'one_factor',
|
||||
domain: '',
|
||||
defaultPolicy: 'one_factor',
|
||||
smtpHost: '',
|
||||
smtpPort: 587,
|
||||
smtpUsername: '',
|
||||
smtpSender: '',
|
||||
smtpPassword: '',
|
||||
},
|
||||
values: status ? {
|
||||
});
|
||||
|
||||
// Sync form with status data once on load, not continuously
|
||||
if (status && !initialized) {
|
||||
reset({
|
||||
domain: status.domain ?? '',
|
||||
defaultPolicy: status.defaultPolicy || 'one_factor',
|
||||
} : undefined,
|
||||
});
|
||||
smtpHost: status.smtp?.host ?? '',
|
||||
smtpPort: status.smtp?.port || 587,
|
||||
smtpUsername: status.smtp?.username ?? '',
|
||||
smtpSender: status.smtp?.sender ?? '',
|
||||
smtpPassword: '',
|
||||
});
|
||||
setInitialized(true);
|
||||
}
|
||||
|
||||
const enabled = status?.enabled ?? false;
|
||||
const isUpdating = onUpdate.isPending;
|
||||
|
||||
const doUpdate = (data: { enabled?: boolean; domain?: string; defaultPolicy?: string }) => {
|
||||
const doUpdate = (data: { enabled?: boolean; domain?: string; defaultPolicy?: string; smtpHost?: string; smtpPort?: number; smtpUsername?: string; smtpSender?: string; smtpPassword?: string }) => {
|
||||
onUpdate.mutate(data, {
|
||||
onSuccess: () => {
|
||||
setEditing(false);
|
||||
@@ -363,7 +380,11 @@ function ConfigCard({ status, onUpdate, showSuccess, showError }: {
|
||||
<p className="text-sm text-muted-foreground">Authelia is not installed. Install it first: <code className="bg-muted px-1 rounded">apt install authelia</code></p>
|
||||
)}
|
||||
{(enabled || editing) && (
|
||||
<form onSubmit={handleSubmit((data) => doUpdate({ ...data, enabled: true }))} className="space-y-3">
|
||||
<form onSubmit={handleSubmit((data) => {
|
||||
const update: any = { ...data, enabled: true };
|
||||
if (!update.smtpPassword) delete update.smtpPassword; // don't send empty password
|
||||
doUpdate(update);
|
||||
})} className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="auth-domain">Auth Portal Domain</Label>
|
||||
<Input {...register('domain', { required: 'Required' })} id="auth-domain" placeholder="auth.example.com" className="mt-1 font-mono" />
|
||||
@@ -380,6 +401,34 @@ function ConfigCard({ status, onUpdate, showSuccess, showError }: {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="pt-2 border-t">
|
||||
<p className="text-sm font-medium mb-2">Email Notifications (SMTP)</p>
|
||||
<p className="text-xs text-muted-foreground mb-3">Required for password resets and 2FA enrollment. Without SMTP, notifications are written to a local file.</p>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="col-span-2">
|
||||
<Label htmlFor="smtp-host">SMTP Host</Label>
|
||||
<Input {...register('smtpHost')} id="smtp-host" placeholder="smtp.gmail.com" className="mt-1 font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="smtp-port">Port</Label>
|
||||
<Input {...register('smtpPort', { valueAsNumber: true })} id="smtp-port" type="number" placeholder="587" className="mt-1 font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3 mt-3">
|
||||
<div>
|
||||
<Label htmlFor="smtp-sender">Sender Email</Label>
|
||||
<Input {...register('smtpSender')} id="smtp-sender" placeholder="auth@example.com" className="mt-1 font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="smtp-username">Username</Label>
|
||||
<Input {...register('smtpUsername')} id="smtp-username" placeholder="(defaults to sender)" className="mt-1 font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="smtp-password">Password</Label>
|
||||
<Input {...register('smtpPassword')} id="smtp-password" type="password" placeholder={status?.smtp?.host ? '••••••••' : ''} className="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{editing && (
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" size="sm" disabled={isUpdating}>
|
||||
@@ -556,17 +605,6 @@ function EditOIDCClientForm({ client, onSubmit, onCancel, isSubmitting }: {
|
||||
);
|
||||
}
|
||||
|
||||
interface AutheliaStatus {
|
||||
active: boolean;
|
||||
version?: string;
|
||||
enabled: boolean;
|
||||
domain: string;
|
||||
defaultPolicy: string;
|
||||
userCount: number;
|
||||
clientCount: number;
|
||||
installed: boolean;
|
||||
}
|
||||
|
||||
function ProtectedDomainsCard({ authDomain }: { authDomain: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const domainsQuery = useQuery({
|
||||
|
||||
Reference in New Issue
Block a user