mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-03-28 19:11:29 -04:00
Add guards against exposing passwords to the UI (#477)
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
<app-input label="External URL" placeholder="https://sonarr.example.com" type="url" [(value)]="modalExternalUrl"
|
||||
hint="Optional URL used in notifications for clickable links (e.g., when internal Docker URLs are not reachable externally)"
|
||||
helpKey="arr:externalUrl" />
|
||||
<app-input label="API Key" placeholder="Enter API key" type="password" [(value)]="modalApiKey"
|
||||
<app-input label="API Key" placeholder="Enter API key" type="password" [revealable]="false" [(value)]="modalApiKey"
|
||||
hint="API key from your arr application's Settings > General"
|
||||
[error]="modalApiKeyError()"
|
||||
helpKey="arr:apiKey" />
|
||||
|
||||
@@ -146,6 +146,7 @@ export class ArrSettingsComponent implements HasPendingChanges {
|
||||
url: this.modalUrl(),
|
||||
apiKey: this.modalApiKey(),
|
||||
version: (this.modalVersion() as number) ?? 3,
|
||||
instanceId: this.editingInstance()?.id,
|
||||
};
|
||||
this.testing.set(true);
|
||||
this.api.testInstance(this.arrType() as ArrType, request).subscribe({
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
helpKey="download-client:username" />
|
||||
}
|
||||
@if (showPasswordField()) {
|
||||
<app-input label="Password" placeholder="Enter password" type="password" [(value)]="modalPassword"
|
||||
<app-input label="Password" placeholder="Enter password" type="password" [revealable]="false" [(value)]="modalPassword"
|
||||
[hint]="passwordHint()"
|
||||
helpKey="download-client:password" />
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ export class DownloadClientsComponent implements OnInit, HasPendingChanges {
|
||||
username: this.modalUsername(),
|
||||
password: this.modalPassword(),
|
||||
urlBase: this.modalUrlBase(),
|
||||
clientId: this.editingClient()?.id,
|
||||
};
|
||||
this.testing.set(true);
|
||||
this.api.test(request).subscribe({
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
<!-- Discord Fields -->
|
||||
@if (modalType() === 'Discord') {
|
||||
<app-input label="Webhook URL" placeholder="https://discord.com/api/webhooks/..." type="password" [(value)]="modalWebhookUrl"
|
||||
<app-input label="Webhook URL" placeholder="https://discord.com/api/webhooks/..." type="password" [revealable]="false" [(value)]="modalWebhookUrl"
|
||||
hint="Your Discord webhook URL. Create one in your Discord server's channel settings under Integrations."
|
||||
[error]="discordWebhookError()"
|
||||
helpKey="notifications/discord:webhookUrl" />
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
<!-- Telegram Fields -->
|
||||
@if (modalType() === 'Telegram') {
|
||||
<app-input label="Bot Token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" type="password" [(value)]="modalBotToken"
|
||||
<app-input label="Bot Token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" type="password" [revealable]="false" [(value)]="modalBotToken"
|
||||
hint="Create a bot with BotFather and paste the API token"
|
||||
[error]="telegramBotTokenError()"
|
||||
helpKey="notifications/telegram:botToken" />
|
||||
@@ -135,7 +135,7 @@
|
||||
|
||||
<!-- Notifiarr Fields -->
|
||||
@if (modalType() === 'Notifiarr') {
|
||||
<app-input label="API Key" placeholder="Enter API key" type="password" [(value)]="modalApiKey"
|
||||
<app-input label="API Key" placeholder="Enter API key" type="password" [revealable]="false" [(value)]="modalApiKey"
|
||||
hint="Your Notifiarr API key from your dashboard. Requires Passthrough integration."
|
||||
[error]="notifiarrApiKeyError()"
|
||||
helpKey="notifications/notifiarr:apiKey" />
|
||||
@@ -187,12 +187,12 @@
|
||||
<app-input label="Username" placeholder="Enter username" [(value)]="modalNtfyUsername"
|
||||
hint="Your username for basic authentication."
|
||||
helpKey="notifications/ntfy:username" />
|
||||
<app-input label="Password" placeholder="Enter password" type="password" [(value)]="modalNtfyPassword"
|
||||
<app-input label="Password" placeholder="Enter password" type="password" [revealable]="false" [(value)]="modalNtfyPassword"
|
||||
hint="Your password for basic authentication."
|
||||
helpKey="notifications/ntfy:password" />
|
||||
}
|
||||
@if (modalNtfyAuthType() === 'AccessToken') {
|
||||
<app-input label="Access Token" placeholder="Enter access token" type="password" [(value)]="modalNtfyAccessToken"
|
||||
<app-input label="Access Token" placeholder="Enter access token" type="password" [revealable]="false" [(value)]="modalNtfyAccessToken"
|
||||
hint="Your access token for bearer token authentication."
|
||||
helpKey="notifications/ntfy:accessToken" />
|
||||
}
|
||||
@@ -206,11 +206,11 @@
|
||||
|
||||
<!-- Pushover Fields -->
|
||||
@if (modalType() === 'Pushover') {
|
||||
<app-input label="API Token" placeholder="Enter API token" type="password" [(value)]="modalPushoverApiToken"
|
||||
<app-input label="API Token" placeholder="Enter API token" type="password" [revealable]="false" [(value)]="modalPushoverApiToken"
|
||||
hint="Your application API token from Pushover. Create one at pushover.net/apps/build."
|
||||
[error]="pushoverApiTokenError()"
|
||||
helpKey="notifications/pushover:apiToken" />
|
||||
<app-input label="User Key" placeholder="Enter user key" type="password" [(value)]="modalPushoverUserKey"
|
||||
<app-input label="User Key" placeholder="Enter user key" type="password" [revealable]="false" [(value)]="modalPushoverUserKey"
|
||||
hint="Your user/group key from your Pushover dashboard."
|
||||
[error]="pushoverUserKeyError()"
|
||||
helpKey="notifications/pushover:userKey" />
|
||||
@@ -247,7 +247,7 @@
|
||||
hint="The base URL of your Gotify server instance."
|
||||
[error]="gotifyServerUrlError()"
|
||||
helpKey="notifications/gotify:serverUrl" />
|
||||
<app-input label="Application Token" placeholder="Enter application token" type="password" [(value)]="modalGotifyApplicationToken"
|
||||
<app-input label="Application Token" placeholder="Enter application token" type="password" [revealable]="false" [(value)]="modalGotifyApplicationToken"
|
||||
hint="The application token from your Gotify server. Create one under Apps in the Gotify web UI."
|
||||
[error]="gotifyApplicationTokenError()"
|
||||
helpKey="notifications/gotify:applicationToken" />
|
||||
|
||||
@@ -478,6 +478,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
testNotification(): void {
|
||||
const type = this.modalType();
|
||||
this.testing.set(true);
|
||||
const providerId = this.editingProvider()?.id;
|
||||
|
||||
switch (type) {
|
||||
case NotificationProviderType.Discord:
|
||||
@@ -485,6 +486,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
webhookUrl: this.modalWebhookUrl(),
|
||||
username: this.modalUsername() || undefined,
|
||||
avatarUrl: this.modalAvatarUrl() || undefined,
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -496,6 +498,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
chatId: this.modalChatId(),
|
||||
topicId: this.modalTopicId() || undefined,
|
||||
sendSilently: this.modalSendSilently(),
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -505,6 +508,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
this.api.testNotifiarr({
|
||||
apiKey: this.modalApiKey(),
|
||||
channelId: this.modalChannelId(),
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -517,6 +521,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
key: this.modalAppriseKey() || undefined,
|
||||
tags: this.modalAppriseTags() || undefined,
|
||||
serviceUrls: this.modalAppriseServiceUrls().join('\n') || undefined,
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -532,6 +537,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
accessToken: this.modalNtfyAccessToken() || undefined,
|
||||
priority: this.modalNtfyPriority() as NtfyPriority,
|
||||
tags: this.modalNtfyTags().length > 0 ? this.modalNtfyTags() : undefined,
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -548,6 +554,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
retry: this.modalPushoverPriority() === PushoverPriority.Emergency ? (this.modalPushoverRetry() ?? 30) : undefined,
|
||||
expire: this.modalPushoverPriority() === PushoverPriority.Emergency ? (this.modalPushoverExpire() ?? 3600) : undefined,
|
||||
tags: this.modalPushoverTags().length > 0 ? this.modalPushoverTags() : undefined,
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
@@ -559,6 +566,7 @@ export class NotificationsComponent implements OnInit, HasPendingChanges {
|
||||
serverUrl: this.modalGotifyServerUrl(),
|
||||
applicationToken: this.modalGotifyApplicationToken(),
|
||||
priority: parseInt(this.modalGotifyPriority() as string, 10) || 5,
|
||||
providerId,
|
||||
}).subscribe({
|
||||
next: (r) => { this.toast.success(r.message || 'Test sent'); this.testing.set(false); },
|
||||
error: () => { this.toast.error('Test failed'); this.testing.set(false); },
|
||||
|
||||
@@ -26,4 +26,5 @@ export interface TestArrInstanceRequest {
|
||||
url: string;
|
||||
apiKey: string;
|
||||
version: number;
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface TestDownloadClientRequest {
|
||||
username?: string;
|
||||
password?: string;
|
||||
urlBase?: string;
|
||||
clientId?: string;
|
||||
}
|
||||
|
||||
export interface TestConnectionResult {
|
||||
|
||||
@@ -150,6 +150,7 @@ export interface CreateGotifyProviderRequest {
|
||||
export interface TestNotifiarrRequest {
|
||||
apiKey: string;
|
||||
channelId: string;
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestAppriseRequest {
|
||||
@@ -158,6 +159,7 @@ export interface TestAppriseRequest {
|
||||
key?: string;
|
||||
tags?: string;
|
||||
serviceUrls?: string;
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestNtfyRequest {
|
||||
@@ -169,6 +171,7 @@ export interface TestNtfyRequest {
|
||||
accessToken?: string;
|
||||
priority: NtfyPriority;
|
||||
tags?: string[];
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestTelegramRequest {
|
||||
@@ -176,12 +179,14 @@ export interface TestTelegramRequest {
|
||||
chatId: string;
|
||||
topicId?: string;
|
||||
sendSilently: boolean;
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestDiscordRequest {
|
||||
webhookUrl: string;
|
||||
username?: string;
|
||||
avatarUrl?: string;
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestPushoverRequest {
|
||||
@@ -193,12 +198,14 @@ export interface TestPushoverRequest {
|
||||
retry?: number;
|
||||
expire?: number;
|
||||
tags?: string[];
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestGotifyRequest {
|
||||
serverUrl: string;
|
||||
applicationToken: string;
|
||||
priority: number;
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
export interface TestNotificationResult {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#inputEl
|
||||
class="input-field"
|
||||
[class.input-field--error]="error()"
|
||||
[class.input-field--has-eye]="type() === 'password'"
|
||||
[class.input-field--has-eye]="hasEye()"
|
||||
[type]="effectiveType()"
|
||||
[placeholder]="placeholder()"
|
||||
[disabled]="disabled()"
|
||||
@@ -21,7 +21,7 @@
|
||||
[(ngModel)]="value"
|
||||
(blur)="blurred.emit($event)"
|
||||
/>
|
||||
@if (type() === 'password') {
|
||||
@if (hasEye()) {
|
||||
<button type="button" class="input-eye-btn" (click)="toggleSecret($event)" [attr.aria-label]="showSecret() ? 'Hide password' : 'Show password'">
|
||||
<ng-icon [name]="showSecret() ? 'tablerEyeOff' : 'tablerEye'" size="16" />
|
||||
</button>
|
||||
|
||||
@@ -19,6 +19,7 @@ export class InputComponent {
|
||||
type = input<'text' | 'password' | 'email' | 'url' | 'search' | 'datetime-local' | 'date' | 'number'>('text');
|
||||
disabled = input(false);
|
||||
readonly = input(false);
|
||||
revealable = input(true);
|
||||
error = input<string>();
|
||||
hint = input<string>();
|
||||
helpKey = input<string>();
|
||||
@@ -29,8 +30,9 @@ export class InputComponent {
|
||||
blurred = output<FocusEvent>();
|
||||
|
||||
readonly showSecret = signal(false);
|
||||
readonly hasEye = computed(() => this.type() === 'password' && this.revealable());
|
||||
readonly effectiveType = computed(() => {
|
||||
if (this.type() === 'password' && this.showSecret()) return 'text';
|
||||
if (this.hasEye() && this.showSecret()) return 'text';
|
||||
return this.type();
|
||||
});
|
||||
|
||||
@@ -40,6 +42,7 @@ export class InputComponent {
|
||||
|
||||
toggleSecret(event: Event): void {
|
||||
event.preventDefault();
|
||||
if (!this.revealable()) return;
|
||||
this.showSecret.update(v => !v);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user