Skip to content

Commit

Permalink
feat: redirect to referrer page after login (#249)
Browse files Browse the repository at this point in the history
* feat: redirect to referer page after login

* better

* fmt

* central redirect functions in a file

* fmt

* add license

* direct define redirecturl in callback fn

* fmt

* merge `https.ts` into `redirect.ts` along with test

* Update utils/redirect.ts

---------

Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
  • Loading branch information
huai-jie and iuioiua authored Jun 13, 2023
1 parent 7742868 commit b0f5bb5
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 57 deletions.
9 changes: 8 additions & 1 deletion routes/_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { walk } from "std/fs/walk.ts";
import { getSessionId } from "deno_kv_oauth";
import { setRedirectUrlCookie } from "@/utils/redirect.ts";

export interface State {
sessionId?: string;
Expand All @@ -25,5 +26,11 @@ export async function handler(

ctx.state.sessionId = getSessionId(req);

return await ctx.next();
const res = await ctx.next();

if (ctx.destination === "route" && pathname === "/signin") {
setRedirectUrlCookie(req, res);
}

return res;
}
7 changes: 3 additions & 4 deletions routes/account/_middleware.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { State } from "@/routes/_middleware.ts";
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { redirect } from "@/utils/http.ts";
import { getUserBySessionId, User } from "@/utils/db.ts";
import { redirectToLogin } from "@/utils/redirect.ts";

export interface AccountState extends State {
sessionId: string;
user: User;
}

export async function handler(
_req: Request,
req: Request,
ctx: MiddlewareHandlerContext<AccountState>,
) {
const redirectResponse = redirect("/login");

const redirectResponse = redirectToLogin(req.url);
if (!ctx.state.sessionId) return redirectResponse;
const user = await getUserBySessionId(ctx.state.sessionId);
if (!user) return redirectResponse;
Expand Down
2 changes: 1 addition & 1 deletion routes/account/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Handlers } from "$fresh/server.ts";
import { stripe } from "@/utils/payments.ts";
import type { AccountState } from "./_middleware.ts";
import { redirect } from "@/utils/http.ts";
import { redirect } from "@/utils/redirect.ts";

// deno-lint-ignore no-explicit-any
export const handler: Handlers<any, AccountState> = {
Expand Down
2 changes: 1 addition & 1 deletion routes/account/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Handlers } from "$fresh/server.ts";
import { stripe } from "@/utils/payments.ts";
import type { AccountState } from "./_middleware.ts";
import { redirect } from "@/utils/http.ts";
import { redirect } from "@/utils/redirect.ts";

const STRIPE_PREMIUM_PLAN_PRICE_ID = Deno.env.get(
"STRIPE_PREMIUM_PLAN_PRICE_ID",
Expand Down
8 changes: 7 additions & 1 deletion routes/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { stripe } from "@/utils/payments.ts";
import { State } from "./_middleware.ts";
import { handleCallback } from "deno_kv_oauth";
import { client } from "@/utils/kv_oauth.ts";
import {
deleteRedirectUrlCookie,
getRedirectUrlCookie,
} from "@/utils/redirect.ts";

interface GitHubUser {
id: number;
Expand All @@ -35,8 +39,11 @@ export const handler: Handlers<any, State> = {
const { response, accessToken, sessionId } = await handleCallback(
req,
client,
getRedirectUrlCookie(req.headers),
);

deleteRedirectUrlCookie(response.headers);

const githubUser = await getUser(accessToken);

const user = await getUserById(githubUser.id.toString());
Expand All @@ -55,7 +62,6 @@ export const handler: Handlers<any, State> = {
} else {
await setUserSessionId(user, sessionId);
}

return response;
},
};
5 changes: 3 additions & 2 deletions routes/item/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import {
type Item,
type User,
} from "@/utils/db.ts";
import { redirect } from "@/utils/http.ts";
import { redirect } from "@/utils/redirect.ts";
import UserPostedAt from "@/components/UserPostedAt.tsx";
import { pluralize } from "@/utils/display.ts";
import { redirectToLogin } from "@/utils/redirect.ts";

interface ItemPageData extends State {
user: User;
Expand Down Expand Up @@ -71,7 +72,7 @@ export const handler: Handlers<ItemPageData, State> = {
},
async POST(req, ctx) {
if (!ctx.state.sessionId) {
return redirect("/login");
return redirectToLogin(req.url);
}

const form = await req.formData();
Expand Down
2 changes: 1 addition & 1 deletion routes/signin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { Handlers } from "$fresh/server.ts";
import type { State } from "./_middleware.ts";
import { redirect } from "@/utils/http.ts";
import { redirect } from "@/utils/redirect.ts";
import { signIn } from "deno_kv_oauth";
import { client } from "@/utils/kv_oauth.ts";

Expand Down
9 changes: 6 additions & 3 deletions routes/submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import Layout from "@/components/Layout.tsx";
import { BUTTON_STYLES, INPUT_STYLES } from "@/utils/constants.ts";
import type { State } from "@/routes/_middleware.ts";
import { createItem, getUserBySessionId } from "@/utils/db.ts";
import { redirect } from "@/utils/http.ts";
import { redirect } from "@/utils/redirect.ts";
import { redirectToLogin } from "@/utils/redirect.ts";

export const handler: Handlers<State, State> = {
GET(_req, ctx) {
return ctx.state.sessionId ? ctx.render(ctx.state) : redirect("/login");
GET(req, ctx) {
return ctx.state.sessionId
? ctx.render(ctx.state)
: redirectToLogin(req.url);
},
async POST(req, ctx) {
if (!ctx.state.sessionId) {
Expand Down
19 changes: 0 additions & 19 deletions utils/http.ts

This file was deleted.

24 changes: 0 additions & 24 deletions utils/http_test.ts

This file was deleted.

43 changes: 43 additions & 0 deletions utils/redirect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { RedirectStatus, Status } from "std/http/http_status.ts";
import { deleteCookie, getCookies, setCookie } from "std/http/cookie.ts";

export const REDIRECT_URL_COOKIE_NAME = "redirect-url";

/**
* @param location A relative (to the request URL) or absolute URL.
* @param status HTTP status
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location}
*/
export function redirect(
location: string,
status: Status.Created | RedirectStatus = 303,
) {
return new Response(null, {
headers: {
location,
},
status,
});
}

export function redirectToLogin(url: string) {
return redirect(`/signin?from=${url}`);
}

export function setRedirectUrlCookie(req: Request, res: Response) {
const from = new URL(req.url).searchParams.get("from");
setCookie(res.headers, {
name: REDIRECT_URL_COOKIE_NAME,
value: from ?? req.headers.get("referer")!,
path: "/",
});
}

export function deleteRedirectUrlCookie(headers: Headers) {
deleteCookie(headers, REDIRECT_URL_COOKIE_NAME);
}

export function getRedirectUrlCookie(headers: Headers) {
return getCookies(headers)[REDIRECT_URL_COOKIE_NAME];
}
78 changes: 78 additions & 0 deletions utils/redirect_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import {
deleteRedirectUrlCookie,
getRedirectUrlCookie,
redirect,
REDIRECT_URL_COOKIE_NAME,
redirectToLogin,
setRedirectUrlCookie,
} from "./redirect.ts";
import { assert, assertEquals } from "std/testing/asserts.ts";

Deno.test("[redirect] redirect() defaults", () => {
const location = "/hello-there";

const response = redirect(location);
assert(!response.ok);
assertEquals(response.body, null);
assertEquals(response.headers.get("location"), location);
assertEquals(response.status, 303);
});

Deno.test("[redirect] redirect()", () => {
const location = "/hello-there";
const status = 302;

const response = redirect(location, status);
assert(!response.ok);
assertEquals(response.body, null);
assertEquals(response.headers.get("location"), location);
assertEquals(response.status, status);
});

Deno.test("[redirect] redirectToLogin()", () => {
const from = "/hello-there";
const response = redirectToLogin(from);
const location = `/signin?from=${from}`;
assert(!response.ok);
assertEquals(response.body, null);
assertEquals(response.headers.get("location"), location);
assertEquals(response.status, 303);
});

Deno.test("[redirect] setRedirectUrlCookie()", () => {
const redirectUrl = "/hello-there";
const request = new Request(`http://example.com/signin?from=${redirectUrl}`);
const response = new Response();
setRedirectUrlCookie(request, response);
const cookieHeader = response.headers.get("set-cookie");
assertEquals(
cookieHeader,
`${REDIRECT_URL_COOKIE_NAME}=${redirectUrl}; Path=/`,
);
});

Deno.test("[redirect] getRedirectUrlCookie()", () => {
const headers = new Headers();
const redirectUrl = "/hello-there";
headers.set("Cookie", `${REDIRECT_URL_COOKIE_NAME}=${redirectUrl}`);
assertEquals(getRedirectUrlCookie(headers), redirectUrl);
});

Deno.test("[redirect] deleteRedirectUrlCookie()", () => {
const redirectUrl = "/hello-there";
const request = new Request(`http://example.com/signin?from=${redirectUrl}`);
const response = new Response();
setRedirectUrlCookie(request, response);
assert(
!response.headers.get("set-cookie")?.includes(
`${REDIRECT_URL_COOKIE_NAME}=;`,
),
);
deleteRedirectUrlCookie(response.headers);
assert(
response.headers.get("set-cookie")?.includes(
`${REDIRECT_URL_COOKIE_NAME}=;`,
),
);
});

0 comments on commit b0f5bb5

Please sign in to comment.