From b1c36cd7afe6b088bf30d03d3a28d0ecb8318a90 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 4 Sep 2023 10:32:06 +1000 Subject: [PATCH 1/3] feat: protect routes plugin --- e2e_test.ts | 22 +++++ fresh.config.ts | 3 +- fresh.gen.ts | 118 +++++++++++------------- plugins/protected_routes.ts | 52 +++++++++++ routes/account/_middleware.ts | 2 - routes/api/me/_middleware.ts | 2 - routes/dashboard/_middleware.ts | 2 - routes/notifications/_middleware.ts | 2 - routes/{submit/index.tsx => submit.tsx} | 0 routes/submit/_middleware.tsx | 2 - 10 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 plugins/protected_routes.ts delete mode 100644 routes/account/_middleware.ts delete mode 100644 routes/api/me/_middleware.ts delete mode 100644 routes/dashboard/_middleware.ts delete mode 100644 routes/notifications/_middleware.ts rename routes/{submit/index.tsx => submit.tsx} (100%) delete mode 100644 routes/submit/_middleware.tsx diff --git a/e2e_test.ts b/e2e_test.ts index 117f8b764..33e83160b 100644 --- a/e2e_test.ts +++ b/e2e_test.ts @@ -146,6 +146,28 @@ Deno.test("[e2e]", async (test) => { assertEquals(resp.status, 303); }); + await test.step("GET /dashboard/stats", async () => { + const resp = await handler( + new Request("http://localhost/dashboard/stats"), + ); + + assertFalse(resp.ok); + assertFalse(resp.body); + assertEquals(resp.headers.get("location"), "/signin"); + assertEquals(resp.status, 303); + }); + + await test.step("GET /dashboard/users", async () => { + const resp = await handler( + new Request("http://localhost/dashboard/users"), + ); + + assertFalse(resp.ok); + assertFalse(resp.body); + assertEquals(resp.headers.get("location"), "/signin"); + assertEquals(resp.status, 303); + }); + await test.step("GET /submit", async () => { const resp = await handler( new Request("http://localhost/submit"), diff --git a/fresh.config.ts b/fresh.config.ts index 227576c4e..461334e86 100644 --- a/fresh.config.ts +++ b/fresh.config.ts @@ -2,8 +2,9 @@ import twindPlugin from "$fresh/plugins/twindv1.ts"; import twindConfig from "./twind.config.ts"; import kvOAuthPlugin from "./plugins/kv_oauth.ts"; +import protectedRoutes from "./plugins/protected_routes.ts"; import { FreshOptions } from "$fresh/server.ts"; export default { - plugins: [twindPlugin(twindConfig), kvOAuthPlugin], + plugins: [kvOAuthPlugin, protectedRoutes, twindPlugin(twindConfig)], } as FreshOptions; diff --git a/fresh.gen.ts b/fresh.gen.ts index c4709f51c..901424006 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -6,38 +6,33 @@ import * as $0 from "./routes/_404.tsx"; import * as $1 from "./routes/_500.tsx"; import * as $2 from "./routes/_app.tsx"; import * as $3 from "./routes/_middleware.ts"; -import * as $4 from "./routes/account/_middleware.ts"; -import * as $5 from "./routes/account/index.tsx"; -import * as $6 from "./routes/account/manage.ts"; -import * as $7 from "./routes/account/upgrade.ts"; -import * as $8 from "./routes/api/_middleware.ts"; -import * as $9 from "./routes/api/items/[id]/comments.ts"; -import * as $10 from "./routes/api/items/[id]/index.ts"; -import * as $11 from "./routes/api/items/[id]/vote.ts"; -import * as $12 from "./routes/api/items/index.ts"; -import * as $13 from "./routes/api/me/_middleware.ts"; -import * as $14 from "./routes/api/me/notifications.ts"; -import * as $15 from "./routes/api/me/votes.ts"; -import * as $16 from "./routes/api/stripe-webhooks.ts"; -import * as $17 from "./routes/api/users/[login]/index.ts"; -import * as $18 from "./routes/api/users/[login]/items.ts"; -import * as $19 from "./routes/api/users/index.ts"; -import * as $20 from "./routes/blog/[slug].tsx"; -import * as $21 from "./routes/blog/index.tsx"; -import * as $22 from "./routes/dashboard/_middleware.ts"; -import * as $23 from "./routes/dashboard/index.tsx"; -import * as $24 from "./routes/dashboard/stats.tsx"; -import * as $25 from "./routes/dashboard/users.tsx"; -import * as $26 from "./routes/feed.ts"; -import * as $27 from "./routes/index.tsx"; -import * as $28 from "./routes/items/[id].tsx"; -import * as $29 from "./routes/notifications/[id].ts"; -import * as $30 from "./routes/notifications/_middleware.ts"; -import * as $31 from "./routes/notifications/index.tsx"; -import * as $32 from "./routes/pricing.tsx"; -import * as $33 from "./routes/submit/_middleware.tsx"; -import * as $34 from "./routes/submit/index.tsx"; -import * as $35 from "./routes/users/[login].tsx"; +import * as $4 from "./routes/account/index.tsx"; +import * as $5 from "./routes/account/manage.ts"; +import * as $6 from "./routes/account/upgrade.ts"; +import * as $7 from "./routes/api/_middleware.ts"; +import * as $8 from "./routes/api/items/[id]/comments.ts"; +import * as $9 from "./routes/api/items/[id]/index.ts"; +import * as $10 from "./routes/api/items/[id]/vote.ts"; +import * as $11 from "./routes/api/items/index.ts"; +import * as $12 from "./routes/api/me/notifications.ts"; +import * as $13 from "./routes/api/me/votes.ts"; +import * as $14 from "./routes/api/stripe-webhooks.ts"; +import * as $15 from "./routes/api/users/[login]/index.ts"; +import * as $16 from "./routes/api/users/[login]/items.ts"; +import * as $17 from "./routes/api/users/index.ts"; +import * as $18 from "./routes/blog/[slug].tsx"; +import * as $19 from "./routes/blog/index.tsx"; +import * as $20 from "./routes/dashboard/index.tsx"; +import * as $21 from "./routes/dashboard/stats.tsx"; +import * as $22 from "./routes/dashboard/users.tsx"; +import * as $23 from "./routes/feed.ts"; +import * as $24 from "./routes/index.tsx"; +import * as $25 from "./routes/items/[id].tsx"; +import * as $26 from "./routes/notifications/[id].ts"; +import * as $27 from "./routes/notifications/index.tsx"; +import * as $28 from "./routes/pricing.tsx"; +import * as $29 from "./routes/submit.tsx"; +import * as $30 from "./routes/users/[login].tsx"; import * as $$0 from "./islands/Chart.tsx"; import * as $$1 from "./islands/CommentsList.tsx"; import * as $$2 from "./islands/ItemsList.tsx"; @@ -51,38 +46,33 @@ const manifest = { "./routes/_500.tsx": $1, "./routes/_app.tsx": $2, "./routes/_middleware.ts": $3, - "./routes/account/_middleware.ts": $4, - "./routes/account/index.tsx": $5, - "./routes/account/manage.ts": $6, - "./routes/account/upgrade.ts": $7, - "./routes/api/_middleware.ts": $8, - "./routes/api/items/[id]/comments.ts": $9, - "./routes/api/items/[id]/index.ts": $10, - "./routes/api/items/[id]/vote.ts": $11, - "./routes/api/items/index.ts": $12, - "./routes/api/me/_middleware.ts": $13, - "./routes/api/me/notifications.ts": $14, - "./routes/api/me/votes.ts": $15, - "./routes/api/stripe-webhooks.ts": $16, - "./routes/api/users/[login]/index.ts": $17, - "./routes/api/users/[login]/items.ts": $18, - "./routes/api/users/index.ts": $19, - "./routes/blog/[slug].tsx": $20, - "./routes/blog/index.tsx": $21, - "./routes/dashboard/_middleware.ts": $22, - "./routes/dashboard/index.tsx": $23, - "./routes/dashboard/stats.tsx": $24, - "./routes/dashboard/users.tsx": $25, - "./routes/feed.ts": $26, - "./routes/index.tsx": $27, - "./routes/items/[id].tsx": $28, - "./routes/notifications/[id].ts": $29, - "./routes/notifications/_middleware.ts": $30, - "./routes/notifications/index.tsx": $31, - "./routes/pricing.tsx": $32, - "./routes/submit/_middleware.tsx": $33, - "./routes/submit/index.tsx": $34, - "./routes/users/[login].tsx": $35, + "./routes/account/index.tsx": $4, + "./routes/account/manage.ts": $5, + "./routes/account/upgrade.ts": $6, + "./routes/api/_middleware.ts": $7, + "./routes/api/items/[id]/comments.ts": $8, + "./routes/api/items/[id]/index.ts": $9, + "./routes/api/items/[id]/vote.ts": $10, + "./routes/api/items/index.ts": $11, + "./routes/api/me/notifications.ts": $12, + "./routes/api/me/votes.ts": $13, + "./routes/api/stripe-webhooks.ts": $14, + "./routes/api/users/[login]/index.ts": $15, + "./routes/api/users/[login]/items.ts": $16, + "./routes/api/users/index.ts": $17, + "./routes/blog/[slug].tsx": $18, + "./routes/blog/index.tsx": $19, + "./routes/dashboard/index.tsx": $20, + "./routes/dashboard/stats.tsx": $21, + "./routes/dashboard/users.tsx": $22, + "./routes/feed.ts": $23, + "./routes/index.tsx": $24, + "./routes/items/[id].tsx": $25, + "./routes/notifications/[id].ts": $26, + "./routes/notifications/index.tsx": $27, + "./routes/pricing.tsx": $28, + "./routes/submit.tsx": $29, + "./routes/users/[login].tsx": $30, }, islands: { "./islands/Chart.tsx": $$0, diff --git a/plugins/protected_routes.ts b/plugins/protected_routes.ts new file mode 100644 index 000000000..aaabf8a70 --- /dev/null +++ b/plugins/protected_routes.ts @@ -0,0 +1,52 @@ +// Copyright 2023 the Deno authors. All rights reserved. MIT license. +import { Plugin } from "$fresh/server.ts"; +import { ensureSignedIn, type State } from "@/middleware/session.ts"; + +/** + * Adds middleware to the defined protected routes. The middleware throws a + * HTTP 401 Unauthorized-equivalent error if `ctx.state.sessionUser` is + * `undefined` by {@linkcode ensureSignedIn}. + * + * The thrown error is then handled by {@linkcode handleNotSignedInWebpage}, or + * {@linkcode handleNotSignedInRest} if the request is made to a REST API + * endpoint. + * + * @example + * ```ts + * import { FreshOptions } from "$fresh/server.ts"; + * import protectedRoutes from "@/plugins/protected_routes.ts"; + * + * const options: FreshOptions = { + * plugins: [protectedRoutes] + * } + * ``` + * + * @see {@link https://fresh.deno.dev/docs/concepts/plugins|Plugins documentation} + * for more information on Fresh's plugin functionality. + */ +const middleware = { handler: ensureSignedIn }; +export default { + name: "protected-routes", + middlewares: [ + { + path: "/account", + middleware, + }, + { + path: "/dashboard", + middleware, + }, + { + path: "/notifications", + middleware, + }, + { + path: "/submit", + middleware, + }, + { + path: "/api/me", + middleware, + }, + ], +} as Plugin; diff --git a/routes/account/_middleware.ts b/routes/account/_middleware.ts deleted file mode 100644 index f76b77fd1..000000000 --- a/routes/account/_middleware.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2023 the Deno authors. All rights reserved. MIT license. -export { ensureSignedIn as handler } from "@/middleware/session.ts"; diff --git a/routes/api/me/_middleware.ts b/routes/api/me/_middleware.ts deleted file mode 100644 index f76b77fd1..000000000 --- a/routes/api/me/_middleware.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2023 the Deno authors. All rights reserved. MIT license. -export { ensureSignedIn as handler } from "@/middleware/session.ts"; diff --git a/routes/dashboard/_middleware.ts b/routes/dashboard/_middleware.ts deleted file mode 100644 index f76b77fd1..000000000 --- a/routes/dashboard/_middleware.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2023 the Deno authors. All rights reserved. MIT license. -export { ensureSignedIn as handler } from "@/middleware/session.ts"; diff --git a/routes/notifications/_middleware.ts b/routes/notifications/_middleware.ts deleted file mode 100644 index f76b77fd1..000000000 --- a/routes/notifications/_middleware.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2023 the Deno authors. All rights reserved. MIT license. -export { ensureSignedIn as handler } from "@/middleware/session.ts"; diff --git a/routes/submit/index.tsx b/routes/submit.tsx similarity index 100% rename from routes/submit/index.tsx rename to routes/submit.tsx diff --git a/routes/submit/_middleware.tsx b/routes/submit/_middleware.tsx deleted file mode 100644 index f76b77fd1..000000000 --- a/routes/submit/_middleware.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2023 the Deno authors. All rights reserved. MIT license. -export { ensureSignedIn as handler } from "@/middleware/session.ts"; From 61c465a0b0a246f0771f658cd0b7e51d0b1750f1 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 4 Sep 2023 10:34:09 +1000 Subject: [PATCH 2/3] tweak --- plugins/protected_routes.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/protected_routes.ts b/plugins/protected_routes.ts index aaabf8a70..d55639fff 100644 --- a/plugins/protected_routes.ts +++ b/plugins/protected_routes.ts @@ -4,8 +4,9 @@ import { ensureSignedIn, type State } from "@/middleware/session.ts"; /** * Adds middleware to the defined protected routes. The middleware throws a - * HTTP 401 Unauthorized-equivalent error if `ctx.state.sessionUser` is - * `undefined` by {@linkcode ensureSignedIn}. + * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401|HTTP 401 Unauthorized}-equivalent + * error if `ctx.state.sessionUser` is `undefined` by + * {@linkcode ensureSignedIn}. * * The thrown error is then handled by {@linkcode handleNotSignedInWebpage}, or * {@linkcode handleNotSignedInRest} if the request is made to a REST API From 842eea56b3e3035bdafe5bedc508028676be369b Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 4 Sep 2023 10:42:12 +1000 Subject: [PATCH 3/3] tweaks --- plugins/protected_routes.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/protected_routes.ts b/plugins/protected_routes.ts index d55639fff..ae8462943 100644 --- a/plugins/protected_routes.ts +++ b/plugins/protected_routes.ts @@ -3,13 +3,14 @@ import { Plugin } from "$fresh/server.ts"; import { ensureSignedIn, type State } from "@/middleware/session.ts"; /** - * Adds middleware to the defined protected routes. The middleware throws a - * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401|HTTP 401 Unauthorized}-equivalent - * error if `ctx.state.sessionUser` is `undefined` by - * {@linkcode ensureSignedIn}. + * Adds middleware to the defined routes that ensures the client is signed-in + * before proceeding. The {@linkcode ensureSignedIn} middleware throws an error + * equivalent to the + * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401|HTTP 401 Unauthorized} + * error if `ctx.state.sessionUser` is `undefined`. * * The thrown error is then handled by {@linkcode handleNotSignedInWebpage}, or - * {@linkcode handleNotSignedInRest} if the request is made to a REST API + * {@linkcode handleNotSignedInRest}, if the request is made to a REST API * endpoint. * * @example