passbolt-browser-extension 5.10.4 → 5.11.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.
Files changed (45) hide show
  1. package/.gitlab-ci/jobs/publish.yml +11 -1
  2. package/.gitlab-ci/scripts/bin/publish_npm.sh +1 -5
  3. package/.pre-commit-config.yaml +5 -0
  4. package/CHANGELOG.md +49 -0
  5. package/RELEASE_NOTES.md +93 -3
  6. package/build-safari-extension/Passbolt-Safari-Extension/Passbolt - password manager Extension/services/fetch/fetchService.swift +90 -2
  7. package/eslint.config.mjs +0 -6
  8. package/package.json +11 -13
  9. package/src/all/background_page/controller/auth/authLogoutController.js +11 -2
  10. package/src/all/background_page/controller/auth/authLogoutController.test.js +14 -5
  11. package/src/all/background_page/controller/group/groupCreateController.js +58 -0
  12. package/src/all/background_page/controller/group/groupCreateController.test.js +67 -0
  13. package/src/all/background_page/controller/tab/openResourceUriTabController.js +60 -0
  14. package/src/all/background_page/controller/tab/openResourceUriTabController.test.js +108 -0
  15. package/src/all/background_page/error/timeoutError.js +23 -0
  16. package/src/all/background_page/event/appEvents.js +12 -0
  17. package/src/all/background_page/event/groupEvents.js +3 -10
  18. package/src/all/background_page/model/entity/resource/resourceEntity.js +1 -1
  19. package/src/all/background_page/model/entity/sso/ssoLoginUrlEntity.js +2 -0
  20. package/src/all/background_page/model/entity/sso/ssoLoginUrlEntity.test.js +2 -0
  21. package/src/all/background_page/model/group/groupModel.js +0 -15
  22. package/src/all/background_page/service/api/group/groupApiService.js +6 -3
  23. package/src/all/background_page/service/auth/authVerifyLoginChallengeService.test.js +1 -1
  24. package/src/all/background_page/service/auth/decryptUserAuthTokenService.js +2 -2
  25. package/src/all/background_page/service/group/createGroupService.js +48 -0
  26. package/src/all/background_page/service/group/createGroupService.test.js +68 -0
  27. package/src/all/background_page/service/tab/tabService.js +37 -5
  28. package/src/all/background_page/service/tab/tabService.test.js +132 -1
  29. package/src/all/background_page/utils/format/formDataUtils.js +0 -16
  30. package/src/all/background_page/utils/format/formDataUtils.test.data.js +0 -7
  31. package/src/all/background_page/utils/format/formDataUtils.test.js +1 -39
  32. package/src/all/background_page/utils/promise/promiseTimeoutService.js +3 -1
  33. package/src/all/background_page/vendors/locutus/stripslashes.js +25 -0
  34. package/src/all/background_page/vendors/locutus/stripslashes.test.js +64 -0
  35. package/src/all/background_page/vendors/locutus/urldecode.js +21 -0
  36. package/src/all/background_page/vendors/locutus/urldecode.test.js +64 -0
  37. package/src/all/background_page/vendors/locutus/urlencode.test.data.js +23 -0
  38. package/src/all/contentScripts/js/app/App.js +11 -0
  39. package/src/all/contentScripts/js/app/Login.js +11 -0
  40. package/src/chrome/manifest.json +1 -1
  41. package/src/chrome-mv3/manifest.json +1 -1
  42. package/src/firefox/manifest.json +5 -2
  43. package/src/safari/common/polyfill/fetchPolyfill.js +1 -1
  44. package/src/safari/common/polyfill/fetchPolyfill.test.js +8 -4
  45. package/src/safari/manifest.json +1 -1
@@ -39,7 +39,17 @@ publish-edge:
39
39
 
40
40
  publish-to-npmjs:
41
41
  stage: publish
42
- image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:22
42
+ image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:24
43
+ id_tokens:
44
+ NPM_ID_TOKEN:
45
+ aud: "npm:registry.npmjs.org"
46
+ SIGSTORE_ID_TOKEN:
47
+ aud: sigstore
48
+ when: manual
49
+ resource_group: publish/npm
50
+ environment:
51
+ name: publish/npm
52
+ url: https://www.npmjs.com/package/passbolt-browser-extension
43
53
  rules:
44
54
  - if: "$CI_COMMIT_TAG"
45
55
  script:
