diff --git a/cloudbuild-pr.yaml b/cloudbuild-pr.yaml index 2bb911d83..9b4901a34 100644 --- a/cloudbuild-pr.yaml +++ b/cloudbuild-pr.yaml @@ -21,6 +21,8 @@ steps: - --cache-ttl=168h # 1 week # Bake in this revision's corresponding playground sandbox url - --build-arg=PLAYGROUND_SANDBOX=https://pr$_PR_NUMBER-$SHORT_SHA---lit-dev-playground-5ftespv5na-uc.a.run.app/ + # Testing Google Analytics ID + - --build-arg=GOOGLE_ANALYTICS_ID=G-PPMSZR9W18 # Create a new Cloud Run revision for the main site. # diff --git a/packages/lit-dev-content/package.json b/packages/lit-dev-content/package.json index 27d08dc2a..1ce880847 100644 --- a/packages/lit-dev-content/package.json +++ b/packages/lit-dev-content/package.json @@ -18,7 +18,7 @@ "build:samples": "rm -rf samples/js && node ../lit-dev-tools-esm/lib/generate-js-samples.js", "build:samples:watch": "npm run build:samples -- --watch", "fonts:manrope": "rm -rf site/fonts temp && mkdir -p site/fonts && git clone https://github.com/sharanda/manrope.git temp/manrope && cd temp/manrope && git checkout 9ffbc349f4659065b62f780fe6e9d5a93518bd95 && cp fonts/web/*.woff2 ../../site/fonts/ && cd ../.. && rm -rf temp", - "dev:build:site": "rm -rf _dev && ELEVENTY_ENV=dev PLAYGROUND_SANDBOX=http://localhost:5416/ GITHUB_CLIENT_ID=FAKE_CLIENT_ID GITHUB_AUTHORIZE_URL=http://localhost:5417/login/oauth/authorize eleventy", + "dev:build:site": "rm -rf _dev && ELEVENTY_ENV=dev PLAYGROUND_SANDBOX=http://localhost:5416/ GITHUB_CLIENT_ID=FAKE_CLIENT_ID GITHUB_AUTHORIZE_URL=http://localhost:5417/login/oauth/authorize GOOGLE_ANALYTICS_ID=G-PPMSZR9W18 eleventy", "dev:build:site:watch": "npm run dev:build:site -- --watch", "dev:serve": "node ../lit-dev-tools-esm/lib/dev-server.js --open", "format": "../../node_modules/.bin/prettier \"**/*.{ts,js,json,html,css,md}\" --write" diff --git a/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts b/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts index 1dd27bbb2..6d47cdbac 100644 --- a/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts +++ b/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts @@ -39,10 +39,12 @@ export interface ContentSecurityPolicyMiddlewareOptions { const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev'; /** - * TODO(aomarks) Generate this automatically. See + * TODO(aomarks) Generate these automatically. See * https://github.com/lit/lit.dev/issues/531. */ -const GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH = `'sha256-bG+QS/Ob2lFyxJ7r7PCtj/a8YofLHFx4t55RzjR1znI='`; +const GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH = + `'sha256-bG+QS/Ob2lFyxJ7r7PCtj/a8YofLHFx4t55RzjR1znI='` + // With production GA ID. + ` 'sha256-RzTTI/28QrruyqG1AYHiMuUgzLJnScrkQZ+k4vM54sc='`; // With testing GA ID. /** * Creates a Koa middleware that sets the lit.dev Content Security Policy (CSP) @@ -71,7 +73,7 @@ export const contentSecurityPolicyMiddleware = ( ].join('; '); // Policy for the main HTML entrypoints (homepage, docs, playground, etc.) - const htmlCsp = makePolicy( + const entrypointsCsp = makePolicy( // TODO(aomarks) We should also enable trusted types, but that will require // a policy in playground-elements for creating the worker, and a policy // es-module-lexer for doing an eval (see next comment for more on that). @@ -80,7 +82,7 @@ export const contentSecurityPolicyMiddleware = ( // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed. // See comment below about playgroundWorkerCsp. `'unsafe-eval'`, - `https://www.googletagmanager.com/gtag/js`, + `https://www.googletagmanager.com/`, GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH, ...(opts.inlineScriptHashes?.map((hash) => `'${hash}'`) ?? []), // In dev mode, data: scripts are required because @web/dev-server uses them @@ -93,7 +95,12 @@ export const contentSecurityPolicyMiddleware = ( // // In dev mode, ws: connections are required because @web/dev-server uses // them for automatic reloads. - `connect-src 'self' https://unpkg.com/${opts.devMode ? ` ws:` : ''}`, + `connect-src ${[ + `'self'`, + 'https://unpkg.com/', + 'https://www.google-analytics.com/', + ...(opts.devMode ? [` ws:`] : []), + ].join(' ')}`, // Playground previews and embedded YouTube videos. `frame-src ${opts.playgroundPreviewOrigin} https://www.youtube-nocookie.com/`, @@ -176,11 +183,19 @@ export const contentSecurityPolicyMiddleware = ( const strictFallbackCsp = makePolicy(`default-src 'none'`); return async (ctx, next) => { - await next(); - let policy: string; - if (ctx.response.type === 'text/html') { - policy = htmlCsp; + // Note we can't rely on ctx.type being set by the downstream middleware, + // because for a 304 Not Modified response, the Content-Type header will not + // be set. + // + // Also note that we don't necessarily need to set a CSP on 304 responses at + // all, because the browser will use the one from the previous 200 response + // if missing (as it does for all headers). However, by including a CSP on + // 304 responses, we cover the case where the CSP has changed, but the + // file's content (and hence ETag) has not. Note this approach would not + // work if we were using nonces instead of hashes. + if (ctx.path.endsWith('/')) { + policy = entrypointsCsp; } else if (ctx.path.endsWith('/playground-typescript-worker.js')) { policy = playgroundWorkerCsp; } else { @@ -189,5 +204,6 @@ export const contentSecurityPolicyMiddleware = ( // TODO(aomarks) Remove -Report-Only suffix when we are confident the // policy is working. ctx.set('Content-Security-Policy-Report-Only', policy); + return next(); }; };