Skip to content

Commit

Permalink
Add hash instead of unsafe-inline for naive-ui (#33)
Browse files Browse the repository at this point in the history
* Add hash instead of unsafe-inline for naive-ui

* Migrate GA to node 20

* Check errors in browser's console
  • Loading branch information
getCryptoAddress authored Nov 7, 2024
1 parent f587459 commit 80d8326
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 20 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/buildAndDeploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ jobs:
group: build-and-deploy
cancel-in-progress: true
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down Expand Up @@ -49,7 +46,7 @@ jobs:
run: PLAYWRIGHT_USE_BUILD=1 npm run test:e2e

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
if: ${{ github.ref == 'refs/heads/master' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/checkPullRequests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ jobs:
group: ${{ github.head_ref }}
cancel-in-progress: true
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/makeArtifactWithTestScreenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ jobs:
make_artifact_with_test_screenshots:
name: Make artifact with Test Screenshots
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down
39 changes: 37 additions & 2 deletions e2e/vue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
import { expect, test } from "@playwright/test";

// See here how to get started:
// https://playwright.dev/docs/intro
// Documentation: https://playwright.dev/docs/intro

let errorMessagesCount = 0;

const ignoreErrors = [
"ResizeObserver loop completed with undelivered notifications.",
];

// Register a global error listener
test.beforeEach(async ({ page }) => {
errorMessagesCount = 0;

page.on("pageerror", (error) => {
if (ignoreErrors.includes(error.message)) {
return;
}
console.log(">> Console error: ", error);
++errorMessagesCount;
});
});

test.afterEach(() => {
expect(errorMessagesCount).toBe(0);
});

test("visits the app root url, sitemap.txt and robots.txt", async ({
page,
browserName,
}) => {
await page.goto("/");
await page.waitForTimeout(2000);
await expect(page.locator("h1")).toHaveText("Get Crypto Address");

// Next tests are chromium only
if (browserName !== "chromium") {
return;
}

if (process.env.PLAYWRIGHT_USE_BUILD) {
await page.goto("/sitemap.txt");
await page.waitForTimeout(500);
expect(await page.locator("pre").innerText()).toMatchSnapshot(
"sitemap.txt",
);
}

await page.goto("/robots.txt");
await page.waitForTimeout(500);
expect(await page.locator("pre").innerText()).toMatchSnapshot("robots.txt");
});

Expand Down Expand Up @@ -52,6 +84,7 @@ test("General flow", async ({ page, context, browserName }) => {

// Generate new addresses
await page.getByRole("button", { name: "Generate new addresses" }).click();
await page.waitForTimeout(500);

// Check the count of generated addresses
const $addresses = page.locator('[data-test-el="key-address-item"]');
Expand Down Expand Up @@ -88,6 +121,7 @@ test("General flow", async ({ page, context, browserName }) => {
await $openModalButton.click();
const $modal = page.getByRole("dialog");
await $modal.waitFor({ state: "visible", timeout: 1000 });
await page.waitForTimeout(100);
const $modalSecret = $modal.locator(
'[data-test-id="dialog-qr-code-secret"] .n-thing-main__description',
);
Expand All @@ -99,6 +133,7 @@ test("General flow", async ({ page, context, browserName }) => {
const $modalMask = page.locator(".n-modal-mask");
await page.mouse.click(1, 1);
await $modalMask.waitFor({ state: "detached", timeout: 1000 });
await page.waitForTimeout(100);
}

/// Paper wallet page
Expand Down
18 changes: 18 additions & 0 deletions node/csp/addInlineStylesHashesToHtml.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Add hashes of inline styles to the CSP policy in the HTML.
*
* @description Naive-ui uses inline styles to style components.
*
* [tag-nonce]
* @param {string} html
* @param {string[]} listOfHashes
* @returns {*}
*/
export function addInlineStylesHashesToHtml(html, listOfHashes) {
const hashes = listOfHashes.map((hash) => `'${hash}'`).join(" ");

return html.replace(
"style-src 'self'",
`style-src 'self' 'unsafe-hashes' ${hashes}`,
);
}
46 changes: 46 additions & 0 deletions node/csp/getInlineStylesHashes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import crypto from "crypto";

/**
* Calculates SHA-256 hash of the given style content and returns it in base64 format.
*
* ```
* # Input
* max-width:250px;text-align:left;margin:0 auto;width:100%;
* # output
* sha256-O5IIiIzIB9wS0DmNOhwTAp7C6vPXN8QJ3R0ZS+HTUNM=
* ```
*
* @param {string} styleContent
* @returns {string}
*/
function calculateStyleHash(styleContent) {
const hash = crypto
.createHash("sha256")
.update(styleContent, "utf8")
.digest("base64");
return `sha256-${hash}`;
}

/**
* Extracts inline styles from the given HTML content.
*
* @param {string} appHtml - The HTML content to extract inline styles from.
* @returns {string[]} An array of inline style strings.
*/
function getInlineStyles(appHtml) {
return (
appHtml
.match(/ style=".*?"/g)
?.map((line) => line.replace(/^ style="/, "").replace(/"$/, "")) || []
);
}

/**
* Generates an array of unique SHA-256 hashes for all inline styles found in the given HTML content.
*
* @param {string} appHtml - The HTML content to extract and hash inline styles from.
* @returns {string[]} An array of unique SHA-256 hashes in base64 format.
*/
export function getInlineStylesHashes(appHtml) {
return [...new Set(getInlineStyles(appHtml).map(calculateStyleHash))];
}
10 changes: 8 additions & 2 deletions prerender.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fs from "node:fs";
import path from "node:path";
import { createServer } from "vite";
import { addInlineStylesHashesToHtml } from "./node/csp/addInlineStylesHashesToHtml.mjs";
import { getInlineStylesHashes } from "./node/csp/getInlineStylesHashes.mjs";
import generateSitemap from "./node/sitemap/generateSitemap.mjs";

// todo refactor file, separate into functions
Expand All @@ -21,7 +23,12 @@ generateSitemap(routerPaths, "https://getcryptoaddress.github.io", "dist");
for (const routerPath of routerPaths) {
const { appHtml, ctx } = await render(routerPath);

const pageHtml = template
let pageHtml = template;

const styleHashes = getInlineStylesHashes(appHtml);
pageHtml = addInlineStylesHashesToHtml(pageHtml, styleHashes);

pageHtml = pageHtml
.replace("<!--app-head-->", ctx?.teleports?.head || "")
.replace("<!--app-html-->", appHtml)
.replace(/<!--.*?-->/g, "")
Expand All @@ -32,7 +39,6 @@ for (const routerPath of routerPaths) {
recursive: true,
});
fs.writeFileSync(path.join(pageFolder, "index.html"), pageHtml);
console.log("Generated:", path.join(pageFolder, "index.html"));
await new Promise((resolve) => setTimeout(resolve, 300));
}
await vite.close();

0 comments on commit 80d8326

Please sign in to comment.