passbolt-styleguide 5.12.0 → 5.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/RELEASE_NOTES.md CHANGED
@@ -1,54 +1,3 @@
1
- # v5.12.0
1
+ # v5.12.1
2
2
 
3
- ## Browser extension
4
- ### Added
5
- - PB-51015 Add PIN code resource type in resourceTypeSchemasDefinition
6
- - PB-51016 Handle PIN code in resourceTypeEntity
7
- - PB-51017 Handle PIN code in resourceTypesCollection
8
- - PB-51019 PINCODE - 1.5 Create secretDataV5StandalonePinCodeEntity and add...
9
- - PB-51020 PINCODE - 1.6 Add pin code to ResourceEditCreateFormEnumerationTypes
10
- - PB-51023 Update resourceTypesFormEntity to handle the new pin code resource type
11
- - PB-51046 PINCODE - 3.2 Adapt ExternalResourceEntity to handle the pin code resource type schema
12
- - PB-51047 PINCODE - 3.3 Adapt ExportResourcesService to handle the mapping of pin code
13
- - PB-51048 PINCODE - 3.4 Adapt resourcesKdbxImportParser and to map pin code in case it exist to the correct resource types
14
- - PB-51049 Add PIN code icon to passboltDefaultResourceTypeIcons.data
15
- - PB-51050 Update DisplayContentTypesAllowedContentTypesAdministration to handle PIN code
16
- - PB-51051 Add the pin code resource type to DisplayResourceCreationMenu
17
- - PB-51052 Add PIN code in SelectResourceForm
18
- - PB-51053 Create the new pin code resource type form
19
- - PB-51054 Adapt OrchestrateResourceForm to handle the new AddResourcePinCode
20
- - PB-51055 Create the new DisplayResourceDetailsPinCode to display the pin code into detail
21
- - PB-51056 PINCODE - 3.5 Adapt resourcesKdbxExporter and to map pin code in case it exist to the correct resource types
22
- - PB-51073 PINCODE - 2.8 Add pin code into the grid
23
- - PB-51201 Fix notes-related issues
24
- - PB-51246 Add pin code to workspace create menu
25
-
26
- ### Fixed
27
- - PB-49888 The contents of Resource Creation Progress Dialog always shows Creating Password
28
- - PB-50166 Fix break vs continue bug in MoveResourcesService batch permission calculation
29
- - PB-50535 DisplayuserbadgeMenu should display attention required on page served by API if MFA is required
30
- - PB-50617 Add PingOne redirect URL field
31
- - PB-50945 Fix expired session when port is disconnected
32
- - PB-51012 Hide 'set expired' option for already expired resources
33
- - PB-51018 Tighten fields selectors to avoid false positives
34
- - PB-51077 Fix typo "susccessfully" to "successfully"
35
-
36
- ### Security
37
- - PB-50623 Fix GHSA-2328-f5f3-gj25 (HIGH)
38
- - PB-50877 Fix undici GHSA-f269-vfmq-vjvj - MEDIUM CVSS3.1
39
- - PB-50906 Fix svgo GHSA-xpqw-6gx7-v673 - HIGH CVSS3.1
40
- - PB-50907 Fix flatted GHSA-rf6f-7fwh-wjgh - HIGH CVSS4.0
41
- - PB-50908 Fix @xmldom/xmldom GHSA-wh4c-j3r5-mjhp - HIGH CVSS3.1
42
- - PB-50920 Upgrade webpack-cli
43
- - PB-50921 Upgrade web-ext
44
- - PB-51060 Fix protocol-buffers-schema GHSA-j452-xhg8-qg39 - MEDIUM CVSS3.1
45
- - PB-51151 Fix i18next-http-backend GHSA-r5fr-rjxr-66jc - MEDIUM CVSS3.1
46
- - PB-51152 Fix uuid GHSA-w5hq-g745-h8pq - MEDIUM CVSS3.1
47
- - PB-51170 Fix @xmldom/xmldom GHSA-2v35-w6hq-6mfw - HIGH CVSS4.0
48
- - PB-51179 Investigate and/or enforce package cool down mechanism with safe-chain or npm or both
49
-
50
- ### Maintenance
51
- - PB-50224 Add devcontainer to bext
52
- - PB-50301 removed GitLab CI definition as it's been moved to the ci-definitions repo
53
- - PB-50340 Small upgrade for picomatch (Medium)
54
- - PB-51086 keep notify expired session tests skipping
3
+ Fix various issues regarding pin code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passbolt-styleguide",
3
- "version": "5.12.0",
3
+ "version": "5.12.1",
4
4
  "license": "AGPL-3.0",
