@jant/core 0.3.18 → 0.3.20

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.
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Onboarding Middleware
3
3
  *
4
- * Redirects all requests to /setup if onboarding hasn't been completed.
4
+ * Redirects key page routes to /setup if onboarding hasn't been completed.
5
+ * Uses an allowlist approach: only explicitly listed page routes are redirected,
6
+ * so static assets, API endpoints, feeds, and other resources always pass through.
5
7
  * Caches the result in memory so the DB is only queried once per isolate lifetime.
6
8
  */ /** In-memory cache — persists across requests within a Worker isolate */ let onboardingComplete = false;
7
9
  /**
@@ -14,7 +16,7 @@
14
16
  return next();
15
17
  }
16
18
  const path = new URL(c.req.url).pathname;
17
- if (shouldBypass(path)) {
19
+ if (!shouldRedirect(path)) {
18
20
  return next();
19
21
  }
20
22
  const isComplete = await c.var.services.settings.isOnboardingComplete();
@@ -25,8 +27,11 @@
25
27
  return c.redirect("/setup");
26
28
  };
27
29
  }
28
- function shouldBypass(path) {
29
- return path === "/setup" || path === "/health" || path === "/signin" || path === "/signout" || path === "/reset" || path.startsWith("/api/auth/");
30
+ /**
31
+ * Only these page routes are redirected to /setup during onboarding.
32
+ * Everything else (assets, API, feeds, media, etc.) passes through.
33
+ */ function shouldRedirect(path) {
34
+ return path === "/" || path === "/signin" || path === "/reset" || path.startsWith("/dash");
30
35
  }
