diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js
index a00f6f8de..a74d8e05a 100644
--- a/frontend/src/Components/Form/FormInputGroup.js
+++ b/frontend/src/Components/Form/FormInputGroup.js
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
+import PrivacyTextInput from 'Components/Form/PrivacyTextInput';
import Link from 'Components/Link/Link';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
@@ -97,6 +98,9 @@ function getComponent(type) {
case inputTypes.TAG_SELECT:
return TagSelectInputConnector;
+ case inputTypes.PRIVACY_TEXT:
+ return PrivacyTextInput;
+
default:
return TextInput;
}
diff --git a/frontend/src/Components/Form/PrivacyTextInput.css b/frontend/src/Components/Form/PrivacyTextInput.css
new file mode 100644
index 000000000..ef6a0932e
--- /dev/null
+++ b/frontend/src/Components/Form/PrivacyTextInput.css
@@ -0,0 +1,17 @@
+.container {
+ position: relative;
+}
+
+.input {
+ composes: input from '~Components/Form/TextInput.css';
+
+ padding: 6px 30px 6px 16px;
+}
+
+.toggle {
+ position: absolute;
+ margin-left: -25px;
+ width: 20px;
+ text-align: center;
+ line-height: 35px;
+}
diff --git a/frontend/src/Components/Form/PrivacyTextInput.css.d.ts b/frontend/src/Components/Form/PrivacyTextInput.css.d.ts
new file mode 100644
index 000000000..a7d07a37b
--- /dev/null
+++ b/frontend/src/Components/Form/PrivacyTextInput.css.d.ts
@@ -0,0 +1,9 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'container': string;
+ 'input': string;
+ 'toggle': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/frontend/src/Components/Form/PrivacyTextInput.tsx b/frontend/src/Components/Form/PrivacyTextInput.tsx
new file mode 100644
index 000000000..5407349cb
--- /dev/null
+++ b/frontend/src/Components/Form/PrivacyTextInput.tsx
@@ -0,0 +1,46 @@
+import React, { SyntheticEvent, useCallback, useState } from 'react';
+import IconButton from 'Components/Link/IconButton';
+import { icons } from 'Helpers/Props';
+import translate from 'Utilities/String/translate';
+import TextInput from './TextInput';
+import styles from './PrivacyTextInput.css';
+
+interface PrivacyTextInputProps {
+ className: string;
+}
+
+function PrivacyTextInput(props: PrivacyTextInputProps) {
+ const { className = styles.input, ...otherProps } = props;
+
+ const [isVisible, setIsVisible] = useState(false);
+
+ const toggleVisibility = useCallback(() => {
+ setIsVisible(!isVisible);
+ }, [isVisible, setIsVisible]);
+
+ // Prevent a user from copying (or cutting) the password from the input
+ const onCopy = useCallback((event: SyntheticEvent) => {
+ event.preventDefault();
+ event.nativeEvent.stopImmediatePropagation();
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default PrivacyTextInput;
diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js
index 878e3a7ce..908931e9d 100644
--- a/frontend/src/Components/Form/ProviderFieldFormGroup.js
+++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js
@@ -6,7 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
-function getType({ type, selectOptionsProviderAction }) {
+function getType({ type, selectOptionsProviderAction, privacy }) {
switch (type) {
case 'captcha':
return inputTypes.CAPTCHA;
@@ -34,6 +34,9 @@ function getType({ type, selectOptionsProviderAction }) {
case 'tagSelect':
return inputTypes.TAG_SELECT;
case 'textbox':
+ if (privacy === 'userName' || privacy === 'apiKey') {
+ return inputTypes.PRIVACY_TEXT;
+ }
return inputTypes.TEXT;
case 'oAuth':
return inputTypes.OAUTH;
diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js
index 834452242..1bf0e69cd 100644
--- a/frontend/src/Helpers/Props/icons.js
+++ b/frontend/src/Helpers/Props/icons.js
@@ -56,6 +56,7 @@ import {
faExclamationTriangle as fasExclamationTriangle,
faExternalLinkAlt as fasExternalLinkAlt,
faEye as fasEye,
+ faEyeSlash as fasEyeSlash,
faFastBackward as fasFastBackward,
faFastForward as fasFastForward,
faFileExport as fasFileExport,
@@ -245,3 +246,4 @@ export const VIEW = fasEye;
export const WARNING = fasExclamationTriangle;
export const WIKI = fasBookReader;
export const BLOCKLIST = fasBan;
+export const HIDE = fasEyeSlash;
diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js
index 6c4564341..2b2e59b1a 100644
--- a/frontend/src/Helpers/Props/inputTypes.js
+++ b/frontend/src/Helpers/Props/inputTypes.js
@@ -20,6 +20,7 @@ export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
export const TEXT = 'text';
+export const PRIVACY_TEXT = 'privacyText';
export const TEXT_AREA = 'textArea';
export const TEXT_TAG = 'textTag';
export const TAG_SELECT = 'tagSelect';
@@ -46,6 +47,7 @@ export const all = [
DYNAMIC_SELECT,
TAG,
TEXT,
+ PRIVACY_TEXT,
TEXT_AREA,
TEXT_TAG,
TAG_SELECT
diff --git a/src/Prowlarr.Http/ClientSchema/Field.cs b/src/Prowlarr.Http/ClientSchema/Field.cs
index 7ceed1ae9..5053cacee 100644
--- a/src/Prowlarr.Http/ClientSchema/Field.cs
+++ b/src/Prowlarr.Http/ClientSchema/Field.cs
@@ -20,6 +20,7 @@ namespace Prowlarr.Http.ClientSchema
public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
+ public PrivacyLevel Privacy { get; set; }
public string Placeholder { get; set; }
public bool IsFloat { get; set; }
diff --git a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs
index 171c9db04..39e3e1ba3 100644
--- a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs
+++ b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs
@@ -105,6 +105,7 @@ namespace Prowlarr.Http.ClientSchema
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section,
+ Privacy = fieldAttribute.Privacy,
Placeholder = fieldAttribute.Placeholder
};