5
5
  "copyright": "Copyright 2023 Passbolt SA",
6
6
  "description": "Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.",
@@ -1,12 +1,12 @@
1
1
  <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M20 11.6807C20.4825 11.6807 20.8736 11.2896 20.8736 10.8072C20.8736 10.3247 20.4825 9.93359 20 9.93359C19.5175 9.93359 19.1264 10.3247 19.1264 10.8072C19.1264 11.2896 19.5175 11.6807 20 11.6807Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
3
- <path d="M26.115 11.6807C26.5974 11.6807 26.9885 11.2896 26.9885 10.8072C26.9885 10.3247 26.5974 9.93359 26.115 9.93359C25.6325 9.93359 25.2414 10.3247 25.2414 10.8072C25.2414 11.2896 25.6325 11.6807 26.115 11.6807Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
4
- <path d="M13.885 11.6807C14.3675 11.6807 14.7586 11.2896 14.7586 10.8072C14.7586 10.3247 14.3675 9.93359 13.885 9.93359C13.4026 9.93359 13.0115 10.3247 13.0115 10.8072C13.0115 11.2896 13.4026 11.6807 13.885 11.6807Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
5
- <path d="M20 17.795C20.4824 17.795 20.8736 17.4039 20.8736 16.9214C20.8736 16.439 20.4824 16.0479 20 16.0479C19.5175 16.0479 19.1264 16.439 19.1264 16.9214C19.1264 17.4039 19.5175 17.795 20 17.795Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
6
- <path d="M26.115 17.795C26.5974 17.795 26.9885 17.4039 26.9885 16.9214C26.9885 16.439 26.5974 16.0479 26.115 16.0479C25.6325 16.0479 25.2414 16.439 25.2414 16.9214C25.2414 17.4039 25.6325 17.795 26.115 17.795Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
7
- <path d="M13.885 17.795C14.3675 17.795 14.7586 17.4039 14.7586 16.9214C14.7586 16.439 14.3675 16.0479 13.885 16.0479C13.4026 16.0479 13.0115 16.439 13.0115 16.9214C13.0115 17.4039 13.4026 17.795 13.885 17.795Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
8
- <path d="M20 23.9112C20.4824 23.9112 20.8736 23.5201 20.8736 23.0376C20.8736 22.5552 20.4824 22.1641 20 22.1641C19.5175 22.1641 19.1264 22.5552 19.1264 23.0376C19.1264 23.5201 19.5175 23.9112 20 23.9112Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
9
- <path d="M26.115 23.9112C26.5974 23.9112 26.9885 23.5201 26.9885 23.0376C26.9885 22.5552 26.5974 22.1641 26.115 22.1641C25.6325 22.1641 25.2414 22.5552 25.2414 23.0376C25.2414 23.5201 25.6325 23.9112 26.115 23.9112Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
10
- <path d="M13.885 23.9112C14.3675 23.9112 14.7586 23.5201 14.7586 23.0376C14.7586 22.5552 14.3675 22.1641 13.885 22.1641C13.4026 22.1641 13.0115 22.5552 13.0115 23.0376C13.0115 23.5201 13.4026 23.9112 13.885 23.9112Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
11
- <path d="M20 30.0665C20.4825 30.0665 20.8736 29.6754 20.8736 29.1929C20.8736 28.7104 20.4825 28.3193 20 28.3193C19.5175 28.3193 19.1264 28.7104 19.1264 29.1929C19.1264 29.6754 19.5175 30.0665 20 30.0665Z" fill="white" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
2
+ <path d="M20 11.6807C20.4825 11.6807 20.8736 11.2896 20.8736 10.8072C20.8736 10.3247 20.4825 9.93359 20 9.93359C19.5175 9.93359 19.1264 10.3247 19.1264 10.8072C19.1264 11.2896 19.5175 11.6807 20 11.6807Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
3
+ <path d="M26.115 11.6807C26.5974 11.6807 26.9885 11.2896 26.9885 10.8072C26.9885 10.3247 26.5974 9.93359 26.115 9.93359C25.6325 9.93359 25.2414 10.3247 25.2414 10.8072C25.2414 11.2896 25.6325 11.6807 26.115 11.6807Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <path d="M13.885 11.6807C14.3675 11.6807 14.7586 11.2896 14.7586 10.8072C14.7586 10.3247 14.3675 9.93359 13.885 9.93359C13.4026 9.93359 13.0115 10.3247 13.0115 10.8072C13.0115 11.2896 13.4026 11.6807 13.885 11.6807Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M20 17.795C20.4824 17.795 20.8736 17.4039 20.8736 16.9214C20.8736 16.439 20.4824 16.0479 20 16.0479C19.5175 16.0479 19.1264 16.439 19.1264 16.9214C19.1264 17.4039 19.5175 17.795 20 17.795Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
6
+ <path d="M26.115 17.795C26.5974 17.795 26.9885 17.4039 26.9885 16.9214C26.9885 16.439 26.5974 16.0479 26.115 16.0479C25.6325 16.0479 25.2414 16.439 25.2414 16.9214C25.2414 17.4039 25.6325 17.795 26.115 17.795Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <path d="M13.885 17.795C14.3675 17.795 14.7586 17.4039 14.7586 16.9214C14.7586 16.439 14.3675 16.0479 13.885 16.0479C13.4026 16.0479 13.0115 16.439 13.0115 16.9214C13.0115 17.4039 13.4026 17.795 13.885 17.795Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M20 23.9112C20.4824 23.9112 20.8736 23.5201 20.8736 23.0376C20.8736 22.5552 20.4824 22.1641 20 22.1641C19.5175 22.1641 19.1264 22.5552 19.1264 23.0376C19.1264 23.5201 19.5175 23.9112 20 23.9112Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
9
+ <path d="M26.115 23.9112C26.5974 23.9112 26.9885 23.5201 26.9885 23.0376C26.9885 22.5552 26.5974 22.1641 26.115 22.1641C25.6325 22.1641 25.2414 22.5552 25.2414 23.0376C25.2414 23.5201 25.6325 23.9112 26.115 23.9112Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
10
+ <path d="M13.885 23.9112C14.3675 23.9112 14.7586 23.5201 14.7586 23.0376C14.7586 22.5552 14.3675 22.1641 13.885 22.1641C13.4026 22.1641 13.0115 22.5552 13.0115 23.0376C13.0115 23.5201 13.4026 23.9112 13.885 23.9112Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
11
+ <path d="M20 30.0665C20.4825 30.0665 20.8736 29.6754 20.8736 29.1929C20.8736 28.7104 20.4825 28.3193 20 28.3193C19.5175 28.3193 19.1264 28.7104 19.1264 29.1929C19.1264 29.6754 19.5175 30.0665 20 30.0665Z" fill="var(--icon-color)" stroke="var(--icon-color)" stroke-width="var(--icon-stroke-width)" stroke-linecap="round" stroke-linejoin="round"/>
12
12
  </svg>
