@jant/core 0.3.17 → 0.3.19
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
|
|
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 (
|
|
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
|
-
|
|
29
|
-
|
|
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
|
@@ -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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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("
|
|
101
|
-
it("allows /setup
|
|
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
|
|
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 /
|
|
137
|
+
it("allows /signout", async () => {
|
|
116
138
|
const { app, getCallCount } = createApp(false);
|
|
117
|
-
const res = await app.request("/
|
|
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 /
|
|
144
|
+
it("allows /api/auth/*", async () => {
|
|
123
145
|
const { app, getCallCount } = createApp(false);
|
|
124
|
-
const res = await app.request("/
|
|
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 /
|
|
151
|
+
it("allows /assets/*", async () => {
|
|
130
152
|
const { app, getCallCount } = createApp(false);
|
|
131
|
-
const res = await app.request("/
|
|
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 /
|
|
158
|
+
it("allows /feed", async () => {
|
|
137
159
|
const { app, getCallCount } = createApp(false);
|
|
138
|
-
const res = await app.request("/
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 === "/
|
|
46
|
-
path === "/health" ||
|
|
51
|
+
path === "/" ||
|
|
47
52
|
path === "/signin" ||
|
|
48
|
-
path === "/signout" ||
|
|
49
53
|
path === "/reset" ||
|
|
50
|
-
path.startsWith("/
|
|
54
|
+
path.startsWith("/dash")
|
|
51
55
|
);
|
|
52
56
|
}
|
|
53
57
|
|