@@ -9,10 +9,6 @@ CI_SCRIPTS_DIR=$(dirname "$0")/..
9
9
  # shellcheck source=.gitlab-ci/scripts/lib/version-check.sh
10
10
  source "$CI_SCRIPTS_DIR"/lib/version-check.sh
11
11
 
12
- echo //registry.npmjs.org/:_authToken="$NPM_PUBLISH_TOKEN" > .npmrc
13
- echo email="$NPM_PUBLISH_EMAIL" >> .npmrc
14
- echo always-auth=true >> .npmrc
15
-
16
12
  if is_release_candidate "$CI_COMMIT_TAG"; then
17
13
  npm publish --tag next
18
14
  elif is_release_alpha "$CI_COMMIT_TAG"; then
@@ -23,4 +19,4 @@ elif is_stable "$CI_COMMIT_TAG"; then
23
19
  npm publish
24
20
  else
25
21
  echo "The tag format is not supported"
26
- fi
22
+ fi
@@ -0,0 +1,5 @@
1
+ repos:
2
+ - repo: https://github.com/gitleaks/gitleaks
3
+ rev: v8.30.0 # v8.30.0
4
+ hooks:
5
+ - id: gitleaks
package/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [5.11.0] - 2026-04-07
8
+ ### Added
9
+ - PB-49733 SMTP-OAUTH - WP2.1 Update SmtpSettingsService to SmtpSettingsApiService
10
+ - PB-49734 SMTP-OAUTH - WP1.1 Create the SmtpSettingsEntity
11
+ - PB-49737 SMTP-OAUTH - WP2.2 Update SmtpTestSettingsService to SmtpTestSettingsApiService
12
+ - PB-49738 SMTP-OAUTH - WP2.3 Split SmtpSettingsModel to new architecture pattern
13
+ - PB-49739 SMTP-OAUTH - WP2.4 Split SmtpTestSettingsModel to new architecture pattern
14
+ - PB-49740 SMTP-OAUTH - WP3.1 Adapt context with the new SMTP entities
15
+ - PB-49741 SMTP-OAUTH - WP3.2 Adapt ManageSmtpAdministationSettings to handle the new OAUTH fields
16
+ - PB-50058 OAuth SMTP: add the new styleguide to backend
17
+ - PB-50135 SSO with PingOne
18
+ - PB-50157 Enable avatar upload for Safari
19
+ - PB-50254 SCIM-WP1.2 Adapt form to handle the new date field and display warning message when expired
20
+ - PB-50263 Add a username selector compatible with ProxMox
21
+
22
+ ### Fixed
23
+ - PB-46678 Fix quickaccess closing issue on Safari
24
+ - PB-49237 DisplayUserBadgeMenu attention required should be displayed on Administration page served by API
25
+ - PB-49287 When deleting a user, the URL must changed not to reference the deleted user id
26
+ - PB-49476 Fix autofill for websites using identifier as name for username field
27
+ - PB-49619 Fix username input field selector for OVH
28
+ - PB-49849 Sync generator password policy with the administration after save
29
+ - PB-49866 Fix the expiry column in the resource workspace grid is not present anymore
30
+ - PB-49882 Fix username input field selector for Supermicro IPMI WebUI
31
+ - PB-50023 Fix multifield OTP selector matching hidden inputs
32
+ - PB-50077 Fix React router issue that reloads the page unexpectedly
33
+ - PB-50177 Fix autofill issues for two websites
34
+
35
+ ### Maintenance
36
+ - PB-49129 Delegate tab opening to service worker in order to send all cookie via Safari
37
+ - PB-49459 Timeouts not cleared properly when filtering resources/users grids by keywords
38
+ - PB-49705 Add missing TOTP unit tests
39
+ - PB-49730 Setup an environment for publishing to npmjs registry
40
+ - PB-49998 Add required `data_collection_permissions` for Firefox and set it to `none`
41
+ - PB-50013 Make Safari download custom avatars
42
+ - PB-50118 Major upgrade for locutus (Critical) - passbolt-browser-extension
43
+ - PB-50158 Add Safari enablement through a feature flag
44
+ - PB-50200 Move the logic of passbolt.groups.create to GroupCreateController
45
+ - PB-50201 Update group create call in groupApiService to contain "my_group_user" as urlOptions
46
+ - PB-50202 Add supported formats documentation link in export dialog
47
+ - PB-50225 Create a CreateGroupService.js file and move the create call to api service inside it
48
+ - PB-50338 - Fix phantom @babel/preset-react
49
+
50
+ ### Security
51
+ - PB-49608 Fix ReDoS vulnerability in PGP armor regex validation
52
+ - PB-50271 Fix GHSA-25h7-pfq9-p65f - HIGH CVSS3.1
53
+ - PB-50272 Fix brace-expansion vulnerabilities
54
+
55
+
7
56
  ## [5.9.0] - 2026-01-21
