From 3fe6af25f141fb4b1eb92c31ffb9939ac4ece3b0 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 14 Jul 2026 02:15:48 +0200 Subject: [PATCH 1/7] fix(loading): fix issue with sveltekit and scripts fix issues like: ``` Executing inline script violates the following Content Security Policy directive 'script-src 'self''. Either the 'unsafe-inline' keyword, a hash ('sha256-Vv9My0PApDW3C+xGLu9cH98KLrOg/Qhc7hlT1lK5tyM='), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked. ``` --- src/interfaces/web/mod.rs | 60 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/interfaces/web/mod.rs b/src/interfaces/web/mod.rs index 82dbc223..c0ca5eae 100644 --- a/src/interfaces/web/mod.rs +++ b/src/interfaces/web/mod.rs @@ -169,10 +169,43 @@ fn csp_hash(script: &str) -> String { /// Text content of every inline ``, and emit the +/// wrong hash — the real inline script then fails CSP with `script-src 'self'`. fn inline_scripts(html: &str) -> Vec<&str> { let mut scripts = Vec::new(); let mut cursor = 0; - while let Some(rel) = find_ci(&html[cursor..], "` in prose and would otherwise poison the + // scanner. Comment-nesting is not a spec concern. + let next_comment = find_ci(tail, "").map(|r| c + 4 + r + 3); + cursor = match end_rel { + Some(e) => cursor + e, + None => break, // unterminated comment; give up + }; + continue; + } + (Some(c), None) => { + let end_rel = find_ci(&tail[c + 4..], "-->").map(|r| c + 4 + r + 3); + cursor = match end_rel { + Some(e) => cursor + e, + None => break, + }; + continue; + } + (None, None) => break, + _ => {} // next thing is a real \n", + "\n", + ); + let scripts = inline_scripts(html); + assert_eq!(scripts, vec!["alert(1);", "boot();"]); + } + + #[test] + fn unterminated_comment_bails_out_gracefully() { + // Malformed input: `

{#if mode === 'login'} {t('auth.sign_in', 'Sign in')} @@ -262,19 +377,73 @@ {/if}

- {#if page.url.searchParams.get('source') === 'session_expired'} -
- {t('auth.session_expired', 'Your session expired. Please sign in again.')} + {#if sessionExpiredNotice} + + {/if} + + {#if postRegisterNotice && mode === 'login'} +
+ {postRegisterNotice} +
{/if} {#if mode === 'login'} - {#if passwordLoginEnabled} - {#if error}{/if} + + {#if passwordLoginEnabled || magicLinkLoginEnabled} + {#if error} + + {/if} + {#if magicStatus} +
+ {magicStatus.text} +
+ {/if}
-
- -
- - + {#if passwordLoginEnabled} +
+ +
+ + +
+ {#if capsOn} +
{t('auth.caps_lock', 'Caps Lock is on')}
+ {/if}
- {#if capsOn} -
{t('auth.caps_lock', 'Caps Lock is on')}
- {/if} -
+ {/if} - - - {#if magicOpen} -
-

- {t( - 'auth.magic_hint', - "No password? Enter your email and we'll send you a one-time sign-in link." - )} -

-
-
- -
- -
-
- -
- {#if magicStatus} -
- {magicStatus.text} -
- {/if} -
- {/if} {/if} {#if oidc.enabled} @@ -433,19 +569,11 @@ {#if regError}{/if} - {#if regSuccess}
{regSuccess}
{/if}
-
- - -
+
- -
- - -
- {#if regCapsOn} -
{t('auth.caps_lock', 'Caps Lock is on')}
- {/if} + +
-
- -
- - + + {#if passwordLoginEnabled} +
+ +
+ + +
+ {#if regCapsOn} +
{t('auth.caps_lock', 'Caps Lock is on')}
+ {/if}
- {#if matchState} -
- {matchState === 'ok' - ? t('auth.passwords_match', 'Passwords match') - : t('auth.passwords_mismatch', "Passwords don't match")} + {#if !regEmailOnly} +
+ +
+ + +
+ {#if matchState} +
+ {matchState === 'ok' + ? t('auth.passwords_match', 'Passwords match') + : t('auth.passwords_mismatch', "Passwords don't match")} +
+ {/if}
{/if} -
+ {/if}
@@ -591,6 +748,7 @@ data-testid="login-setup-email-input" type="email" bind:value={setupEmail} + bind:this={setupEmailInput} autocomplete="email" required disabled={busy} @@ -691,7 +849,6 @@
{/if} - {/if}
-
-
- - {#if passwordLoginEnabled} -
- -
- - -
- {#if capsOn} -
{t('auth.caps_lock', 'Caps Lock is on')}
- {/if} -
- {/if} - - - - {/if} - - {#if oidc.enabled} - {#if passwordLoginEnabled} -
{t('auth.or', 'or')}
- {/if} - - - {t( - 'auth.sso_login_provider', - { provider: oidc.provider_name ?? 'SSO' }, - 'Sign in with {{provider}}' - )} - - {/if} - - {#if passwordLoginEnabled} -
- {t('auth.no_account', 'No account?')} - + {error}
{/if} - - {#if setupAvailable} -
- {t('auth.admin_setup', 'First time?')} - + {#if magicStatus} +
+ {magicStatus.text}
{/if} - {:else if mode === 'register'} - {#if regError}{/if} -
- +
- - -
-
-
- + {#if passwordLoginEnabled}
-
- {#if !regEmailOnly} -
- -
- - -
- {#if matchState} -
- {matchState === 'ok' - ? t('auth.passwords_match', 'Passwords match') - : t('auth.passwords_mismatch', "Passwords don't match")} -
- {/if} -
- {/if} {/if} - -
-
- {t('auth.have_account', 'Already have an account?')} - -
- {:else} -
-
-
1
-
{t('auth.setup_step1', 'Admin')}
-
-
-
2
-
{t('auth.setup_step2', 'System')}
-
-
-
3
-
{t('auth.setup_step3', 'Completed')}
-
-
- - {#if setupError}{/if} - {#if setupSuccess}
{setupSuccess}
{/if} - -
-
- -
- -
-
- -
- -
- -
-
- -
- -
- - -
- {#if setupCapsOn} -
{t('auth.caps_lock', 'Caps Lock is on')}
- {/if} -
- -
- -
- - -
- {#if setupMatchState} -
- {setupMatchState === 'ok' - ? t('auth.passwords_match', 'Passwords match') - : t('auth.passwords_mismatch', "Passwords don't match")} -
- {/if} -
+ {/if} + {#if oidc.enabled} + {#if passwordLoginEnabled} +
{t('auth.or', 'or')}
+ {/if} + + + {t( + 'auth.sso_login_provider', + { provider: oidc.provider_name ?? 'SSO' }, + 'Sign in with {{provider}}' + )} + + {/if} + + {#if passwordLoginEnabled}
- {t('auth.back_to_login', 'Already configured?')} + {t('auth.no_account', 'No account?')}
{/if} + {#if setupAvailable} +
+ {t('auth.admin_setup', 'First time?')} + +
+ {/if} + {:else if mode === 'register'} + {#if regError}{/if} +
+ +
+ + +
+
+ + +
+ + {#if passwordLoginEnabled} +
+ +
+ + +
+ {#if regCapsOn} +
{t('auth.caps_lock', 'Caps Lock is on')}
+ {/if} +
+ {#if !regEmailOnly} +
+ +
+ + +
+ {#if matchState} +
+ {matchState === 'ok' + ? t('auth.passwords_match', 'Passwords match') + : t('auth.passwords_mismatch', "Passwords don't match")} +
+ {/if} +
+ {/if} + {/if} + +
+
+ {t('auth.have_account', 'Already have an account?')} + +
+ {:else} +
+
+
1
+
{t('auth.setup_step1', 'Admin')}
+
+
+
2
+
{t('auth.setup_step2', 'System')}
+
+
+
3
+
{t('auth.setup_step3', 'Completed')}
+
+
+ + {#if setupError}{/if} + {#if setupSuccess}
{setupSuccess}
{/if} + +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+ {#if setupCapsOn} +
{t('auth.caps_lock', 'Caps Lock is on')}
+ {/if} +
+ +
+ +
+ + +
+ {#if setupMatchState} +
+ {setupMatchState === 'ok' + ? t('auth.passwords_match', 'Passwords match') + : t('auth.passwords_mismatch', "Passwords don't match")} +
+ {/if} +
+ + +
+ +
+ {t('auth.back_to_login', 'Already configured?')} + +
+ {/if} +