diff --git a/assets/js/components/Auth/PasswordInput.vue b/assets/js/components/Auth/PasswordInput.vue
index 88265c6ee..71bea86cb 100644
--- a/assets/js/components/Auth/PasswordInput.vue
+++ b/assets/js/components/Auth/PasswordInput.vue
@@ -13,12 +13,12 @@
class="form-control"
autocomplete="current-password"
type="password"
- :required="required"
+ required
@input="updatePassword"
/>
-
@@ -180,6 +180,9 @@ const validateStatus = (code: number) => [200, 204, 401, 403].includes(code);
export default defineComponent({
name: "BackupRestoreModal",
components: { GenericModal, PropertyFileField, FormRow, PasswordInput },
+ props: {
+ authDisabled: Boolean,
+ },
data() {
return {
selectedReset: {
diff --git a/assets/js/views/Config.vue b/assets/js/views/Config.vue
index 5ef814e93..1d78d6820 100644
--- a/assets/js/views/Config.vue
+++ b/assets/js/views/Config.vue
@@ -373,7 +373,7 @@
-
+
@@ -598,6 +598,11 @@ export default {
messagingTags() {
return { configured: { value: store.state?.messaging || false } };
},
+ backupRestoreProps() {
+ return {
+ authDisabled: store.state?.authDisabled || false,
+ };
+ },
},
watch: {
offline() {
diff --git a/cmd/root.go b/cmd/root.go
index a087e5f4b..625dffea6 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -320,6 +320,7 @@ func runRoot(cmd *cobra.Command, args []string) {
if ok, _ := cmd.Flags().GetBool(flagDisableAuth); ok {
log.WARN.Println("❗❗❗ Authentication is disabled. This is dangerous. Your data and credentials are not protected.")
authObject.SetAuthMode(auth.Disabled)
+ valueChan <- util.Param{Key: keys.AuthDisabled, Val: true}
}
if ok, _ := cmd.Flags().GetBool(flagDemoMode); ok {
diff --git a/core/keys/global.go b/core/keys/global.go
index 4315cd9c0..c8c6647bf 100644
--- a/core/keys/global.go
+++ b/core/keys/global.go
@@ -19,4 +19,5 @@ const (
Plant = "plant"
Telemetry = "telemetry"
DemoMode = "demoMode"
+ AuthDisabled = "authDisabled"
)
diff --git a/i18n/de.json b/i18n/de.json
index 463134c2f..446a97525 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -447,7 +447,6 @@
"title": "Sichern"
},
"cancel": "Abbrechen",
- "confirmWithPassword": "Aktion bestätigen",
"description": "Sichern, wiederherstellen und zurücksetzen deiner Daten. Nützlich, wenn du deine Daten auf ein anderes System übertragen möchtest.",
"note": "Hinweis: Alle oben genannten Aktionen betreffen nur deine Datenbankdaten. Die evcc.yaml-Konfigurationsdatei bleibt unverändert.",
"reset": {
@@ -665,12 +664,11 @@
"loginModal": {
"cancel": "Abbrechen",
"demoMode": "Login ist im Demo-Modus nicht verfügbar.",
- "error": "Login fehlgeschlagen: ",
"iframeHint": "Öffne evcc in einem neuen Tab.",
"iframeIssue": "Das Passwort ist korrekt, aber dein Browser hat das Authentifizierungscookie abgelehnt. Dies kann passieren, wenn du evcc in einem iframe über HTTP verwendest.",
"invalid": "Passwort ist ungültig.",
"login": "Anmelden",
- "password": "Passwort",
+ "password": "Administrator Passwort",
"reset": "Passwort zurücksetzen?",
"title": "Authentifizierung"
},
diff --git a/i18n/en.json b/i18n/en.json
index 85dbdba44..709af452e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -447,7 +447,6 @@
"title": "Backup"
},
"cancel": "Cancel",
- "confirmWithPassword": "Confirm action",
"description": "Backup, restore and reset your data. Useful if you want to move your data to another system.",
"note": "Note: All above actions only affect your database data. The evcc.yaml configuration file remains unchanged.",
"reset": {
@@ -665,12 +664,11 @@
"loginModal": {
"cancel": "Cancel",
"demoMode": "Login is not supported in demo mode.",
- "error": "Login failed: ",
"iframeHint": "Open evcc in a new tab.",
"iframeIssue": "Your password is correct, but your browser seems to have dropped the authentication cookie. This can happen if you run evcc in an iframe via HTTP.",
"invalid": "Password is invalid.",
"login": "Login",
- "password": "Password",
+ "password": "Administrator Password",
"reset": "Reset password?",
"title": "Authentication"
},
diff --git a/server/http_site_handler.go b/server/http_site_handler.go
index e9ad4fdef..ff8fb98e3 100644
--- a/server/http_site_handler.go
+++ b/server/http_site_handler.go
@@ -354,6 +354,11 @@ func logHandler(w http.ResponseWriter, r *http.Request) {
jsonResult(w, log)
}
+// adminPasswordValid validates the admin password and returns true if valid
+func adminPasswordValid(authObject auth.Auth, password string) bool {
+ return authObject.GetAuthMode() == auth.Disabled || authObject.IsAdminPasswordValid(password)
+}
+
func getBackup(authObject auth.Auth) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req loginRequest
@@ -362,7 +367,7 @@ func getBackup(authObject auth.Auth) http.HandlerFunc {
return
}
- if authObject.GetAuthMode() == auth.Enabled && !authObject.IsAdminPasswordValid(req.Password) {
+ if !adminPasswordValid(authObject, req.Password) {
http.Error(w, "Invalid password", http.StatusUnauthorized)
return
}
@@ -422,7 +427,7 @@ func restoreDatabase(authObject auth.Auth, shutdown func()) http.HandlerFunc {
return
}
- if authObject.GetAuthMode() == auth.Enabled && !authObject.IsAdminPasswordValid(r.FormValue("password")) {
+ if !adminPasswordValid(authObject, r.FormValue("password")) {
http.Error(w, "Invalid password", http.StatusUnauthorized)
return
}
@@ -479,7 +484,7 @@ func resetDatabase(authObject auth.Auth, shutdown func()) http.HandlerFunc {
return
}
- if authObject.GetAuthMode() == auth.Enabled && !authObject.IsAdminPasswordValid(req.Password) {
+ if !adminPasswordValid(authObject, req.Password) {
http.Error(w, "Invalid password", http.StatusUnauthorized)
return
}
diff --git a/tests/auth.spec.ts b/tests/auth.spec.ts
index 2707470d6..608da61dc 100644
--- a/tests/auth.spec.ts
+++ b/tests/auth.spec.ts
@@ -53,12 +53,12 @@ test("login", async ({ page }) => {
await expect(login.getByRole("heading", { name: "Authentication" })).toBeVisible();
// enter wrong password
- await login.getByLabel("Password").fill("wrong");
+ await login.getByLabel("Administrator Password").fill("wrong");
await login.getByRole("button", { name: "Login" }).click();
- await expect(login.getByText("Login failed: Password is invalid.")).toBeVisible();
+ await expect(login.getByText("Password is invalid.")).toBeVisible();
// enter correct password
- await login.getByLabel("Password").fill("secret");
+ await login.getByLabel("Administrator Password").fill("secret");
await login.getByRole("button", { name: "Login" }).click();
await expectModalHidden(login);
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();
@@ -86,7 +86,7 @@ test("http iframe hint", async ({ page }) => {
});
// enter correct password
- await login.getByLabel("Password").fill("secret");
+ await login.getByLabel("Administrator Password").fill("secret");
await login.getByRole("button", { name: "Login" }).click();
// iframe hint visible (login-iframe-hint)
@@ -105,7 +105,7 @@ test("update password", async ({ page }) => {
await page.goto("/#/config");
const loginModal = page.getByTestId("login-modal");
await expectModalVisible(loginModal);
- await loginModal.getByLabel("Password").fill(oldPassword);
+ await loginModal.getByLabel("Administrator Password").fill(oldPassword);
await loginModal.getByRole("button", { name: "Login" }).click();
await expectModalHidden(loginModal);
@@ -134,7 +134,7 @@ test("update password", async ({ page }) => {
await expectTopNavigationClosed(page);
const loginNew = page.getByTestId("login-modal");
await expectModalVisible(loginNew);
- await loginNew.getByLabel("Password").fill(newPassword);
+ await loginNew.getByLabel("Administrator Password").fill(newPassword);
await loginNew.getByRole("button", { name: "Login" }).click();
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();
await expectModalHidden(loginNew);
diff --git a/tests/backup-restore.spec.ts b/tests/backup-restore.spec.ts
index f5f3af689..57c923d54 100644
--- a/tests/backup-restore.spec.ts
+++ b/tests/backup-restore.spec.ts
@@ -33,6 +33,7 @@ test.describe("reset", async () => {
await modal.getByRole("button", { name: "Reset..." }).click();
const confirmModal = page.getByTestId("backup-restore-confirm-modal");
await expectModalVisible(confirmModal);
+ await expect(confirmModal.getByLabel("Administrator Password")).not.toBeVisible(); // disable auth mode
await confirmModal.getByRole("button", { name: "Reset & restart" }).click();
await expectModalHidden(confirmModal);
await expectModalHidden(modal);
@@ -87,6 +88,7 @@ test.describe("reset", async () => {
await modal.getByRole("button", { name: "Reset..." }).click();
const confirmModal = page.getByTestId("backup-restore-confirm-modal");
await expectModalVisible(confirmModal);
+ await expect(confirmModal.getByLabel("Administrator Password")).not.toBeVisible(); // disable auth mode
await confirmModal.getByRole("button", { name: "Reset & restart" }).click();
await expectModalHidden(confirmModal);
await expectModalHidden(modal);
@@ -232,10 +234,11 @@ test.describe("backup and restore", async () => {
await backupModal.getByRole("button", { name: "Download backup..." }).click();
const backupConfirmModal = page.getByTestId("backup-restore-confirm-modal");
await expectModalVisible(backupConfirmModal);
- const passwordField = backupConfirmModal.getByLabel("Password");
+ const passwordField = backupConfirmModal.getByLabel("Administrator Password");
+ await expect(passwordField).toBeVisible();
await passwordField.fill("wrongpassword");
await backupConfirmModal.getByRole("button", { name: "Download backup" }).click();
- await expect(backupConfirmModal.getByText("Login failed: Password is invalid.")).toBeVisible();
+ await expect(backupConfirmModal.getByText("Password is invalid.")).toBeVisible();
await passwordField.clear();
await passwordField.fill("secret");
const downloadPromise = page.waitForEvent("download");
diff --git a/tests/config-onboarding.spec.ts b/tests/config-onboarding.spec.ts
index 2c75c95f5..716b9567a 100644
--- a/tests/config-onboarding.spec.ts
+++ b/tests/config-onboarding.spec.ts
@@ -32,7 +32,7 @@ test.describe("onboarding", async () => {
// login
const login = page.getByTestId("login-modal");
await expectModalVisible(login);
- await login.getByLabel("Password").fill(PASSWORD);
+ await login.getByLabel("Administrator Password").fill(PASSWORD);
await login.getByRole("button", { name: "Login" }).click();
await expectModalHidden(login);