8
57
  ### Fixed
9
58
  - PB-43511 Display the "Migrate metadata" admin home page card icon with a 2px stroke width
package/RELEASE_NOTES.md CHANGED
@@ -1,7 +1,97 @@
1
- Passbolt 5.10.4 is a hotfix release. It resolves a bug on the resources workspace where filtering resources by URIs would cause the application to crash, a regression surfaced by the latest upgrade of the UI framework.
1
+ Passbolt 5.11.0 introduces improvements to enterprise authentication and integration capabilities, alongside continued security hardening.
2
2
 
3
- If you encountered the issue before updating, resetting your column customization in the workspace will restore normal behavior.
3
+ This release adds support for OAuth-based SMTP authentication for Microsoft Exchange Online and expands SSO coverage with PingOne. It also includes the finalisation of SCIM following external audit fixes.
4
+
5
+ ## SMTP OAuth support for Microsoft Exchange Online
6
+
7
+ Passbolt 5.11 introduces OAuth 2.0 support for SMTP with Microsoft Exchange Online, replacing legacy username/password authentication.
8
+
9
+ Administrators can configure the OAuth (Client Credentials) method by registering an application in Microsoft Entra ID and providing the required tenant ID, client ID, client secret, and service account email.
10
+
11
+ At runtime, Passbolt retrieves short-lived access tokens to authenticate SMTP connections without user interaction, improving security and aligning with modern authentication standards.
12
+
13
+
14
+ ## PingOne SSO support (Passbolt Pro)
15
+
16
+ Passbolt 5.11 adds support for PingOne as a new SSO provider, enabling organisations to authenticate users via their existing Ping Identity infrastructure.
17
+
18
+ The integration is based on OpenID Connect (OIDC) using the Authorization Code flow, with Passbolt delegating authentication to PingOne and receiving a verified user identity via ID tokens.
19
+
20
+ Administrators can configure PingOne from the SSO settings using the required environment ID, client ID, client secret, and base URL, with a dry-run option available to validate the setup before activation. Once enabled, users are redirected to PingOne for authentication and seamlessly logged into Passbolt, including during account recovery.
21
+
22
+ This addition expands Passbolt’s SSO coverage for enterprise environments and removes a key adoption blocker for organisations standardised on Ping Identity.
23
+
24
+ ## SCIM: audit fixes and general availability (Passbolt Pro)
25
+
26
+ Following the external security audit conducted by Cure53, this release includes fixes addressing the identified findings in the SCIM provisioning implementation.
27
+
28
+ With these changes, SCIM is now considered stable and exits beta.
29
+
30
+ The audit-driven improvements strengthen validation, error handling, and overall robustness of the provisioning flow. SCIM is now ready for production use in environments requiring automated user lifecycle management.
31
+
32
+ ## Security improvements
33
+
34
+ This release continues the ongoing security hardening effort across the platform.
35
+
36
+ In addition to the SCIM audit fixes, improvements have been made to align with external audit recommendations and reduce potential attack surface in authentication and integration layers.
37
+
38
+ ## Maintenance & performance
39
+
40
+ This release includes general performance improvements, particularly around background job processing and email delivery workflows.
41
+
42
+ Email-related operations are now more efficient and better distributed, reducing bottlenecks in high-load environments.
43
+
44
+ As usual, additional optimisations are already in progress for upcoming releases.
45
+
46
+ ## Conclusion
47
+
48
+ As usual, the release is also packed with additional improvements and fixes. Check out the changelog to learn more.
49
+
50
+ Many thanks to everyone who provided feedback, reported bugs, and contributed to making passbolt better!
51
+
52
+ ### Added
53
+ - PB-49733 SMTP-OAUTH - WP2.1 Update SmtpSettingsService to SmtpSettingsApiService
54
+ - PB-49734 SMTP-OAUTH - WP1.1 Create the SmtpSettingsEntity
55
+ - PB-49737 SMTP-OAUTH - WP2.2 Update SmtpTestSettingsService to SmtpTestSettingsApiService
56
+ - PB-49738 SMTP-OAUTH - WP2.3 Split SmtpSettingsModel to new architecture pattern
57
+ - PB-49739 SMTP-OAUTH - WP2.4 Split SmtpTestSettingsModel to new architecture pattern
58
+ - PB-49740 SMTP-OAUTH - WP3.1 Adapt context with the new SMTP entities
59
+ - PB-49741 SMTP-OAUTH - WP3.2 Adapt ManageSmtpAdministationSettings to handle the new OAUTH fields
60
+ - PB-50058 OAuth SMTP: add the new styleguide to backend
61
+ - PB-50135 SSO with PingOne
62
+ - PB-50157 Enable avatar upload for Safari
63
+ - PB-50254 SCIM-WP1.2 Adapt form to handle the new date field and display warning message when expired
64
+ - PB-50263 Add a username selector compatible with ProxMox
4
65
 
