diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b14fa13..54c916d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -72,6 +72,7 @@ In this release, the schema version has changed from 12 to 13.
### Security
+- Enforced password strength policy ([3503]).
- Weaker cipher suites that use the CBC (cipher block chaining) mode of
operation have been disabled ([#2993]).
@@ -79,6 +80,7 @@ In this release, the schema version has changed from 12 to 13.
[#2993]: https://github.com/AdguardTeam/AdGuardHome/issues/2993
[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
[#3367]: https://github.com/AdguardTeam/AdGuardHome/issues/3367
+[#3503]: https://github.com/AdguardTeam/AdGuardHome/issues/3503
[#4216]: https://github.com/AdguardTeam/AdGuardHome/issues/4216
[#4221]: https://github.com/AdguardTeam/AdGuardHome/issues/4221
[#4238]: https://github.com/AdguardTeam/AdGuardHome/issues/4238
diff --git a/client/package-lock.json b/client/package-lock.json
index 09746154..ba1d3772 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1700,6 +1700,12 @@
"v8-to-istanbul": "^4.1.3"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@@ -1720,6 +1726,12 @@
"supports-color": "^7.1.0"
}
},
+ "char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1747,6 +1759,25 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
+ "string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "requires": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@@ -3959,10 +3990,9 @@
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
},
"char-regex": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
- "dev": true
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz",
+ "integrity": "sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA=="
},
"character-entities": {
"version": "1.2.4",
@@ -10037,6 +10067,12 @@
"string-length": "^4.0.1"
},
"dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@@ -10057,6 +10093,12 @@
"supports-color": "^7.1.0"
}
},
+ "char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -10078,6 +10120,25 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "requires": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@@ -14193,38 +14254,26 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
"string-length": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
- "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==",
- "dev": true,
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
+ "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
"requires": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
+ "char-regex": "^2.0.0",
+ "strip-ansi": "^7.0.1"
},
"dependencies": {
"ansi-regex": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
- "dev": true
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
},
"strip-ansi": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
- "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
- "dev": true,
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
"requires": {
- "ansi-regex": "^5.0.0"
+ "ansi-regex": "^6.0.1"
}
}
}
@@ -14297,6 +14346,15 @@
"define-properties": "^1.1.3"
}
},
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"stringify-entities": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz",
diff --git a/client/package.json b/client/package.json
index aa96f271..22b83010 100644
--- a/client/package.json
+++ b/client/package.json
@@ -42,6 +42,7 @@
"redux-actions": "^2.6.5",
"redux-form": "^8.3.5",
"redux-thunk": "^2.3.0",
+ "string-length": "^5.0.1",
"url-polyfill": "^1.1.9"
},
"devDependencies": {
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 07021b59..9917ef2e 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -627,5 +627,6 @@
"use_saved_key": "Use the previously saved key",
"parental_control": "Parental Control",
"safe_browsing": "Safe Browsing",
- "served_from_cache": "{{value}} (served from cache)"
+ "served_from_cache": "{{value}} (served from cache)",
+ "form_error_password_length": "Password must be at least {{value}} characters long"
}
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index d4477b72..197cabba 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -26,6 +26,8 @@ export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)
export const R_CLIENT_ID = /^[a-z0-9-]{1,63}$/;
+export const MIN_PASSWORD_LENGTH = 8;
+
export const HTML_PAGES = {
INSTALL: '/install.html',
LOGIN: '/login.html',
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index 2ebf6a30..862a2ca4 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -1,4 +1,5 @@
import i18next from 'i18next';
+import stringLength from 'string-length';
import {
MAX_PORT,
@@ -13,6 +14,7 @@ import {
UNSAFE_PORTS,
R_CLIENT_ID,
R_DOMAIN,
+ MIN_PASSWORD_LENGTH,
} from './constants';
import { ip4ToInt, isValidAbsolutePath } from './form';
import { isIpInCidr, parseSubnetMask } from './helpers';
@@ -320,10 +322,20 @@ export const validatePath = (value) => {
* @param cidr {string}
* @returns {Function}
*/
-
export const validateIpv4InCidr = (valueIp, allValues) => {
if (!isIpInCidr(valueIp, allValues.cidr)) {
return i18next.t('form_error_subnet', { ip: valueIp, cidr: allValues.cidr });
}
return undefined;
};
+
+/**
+ * @param value {string}
+ * @returns {Function}
+ */
+export const validatePasswordLength = (value) => {
+ if (value && stringLength(value) < MIN_PASSWORD_LENGTH) {
+ return i18next.t('form_error_password_length', { value: MIN_PASSWORD_LENGTH });
+ }
+ return undefined;
+};
diff --git a/client/src/install/Setup/Auth.js b/client/src/install/Setup/Auth.js
index b4184191..c2314aa5 100644
--- a/client/src/install/Setup/Auth.js
+++ b/client/src/install/Setup/Auth.js
@@ -8,6 +8,7 @@ import i18n from '../../i18n';
import Controls from './Controls';
import { renderInputField } from '../../helpers/form';
import { FORM_NAME } from '../../helpers/constants';
+import { validatePasswordLength } from '../../helpers/validators';
const required = (value) => {
if (value || value === 0) {
@@ -67,7 +68,7 @@ const Auth = (props) => {
type="password"
className="form-control"
placeholder={ t('install_auth_password_enter') }
- validate={[required]}
+ validate={[required, validatePasswordLength]}
autoComplete="new-password"
/>
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 82598078..98cbf31a 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -13,6 +13,7 @@ import (
"runtime"
"strings"
"time"
+ "unicode/utf8"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@@ -359,6 +360,9 @@ func shutdownSrv(ctx context.Context, srv *http.Server) {
}
}
+// PasswordMinRunes is the minimum length of user's password in runes.
+const PasswordMinRunes = 8
+
// Apply new configuration, start DNS server, restart Web server
func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
req, restartHTTP, err := decodeApplyConfigReq(r.Body)
@@ -368,6 +372,18 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
return
}
+ if utf8.RuneCountInString(req.Password) < PasswordMinRunes {
+ aghhttp.Error(
+ r,
+ w,
+ http.StatusUnprocessableEntity,
+ "password must be at least %d symbols long",
+ PasswordMinRunes,
+ )
+
+ return
+ }
+
err = aghnet.CheckPort("udp", req.DNS.IP, req.DNS.Port)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index ef32f6ea..0762fdf2 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,12 @@
## v0.107.3: API changes
+### The new possible status code in `/install/configure` response.
+
+* The new status code `422 Unprocessable Entity` in the response for
+ `POST /install/configure` which means that the specified password does not
+ meet the strength requirements.
+
### The new field `"version"` in `AddressesInfo`
* The new field `"version"` in `GET /install/get_addresses` is the version of
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 30d25b44..85c372a3 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1088,6 +1088,9 @@
'description': >
Failed to parse initial configuration or cannot listen to the
specified addresses.
+ '422':
+ 'description': >
+ The specified password does not meet the strength requirements.
'500':
'description': 'Cannot start the DNS server'
'/login':