@@ -22,6 +22,7 @@ import { PinCodeGenerator, PIN_CODE_LENGTH_CONSTRAINTS } from "../../../../share
22
22
  import DiceSVG from "../../../../img/svg/dice.svg";
23
23
  import CaretDownSVG from "../../../../img/svg/caret_down.svg";
24
24
  import CaretRightSVG from "../../../../img/svg/caret_right.svg";
25
+ import AttentionSVG from "../../../../img/svg/attention.svg";
25
26
 
26
27
  class AddResourcePinCode extends Component {
27
28
  constructor(props) {
@@ -158,12 +159,22 @@ class AddResourcePinCode extends Component {
158
159
  }
159
160
 
160
161
  /**
161
- * Does the pin code length exceeds the max pin code length?
162
+ * Checks if the pin code length exceeds the max pin code length
162
163
  */
163
164
  isMaxLengthError() {
164
165
  return this.props.errors?.details?.secret?.hasError("pin_code", "maxLength");
165
166
  }
166
167
 
168
+ /**
169
+ * Checks if there is a max length warning for a specific property.
170
+ * @param {string} propName The name of the property to check.
171
+ * @param {string} association The association name.
172
+ * @returns {boolean}
173
+ */
174
+ isMaxLengthWarnings(propName, association) {
175
+ return !this.isMaxLengthError() && this.props.warnings?.hasError(`${association}.${propName}`, "maxLength");
176
+ }
177
+
167
178
  /**
168
179
  * Focus the first erroring field, if any.
169
180
  */
@@ -192,6 +203,7 @@ class AddResourcePinCode extends Component {
192
203
  >
193
204
  <label htmlFor="resource-pin-code">
194
205
  <Trans>Code</Trans>
206
+ {this.isMaxLengthWarnings("pin_code", "secret") && <AttentionSVG className="attention-required" />}
195
207
  </label>
196
208
  <div className="password-button-inline">
197
209
  <Password
@@ -200,6 +212,7 @@ class AddResourcePinCode extends Component {
200
212
  autoComplete="off"
201
213
  placeholder={this.translate("Pin code")}
202
214
  preview={true}
215
+ maxLength={PIN_CODE_LENGTH_CONSTRAINTS.MAX}
203
216
  value={this.props.resource?.secret?.pin_code}
204
217
  onChange={this.handlePinCodeInputChange}
205
218
  inputRef={this.pinCodeInputRef}
@@ -215,6 +228,14 @@ class AddResourcePinCode extends Component {
215
228
  </button>
216
229
  </div>
217
230
  {this.hasFieldPinCodeError() && <div className="pin-code error-message">{this.pinCodeErrorMessage}</div>}
231
+ {this.isMaxLengthWarnings("pin_code", "secret") && (
232
+ <div className="pin-code warning-message">
233
+ <strong>
234
+ <Trans>Warning:</Trans>
235
+ </strong>{" "}
236
+ <Trans>this is the maximum size for this field, make sure your data was not truncated.</Trans>
237
+ </div>
238
+ )}
218
239
  </div>
219
240
  </div>
220
241
  <div className="additional-information">
@@ -61,3 +61,14 @@ export function pinCodeErrors(rule) {
61
61
 
62
62
  return errors;
63
63
  }
64
+
65
+ /**
66
+ * Build warnings prop with a single `secret.pin_code` rule violation.
67
+ * @param {string} rule e.g. "maxLength"
68
+ * @returns {EntityValidationError}
69
+ */
70
+ export function pinCodeWarnings(rule) {
71
+ const warnings = new EntityValidationError();
72
+ warnings.addError("secret.pin_code", rule, `pin_code ${rule} warning`);
73
+ return warnings;
74
+ }
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import AddResourcePinCodePage from "./AddResourcePinCode.test.page";
16
- import { defaultProps, defaultPropsWithValue, pinCodeErrors } from "./AddResourcePinCode.test.data";
16
+ import { defaultProps, defaultPropsWithValue, pinCodeErrors, pinCodeWarnings } from "./AddResourcePinCode.test.data";
17
17
  import { PinCodeGenerator } from "../../../../shared/lib/SecretGenerator/PinCodeGenerator";
18
18
 
19
19
  beforeEach(() => {
@@ -230,4 +230,36 @@ describe("AddResourcePinCode", () => {
230
230
  expect(page.pinCodeErrorMessage.textContent).toEqual("The PIN code is required.");
231
231
  });
232
232
  });
233
+
234
+ describe("Warnings", () => {
235
+ it("renders the maxLength warning message and the attention icon when warnings include secret.pin_code maxLength.", () => {
236
+ expect.assertions(2);
237
+
238
+ const props = defaultProps({
239
+ warnings: pinCodeWarnings("maxLength"),
240
+ resource: { secret: { pin_code: "123456789012" } },
241
+ });
242
+ const page = new AddResourcePinCodePage(props);
243
+
244
+ expect(page.pinCodeWarningMessage.textContent).toEqual(
245
+ "Warning: this is the maximum size for this field, make sure your data was not truncated.",
246
+ );
247
+ expect(page.attentionIcon).not.toBeNull();
248
+ });
249
+
250
+ it("suppresses the maxLength warning when a maxLength error is also present.", () => {
251
+ expect.assertions(3);
252
+
253
+ const props = defaultProps({
254
+ errors: pinCodeErrors("maxLength"),
255
+ warnings: pinCodeWarnings("maxLength"),
256
+ resource: { secret: { pin_code: "1234567890123" } },
257
+ });
258
+ const page = new AddResourcePinCodePage(props);
259
+
260
+ expect(page.pinCodeErrorMessage.textContent).toEqual("The PIN code cannot exceed 12 digits.");
261
+ expect(page.pinCodeWarningMessage).toBeNull();
262
+ expect(page.attentionIcon).toBeNull();
263
+ });
264
+ });
233
265
  });
@@ -113,6 +113,34 @@ class FilterResourcesByFavoritePage extends React.Component {
113
113
  return search ? filterResourcesBySearch(favoriteResources, search, BROWSED_RESOURCES_LIMIT) : favoriteResources;
114
114
  });
115
115
 
116
+ /**
117
+ * Is password resource
118
+ * @param {string} resourceTypeId
119
+ * @returns {boolean}
120
+ */
121
+ isPasswordResource(resourceTypeId) {
122
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
123
+ }
124
+
125
+ /**
126
+ * Is OTP resource
127
+ * @param {string} resourceTypeId
128
+ * @returns {boolean}
129
+ */
130
+ isOTPResource(resourceTypeId) {
131
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
132
+ }
133
+
134
+ /**
135
+ * Get resource filtered by resource type to have only resource with password and totp
136
+ * @return {Array}
137
+ */
138
+ get resourcesFilterByResourceTypePasswordAndTotp() {
139
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
140
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
141
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
142
+ }
143
+
116
144
  /**
117
145
  * Has metadata types settings
118
146
  * @returns {boolean}
@@ -161,7 +189,10 @@ class FilterResourcesByFavoritePage extends React.Component {
161
189
  let browsedResources;
162
190
 
163
191
  if (isReady) {
164
- browsedResources = this.filterSearchedResources(this.props.resources, this.props.context.search);
192
+ browsedResources = this.filterSearchedResources(
193
+ this.resourcesFilterByResourceTypePasswordAndTotp,
194
+ this.props.context.search,
195
+ );
165
196
  }
166
197
 
167
198
  /**
@@ -222,6 +222,34 @@ class FilterResourcesByGroupPage extends React.Component {
222
222
  search ? this.filterGroupsBySearch(groups, search, BROWSED_GROUPS_LIMIT) : groups.slice(0, BROWSED_GROUPS_LIMIT),
223
223
  );
224
224
 
225
+ /**
226
+ * Is password resource
227
+ * @param {string} resourceTypeId
228
+ * @returns {boolean}
229
+ */
230
+ isPasswordResource(resourceTypeId) {
231
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
232
+ }
233
+
234
+ /**
235
+ * Is OTP resource
236
+ * @param {string} resourceTypeId
237
+ * @returns {boolean}
238
+ */
239
+ isOTPResource(resourceTypeId) {
240
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
241
+ }
242
+
243
+ /**
244
+ * Get resource filtered by resource type to have only resource with password and totp
245
+ * @return {Array}
246
+ */
247
+ get resourcesFilterByResourceTypePasswordAndTotp() {
248
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
249
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
250
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
251
+ }
252
+
225
253
  /**
226
254
  * Has metadata types settings
227
255
  * @returns {boolean}
@@ -278,7 +306,7 @@ class FilterResourcesByGroupPage extends React.Component {
278
306
  isReady = this.props.resources !== null && this.state.groupResourceIds !== null;
279
307
  if (isReady) {
280
308
  browsedResources = this.filterSearchedResources(
281
- this.props.resources,
309
+ this.resourcesFilterByResourceTypePasswordAndTotp,
282
310
  this.state.groupResourceIds,
283
311
  this.props.context.search,
284
312
  );
@@ -121,6 +121,34 @@ class FilterResourcesByItemsIOwnPage extends React.Component {
121
121
  return search ? filterResourcesBySearch(ownedResources, search, BROWSED_RESOURCES_LIMIT) : ownedResources;
122
122
  });
123
123
 
124
+ /**
125
+ * Is password resource
126
+ * @param {string} resourceTypeId
127
+ * @returns {boolean}
128
+ */
129
+ isPasswordResource(resourceTypeId) {
130
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
131
+ }
132
+
133
+ /**
134
+ * Is OTP resource
135
+ * @param {string} resourceTypeId
136
+ * @returns {boolean}
137
+ */
138
+ isOTPResource(resourceTypeId) {
139
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
140
+ }
141
+
142
+ /**
143
+ * Get resource filtered by resource type to have only resource with password and totp
144
+ * @return {Array}
145
+ */
146
+ get resourcesFilterByResourceTypePasswordAndTotp() {
147
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
148
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
149
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
150
+ }
151
+
124
152
  /**
125
153
  * Has metadata types settings
126
154
  * @returns {boolean}
@@ -173,7 +201,10 @@ class FilterResourcesByItemsIOwnPage extends React.Component {
173
201
  let browsedResources;
174
202
 
175
203
  if (isReady) {
176
- browsedResources = this.filterSearchedResources(this.props.resources, this.props.context.search);
204
+ browsedResources = this.filterSearchedResources(
205
+ this.resourcesFilterByResourceTypePasswordAndTotp,
206
+ this.props.context.search,
207
+ );
177
208
  }
178
209
 
179
210
  return (
@@ -68,13 +68,32 @@ class FilterResourcesByRecentlyModifiedPage extends React.Component {
68
68
  this.props.history.push(`/webAccessibleResources/quickaccess/resources/view/${resourceId}`);
69
69
  }
70
70
 
71
- async findAndLoadResources() {
72
- const storageData = await this.props.context.storage.local.get(["resources"]);
73
- if (storageData.resources) {
74
- const resources = storageData.resources;
75
- this.sortResourcesByModifiedDesc(resources);
76
- this.setState({ resources });
77
- }
71
+ /**
72
+ * Is password resource
73
+ * @param {string} resourceTypeId
74
+ * @returns {boolean}
75
+ */
76
+ isPasswordResource(resourceTypeId) {
77
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
78
+ }
79
+
80
+ /**
81
+ * Is OTP resource
82
+ * @param {string} resourceTypeId
83
+ * @returns {boolean}
84
+ */
85
+ isOTPResource(resourceTypeId) {
86
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
87
+ }
88
+
89
+ /**
90
+ * Get resource filtered by resource type to have only resource with password and totp
91
+ * @return {Array}
92
+ */
93
+ get resourcesFilterByResourceTypePasswordAndTotp() {
94
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
95
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
96
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
78
97
  }
79
98
 
80
99
  /**
@@ -82,7 +101,7 @@ class FilterResourcesByRecentlyModifiedPage extends React.Component {
82
101
  * @returns {Array}
83
102
  */
84
103
  sortResourcesByModifiedDesc() {
85
- return this.props.resources.sort(
104
+ return this.resourcesFilterByResourceTypePasswordAndTotp.sort(
86
105
  (resource1, resource2) => new Date(resource2.modified) - new Date(resource1.modified),
87
106
  );
88
107
  }
@@ -115,6 +115,34 @@ class FilterResourcesBySharedWithMePage extends React.Component {
115
115
  : resourcesSharedWithMe;
116
116
  });
117
117
 
118
+ /**
119
+ * Is password resource
120
+ * @param {string} resourceTypeId
121
+ * @returns {boolean}
122
+ */
123
+ isPasswordResource(resourceTypeId) {
124
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
125
+ }
126
+
127
+ /**
128
+ * Is OTP resource
129
+ * @param {string} resourceTypeId
130
+ * @returns {boolean}
131
+ */
132
+ isOTPResource(resourceTypeId) {
133
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
134
+ }
135
+
136
+ /**
137
+ * Get resource filtered by resource type to have only resource with password and totp
138
+ * @return {Array}
139
+ */
140
+ get resourcesFilterByResourceTypePasswordAndTotp() {
141
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
142
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
143
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
144
+ }
145
+
118
146
  /**
119
147
  * Has metadata types settings
120
148
  * @returns {boolean}
@@ -167,7 +195,10 @@ class FilterResourcesBySharedWithMePage extends React.Component {
167
195
  let browsedResources;
168
196
 
169
197
  if (isReady) {
170
- browsedResources = this.filterSearchedResources(this.props.resources, this.props.context.search);
198
+ browsedResources = this.filterSearchedResources(
199
+ this.resourcesFilterByResourceTypePasswordAndTotp,
200
+ this.props.context.search,
201
+ );
171
202
  }
172
203
 
173
204
  return (
@@ -165,6 +165,34 @@ class FilterResourcesByTagPage extends React.Component {
165
165
  : resourcesMatchingTag;
166
166
  });
167
167
 
168
+ /**
169
+ * Is password resource
170
+ * @param {string} resourceTypeId
171
+ * @returns {boolean}
172
+ */
173
+ isPasswordResource(resourceTypeId) {
174
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
175
+ }
176
+
177
+ /**
178
+ * Is OTP resource
179
+ * @param {string} resourceTypeId
180
+ * @returns {boolean}
181
+ */
182
+ isOTPResource(resourceTypeId) {
183
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
184
+ }
185
+
186
+ /**
187
+ * Get resource filtered by resource type to have only resource with password and totp
188
+ * @return {Array}
189
+ */
190
+ get resourcesFilterByResourceTypePasswordAndTotp() {
191
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
192
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
193
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
194
+ }
195
+
168
196
  /**
169
197
  * Get the tags to display
170
198
  * @param {Array<Object>} tags the tag list to filter from
@@ -251,14 +279,11 @@ class FilterResourcesByTagPage extends React.Component {
251
279
  let browsedTags, browsedResources;
252
280
 
253
281
  if (isReady) {
282
+ const resources = this.resourcesFilterByResourceTypePasswordAndTotp;
254
283
  if (listTagsOnly) {
255
- browsedTags = this.filterSearchedTags(this.props.resources, this.props.context.search);
284
+ browsedTags = this.filterSearchedTags(resources, this.props.context.search);
256
285
  } else {
257
- browsedResources = this.filterSearchedResources(
258
- this.props.resources,
259
- this.props.context.search,
260
- selectedTag?.id,
261
- );
286
+ browsedResources = this.filterSearchedResources(resources, this.props.context.search, selectedTag?.id);
262
287
  }
263
288
  }
264
289
 
@@ -133,10 +133,7 @@ class HomePage extends React.Component {
133
133
 
134
134
  for (const i in resources) {
135
135
  const resource = resources[i];
136
- if (
137
- (this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id)) &&
138
- CanSuggestService.canSuggestUris(activeTabUrl, resource.metadata.uris)
139
- ) {
136
+ if (CanSuggestService.canSuggestUris(activeTabUrl, resource.metadata.uris)) {
140
137
  suggestedResources.push(resource);
141
138
  if (suggestedResources.length === SUGGESTED_RESOURCES_LIMIT) {
142
139
  break;
@@ -195,18 +192,30 @@ class HomePage extends React.Component {
195
192
 
196
193
  /**
197
194
  * Is password resource
195
+ * @param {string} resourceTypeId
198
196
  * @returns {boolean}
199
197
  */
200
- isPasswordResource(resourceId) {
201
- return this.props.resourceTypes?.getFirstById(resourceId)?.hasPassword();
198
+ isPasswordResource(resourceTypeId) {
199
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasPassword();
202
200
  }
203
201
 
204
202
  /**
205
203
  * Is OTP resource
204
+ * @param {string} resourceTypeId
206
205
  * @returns {boolean}
207
206
  */
208
- isOTPResource(resourceId) {
209
- return this.props.resourceTypes?.getFirstById(resourceId)?.hasTotp();
207
+ isOTPResource(resourceTypeId) {
208
+ return this.props.resourceTypes?.getFirstById(resourceTypeId)?.hasTotp();
209
+ }
210
+
211
+ /**
212
+ * Get resource filtered by resource type to have only resource with password and totp
213
+ * @return {Array}
214
+ */
215
+ get resourcesFilterByResourceTypePasswordAndTotp() {
216
+ const keepOnlyResourcesPasswordAndTotp = (resource) =>
217
+ this.isPasswordResource(resource.resource_type_id) || this.isOTPResource(resource.resource_type_id);
218
+ return this.props.resources.filter(keepOnlyResourcesPasswordAndTotp);
210
219
  }
211
220
 
212
221
  /**
@@ -266,8 +275,9 @@ class HomePage extends React.Component {
266
275
  let browsedResources, suggestedResources;
267
276
 
268
277
  if (isReady) {
269
- browsedResources = this.filterSearchedResources(this.props.resources, this.props.context.search);
270
- suggestedResources = this.filterSuggestedResources(this.props.resources, this.state.activeTabUrl);
278
+ const resources = this.resourcesFilterByResourceTypePasswordAndTotp;
279
+ browsedResources = this.filterSearchedResources(resources, this.props.context.search);
280
+ suggestedResources = this.filterSuggestedResources(resources, this.state.activeTabUrl);
271
281
  }
272
282
 
273
283
  return (
@@ -184,7 +184,7 @@ class Password extends Component {
184
184
  <input
185
185
  id={this.props.id}
186
186
  name={this.props.name}
187
- maxLength="4096"
187
+ maxLength={this.props.maxLength}
188
188
  placeholder={this.props.placeholder}
189
189
  type={this.state.viewPassword && !this.props.disabled ? "text" : "password"}
190
190
  onKeyUp={this.props.onKeyUp}
@@ -232,6 +232,7 @@ Password.defaultProps = {
232
232
  id: "",
233
233
  name: "",
234
234
  autoComplete: "off",
235
+ maxLength: 4096,
235
236
  };
236
237
 
237
238
  Password.propTypes = {
@@ -252,6 +253,7 @@ Password.propTypes = {
252
253
  backgroundColor: PropTypes.string,
253
254
  textColor: PropTypes.string,
254
255
  }), // The securityTokenDto
256
+ maxLength: PropTypes.number, // The max length of the input
255
257
  };
256
258
 
257
259
  export default withTranslation("common")(Password);
@@ -102,4 +102,30 @@ describe("As LU I should see the user confirm passphrase page", () => {
102
102
  expect(page.isObfuscated).toBeTruthy();
103
103
  });
104
104
  });
105
+
106
+ describe("As LU I cannot exceed the password max length", () => {
107
+ it("maxLength is 4096 when no maxLength prop is provided", () => {
108
+ expect.assertions(1);
109
+ const props = defaultProps();
110
+ const page = new PasswordPage(props);
111
+ expect(page.passwordInput.maxLength).toBe(4096);
112
+ });
113
+
114
+ it("forwards a custom maxLength prop to the input element", () => {
115
+ expect.assertions(1);
116
+ const props = defaultProps({ maxLength: 12 });
117
+ const page = new PasswordPage(props);
118
+ expect(page.passwordInput.maxLength).toBe(12);
119
+ });
120
+
121
+ it("reflects an updated maxLength when the prop changes", () => {
122
+ expect.assertions(2);
123
+ const props = defaultProps({ maxLength: 8 });
124
+ const page = new PasswordPage(props);
125
+ expect(page.passwordInput.maxLength).toBe(8);
126
+
127
+ page.rerender(defaultProps({ maxLength: 16 }));
128
+ expect(page.passwordInput.maxLength).toBe(16);
129
+ });
130
+ });
105
131
  });