5
66
  ### Fixed
6
- - PB-50034 As a user I should be able to sort by uris
67
+ - PB-46678 Fix quickaccess closing issue on Safari
68
+ - PB-49237 DisplayUserBadgeMenu attention required should be displayed on Administration page served by API
69
+ - PB-49287 When deleting a user, the URL must changed not to reference the deleted user id
70
+ - PB-49476 Fix autofill for websites using identifier as name for username field
71
+ - PB-49619 Fix username input field selector for OVH
72
+ - PB-49849 Sync generator password policy with the administration after save
73
+ - PB-49866 Fix the expiry column in the resource workspace grid is not present anymore
74
+ - PB-49882 Fix username input field selector for Supermicro IPMI WebUI
75
+ - PB-50023 Fix multifield OTP selector matching hidden inputs
76
+ - PB-50077 Fix React router issue that reloads the page unexpectedly
77
+ - PB-50177 Fix autofill issues for two websites
78
+
79
+ ### Maintenance
80
+ - PB-49129 Delegate tab opening to service worker in order to send all cookie via Safari
7
81
  - PB-49459 Timeouts not cleared properly when filtering resources/users grids by keywords
82
+ - PB-49705 Add missing TOTP unit tests
83
+ - PB-49730 Setup an environment for publishing to npmjs registry
84
+ - PB-49998 Add required `data_collection_permissions` for Firefox and set it to `none`
85
+ - PB-50013 Make Safari download custom avatars
86
+ - PB-50118 Major upgrade for locutus (Critical) - passbolt-browser-extension
87
+ - PB-50158 Add Safari enablement through a feature flag
88
+ - PB-50200 Move the logic of passbolt.groups.create to GroupCreateController
89
+ - PB-50201 Update group create call in groupApiService to contain "my_group_user" as urlOptions
90
+ - PB-50202 Add supported formats documentation link in export dialog
91
+ - PB-50225 Create a CreateGroupService.js file and move the create call to api service inside it
92
+ - PB-50338 - Fix phantom @babel/preset-react
93
+
94
+ ### Security
95
+ - PB-49608 Fix ReDoS vulnerability in PGP armor regex validation
96
+ - PB-50271 Fix GHSA-25h7-pfq9-p65f - HIGH CVSS3.1
97
+ - PB-50272 Fix brace-expansion vulnerabilities
@@ -23,6 +23,14 @@
23
23
 
24
24
  import Foundation
25
25
 
