Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ private FormConstants() {
/** The resource type for email input v1 */
public static final String RT_FD_FORM_EMAIL_V1 = RT_FD_FORM_PREFIX + "emailinput/v1/emailinput";

/** The resource type for password input v1 */
public static final String RT_FD_FORM_PASSWORD_V1 = RT_FD_FORM_PREFIX + "passwordinput/v1/passwordinput";

/** The resource type for button v1 */
public static final String RT_FD_FORM_BUTTON_V1 = RT_FD_FORM_PREFIX + "button/v1/button";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ private ReservedProperties() {
public static final String PN_SHOW_AS_POPUP = "showAsPopup";
public static final String PN_TEXT_IS_RICH = "textIsRich";
public static final String PN_MULTILINE = "multiLine";
public static final String PN_SHOW_HIDE_PASSWORD = "showHidePassword";
Comment thread
shravani-0811 marked this conversation as resolved.
public static final String PN_DESIGN_DEFAULT_TYPE = Title.PN_DESIGN_DEFAULT_TYPE;
public static final String PN_TITLE_LINK_DISABLED = Title.PN_TITLE_LINK_DISABLED;
public static final String PN_DRAG_DROP_TEXT = "dragDropText";
Expand All @@ -173,6 +174,7 @@ private ReservedProperties() {
public static final String FD_DIALOG_LABEL = "fd:dialogLabel";
public static final String FD_XFA_SCRIPTS = "fd:xfaScripts";
public static final String FD_FILE_ACCEPT_EXTENSIONS = "fd:acceptExtensions";
public static final String FD_SHOW_HIDE_PASSWORD = "fd:showHidePassword";

public static final String FD_DRAFT_ID = "fd:draftId";
public static final String FD_CHANGE_EVENT_BEHAVIOUR = "fd:changeEventBehaviour";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2026 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.forms.core.components.internal.models.v1.form;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.annotation.Nullable;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.jetbrains.annotations.NotNull;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.forms.core.components.internal.form.FormConstants;
import com.adobe.cq.forms.core.components.internal.form.ReservedProperties;
import com.adobe.cq.forms.core.components.models.form.FieldType;
import com.adobe.cq.forms.core.components.models.form.PasswordInput;
import com.adobe.cq.forms.core.components.util.AbstractFieldImpl;

@Model(
adaptables = { SlingHttpServletRequest.class, Resource.class },
adapters = { PasswordInput.class, ComponentExporter.class },
resourceType = { FormConstants.RT_FD_FORM_PASSWORD_V1 })
@Exporter(
name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PasswordInputImpl extends AbstractFieldImpl implements PasswordInput {

@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_PATTERN)
@Nullable
protected String pattern;

@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_AUTOCOMPLETE)
@Nullable
protected String autocomplete;

@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SHOW_HIDE_PASSWORD)
@Default(booleanValues = true)
protected boolean showHidePassword;

@Override
public String getFieldType() {
return super.getFieldType(FieldType.PASSWORD);
}

@Override
@Nullable
public Object[] getDefault() {
// password values must never be exposed in rendered markup or the exported JSON model,
// regardless of how the underlying property was set (authoring dialog, direct content write, etc.)
return null;
}

@Override
@Nullable
public Integer getMinLength() {
return minLength;
}

@Override
@Nullable
public Integer getMaxLength() {
return maxLength;
}

@Override
@Nullable
public String getPattern() {
return pattern;
}

@Override
public String getAutoComplete() {
return autocomplete;
}

@Override
public boolean isShowHidePasswordEnabled() {
return showHidePassword;
}

/**
* Exposes {@code showHidePassword} as the OOTB {@code fd:}-prefixed custom property so it is available to headless
* clients (the boolean getter above is only a convenience for the HTL rendering path and is not itself serialized).
*/
@Override
public @NotNull Map<String, Object> getProperties() {
Map<String, Object> properties = new LinkedHashMap<>(super.getProperties());
properties.put(ReservedProperties.FD_SHOW_HIDE_PASSWORD, showHidePassword);
return properties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2026 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.forms.core.components.models.form;

import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ConsumerType;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Defines the form {@code Password} Sling Model used for the
* {@code /apps/core/fd/components/form/passwordinput/v1/passwordinput} component.
*
* <p>
* Password is modelled as a first-class field type (rather than a variant of {@link TextInput}) so that the
* password-specific contract (visibility toggle, default-value masking, credential autofill tokens) does not leak
* into the text, email and telephone components that share {@code TextInput}.
* </p>
*
* @since com.adobe.cq.forms.core.components.models.form 5.13.0
*/
@ConsumerType
public interface PasswordInput extends Field, StringConstraint {

/**
* Returns {@code true} if the password show/hide visibility toggle should be rendered, otherwise {@code false}.
*
* @return {@code true} if the visibility toggle should be rendered, otherwise {@code false}
* @since com.adobe.cq.forms.core.components.models.form 5.13.0
*/
@JsonIgnore
default boolean isShowHidePasswordEnabled() {
return true;
}

/**
* Returns {@code "off"} if autocomplete is disabled, otherwise the configured autofill token (for example
* {@code new-password} or {@code current-password}) @see
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete">here</a>.
*
* @return the autocomplete attribute value, or {@code null} if not set
* @since com.adobe.cq.forms.core.components.models.form 5.13.0
*/
@JsonProperty("autocomplete")
@Nullable
default String getAutoComplete() {
return null;
}

/**
* Returns the format of the form field as specified in the json schema specification. Password fields have no
* format by default.
*
* @return the format of the form field
* @since com.adobe.cq.forms.core.components.models.form 5.13.0
*/
@Override
@Nullable
default String getFormat() {
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2026 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.forms.core.components.internal.models.v1.form;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;

import com.adobe.cq.forms.core.Utils;
import com.adobe.cq.forms.core.components.internal.form.FormConstants;
import com.adobe.cq.forms.core.components.models.form.FieldType;
import com.adobe.cq.forms.core.components.models.form.PasswordInput;
import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

@ExtendWith(AemContextExtension.class)
public class PasswordInputImplTest {
private static final String BASE = "/form/textinput";
private static final String CONTENT_ROOT = "/content";
private static final String PATH_PASSWORD = CONTENT_ROOT + "/password-textinput";
private static final String PATH_PASSWORD_TOGGLE_DISABLED = CONTENT_ROOT + "/password-textinput-toggle-disabled";

private final AemContext context = FormsCoreComponentTestContext.newAemContext();

@BeforeEach
void setUp() {
context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT);
}

@Test
void testFieldType() {
PasswordInput passwordInput = Utils.getComponentUnderTest(PATH_PASSWORD, PasswordInput.class, context);
assertEquals(FieldType.PASSWORD.getValue(), passwordInput.getFieldType());
assertEquals(FormConstants.RT_FD_FORM_PASSWORD_V1, passwordInput.getExportedType());
}

@Test
void testShowHidePasswordEnabledByDefault() {
PasswordInput passwordInput = Utils.getComponentUnderTest(PATH_PASSWORD, PasswordInput.class, context);
assertTrue(passwordInput.isShowHidePasswordEnabled());
PasswordInput passwordInputMock = Mockito.mock(PasswordInput.class);
Mockito.when(passwordInputMock.isShowHidePasswordEnabled()).thenCallRealMethod();
assertTrue(passwordInputMock.isShowHidePasswordEnabled());
}

@Test
void testShowHidePasswordCanBeDisabled() {
PasswordInput passwordInput = Utils.getComponentUnderTest(PATH_PASSWORD_TOGGLE_DISABLED, PasswordInput.class, context);
assertFalse(passwordInput.isShowHidePasswordEnabled());
}

@Test
void testDefaultValueIsMaskedForPassword() {
// password default values must never be exposed in the exported model, regardless of stored content
PasswordInput passwordInput = Utils.getComponentUnderTest(PATH_PASSWORD, PasswordInput.class, context);
assertNull(passwordInput.getDefault());
}

@Test
void testJSONExport() throws Exception {
PasswordInput passwordInput = Utils.getComponentUnderTest(PATH_PASSWORD, PasswordInput.class, context);
Utils.testJSONExport(passwordInput, Utils.getTestExporterJSONPath(BASE, PATH_PASSWORD));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"id": "passwordinput-5c6da9d601",
"fieldType": "password",
"name": "abc",
"type": "string",
"minLength": 8,
"maxLength": 20,
"pattern": "^[a-zA-Z0-9]+$",
"label": {
"value": "def"
},
"properties": {
"fd:showHidePassword": true,
"fd:path": "/content/password-textinput"
},
"events": {
"custom:setProperty": [
"$event.payload"
]
},
":type": "core/fd/components/form/passwordinput/v1/passwordinput"
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,23 @@
"sling:resourceType" : "core/fd/components/form/textinput/v1/textinput",
"name" : "abc",
"jcr:title" : "def"
},
"password-textinput" : {
"jcr:primaryType": "nt:unstructured",
"sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput",
"name" : "abc",
"jcr:title" : "def",
"fieldType": "password",
"minLength": 8,
"maxLength": 20,
"pattern": "^[a-zA-Z0-9]+$"
},
"password-textinput-toggle-disabled" : {
"jcr:primaryType": "nt:unstructured",
"sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput",
"name" : "abc",
"jcr:title" : "def",
"fieldType": "password",
"showHidePassword": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Component"
jcr:title="Adaptive Form Password Input"
jcr:description="Add a field to capture passwords, with an optional show/hide toggle."
sling:resourceSuperType="core/fd/components/form/passwordinput/v1/passwordinput"
componentGroup="Core Components Examples - Adaptive Form"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Password Input"
autocomplete="new-password"
fieldType="password"/>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<imagechoice/>
<numberinput/>
<panelcontainer/>
<passwordinput/>
<radiobutton/>
<resetbutton/>
<submitbutton/>
Expand Down
Loading
Loading