fix: harden protected login and browser accessibility
This commit is contained in:
parent
63bf7fad3b
commit
2372947430
|
|
@ -30,7 +30,7 @@
|
|||
--color-base-100: oklch(30.33% 0.016 252.42);
|
||||
--color-base-200: oklch(25.26% 0.014 253.1);
|
||||
--color-base-300: oklch(20.15% 0.012 254.09);
|
||||
--color-base-content: #f3f7ff;
|
||||
--color-base-content: #ffffff;
|
||||
--color-primary: #a5b4fc;
|
||||
--color-primary-content: #18162a;
|
||||
--color-secondary: #a5b4fc;
|
||||
|
|
@ -128,6 +128,21 @@
|
|||
color: var(--color-base-content) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-ghost,
|
||||
[data-theme="dark"] .btn-outline {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-soft.btn-primary {
|
||||
color: var(--color-primary-content);
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.locale-active {
|
||||
color: var(--color-neutral-content) !important;
|
||||
background-color: var(--color-neutral) !important;
|
||||
}
|
||||
|
||||
.aid-map {
|
||||
min-height: 22rem;
|
||||
border-radius: 1.25rem;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ const activateProtectedTokenFragment = () => {
|
|||
}
|
||||
|
||||
activateProtectedTokenFragment()
|
||||
window.addEventListener("hashchange", activateProtectedTokenFragment)
|
||||
|
||||
const liveSocket = new LiveSocket("/live", Socket, {
|
||||
longPollFallbackMs: 2500,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ test("browser and Android share chat and live tracking state", async ({ browser
|
|||
await expect(requester.page.getByText(androidMessage, { exact: true })).toBeVisible();
|
||||
|
||||
if (phase === "observe_android") {
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared location");
|
||||
await requester.page
|
||||
.getByPlaceholder("Write a safe coordination message…")
|
||||
.fill(browserReply);
|
||||
|
|
@ -32,7 +32,7 @@ test("browser and Android share chat and live tracking state", async ({ browser
|
|||
await expect(requester.page.getByText(browserReply, { exact: true })).toBeVisible();
|
||||
} else if (phase === "observe_stopped") {
|
||||
await expect(requester.page.getByText(browserReply, { exact: true })).toBeVisible();
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location");
|
||||
} else {
|
||||
throw new Error(`Unsupported CROSS_CLIENT_PHASE: ${phase}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const CACHE = "who-need-help-static-v3";
|
||||
const CACHE_PREFIX = "who-need-help-static-";
|
||||
const STALE_CACHE = "who-need-help-static-v0";
|
||||
const SHELL_PATHS = [
|
||||
"/offline.html",
|
||||
|
|
@ -45,6 +45,21 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin
|
|||
expect(manifestResponse.status()).toBe(200);
|
||||
expect(manifestResponse.headers()["content-type"]).toContain("application/manifest+json");
|
||||
|
||||
const serviceWorkerResponse = await request.get("/sw.js");
|
||||
expect(serviceWorkerResponse.status()).toBe(200);
|
||||
expect(serviceWorkerResponse.headers()["content-type"]).toContain("javascript");
|
||||
const serviceWorker = await serviceWorkerResponse.text();
|
||||
const cacheVersionMatch = serviceWorker.match(
|
||||
/const CACHE = `\$\{CACHE_PREFIX\}(v[0-9]+)`/,
|
||||
);
|
||||
expect(cacheVersionMatch).not.toBeNull();
|
||||
|
||||
if (!cacheVersionMatch) {
|
||||
throw new Error("The service worker does not declare a versioned static cache");
|
||||
}
|
||||
|
||||
const cacheName = `${CACHE_PREFIX}${cacheVersionMatch[1]}`;
|
||||
|
||||
const manifest = (await manifestResponse.json()) as {
|
||||
name: string;
|
||||
short_name: string;
|
||||
|
|
@ -102,12 +117,12 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin
|
|||
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => caches.keys()))
|
||||
.toContain(CACHE);
|
||||
.toContain(cacheName);
|
||||
|
||||
const cachedShell = await page.evaluate(async (cacheName) => {
|
||||
const cache = await caches.open(cacheName);
|
||||
const cachedShell = await page.evaluate(async (activeCacheName) => {
|
||||
const cache = await caches.open(activeCacheName);
|
||||
return (await cache.keys()).map((entry) => new URL(entry.url).pathname).sort();
|
||||
}, CACHE);
|
||||
}, cacheName);
|
||||
expect(cachedShell).toEqual(expect.arrayContaining(SHELL_PATHS));
|
||||
|
||||
await page.goto("/safety");
|
||||
|
|
@ -146,7 +161,7 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin
|
|||
await expect
|
||||
.poll(() => updatedPage.evaluate(() => caches.keys()))
|
||||
.not.toContain(STALE_CACHE);
|
||||
expect(await updatedPage.evaluate(() => caches.keys())).toContain(CACHE);
|
||||
expect(await updatedPage.evaluate(() => caches.keys())).toContain(cacheName);
|
||||
|
||||
await context.setOffline(true);
|
||||
const offlineResponse = await updatedPage.goto("/requests");
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta
|
|||
await expect(helper.page.getByRole("button", { name: "Stop and delete position" })).toBeVisible();
|
||||
await expect.poll(() => positionCount(helper.page)).toBe(1);
|
||||
await expect.poll(() => positionCount(requester.page)).toBe(1);
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared location");
|
||||
|
||||
await helper.page
|
||||
.getByPlaceholder("Write a safe coordination message…")
|
||||
|
|
@ -173,7 +173,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta
|
|||
await expect.poll(() => positionCount(helper.page), { timeout: 30_000 }).toBe(1);
|
||||
await expect.poll(() => positionCount(requester.page), { timeout: 30_000 }).toBe(1);
|
||||
await expect.poll(() => markerKinds(requester.page), { timeout: 30_000 }).toContain(
|
||||
"Shared live location",
|
||||
"Shared location",
|
||||
);
|
||||
|
||||
await helper.page
|
||||
|
|
@ -188,7 +188,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta
|
|||
await expect(helper.page.getByRole("button", { name: "Share location" })).toBeVisible();
|
||||
await expect.poll(() => positionCount(helper.page)).toBe(0);
|
||||
await expect.poll(() => positionCount(requester.page)).toBe(0);
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location");
|
||||
|
||||
await testInfo.attach("active-failover-evidence", {
|
||||
contentType: "application/json",
|
||||
|
|
|
|||
|
|
@ -322,7 +322,13 @@ export async function registerAndConfirm(
|
|||
.check();
|
||||
await page.getByRole("button", { name: "Create an account" }).click();
|
||||
await expect(page).toHaveURL(/\/users\/log-in$/);
|
||||
await expect(page.getByText(`An email was sent to ${email}`)).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole("alert")
|
||||
.getByText(
|
||||
"If this address can be registered or signed in, login instructions will arrive shortly.",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await gotoWithTransientRetry(page, await waitForMagicLink(request, email));
|
||||
await expect(page.locator("#magic-link-fragment-form")).toBeVisible();
|
||||
|
|
@ -357,8 +363,10 @@ export async function loginWithMagicLink(
|
|||
page,
|
||||
await waitForMagicLink(request, email, previousMessageID),
|
||||
);
|
||||
await expect(page.getByRole("heading", { name: `Welcome ${email}` })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Log me in only this time" }).click();
|
||||
await expect(page.locator("#magic-link-fragment-form")).toBeVisible();
|
||||
await page
|
||||
.getByRole("button", { name: "Confirm and log in only this time" })
|
||||
.click();
|
||||
await expect(page.getByText(email, { exact: true })).toBeVisible();
|
||||
|
||||
return { context, page, email };
|
||||
|
|
|
|||
|
|
@ -83,11 +83,11 @@ test("two users complete medicine tracking, handover, realtime chat, and blind r
|
|||
});
|
||||
await helper.page.getByRole("button", { name: "Share location" }).click();
|
||||
await expect(helper.page.getByRole("button", { name: "Stop and delete position" })).toBeVisible();
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).toContain("Shared location");
|
||||
|
||||
await helper.page.getByRole("button", { name: "Stop and delete position" }).click();
|
||||
await expect(helper.page.getByRole("button", { name: "Share location" })).toBeVisible();
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location");
|
||||
await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location");
|
||||
|
||||
await helper.page.getByRole("button", { name: "Start helping" }).click();
|
||||
const handoverCode = (
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ defmodule WhoNeedHelpWeb.Layouts do
|
|||
|
||||
def app(assigns) do
|
||||
~H"""
|
||||
<header class="sticky top-0 z-30 border-b border-base-300 bg-base-100/90 px-4 py-2 backdrop-blur sm:px-6 lg:px-8">
|
||||
<header class="sticky top-0 z-30 border-b border-base-300 bg-base-100 px-4 py-2 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto flex min-h-12 w-full max-w-7xl items-center justify-between gap-3">
|
||||
<a
|
||||
href="/"
|
||||
|
|
@ -195,7 +195,7 @@ defmodule WhoNeedHelpWeb.Layouts do
|
|||
href="?locale=en"
|
||||
class={[
|
||||
"btn btn-ghost btn-xs join-item",
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "en" && "btn-active"
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "en" && "btn-active locale-active"
|
||||
]}
|
||||
aria-label="English"
|
||||
aria-current={Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "en" && "page"}
|
||||
|
|
@ -206,7 +206,7 @@ defmodule WhoNeedHelpWeb.Layouts do
|
|||
href="?locale=uk"
|
||||
class={[
|
||||
"btn btn-ghost btn-xs join-item",
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "uk" && "btn-active"
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "uk" && "btn-active locale-active"
|
||||
]}
|
||||
aria-label="Українська"
|
||||
aria-current={Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "uk" && "page"}
|
||||
|
|
@ -217,7 +217,7 @@ defmodule WhoNeedHelpWeb.Layouts do
|
|||
href="?locale=ru"
|
||||
class={[
|
||||
"btn btn-ghost btn-xs join-item",
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "ru" && "btn-active"
|
||||
Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "ru" && "btn-active locale-active"
|
||||
]}
|
||||
aria-label="Русский"
|
||||
aria-current={Gettext.get_locale(WhoNeedHelpWeb.Gettext) == "ru" && "page"}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user