26
+ private extension Data {
27
+ mutating func append(_ string: String) {
28
+ if let data = string.data(using: .utf8) {
29
+ append(data)
30
+ }
31
+ }
32
+ }
33
+
26
34
  final class FetchService {
27
35
 
28
36
  // Runs a fetch on the API with profile isolation.
@@ -35,19 +43,31 @@ final class FetchService {
35
43
  // profileUUID: The Safari profile UUID for session isolation
36
44
  static func fetch(url: URL, options: [String: Any], profileUUID: String) async throws -> [String: Any] {
37
45
  let method = options["method"] as? String ?? "GET"
38
- let body = options["body"] as? String ?? ""
39
46
  let headers = options["headers"] as? [String: String] ?? [:]
40
47
  let cookies = options["cookies"] as? String
41
48
 
42
49
  var httpRequest = URLRequest(url: url)
43
50
  httpRequest.httpMethod = method
44
- httpRequest.httpBody = body.data(using: .utf8)
51
+
52
+ // Build the body: structured FormData array (may contain files) or plain string
53
+ if let formDataArray = options["body"] as? [[String: Any]] {
54
+ let boundary = UUID().uuidString
55
+ httpRequest.httpBody = buildMultipartBody(formDataArray: formDataArray, boundary: boundary)
56
+ httpRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
57
+ } else {
58
+ let body = options["body"] as? String ?? ""
59
+ httpRequest.httpBody = body.data(using: .utf8)
60
+ }
45
61
 
46
62
  // SECURITY: Add cache-control headers to prevent response caching
47
63
  httpRequest.setValue("no-cache, no-store, must-revalidate", forHTTPHeaderField: "Cache-Control")
48
64
  httpRequest.setValue("no-cache", forHTTPHeaderField: "Pragma")
49
65
 
50
66
  for header in headers {
67
+ // Skip Content-Type when we already set it for multipart
68
+ if header.key.lowercased() == "content-type" && httpRequest.value(forHTTPHeaderField: "Content-Type")?.contains("multipart") == true {
69
+ continue
70
+ }
51
71
  httpRequest.setValue(String(describing: header.value), forHTTPHeaderField: String(describing: header.key))
52
72
  }
53
73
 
@@ -59,6 +79,74 @@ final class FetchService {
59
79
  return try await doFetch(request: httpRequest, profileUUID: profileUUID)
60
80
  }
61
81
 
82
+ /// Build a multipart/form-data body from the structured FormData array sent by JavaScript.
83
+ /// Each entry has: key, value, type ("SCALAR" or "FILE"), and optionally name (for files).
84
+ /// File values use the data URL format: "data:<mimeType>;base64,<base64Data>"
85
+ private static func buildMultipartBody(formDataArray: [[String: Any]], boundary: String) -> Data {
86
+ var body = Data()
87
+
88
+ for entry in formDataArray {
89
+ guard let key = entry["key"] as? String,
90
+ let value = entry["value"] as? String,
91
+ let type = entry["type"] as? String else {
92
+ continue
93
+ }
94
+
95
+ let sanitizedKey = sanitizeHeaderValue(key)
96
+ body.append("--\(boundary)\r\n")
97
+
98
+ if type == "FILE" {
99
+ let rawFilename = entry["name"] as? String ?? "file"
100
+ let sanitizedFilename = sanitizeFilename(rawFilename)
101
+ let (mimeType, fileData) = decodeDataURL(value)
102
+ let sanitizedMimeType = sanitizeHeaderValue(mimeType)
103
+
104
+ body.append("Content-Disposition: form-data; name=\"\(sanitizedKey)\"; filename=\"\(sanitizedFilename)\"\r\n")
105
+ body.append("Content-Type: \(sanitizedMimeType)\r\n\r\n")
106
+ body.append(fileData)
107
+ body.append("\r\n")
108
+ } else {
109
+ body.append("Content-Disposition: form-data; name=\"\(sanitizedKey)\"\r\n\r\n")
110
+ body.append("\(value)\r\n")
111
+ }
112
+ }
113
+
114
+ body.append("--\(boundary)--\r\n")
115
+ return body
116
+ }
117
+
118
+ /// Sanitize a filename for use in Content-Disposition headers.
119
+ /// Strips path traversal components, quotes, and CRLF characters.
120
+ private static func sanitizeFilename(_ raw: String) -> String {
121
+ let nameOnly = raw.components(separatedBy: CharacterSet(charactersIn: "/\\")).last ?? "file"
122
+ return sanitizeHeaderValue(nameOnly)
123
+ }
124
+
125
+ /// Sanitize a value interpolated into an HTTP header by removing quotes and CRLF.
126
+ private static func sanitizeHeaderValue(_ raw: String) -> String {
127
+ raw.replacingOccurrences(of: "\"", with: "")
128
+ .replacingOccurrences(of: "\r", with: "")
129
+ .replacingOccurrences(of: "\n", with: "")
130
+ }
131
+
132
+ /// Decode a data URL (e.g. "data:image/png;base64,iVBOR...") into its MIME type and binary data.
133
+ private static func decodeDataURL(_ dataURL: String) -> (mimeType: String, data: Data) {
134
+ let parts = dataURL.components(separatedBy: ",")
135
+ guard parts.count == 2,
136
+ let base64Data = Data(base64Encoded: parts[1]) else {
137
+ return ("application/octet-stream", Data())
138
+ }
139
+
140
+ // Extract MIME type from "data:image/png;base64"
141
+ let header = parts[0]
142
+ let mimeType = header
143
+ .replacingOccurrences(of: "data:", with: "")
144
+ .components(separatedBy: ";")
145
+ .first ?? "application/octet-stream"
146
+
147
+ return (mimeType, base64Data)
148
+ }
149
+
62
150
  // The actual fetch sent to the API using a profile-isolated session
63
151
  private static func doFetch(request: URLRequest, profileUUID: String) async throws -> [String: Any] {
64
152
  // SECURITY: Use profile-specific session, NEVER URLSession.shared
package/eslint.config.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import globals from "globals";
2
- import babelParser from "@babel/eslint-parser";
3
2
  import path from "path";
4
3
  import { fileURLToPath } from "url";
5
4
 
@@ -32,18 +31,13 @@ export default [
32
31
  files: ["**/*.{js,jsx,mjs,cjs}"],
33
32
 
34
33
  languageOptions: {
35
- parser: babelParser,
36
34
  ecmaVersion: 2024,
37
35
  sourceType: "module",
38
36
 
39
37
  parserOptions: {
40
- requireConfigFile: false,
41
38
  ecmaFeatures: {
42
39
  jsx: true,
43
40
  },
44
- babelOptions: {
45
- presets: ["@babel/preset-react"],
46
- },
47
41
  },
48
42
 
49
43
  globals: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passbolt-browser-extension",
3
- "version": "5.10.4",
3
+ "version": "5.11.1",
4
4
  "license": "AGPL-3.0",
5
5
  "copyright": "Copyright 2025 Passbolt SA",
6
6
  "description": "Passbolt web extension for the open source password manager for teams",
@@ -19,10 +19,9 @@
19
19
  "ip-regex": "^5.0.0",
20
20
  "jssha": "~3.3.1",
21
21
  "kdbxweb": "2.1.1",
22
- "locutus": "~2.0.39",
23
22
  "openpgp": "^6.1.1",
24
23
  "papaparse": "^5.5.2",
25
- "passbolt-styleguide": "^5.10.7",
24
+ "passbolt-styleguide": "^5.11.3",
26
25
  "react": "^18.3.1",
27
26
  "react-dom": "^18.3.1",
28
27
  "secrets-passbolt": "github:passbolt/secrets.js#v2.0.1",
@@ -33,31 +32,30 @@
33
32
  },
34
33
  "devDependencies": {
35
34
  "@babel/core": "^7.23.2",
36
- "@babel/eslint-parser": "^7.22.9",
37
35
  "@babel/helpers": "^7.26.10",
38
36
  "@babel/plugin-transform-runtime": "^7.22.9",
39
37
  "@babel/preset-env": "^7.22.9",
40
- "@babel/preset-react": "^7.22.5",
38
+ "@babel/preset-react": "^7.28.5",
41
39
  "@babel/runtime": "^7.27.0",
42
40
  "@babel/runtime-corejs3": "^7.26.10",
43
- "@eslint/js": "^9.37.0",
41
+ "@eslint/js": "^9.39.4",
44
42
  "@svgr/webpack": "^8.1.0",
45
43
  "babel-jest": "^29.6.2",
46
44
  "babel-loader": "^8.2.3",
47
45
  "buffer": "^6.0.3",
48
46
  "crx": "^5.0.1",
49
- "eslint": "^9.37.0",
47
+ "eslint": "^9.39.4",
50
48
  "eslint-config-prettier": "^10.1.8",
51
49
  "eslint-import-resolver-alias": "^1.1.2",
52
50
  "eslint-plugin-import": "^2.32.0",
53
- "eslint-plugin-jest": "^29.0.1",
54
- "eslint-plugin-n": "^17.23.1",
55
- "eslint-plugin-no-unsanitized": "^4.1.4",
56
- "eslint-plugin-prettier": "^5.5.4",
51
+ "eslint-plugin-jest": "^29.15.1",
52
+ "eslint-plugin-n": "^17.24.0",
53
+ "eslint-plugin-no-unsanitized": "^4.1.5",
54
+ "eslint-plugin-prettier": "^5.5.5",
57
55
  "eslint-plugin-promise": "^7.2.1",
58
56
  "eslint-plugin-react": "^7.37.5",
59
- "eslint-plugin-regexp": "^2.10.0",
60
- "eslint-plugin-security": "^3.0.1",
57
+ "eslint-plugin-regexp": "^3.1.0",
58
+ "eslint-plugin-security": "^4.0.0",
61
59
  "filereader": "^0.10.3",
62
60
  "formdata-node": "^6.0.3",
63
61
  "globals": "^16.4.0",
@@ -54,8 +54,17 @@ class AuthLogoutController {
54
54
  return;
55
55
  }
56
56
 
57
- const url = this.apiClientOptions.getBaseUrl().toString();
58
- await browser.tabs.update(this.worker.tab.id, { url });
57
+ /*
58
+ * Use tabs.reload instead of tabs.update to ensure the page is fully
59
+ * reloaded from the server. A tabs.update navigation may restore the
60
+ * page from the browser's Back/Forward Cache (BFCache), resulting in
61
+ * a stale page with a dead extension message port after sign-out.
62
+ *
63
+ * See: PB-50644
64
+ */
65
+ await browser.tabs.reload(this.worker.tab.id);
66
+ // const url = this.apiClientOptions.getBaseUrl().toString();
67
+ // await browser.tabs.update(this.worker.tab.id, { url });
59
68
  }
60
69
  }
61
70
 
@@ -34,7 +34,7 @@ describe("AuthLogoutController", () => {
34
34
  });
35
35
 
36
36
  it("Should sign-out the user and redirect after.", async () => {
37
- expect.assertions(3);
37
+ expect.assertions(2);
38
38
  const logoutSpy = jest.spyOn(AuthModel.prototype, "logout").mockImplementation(() => {});
39
39
 
40
40
  const worker = {
@@ -48,10 +48,19 @@ describe("AuthLogoutController", () => {
48
48
  await controller.exec(true);
49
49
 
50
50
  expect(logoutSpy).toHaveBeenCalledTimes(1);
51
- expect(browser.tabs.update).toHaveBeenCalledTimes(1);
52
- expect(browser.tabs.update).toHaveBeenCalledWith(worker.tab.id, {
53
- url: apiClientOptions.getBaseUrl().toString(),
54
- });
51
+ expect(browser.tabs.reload).toHaveBeenCalledTimes(1);
52
+ /*
53
+ * Use tabs.reload instead of tabs.update to ensure the page is fully
54
+ * reloaded from the server. A tabs.update navigation may restore the
55
+ * page from the browser's Back/Forward Cache (BFCache), resulting in
56
+ * a stale page with a dead extension message port after sign-out.
57
+ *
58
+ * See: PB-50644
59
+ */
60
+ // expect(browser.tabs.update).toHaveBeenCalledTimes(1);
61
+ // expect(browser.tabs.update).toHaveBeenCalledWith(worker.tab.id, {
62
+ // url: apiClientOptions.getBaseUrl().toString(),
63
+ // });
55
64
  });
56
65
  });
57
66
  });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.11.0
13
+ */
14
+ import CreateGroupService from "../../service/group/createGroupService";
15
+ import GroupEntity from "../../model/entity/group/groupEntity";
16
+
17
+ class GroupCreateController {
18
+ /**
19
+ * GroupCreateController constructor
20
+ *
21
+ * @param {Worker} worker
22
+ * @param {string} requestId
23
+ * @param {ApiClientOptions} apiClientOptions the api client options
24
+ * @param {AccountEntity} account The account associated to the worker.
25
+ */
26
+ constructor(worker, requestId, apiClientOptions, account) {
27
+ this.worker = worker;
28
+ this.requestId = requestId;
29
+ this.createGroupService = new CreateGroupService(apiClientOptions, account);
30
+ }
31
+
32
+ /**
33
+ * Controller executor.
34
+ * @param {object} groupDto The group data
35
+ * @returns {Promise<void>}
36
+ */
37
+ async _exec(groupDto) {
38
+ try {
39
+ const group = await this.exec(groupDto);
40
+ this.worker.port.emit(this.requestId, "SUCCESS", group);
41
+ } catch (error) {
42
+ console.error(error);
43
+ this.worker.port.emit(this.requestId, "ERROR", error);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Create a group.
49
+ * @param {object} groupDto The group data
50
+ * @returns {Promise<GroupEntity>}
51
+ */
52
+ async exec(groupDto) {
53
+ const groupEntity = new GroupEntity(groupDto);
54
+ return this.createGroupService.create(groupEntity);
55
+ }
56
+ }
57
+
58
+ export default GroupCreateController;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.11.0
13
+ */
14
+ import AccountEntity from "../../model/entity/account/accountEntity";
15
+ import { defaultAccountDto } from "../../model/entity/account/accountEntity.test.data";
16
+ import { defaultApiClientOptions } from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
17
+ import GroupCreateController from "./groupCreateController";
18
+ import { defaultGroupDto } from "passbolt-styleguide/src/shared/models/entity/group/groupEntity.test.data";
19
+ import GroupEntity from "../../model/entity/group/groupEntity";
20
+
21
+ describe("GroupCreateController", () => {
22
+ let controller, account, apiClientOptions;
23
+
24
+ beforeEach(async () => {
25
+ account = new AccountEntity(defaultAccountDto());
26
+ apiClientOptions = defaultApiClientOptions();
27
+ controller = new GroupCreateController(null, null, apiClientOptions, account);
28
+ });
29
+
30
+ describe("GroupCreateController::exec", () => {
31
+ it("Should create a group via the service.", async () => {
32
+ expect.assertions(2);
33
+
34
+ const groupDto = defaultGroupDto({}, { withGroupsUsers: true, withMyGroupUser: true });
35
+ const createdGroupEntity = new GroupEntity(groupDto);
36
+
37
+ jest.spyOn(controller.createGroupService, "create").mockResolvedValue(createdGroupEntity);
38
+
39
+ const result = await controller.exec(groupDto);
40
+
41
+ expect(result).toBeInstanceOf(GroupEntity);
42
+ expect(result.name).toEqual(groupDto.name);
43
+ });
44
+
45
+ it("Should call the service with a GroupEntity.", async () => {
46
+ expect.assertions(2);
47
+
48
+ const groupDto = defaultGroupDto({}, { withGroupsUsers: true, withMyGroupUser: true });
49
+ const createdGroupEntity = new GroupEntity(groupDto);
50
+
51
+ jest.spyOn(controller.createGroupService, "create").mockResolvedValue(createdGroupEntity);
52
+
53
+ await controller.exec(groupDto);
54
+
55
+ expect(controller.createGroupService.create).toHaveBeenCalledTimes(1);
56
+ expect(controller.createGroupService.create).toHaveBeenCalledWith(expect.any(GroupEntity));
57
+ });
58
+
59
+ it("Should throw if the group dto is invalid.", async () => {
60
+ expect.assertions(1);
61
+
62
+ const groupDto = { name: "" };
63
+
64
+ await expect(controller.exec(groupDto)).rejects.toThrow();
65
+ });
66
+ });
67
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.11.0
13
+ */
14
+
15
+ import sanitizeUrl, { urlProtocols } from "passbolt-styleguide/src/react-extension/lib/Sanitize/sanitizeUrl";
16
+ import BrowserTabService from "../../service/ui/browserTab.service";
17
+
18
+ export default class OpenResourceUriTabController {
19
+ /**
20
+ * @constructor
21
+ * @param {Worker} worker The associated worker.
22
+ * @param {string} requestId The associated request id.
23
+ */
24
+ constructor(worker, requestId) {
25
+ this.worker = worker;
26
+ this.requestId = requestId;
27
+ }
28
+
29
+ /**
30
+ * Wrapper of exec function to run it with worker.
31
+ * @return {Promise<void>}
32
+ */
33
+ async _exec() {
34
+ try {
35
+ await this.exec.apply(this, arguments);
36
+ this.worker.port.emit(this.requestId, "SUCCESS");
37
+ } catch (error) {
38
+ console.error(error);
39
+ this.worker.port.emit(this.requestId, "ERROR", error);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Opens the given url in a new tab
45
+ * @param {string} uriString the URI to try to open on a new tab
46
+ * @returns {Promise<void>}
47
+ */
48
+ async exec(urlString) {
49
+ const url = sanitizeUrl(urlString, {
50
+ whiteListedProtocols: [urlProtocols.HTTPS, urlProtocols.HTTP],
51
+ defaultProtocol: urlProtocols.HTTPS,
52
+ });
53
+
54
+ if (!url) {
55
+ throw new Error("The given URL is not valid for opening in a new tab.");
56
+ }
57
+
58
+ await BrowserTabService.openTab(url);
59
+ }
60
+ }