refactor(notification): make username and password optional (smtp) (#126)
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled

This commit is contained in:
Nico 2025-12-10 21:36:18 +01:00 committed by GitHub
parent ef8fd7228f
commit c3acc26e05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 83 additions and 31 deletions

View file

@ -164,6 +164,11 @@ export type ListVolumesResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -191,7 +196,7 @@ export type ListVolumesResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}>; }>;
}; };
@ -211,6 +216,11 @@ export type CreateVolumeData = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -255,6 +265,11 @@ export type CreateVolumeResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -282,7 +297,7 @@ export type CreateVolumeResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
}; };
@ -302,6 +317,11 @@ export type TestConnectionData = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -399,6 +419,11 @@ export type GetVolumeResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -426,7 +451,7 @@ export type GetVolumeResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
}; };
@ -448,6 +473,11 @@ export type UpdateVolumeData = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -501,6 +531,11 @@ export type UpdateVolumeResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -528,7 +563,7 @@ export type UpdateVolumeResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
}; };
@ -1397,6 +1432,11 @@ export type ListBackupSchedulesResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -1424,7 +1464,7 @@ export type ListBackupSchedulesResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
volumeId: number; volumeId: number;
@ -1636,6 +1676,11 @@ export type GetBackupScheduleResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -1663,7 +1708,7 @@ export type GetBackupScheduleResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
volumeId: number; volumeId: number;
@ -1856,6 +1901,11 @@ export type GetBackupScheduleForVolumeResponses = {
version: '3' | '4' | '4.1'; version: '3' | '4' | '4.1';
port?: number; port?: number;
readOnly?: boolean; readOnly?: boolean;
} | {
backend: 'rclone';
path: string;
remote: string;
readOnly?: boolean;
} | { } | {
backend: 'smb'; backend: 'smb';
password: string; password: string;
@ -1883,7 +1933,7 @@ export type GetBackupScheduleForVolumeResponses = {
name: string; name: string;
shortId: string; shortId: string;
status: 'error' | 'mounted' | 'unmounted'; status: 'error' | 'mounted' | 'unmounted';
type: 'directory' | 'nfs' | 'smb' | 'webdav'; type: 'directory' | 'nfs' | 'rclone' | 'smb' | 'webdav';
updatedAt: number; updatedAt: number;
}; };
volumeId: number; volumeId: number;
@ -1987,13 +2037,13 @@ export type GetScheduleNotificationsResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2077,13 +2127,13 @@ export type UpdateScheduleNotificationsResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2380,13 +2430,13 @@ export type ListNotificationDestinationsResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2441,13 +2491,13 @@ export type CreateNotificationDestinationData = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2501,13 +2551,13 @@ export type CreateNotificationDestinationResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2608,13 +2658,13 @@ export type GetNotificationDestinationResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2669,13 +2719,13 @@ export type UpdateNotificationDestinationData = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;
@ -2739,13 +2789,13 @@ export type UpdateNotificationDestinationResponses = {
type: 'telegram'; type: 'telegram';
} | { } | {
from: string; from: string;
password: string;
smtpHost: string; smtpHost: string;
smtpPort: number; smtpPort: number;
to: Array<string>; to: Array<string>;
type: 'email'; type: 'email';
useTLS: boolean; useTLS: boolean;
username: string; password?: string;
username?: string;
} | { } | {
priority: 'default' | 'high' | 'low' | 'max' | 'min'; priority: 'default' | 'high' | 'low' | 'max' | 'min';
topic: string; topic: string;

View file

@ -198,7 +198,7 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="user@example.com" /> <Input {...field} placeholder="user@example.com" />
</FormControl> </FormControl>
@ -211,7 +211,7 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Password</FormLabel> <FormLabel>Password (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="password" placeholder="••••••••" /> <Input {...field} type="password" placeholder="••••••••" />
</FormControl> </FormControl>

View file

@ -17,8 +17,8 @@ export const emailNotificationConfigSchema = type({
type: "'email'", type: "'email'",
smtpHost: "string", smtpHost: "string",
smtpPort: "1 <= number <= 65535", smtpPort: "1 <= number <= 65535",
username: "string", username: "string?",
password: "string", password: "string?",
from: "string", from: "string",
to: "string[]", to: "string[]",
useTLS: "boolean", useTLS: "boolean",

View file

@ -1,10 +1,12 @@
import type { NotificationConfig } from "~/schemas/notifications"; import type { NotificationConfig } from "~/schemas/notifications";
export function buildEmailShoutrrrUrl(config: Extract<NotificationConfig, { type: "email" }>): string { export function buildEmailShoutrrrUrl(config: Extract<NotificationConfig, { type: "email" }>): string {
const auth = `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}`; const auth = config.username && config.password
? `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@`
: "";
const host = `${config.smtpHost}:${config.smtpPort}`; const host = `${config.smtpHost}:${config.smtpPort}`;
const toRecipients = config.to.map((email) => encodeURIComponent(email)).join(","); const toRecipients = config.to.map((email) => encodeURIComponent(email)).join(",");
const useStartTLS = config.useTLS ? "yes" : "no"; const useStartTLS = config.useTLS ? "yes" : "no";
return `smtp://${auth}@${host}/?from=${encodeURIComponent(config.from)}&to=${toRecipients}&starttls=${useStartTLS}`; return `smtp://${auth}${host}/?from=${encodeURIComponent(config.from)}&to=${toRecipients}&starttls=${useStartTLS}`;
} }

View file

@ -38,7 +38,7 @@ async function encryptSensitiveFields(config: NotificationConfig): Promise<Notif
case "email": case "email":
return { return {
...config, ...config,
password: await cryptoUtils.encrypt(config.password), password: config.password ? await cryptoUtils.encrypt(config.password) : undefined,
}; };
case "slack": case "slack":
return { return {
@ -85,7 +85,7 @@ async function decryptSensitiveFields(config: NotificationConfig): Promise<Notif
case "email": case "email":
return { return {
...config, ...config,
password: await cryptoUtils.decrypt(config.password), password: config.password ? await cryptoUtils.decrypt(config.password) : undefined,
}; };
case "slack": case "slack":
return { return {