31
36
  /**
32
37
  * Reset the onboarding cache. Only for testing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@ function createApp(complete: boolean) {
32
32
  // Register routes for testing
33
33
  app.get("/", (c) => c.text("Home"));
34
34
  app.get("/dash", (c) => c.text("Dashboard"));
35
+ app.get("/dash/posts", (c) => c.text("Posts"));
35
36
  app.get("/archive", (c) => c.text("Archive"));
36
37
  app.get("/p/abc", (c) => c.text("Post"));
37
38
  app.get("/setup", (c) => c.text("Setup"));
@@ -40,6 +41,11 @@ function createApp(complete: boolean) {
40
41
  app.get("/signout", (c) => c.text("Signout"));
41
42
  app.get("/reset", (c) => c.text("Reset"));
42
43
  app.get("/api/auth/session", (c) => c.json({ ok: true }));
44
+ app.get("/assets/client-B2b-1X3C.js", (c) => c.text("js"));
45
+ app.get("/feed", (c) => c.text("rss"));
46
+ app.get("/robots.txt", (c) => c.text("robots"));
47
+ app.get("/sitemap.xml", (c) => c.text("sitemap"));
48
+ app.get("/media/abc.jpg", (c) => c.text("image"));
43
49
 
44
50
  return { app, getCallCount: mock.getCallCount };
45
51
  }
@@ -49,25 +55,41 @@ describe("requireOnboarding", () => {
49
55
  resetOnboardingCache();
50
56
  });
51
57
 
52
- it("redirects to /setup when onboarding not complete", async () => {
53
- const { app } = createApp(false);
54
- const res = await app.request("/", { redirect: "manual" });
55
- expect(res.status).toBe(302);
56
- expect(res.headers.get("Location")).toBe("/setup");
57
- });
58
+ describe("redirected paths", () => {
59
+ it("redirects / to /setup when onboarding not complete", async () => {
60
+ const { app } = createApp(false);
61
+ const res = await app.request("/", { redirect: "manual" });
62
+ expect(res.status).toBe(302);
63
+ expect(res.headers.get("Location")).toBe("/setup");
64
+ });
58
65
 
59
- it("redirects /dash to /setup when onboarding not complete", async () => {
60
- const { app } = createApp(false);
61
- const res = await app.request("/dash", { redirect: "manual" });
62
- expect(res.status).toBe(302);
63
- expect(res.headers.get("Location")).toBe("/setup");
64
- });
66
+ it("redirects /dash to /setup when onboarding not complete", async () => {
67
+ const { app } = createApp(false);
68
+ const res = await app.request("/dash", { redirect: "manual" });
69
+ expect(res.status).toBe(302);
70
+ expect(res.headers.get("Location")).toBe("/setup");
71
+ });
72
+
73
+ it("redirects /dash/* to /setup when onboarding not complete", async () => {
74
+ const { app } = createApp(false);
75
+ const res = await app.request("/dash/posts", { redirect: "manual" });
76
+ expect(res.status).toBe(302);
77
+ expect(res.headers.get("Location")).toBe("/setup");
78
+ });
79
+
80
+ it("redirects /signin to /setup when onboarding not complete", async () => {
81
+ const { app } = createApp(false);
82
+ const res = await app.request("/signin", { redirect: "manual" });
83
+ expect(res.status).toBe(302);
84
+ expect(res.headers.get("Location")).toBe("/setup");
85
+ });
65
86
 
66
- it("redirects /archive to /setup when onboarding not complete", async () => {
67
- const { app } = createApp(false);
68
- const res = await app.request("/archive", { redirect: "manual" });
69
- expect(res.status).toBe(302);
70
- expect(res.headers.get("Location")).toBe("/setup");
87
+ it("redirects /reset to /setup when onboarding not complete", async () => {
88
+ const { app } = createApp(false);
89
+ const res = await app.request("/reset", { redirect: "manual" });
90
+ expect(res.status).toBe(302);
91
+ expect(res.headers.get("Location")).toBe("/setup");
92
+ });
71
93
  });
72
94
 
73
95
  it("allows through when onboarding is complete", async () => {
@@ -97,45 +119,80 @@ describe("requireOnboarding", () => {
97
119
  expect(getCallCount()).toBe(2); // queried again
98
120
  });
99
121
 
100
- describe("bypass paths", () => {
101
- it("allows /setup without checking DB", async () => {
122
+ describe("non-redirected paths (pass through without DB check)", () => {
123
+ it("allows /setup", async () => {
102
124
  const { app, getCallCount } = createApp(false);
103
125
  const res = await app.request("/setup");
104
126
  expect(res.status).toBe(200);
105
127
  expect(getCallCount()).toBe(0);
106
128
  });
107
129
 
108
- it("allows /health without checking DB", async () => {
130
+ it("allows /health", async () => {
109
131
  const { app, getCallCount } = createApp(false);
110
132
  const res = await app.request("/health");
111
133
  expect(res.status).toBe(200);
112
134
  expect(getCallCount()).toBe(0);
113
135
  });
114
136
 
115
- it("allows /signin without checking DB", async () => {
137
+ it("allows /signout", async () => {
116
138
  const { app, getCallCount } = createApp(false);
117
- const res = await app.request("/signin");
139
+ const res = await app.request("/signout");
118
140
  expect(res.status).toBe(200);
119
141
  expect(getCallCount()).toBe(0);
120
142
  });
121
143
 
122
- it("allows /signout without checking DB", async () => {
144
+ it("allows /api/auth/*", async () => {
123
145
  const { app, getCallCount } = createApp(false);
124
- const res = await app.request("/signout");
146
+ const res = await app.request("/api/auth/session");
125
147
  expect(res.status).toBe(200);
126
148
  expect(getCallCount()).toBe(0);
127
149
  });
128
150
 
129
- it("allows /reset without checking DB", async () => {
151
+ it("allows /assets/*", async () => {
130
152
  const { app, getCallCount } = createApp(false);
131
- const res = await app.request("/reset");
153
+ const res = await app.request("/assets/client-B2b-1X3C.js");
132
154
  expect(res.status).toBe(200);
133
155
  expect(getCallCount()).toBe(0);
134
156
  });
135
157
 
136
- it("allows /api/auth/* without checking DB", async () => {
158
+ it("allows /feed", async () => {
137
159
  const { app, getCallCount } = createApp(false);
138
- const res = await app.request("/api/auth/session");
160
+ const res = await app.request("/feed");
161
+ expect(res.status).toBe(200);
162
+ expect(getCallCount()).toBe(0);
163
+ });
164
+
165
+ it("allows /robots.txt", async () => {
166
+ const { app, getCallCount } = createApp(false);
167
+ const res = await app.request("/robots.txt");
168
+ expect(res.status).toBe(200);
169
+ expect(getCallCount()).toBe(0);
170
+ });
171
+
172
+ it("allows /sitemap.xml", async () => {
173
+ const { app, getCallCount } = createApp(false);
174
+ const res = await app.request("/sitemap.xml");
175
+ expect(res.status).toBe(200);
176
+ expect(getCallCount()).toBe(0);
177
+ });
178
+
179
+ it("allows /media/*", async () => {
180
+ const { app, getCallCount } = createApp(false);
181
+ const res = await app.request("/media/abc.jpg");
182
+ expect(res.status).toBe(200);
183
+ expect(getCallCount()).toBe(0);
184
+ });
185
+
186
+ it("allows /archive", async () => {
187
+ const { app, getCallCount } = createApp(false);
188
+ const res = await app.request("/archive");
189
+ expect(res.status).toBe(200);
190
+ expect(getCallCount()).toBe(0);
191
+ });
192
+
193
+ it("allows /p/*", async () => {
194
+ const { app, getCallCount } = createApp(false);
195
+ const res = await app.request("/p/abc");
139
196
  expect(res.status).toBe(200);
140
197
  expect(getCallCount()).toBe(0);
141
198
  });
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Onboarding Middleware
3
3
  *
4
- * Redirects all requests to /setup if onboarding hasn't been completed.
4
+ * Redirects key page routes to /setup if onboarding hasn't been completed.
5
+ * Uses an allowlist approach: only explicitly listed page routes are redirected,
6
+ * so static assets, API endpoints, feeds, and other resources always pass through.
5
7
  * Caches the result in memory so the DB is only queried once per isolate lifetime.
6
8
  */
7
9
 
@@ -26,7 +28,7 @@ export function requireOnboarding(): MiddlewareHandler<Env> {
26
28
  }
27
29
 
28
30
  const path = new URL(c.req.url).pathname;
29
- if (shouldBypass(path)) {
31
+ if (!shouldRedirect(path)) {
30
32
  return next();
31
33
  }
32
34
 
@@ -40,14 +42,16 @@ export function requireOnboarding(): MiddlewareHandler<Env> {
40
42
  };
41
43
  }
42
44
 
43
- function shouldBypass(path: string): boolean {
45
+ /**
46
+ * Only these page routes are redirected to /setup during onboarding.
47
+ * Everything else (assets, API, feeds, media, etc.) passes through.
48
+ */
49
+ function shouldRedirect(path: string): boolean {
44
50
  return (
45
- path === "/setup" ||
46
- path === "/health" ||
51
+ path === "/" ||
47
52
  path === "/signin" ||
48
- path === "/signout" ||
49
53
  path === "/reset" ||
50
- path.startsWith("/api/auth/")
54
+ path.startsWith("/dash")
51
55
  );
52
56
  }
53
57