lovasoa / SQLpage

SQL-only webapp builder, empowering data analysts to build websites and applications quickly
https://sql.ophir.dev
MIT License
880 stars 62 forks source link

Error loading javascript in project root #467

Open srcarvalho12 opened 6 days ago

srcarvalho12 commented 6 days ago

It is possible to get around this:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://cdn.jsdelivr.net". Either the 'unsafe-inline' keyword, a hash ('sha256-XkxQ/UEKO+cl+Q7H6G/04zQC/hALRvbbVW9YE8/SpZw='), or a nonce ('nonce-...') is required to enable inline execution .

lovasoa commented 6 days ago

Hello ! Did you try to inline a script in a custom component ?

The (restrictive) content-security policy currently cannot be disabled. Its goal is to prevent you from accidentally create components that are vulnerable to XSS attacks. The easiest workaround is to write your code in a separate javascript file, and to include it with a script tag, and using the javascript property of the shell component.

In my_component.handlebars, replace

<script>
  my_custom_code("{{ my_data }}");
</script>

with

<script src="/script.js" data-something="{{ my_data }}"></script>
amrutadotorg commented 6 days ago

Hi, in PostgreSQL (not sure about other db) you can do something like this:

shell.sql:

WITH nonce AS (
    SELECT generate_nonce() AS value
)
-- Set the CSP header with the generated nonce
SELECT 
    'http_header' AS component,
    CONCAT(
        'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net https://*.vimeocdn.com https://*.vimeo.com https://*.soundcloud.com https://*.sndcdn.com https://*.cloudflare.com ''nonce-', 
        (SELECT value FROM nonce), 
        ''''
    ) AS "Content-Security-Policy",
    (SELECT value FROM nonce) AS nonce_value;

SELECT 'shell' AS component,
[your shell code here...]

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE OR REPLACE FUNCTION public.generate_nonce()
RETURNS text
LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN encode(gen_random_bytes(16), 'base64');
END;
$function$;
lovasoa commented 6 days ago

@amrutadotorg , do you really need the nonce ? If you just generate a nonce, put it the header, and then immediately forget about it, you could just as well generate a Content-Security-Policy without a nonce, right ?

srcarvalho12 commented 6 days ago

I would like to use EmulatoJS to add emulators to my website and I need to import the EmulatorJS library, I have already downloaded the files and I am trying to access them through the project root

amrutadotorg commented 6 days ago

@lovasoa right, so what would be the best way to deal with CSP when using external sources like

https://*.openstreetmap.org https://cdn.jsdelivr.net https://*.vimeocdn.com https://*.vimeo.com https://*.soundcloud.com https://*.sndcdn.com https://*.cloudflare.com ?

like here:

WITH soundcloud_data AS (
    SELECT
        meta_value::jsonb AS audio_data
    FROM
        post_meta
    WHERE
        post_id = $id::int AND meta_key = 'soundcloud'
)
SELECT
    audio_data->>'url' AS embed,
    'autoplay' AS allow,
    'iframe' AS embed_mode,
    '300' AS height,
    'audio'   as id
FROM
    soundcloud_data
WHERE
    audio_data->>'url' IS NOT NULL;
lovasoa commented 6 days ago

@amrutadotorg : You could just remove the nonce from the CSP, since it looks like you are not using it anywhere. Keep the other sources and remove the nonce- part.

@srcarvalho12 : can you show us what you are doing ? If you just want to include external js sources, you can download them and include them with a local path using the javascript attribute of the shell component

srcarvalho12 commented 6 days ago

Yes yes, I'm adding the local files part, I'm adding the following code: (The files are inside the root where index.sql is called)

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script>
    EJS_player = "#gameview";
    EJS_core = "gba";
    EJS_pathtodata = "/data/";
    EJS_gameUrl = "https://allancoding-website-files.on.drv.tw/ROM_FILE_NAME_&_PATH.zip";
</script>
<script src="/data/loader.js"></script>
lovasoa commented 6 days ago

Oh, so you are serving a .html file directly without executing any sql ? You can move the inline script to a separate file:

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script src="/mygame.js"></script>
<script src="/data/loader.js"></script>

and in mygame.js:

    EJS_player = "#gameview";
    EJS_core = "gba";
    EJS_pathtodata = "/data/";
    EJS_gameUrl = "https://allancoding-website-files.on.drv.tw/ROM_FILE_NAME_&_PATH.zip";
srcarvalho12 commented 6 days ago

Ahhhhh wonderful, that's right! And I believe that to pass parameters the following can be done:

<script src="mygame.js" data-param1="value1" data-param2="value2"></script>

And in mygame.js I can receive it like this:


const scriptTag = document.currentScript;
const param1 = scriptTag.getAttribute('data-param1');
const param2 = scriptTag.getAttribute('data-param2');
srcarvalho12 commented 6 days ago

I sincerely thank you for your help!

amrutadotorg commented 6 days ago

Thank you, I changed to:

-- Set the CSP header without generating a nonce
SELECT 
    'http_header' AS component,
    'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net https://*.vimeocdn.com https://*.vimeo.com https://*.soundcloud.com https://*.sndcdn.com https://*.cloudflare.com' AS "Content-Security-Policy";
srcarvalho12 commented 6 days ago

But anyway I get when trying to run the emulator:


emulator.min.js:1 Refused to create a worker from 'blob:http://localhost:3000/eb69bfd7-132f-4c8c-8f67-dbb4e565856d' because it violates the following Content Security Policy directive: "script-src 'self 'https://cdn.jsdelivr.net". Note that 'worker-src' was not explicitly set, so 'script-src' is used as a fallback.

downloadFile.responseType@emulator.min.js:1
a.onload@emulator.min.js:1
load (asynchronous)
downloadFile@emulator.min.js:1
(anonymous) @emulator.min.js:1
s@emulator.min.js:1
r@emulator.min.js:1
checkCompression@emulator.min.js:1
n@emulator.min.js:1
(anonymous) @emulator.min.js:1
Promise.then (asynchronous)
downloadGameCore@emulator.min.js:1
startButtonClicked@emulator.min.js:1
emulator.min.js:1 Uncaught DOMException: Failed to construct 'Worker': Access to the script at 'blob:http://localhost:3000/eb69bfd7-132f-4c8c-8f67-dbb4e565856d' is denied by the document's Content Security Policy.
    at downloadFile.responseType (http://localhost:3000/data/emulator.min.js:1:231400)
    at a.onload (http://localhost:3000/data/emulator.min.js:1:221028)
lovasoa commented 6 days ago
const param1 = scriptTag.getAttribute('data-param1');
const param2 = scriptTag.getAttribute('data-param2');

You can use the dataset object

const {param1, param2} = scriptTag.dataset;

But anyway I get when trying to run the emulator: emulator.min.js:1 Refused to create a worker from 'blob:http://...' because it violates the following Content Security Policy directive: "script-src 'self 'https://cdn.jsdelivr.net". Note that 'worker-src' was not explicitly set, so 'script-src' is used as a fallback.

Indeed. I'll add a way to customize the CSP in future versions. In the meantime, you can create a custom component.

sqlpage/templates/emulator.handlebars

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script src="/mygame.js" data-gameurl="{{ gameurl }}"></script>
<script src="/data/loader.js"></script>

game.sql

SELECT 'http_header' AS component, 'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net; worker-src ''self'' blob:' AS "Content-Security-Policy";

select 'emulator' as component, 'http://my-url' as gameurl;
srcarvalho12 commented 6 days ago

All good! Thank you for your help, from the bottom of my heart!

srcarvalho12 commented 6 days ago

I used this to http_header and it worked:


SELECT 'http_header' AS component, 'script-src ''self'' ''unsafe-eval'' blob: https://*.openstreetmap.org https://cdn.jsdelivr.net https://cdn.emulatorjs .org; worker-src ''self'' blob:' AS "Content-Security-Policy";