Skip to content

Commit

Permalink
Auth: add iframe hint (evcc-io#14051)
Browse files Browse the repository at this point in the history
  • Loading branch information
naltatis authored May 24, 2024
1 parent ae6cfbe commit fbf59d1
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 5 deletions.
31 changes: 26 additions & 5 deletions assets/js/components/LoginModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
</div>

<p v-if="error" class="text-danger my-4">{{ $t("loginModal.error") }}{{ error }}</p>
<a
v-if="iframeHint"
class="text-muted my-4 d-block text-center"
:href="evccUrl"
target="_blank"
data-testid="login-iframe-hint"
>
{{ $t("loginModal.iframeHint") }}
</a>

<button type="submit" class="btn btn-primary w-100 mb-3" :disabled="loading">
<span
Expand All @@ -52,7 +61,7 @@
import GenericModal from "./GenericModal.vue";
import Modal from "bootstrap/js/dist/modal";
import api from "../api";
import { updateAuthStatus, getAndClearNextUrl } from "../auth";
import { updateAuthStatus, getAndClearNextUrl, isLoggedIn } from "../auth";
import { docsPrefix } from "../i18n";
export default {
Expand All @@ -64,13 +73,17 @@ export default {
password: "",
loading: false,
resetHint: false,
iframeHint: false,
error: "",
};
},
computed: {
resetUrl() {
return `${docsPrefix()}/docs/faq#password-reset`;
},
evccUrl() {
return window.location.href;
},
},
methods: {
open() {
Expand All @@ -82,6 +95,7 @@ export default {
this.loading = false;
this.error = "";
this.resetHint = false;
this.iframeHint = false;
},
focus() {
console.log(this.$refs.password);
Expand All @@ -99,13 +113,20 @@ export default {
validateStatus: (code) => [200, 401].includes(code),
});
this.resetHint = false;
this.iframeHint = false;
this.error = "";
if (res.status === 200) {
this.closeModal();
await updateAuthStatus();
const target = getAndClearNextUrl();
if (target) this.$router.push(target);
this.password = "";
if (isLoggedIn()) {
this.closeModal();
const target = getAndClearNextUrl();
if (target) this.$router.push(target);
this.password = "";
} else {
// login successful but auth cookie doesnt work
this.error = this.$t("loginModal.iframeIssue");
this.iframeHint = true;
}
}
if (res.status === 401) {
this.error = this.$t("loginModal.invalid");
Expand Down
2 changes: 2 additions & 0 deletions i18n/de.toml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ update = "Aktualisieren"
[loginModal]
cancel = "Abbrechen"
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"
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ update = "Auto update"
[loginModal]
cancel = "Cancel"
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"
Expand Down
29 changes: 29 additions & 0 deletions tests/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ test("login", async ({ page }) => {
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();
});

test("http iframe hint", async ({ page }) => {
// set initial password
const modal = page.getByTestId("password-modal");
await modal.getByLabel("New password").fill("secret");
await modal.getByLabel("Repeat password").fill("secret");
await modal.getByRole("button", { name: "Create Password" }).click();

// go to config
await page.getByTestId("topnavigation-button").click();
await page.getByRole("link", { name: "Configuration" }).click();

// login modal
const login = page.getByTestId("login-modal");
await expect(login).toBeVisible();
await expect(login.getByRole("heading", { name: "Authentication" })).toBeVisible();

// rewrite api call to simulate lost auth cookie
await page.route("**/api/auth/status", (route) => {
route.fulfill({ status: 200, body: "false" });
});

// enter correct password
await login.getByLabel("Password").fill("secret");
await login.getByRole("button", { name: "Login" }).click();

// iframe hint visible (login-iframe-hint)
await expect(login.getByTestId("login-iframe-hint")).toBeVisible();
});

test("update password", async ({ page }) => {
const oldPassword = "secret";
const newPassword = "newsecret";
Expand Down

0 comments on commit fbf59d1

Please sign in to comment.