zudoku 0.78.1 → 0.78.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js CHANGED
@@ -3853,7 +3853,7 @@ import {
3853
3853
  // package.json
3854
3854
  var package_default = {
3855
3855
  name: "zudoku",
3856
- version: "0.78.0",
3856
+ version: "0.78.1",
3857
3857
  type: "module",
3858
3858
  sideEffects: [
3859
3859
  "**/*.css",
@@ -7786,6 +7786,7 @@ async function getViteConfig(dir, configEnv, options = {}) {
7786
7786
  "process.env.ZUDOKU_VERSION": JSON.stringify(package_default.version),
7787
7787
  "process.env.IS_ZUPLO": ZuploEnv.isZuplo,
7788
7788
  "import.meta.env.IS_ZUPLO": ZuploEnv.isZuplo,
7789
+ "import.meta.env.ZUDOKU_HAS_SERVER": JSON.stringify(options.ssr === true),
7789
7790
  "import.meta.env.ZUPLO_PUBLIC_DEPLOYMENT_NAME": JSON.stringify(deploymentName),
7790
7791
  ...defineEnvVars([
7791
7792
  "SENTRY_DSN",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.78.1",
3
+ "version": "0.78.2",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -13,7 +13,9 @@ import { BootstrapClient } from "../lib/components/Bootstrap.js";
13
13
  import { joinUrl } from "../lib/util/joinUrl.js";
14
14
  import { getRoutesByConfig, shikiReady } from "./main.js";
15
15
 
16
- setupCookieSync(authState, joinUrl(config.basePath, "/__z/auth/session"));
16
+ if (import.meta.env.ZUDOKU_HAS_SERVER) {
17
+ setupCookieSync(authState, joinUrl(config.basePath, "/__z/auth/session"));
18
+ }
17
19
 
18
20
  const routes = getRoutesByConfig(config);
19
21
  // biome-ignore lint/style/noNonNullAssertion: We know the root element exists
@@ -54,6 +54,31 @@ const safeSerialize = (data: unknown) =>
54
54
  .replace(/\u2028/g, "\\u2028")
55
55
  .replace(/\u2029/g, "\\u2029");
56
56
 
57
+ // Profile must come from the verifier so a forged cookie can't render
58
+ // protected HTML. SSG has no runtime cookie and short-circuits.
59
+ const resolveSsrAuth = async (
60
+ request: Request,
61
+ ): Promise<SSRAuthState | undefined> => {
62
+ if (!import.meta.env.ZUDOKU_HAS_SERVER || !configuredAuthProvider) return;
63
+
64
+ const { accessToken } = parseCookies(request);
65
+ if (!accessToken || !verifier)
66
+ return { accessToken: undefined, profile: null };
67
+
68
+ try {
69
+ const verified = await verifier(accessToken);
70
+ return verified
71
+ ? { accessToken, profile: verified.profile }
72
+ : { accessToken: undefined, profile: null };
73
+ } catch (error) {
74
+ logger.error(
75
+ `SSR auth verifier error (${request.method} ${request.url}):`,
76
+ error,
77
+ );
78
+ return { accessToken: undefined, profile: null };
79
+ }
80
+ };
81
+
57
82
  // Statically importing shiki.ts here ensures it's in the SSR bundle.
58
83
  // main.tsx dynamically imports it instead to enable lazy loading on the client.
59
84
  await import("virtual:zudoku-shiki-register").then(
@@ -75,27 +100,7 @@ export const handleRequest = async ({
75
100
  basePath?: string;
76
101
  bypassProtection?: boolean;
77
102
  }): Promise<Response> => {
78
- const { accessToken } = parseCookies(request);
79
- // Always derive profile from the verifier, never trust the client cookie.
80
- // An unverified profile would let a stale/forged cookie render protected HTML.
81
- let verifiedProfile: SSRAuthState["profile"] = null;
82
- if (accessToken && verifier) {
83
- try {
84
- const verified = await verifier(accessToken);
85
- if (verified) verifiedProfile = verified.profile;
86
- } catch (error) {
87
- logger.error(
88
- `SSR auth verifier error (${request.method} ${request.url}):`,
89
- error,
90
- );
91
- }
92
- }
93
- const ssrAuth: SSRAuthState | undefined = configuredAuthProvider
94
- ? {
95
- accessToken: verifiedProfile ? accessToken : undefined,
96
- profile: verifiedProfile,
97
- }
98
- : undefined;
103
+ const ssrAuth = await resolveSsrAuth(request);
99
104
 
100
105
  // No-op lazy() on protected subtrees for unauthed requests so loaders
101
106
  // don't run for a 401 render.
package/src/app/main.tsx CHANGED
@@ -151,8 +151,9 @@ export const getRoutesByConfig = (config: ZudokuConfig): RouteObject[] => {
151
151
  </Meta>
152
152
  ),
153
153
  errorElement: <RouterError />,
154
+ // Chunk-gate protected routes only in SSR; SSG has no 401 to avoid.
154
155
  children:
155
- typeof window === "undefined"
156
+ typeof window === "undefined" || !import.meta.env.ZUDOKU_HAS_SERVER
156
157
  ? processRoutes(routes)
157
158
  : wrapProtectedRoutes(
158
159
  processRoutes(routes),
@@ -49,11 +49,11 @@ const noopStorage: StateStorage = {
49
49
  const ssrAuthInitial =
50
50
  typeof window !== "undefined" ? window.ZUDOKU_SSR_AUTH : undefined;
51
51
 
52
- // When the server injected ZUDOKU_SSR_AUTH, cookies are the single source of
53
- // truth. Persisting would let stale localStorage contradict the SSR signal
54
- // (ghost login after cookies expire). SSG has no SSR signal, so persist the
55
- // full snapshot for reload continuity.
56
- const ssrMode = ssrAuthInitial !== undefined;
52
+ // SSR builds use cookies as the source of truth; SSG uses localStorage.
53
+ // `import.meta.env` is missing when this module is loaded outside a Vite
54
+ // build (e.g. esbuild bundling a vite.*.config.ts), so guard the access.
55
+ const ssrMode =
56
+ typeof import.meta.env !== "undefined" && import.meta.env.ZUDOKU_HAS_SERVER;
57
57
 
58
58
  export const authState = create<AuthState>()(
59
59
  persist(
@@ -123,6 +123,7 @@ export async function getViteConfig(
123
123
  "process.env.ZUDOKU_VERSION": JSON.stringify(packageJson.version),
124
124
  "process.env.IS_ZUPLO": ZuploEnv.isZuplo,
125
125
  "import.meta.env.IS_ZUPLO": ZuploEnv.isZuplo,
126
+ "import.meta.env.ZUDOKU_HAS_SERVER": JSON.stringify(options.ssr === true),
126
127
  "import.meta.env.ZUPLO_PUBLIC_DEPLOYMENT_NAME":
127
128
  JSON.stringify(deploymentName),
128
129
  ...defineEnvVars([
package/src/vite-env.d.ts CHANGED
@@ -5,4 +5,5 @@ declare module "vite/modulepreload-polyfill" {}
5
5
  interface ImportMetaEnv {
6
6
  readonly IS_ZUPLO: boolean;
7
7
  readonly ZUPLO_BUILD_ID?: string;
8
+ readonly ZUDOKU_HAS_SERVER: boolean;
8
9
  }