fix(web): redirect HTTP to HTTPS to fix Safari CORS hang

When users type 'mana.how' (no scheme), Safari and other browsers default
to HTTP. Cloudflare/cloudflared serves the page over HTTP without
rewriting the scheme. The browser then sends 'Origin: http://mana.how'
on every fetch, but mana-auth CORS only allows 'https://mana.how'.

Result: every auth request fails, the SSO check throws, AuthGate hangs
on the loading spinner forever, and the page never finishes loading.

Fix: detect HTTP requests in hooks.server.ts via cf-visitor /
x-forwarded-proto / event.url.protocol and 301-redirect to HTTPS before
serving any content. Localhost is exempted for dev.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-11 20:38:21 +02:00
parent f3cc853e08
commit 7ba058c017
14 changed files with 174 additions and 2 deletions

View file

@ -372,6 +372,12 @@ const lavenderDark: ThemeColors = {
/**
* Complete theme variant definitions
*
* Each theme can also carry a `paper` descriptor a tileable texture
* the workbench page-shell uses as a grain overlay. Swap the filename
* here to change the texture for a whole theme in one place. Assets
* live under `apps/<app>/static/textures/paper/` and are CC0-licensed
* (see that directory's LICENSE.txt for provenance).
*/
export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
lume: {
@ -382,6 +388,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 47,
light: lumeLight,
dark: lumeDark,
paper: {
url: '/textures/paper/paper-001.jpg',
blendMode: 'multiply',
opacityLight: 0.4,
opacityDark: 0.18,
},
},
nature: {
name: 'nature',
@ -391,6 +403,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 122,
light: natureLight,
dark: natureDark,
paper: {
url: '/textures/paper/cardboard-001.jpg',
blendMode: 'multiply',
opacityLight: 0.32,
opacityDark: 0.14,
},
},
stone: {
name: 'stone',
@ -400,6 +418,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 200,
light: stoneLight,
dark: stoneDark,
paper: {
url: '/textures/paper/paper-005.jpg',
blendMode: 'multiply',
opacityLight: 0.35,
opacityDark: 0.15,
},
},
ocean: {
name: 'ocean',
@ -409,6 +433,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 199,
light: oceanLight,
dark: oceanDark,
paper: {
url: '/textures/paper/paper-003.jpg',
blendMode: 'multiply',
opacityLight: 0.3,
opacityDark: 0.12,
},
},
// Extended themes (not in PillNav by default, can be pinned)
sunset: {
@ -419,6 +449,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 15,
light: sunsetLight,
dark: sunsetDark,
paper: {
url: '/textures/paper/paper-006.jpg',
blendMode: 'multiply',
opacityLight: 0.38,
opacityDark: 0.16,
},
},
midnight: {
name: 'midnight',
@ -428,6 +464,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 260,
light: midnightLight,
dark: midnightDark,
paper: {
url: '/textures/paper/paper-004.jpg',
blendMode: 'overlay',
opacityLight: 0.35,
opacityDark: 0.22,
},
},
rose: {
name: 'rose',
@ -437,6 +479,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 340,
light: roseLight,
dark: roseDark,
paper: {
url: '/textures/paper/paper-002.jpg',
blendMode: 'multiply',
opacityLight: 0.32,
opacityDark: 0.14,
},
},
lavender: {
name: 'lavender',
@ -446,6 +494,12 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
hue: 270,
light: lavenderLight,
dark: lavenderDark,
paper: {
url: '/textures/paper/cardboard-002.jpg',
blendMode: 'soft-light',
opacityLight: 0.4,
opacityDark: 0.18,
},
},
};

View file

@ -104,6 +104,33 @@ export interface ThemeVariantDefinition {
hue: number;
light: ThemeColors;
dark: ThemeColors;
/**
* Optional "paper grain" overlay for workbench page surfaces.
* Each theme can ship its own tileable texture to give pages a
* distinct tactile character. The consuming app is responsible for
* serving the asset at the given URL (typically under `/textures/`).
* When undefined, no paper overlay is applied for this theme.
*/
paper?: {
/** URL / absolute path to a seamless tileable texture */
url: string;
/** CSS blend-mode for the overlay vs. the underlying card bg */
blendMode?:
| 'multiply'
| 'overlay'
| 'soft-light'
| 'hard-light'
| 'screen'
| 'darken'
| 'lighten'
| 'color-burn';
/** Opacity of the paper layer in light mode (0..1, default 0.35) */
opacityLight?: number;
/** Opacity of the paper layer in dark mode (0..1, default 0.15) */
opacityDark?: number;
/** CSS background-size (default "240px 240px") */
size?: string;
};
}
/**

View file

@ -112,6 +112,25 @@ export function applyThemeToDocument(
root.style.setProperty(key, value);
});
// Set per-theme paper-grain CSS variables (consumed by PageShell).
// Unset the vars for themes without a paper config so they don't
// leak across theme switches.
const paper = THEME_DEFINITIONS[variant]?.paper;
if (paper) {
root.style.setProperty('--paper-texture', `url("${paper.url}")`);
root.style.setProperty('--paper-blend-mode', paper.blendMode ?? 'multiply');
root.style.setProperty(
'--paper-opacity',
String(effectiveMode === 'dark' ? (paper.opacityDark ?? 0.15) : (paper.opacityLight ?? 0.35))
);
root.style.setProperty('--paper-size', paper.size ?? '240px 240px');
} else {
root.style.removeProperty('--paper-texture');
root.style.removeProperty('--paper-blend-mode');
root.style.removeProperty('--paper-opacity');
root.style.removeProperty('--paper-size');
}
// Set data-theme attribute
root.setAttribute('data-theme', variant);