Skip to content

Commit 8323d9b

Browse files
committed
Merge branch 'sea-snake/remove-last-used-identity' of https://github.com/dfinity/internet-identity into sea-snake/remove-last-used-identity
2 parents 0f8eaae + 592baaf commit 8323d9b

File tree

8 files changed

+108
-1
lines changed

8 files changed

+108
-1
lines changed

.github/workflows/canister-tests.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ jobs:
502502
matrix:
503503
device: ["desktop", "mobile"]
504504
shard: ["1_6", "2_6", "3_6", "4_6", "5_6", "6_6"]
505+
include:
506+
- device: "chrome-extension"
507+
shard: "1_1"
505508
# Make sure that one failing test does not cancel all other matrix jobs
506509
fail-fast: false
507510

@@ -589,6 +592,10 @@ jobs:
589592
dev_server_pid=$!
590593
echo "dev_server_pid=$dev_server_pid" >> "$GITHUB_OUTPUT"
591594
595+
- name: Build test app frontend (chrome extension)
596+
if: matrix.device == 'chrome-extension'
597+
run: npm run build --prefix demos/test-app
598+
592599
- name: Run Playwright tests
593600
run: |
594601
npx playwright test --project ${{ matrix.device }} --workers 1 --shard=$(tr <<<'${{ matrix.shard }}' -s _ /) --reporter=line,html
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
chrome.runtime.onInstalled.addListener(() => {});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "II Test App",
4+
"version": "1.0",
5+
"background": {
6+
"service_worker": "background.js"
7+
},
8+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsO+O98URHgu2I7Zbpfc28nI0GU7t4smaMaG25MEeHSjzLW2khGsGrBAFZBWrZ910RDGdZqp+1fCn+rr86fT/ahd05EBWJPCZ2R1JtDGhju2Rf2kGGiHMctlUHgx+J3O1T1mL/iOfjhWQ8p26keeaL36Tfp3FI7gUL9GgZV/mwE5NssvlPnZZpGI5Zxdtrh8q2gMzvGuu5LLwEO2GXPy9l3ExcDlRax9CUVAB5oCVlWaF3Lrbz0BZa+Ak6nJ0UGownAk470FneWKcl3UdPSkAM9yhCxtWcnImJOnT/LA8wqkF6H6Wg43gUMGbBCug7VCbYL/+T26qYLQSGH7RW7oxqwIDAQAB",
9+
"content_security_policy": {
10+
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
11+
}
12+
}

playwright.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,9 @@ export default defineConfig({
5959
},
6060
},
6161
},
62+
{
63+
name: "chrome-extension",
64+
testDir: "./src/frontend/tests/e2e-playwright-chrome-extension",
65+
},
6266
],
6367
});

src/frontend/src/lib/utils/transport/legacy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
AuthRequestCodec,
1212
DelegationParamsCodec,
1313
AuthResponseCodec,
14+
OriginSchema,
1415
} from "$lib/utils/transport/utils";
1516
import {
1617
Delegation,
@@ -27,7 +28,7 @@ const REDIRECT_SESSION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
2728
const OUTER_DELEGATION_EXPIRATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
2829

2930
const RedirectMessageSchema = z.object({
30-
origin: z.httpUrl(),
31+
origin: OriginSchema, // Accepts http, https, chrome-extension, etc.
3132
data: z.unknown(),
3233
});
3334

src/frontend/src/lib/utils/transport/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ export const StringOrNumberToBigIntCodec = z.codec(
133133
},
134134
);
135135

136+
/**
137+
* Validates that a string is a valid origin with
138+
* no path, query, or fragment. Accepts any URL
139+
* scheme (e.g. http, https, chrome-extension).
140+
*/
136141
export const OriginSchema = z.string().refine(
137142
(value) => {
138143
try {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from "@playwright/test";
2+
import { chromeExtensionTest } from "../e2e-playwright/fixtures/chrome-extension";
3+
import { addVirtualAuthenticator } from "../e2e-playwright/utils";
4+
5+
const authorizeTest = chromeExtensionTest.extend({
6+
authorizeConfig: ({ extensionUrl }, use) =>
7+
use({
8+
protocol: "legacy",
9+
testAppURL: extensionUrl,
10+
}),
11+
});
12+
13+
authorizeTest.describe("Authorize from chrome extension", () => {
14+
authorizeTest.afterEach(({ authorizedPrincipal }) => {
15+
expect(authorizedPrincipal?.isAnonymous()).toBe(false);
16+
});
17+
18+
authorizeTest("should authenticate", async ({ authorizePage }) => {
19+
await addVirtualAuthenticator(authorizePage.page);
20+
await authorizePage.page
21+
.getByRole("button", { name: "Continue with Passkey" })
22+
.click();
23+
await authorizePage.page
24+
.getByRole("button", { name: "Create new identity" })
25+
.click();
26+
await authorizePage.page.getByLabel("Identity name").fill("Extension User");
27+
await authorizePage.page
28+
.getByRole("button", { name: "Create identity" })
29+
.click();
30+
await authorizePage.page
31+
.getByRole("button", { name: "Continue", exact: true })
32+
.click();
33+
});
34+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { chromium, type BrowserContext } from "@playwright/test";
2+
import path from "path";
3+
import { test } from "./";
4+
5+
const pathToExtension = path.resolve("demos/test-app/dist");
6+
7+
export const chromeExtensionTest = test.extend<{
8+
context: BrowserContext;
9+
extensionId: string;
10+
extensionUrl: string;
11+
}>({
12+
// Spread operator is required by PlayWright
13+
// eslint-disable-next-line no-empty-pattern
14+
context: async ({}, use) => {
15+
const context = await chromium.launchPersistentContext("", {
16+
channel: "chromium",
17+
args: [
18+
`--disable-extensions-except=${pathToExtension}`,
19+
`--load-extension=${pathToExtension}`,
20+
"--ignore-certificate-errors",
21+
"--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost",
22+
],
23+
});
24+
// Close the default about:blank page so that page indices align
25+
// with what the authorize fixtures expect.
26+
const [defaultPage] = context.pages();
27+
if (defaultPage !== undefined) {
28+
await defaultPage.close();
29+
}
30+
await use(context);
31+
await context.close();
32+
},
33+
extensionId: async ({ context }, use) => {
34+
let [serviceWorker] = context.serviceWorkers();
35+
if (serviceWorker === undefined) {
36+
serviceWorker = await context.waitForEvent("serviceworker");
37+
}
38+
await use(serviceWorker.url().split("/")[2]);
39+
},
40+
extensionUrl: async ({ extensionId }, use) => {
41+
await use(`chrome-extension://${extensionId}/index.html`);
42+
},
43+
});

0 commit comments

Comments
 (0)