Skip to content

Commit 9d19634

Browse files
committed
add referer assertions (very basic)
1 parent 15bc779 commit 9d19634

7 files changed

Lines changed: 37 additions & 10 deletions

File tree

PLANNING.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ Internal notes by core developers
4141

4242
- make a note about wikis not being able to talk to each other
4343
- verify all of the authentication and security
44-
- possibly make material design 3 ui with web commponents
4544
- make sure the getting started doc is correct
4645
- put site restrictions in a file
4746
- sell people on contributing to the project
@@ -52,6 +51,5 @@ Internal notes by core developers
5251
- multiple folder views (flat, folder, tag)
5352
- active node server for a specific folder (also a massive security bypass)
5453
- instructions for process users on different platforms (e.g. linux permission users)
55-
- Recipe templates, which have a simple list of plugins and readonly bags, and are assigned to the writable bag.
5654

5755

packages/mws/src/RequestState.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,32 @@ export class StateObject<
8888
}
8989

9090

91-
getRefererRecipe() {
91+
assertWikiReferer(recipe_slug: string) {
9292
const state = this;
93-
if (!state.headers.get("referer")) return;
94-
const referer = new URL(state.headers.get("referer")!);
93+
if (!state.headers.referer) return;
94+
const referer = new URL(state.headers.referer);
9595
// console.log("Referer", state.headers.referer, referer);
9696
if (!referer.pathname.startsWith(state.pathPrefix))
9797
throw state.sendEmpty(404, { "x-reason": "invalid path prefix" });
9898
if (!referer.pathname.startsWith(state.pathPrefix + "/wiki/"))
9999
return; // keep going
100100
// we now get the recipe name from the referer
101101
const recipe_name = referer.pathname.substring(state.pathPrefix.length + "/wiki/".length);
102-
return decodeURIComponent(recipe_name) as PrismaField<"Recipe", "id">;
102+
const referer_slug = decodeURIComponent(recipe_name) as PrismaField<"Recipe", "id">;
103+
if (referer_slug !== recipe_slug)
104+
throw new SendError("CROSS_WIKI_ACCESS_DENIED", 403, null);
105+
}
106+
107+
assertAdminReferer() {
108+
const state = this;
109+
// referer is a voluntary header
110+
if (!state.headers.referer) return;
111+
const referer = new URL(state.headers.referer);
112+
// deny referers from outside our pathprefix
113+
if (!referer.pathname.startsWith(state.pathPrefix))
114+
throw new SendError("ADMIN_ACCESS_DENIED", 403, null);
115+
if (referer.pathname.startsWith(state.pathPrefix + "/wiki/"))
116+
throw new SendError("ADMIN_ACCESS_DENIED", 403, null);
103117
}
104118

105119

packages/mws/src/SendError.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ declare module "@tiddlywiki/server" {
7474

7575
"OPERATION_NOT_PERMITTED":
7676
SendErrorItem<403, { reason: string }>
77+
78+
"CROSS_WIKI_ACCESS_DENIED":
79+
SendErrorItem<403, null>
80+
"ADMIN_ACCESS_DENIED":
81+
SendErrorItem<403, null>
7782
}
7883
}
7984
SendError.oninstance.push(e => {

packages/mws/src/new-managers/RecipeRoutes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const RecipeStatus = zodRoute({
1616
}),
1717
inner: async (state) => {
1818
const { recipe_slug } = state.pathParams;
19+
state.assertWikiReferer(recipe_slug);
1920
const recipe = await RecipeResolver.assertRecipe({
2021
state,
2122
recipe_slug,
@@ -55,6 +56,7 @@ export const TiddlerList = zodRoute({
5556
}),
5657
inner: async (state) => {
5758
const { recipe_slug } = state.pathParams;
59+
state.assertWikiReferer(recipe_slug);
5860
const recipe = await RecipeResolver.assertRecipe({
5961
state,
6062
recipe_slug,
@@ -81,6 +83,7 @@ export const RecipeStore = zodRoute({
8183
zodQueryKeys: ["cache"],
8284
inner: async (state) => {
8385
const { recipe_slug } = state.pathParams;
86+
state.assertWikiReferer(recipe_slug);
8487
throw await serveIndex(state, recipe_slug, "store");
8588
},
8689
});
@@ -97,6 +100,8 @@ export const RecipeUpdates = zodRoute({
97100
zodQueryKeys: ["since"],
98101
inner: async (state) => {
99102
const { recipe_slug } = state.pathParams;
103+
state.assertWikiReferer(recipe_slug);
104+
100105
const recipe = await RecipeResolver.assertRecipe({
101106
state,
102107
recipe_slug,
@@ -160,6 +165,9 @@ export const TiddlerBatch = zodRoute({
160165
}),
161166
inner: async (state) => {
162167
const { recipe_slug, op } = state.pathParams;
168+
state.assertWikiReferer(recipe_slug)
169+
170+
163171

164172
const recipe = await RecipeResolver.assertRecipe({
165173
state,

packages/mws/src/new-managers/TabDataAdapter.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
RoleImportWriter,
2424
TemplateImportWriter,
2525
UserImportWriter
26-
} from "./TabImportWriter";
26+
} from "./TabUpserts";
2727
import {
2828
BagPermissionLevel,
2929
RecipePermissionLevel
@@ -83,7 +83,7 @@ export {
8383
type ImportedRoleRows,
8484
type ImportedTemplateRow,
8585
type ImportedUserRows,
86-
} from "./TabImportWriter";
86+
} from "./TabUpserts";
8787

8888

8989
// #region abstracts
@@ -532,7 +532,7 @@ export const AdminSave = zodRoute({
532532
}),
533533
zodRequestBody: z => z.any(),
534534
inner: async (state) => {
535-
535+
state.assertAdminReferer();
536536
state.asserted = state.user.isAdmin;
537537
state.data = JSON.parse(JSON.stringify(state.data), (key: any, val: any) => {
538538
if (typeof val === "string" && val.startsWith(IdString.prefix))
@@ -585,6 +585,7 @@ export const AdminLoad = zodRoute({
585585
zodPathParams: z => ({}),
586586
inner: async (state) => {
587587
// access is checked in each list
588+
state.assertAdminReferer();
588589
state.asserted = state.user.isLoggedIn;
589590
const res = await state.$transaction(async prisma => {
590591
const roles = await new RoleImportWriter(prisma, false).getIdMapper();
File renamed without changes.

packages/mws/src/new-managers/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { RecipeStatus, RecipeStore, RecipeUpdates, TiddlerBatch, TiddlerList } f
1717

1818
export * from "./RecipeResolver";
1919
export * from "./TabDataAdapter";
20-
export * from "./TabImportWriter";
20+
export * from "./TabUpserts";
2121
export * from "./wiki-utils";
2222
export * from "./wiki-contract";
2323

@@ -84,6 +84,7 @@ serverEvents.on("mws.routes", (root) => {
8484
}), new Error());
8585

8686
const { recipe_slug } = state.pathParams;
87+
state.assertWikiReferer(recipe_slug);
8788
return await serveIndex(state, recipe_slug, "index");
8889

8990
}, async (state, e) => {

0 commit comments

Comments
 (0)