From 76fa4872f1fc8a4923d85e16fcecb76978cde547 Mon Sep 17 00:00:00 2001 From: aronmal Date: Mon, 11 Mar 2024 15:11:13 +0100 Subject: [PATCH] Fix: login cookies and refresh token --- src/lib/accessToken.ts | 34 +++++++++++++++++++++ src/middleware.ts | 29 +++++++++--------- src/routes/api/auth/callback/discord.ts | 1 - src/routes/api/auth/login.ts | 5 ++-- src/routes/api/auth/logout.ts | 15 ++++------ src/routes/config/[guildId].tsx | 39 +++++++++++++------------ src/routes/config/index.tsx | 12 ++------ 7 files changed, 80 insertions(+), 55 deletions(-) create mode 100644 src/lib/accessToken.ts diff --git a/src/lib/accessToken.ts b/src/lib/accessToken.ts new file mode 100644 index 0000000..1241116 --- /dev/null +++ b/src/lib/accessToken.ts @@ -0,0 +1,34 @@ +import { eq } from "drizzle-orm"; +import db from "~/drizzle"; +import { discordTokens } from "~/drizzle/schema"; +import { discord } from "./auth"; + +const getAccessToken = async (userId: string) => { + let tokens = await db.query.discordTokens + .findFirst({ + where: eq(discordTokens.userId, userId), + }) + .execute(); + + if (tokens && new Date() >= tokens.expiresAt) { + const newTokens = await discord.refreshAccessToken(tokens.refreshToken); + + tokens = ( + await db + .update(discordTokens) + .set({ + accessToken: newTokens.accessToken, + expiresAt: newTokens.accessTokenExpiresAt, + refreshToken: newTokens.refreshToken, + }) + .where(eq(discordTokens.userId, userId)) + .returning() + .execute() + )[0]; + } + + if (!tokens) return tokens; + return tokens; +}; + +export default getAccessToken; diff --git a/src/middleware.ts b/src/middleware.ts index cbbf2d0..ffb2b48 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,7 +2,7 @@ import { createMiddleware } from "@solidjs/start/middleware"; import colors from "colors"; import fs from "fs"; import { verifyRequestOrigin } from "lucia"; -import { appendHeader, getCookie, getHeader } from "vinxi/http"; +import { getCookie, getHeader, setCookie } from "vinxi/http"; import { lucia } from "./lib/auth"; colors.enable(); @@ -12,8 +12,8 @@ let started: boolean = false; export default createMiddleware({ onRequest: async (event) => { if (event.nativeEvent.node.req.method !== "GET") { - const originHeader = getHeader(event.nativeEvent, "Origin") ?? null; - const hostHeader = getHeader(event.nativeEvent, "Host") ?? null; + const originHeader = getHeader("Origin") ?? null; + const hostHeader = getHeader("Host") ?? null; if ( !originHeader || !hostHeader || @@ -24,8 +24,7 @@ export default createMiddleware({ } } - const sessionId = - getCookie(event.nativeEvent, lucia.sessionCookieName) ?? null; + const sessionId = getCookie(lucia.sessionCookieName) ?? null; if (!sessionId) { event.nativeEvent.context.session = null; event.nativeEvent.context.user = null; @@ -34,20 +33,22 @@ export default createMiddleware({ const { session, user } = await lucia.validateSession(sessionId); if (session && session.fresh) { - appendHeader( - event.nativeEvent, - "Set-Cookie", - lucia.createSessionCookie(session.id).serialize(), + const sessionCookie = lucia.createSessionCookie(session.id); + setCookie( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, ); } if (!session) { - appendHeader( - event.nativeEvent, - "Set-Cookie", - lucia.createBlankSessionCookie().serialize(), + const sessionCookie = lucia.createBlankSessionCookie(); + setCookie( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, ); } - console.log(3); + event.nativeEvent.context.session = session; event.nativeEvent.context.user = user; }, diff --git a/src/routes/api/auth/callback/discord.ts b/src/routes/api/auth/callback/discord.ts index 07519b8..36db3b9 100644 --- a/src/routes/api/auth/callback/discord.ts +++ b/src/routes/api/auth/callback/discord.ts @@ -96,7 +96,6 @@ export async function GET(event: APIEvent): Promise { expiresAt: tokens.accessTokenExpiresAt, refreshToken: tokens.refreshToken, }) - .returning() .execute(); const session = await lucia.createSession( userId, diff --git a/src/routes/api/auth/login.ts b/src/routes/api/auth/login.ts index bc4a45b..20243b5 100644 --- a/src/routes/api/auth/login.ts +++ b/src/routes/api/auth/login.ts @@ -1,4 +1,3 @@ -import { APIEvent } from "@solidjs/start/server"; import { generateState } from "arctic"; import httpStatus from "http-status"; import { setCookie } from "vinxi/http"; @@ -7,13 +6,13 @@ import { discord } from "~/lib/auth"; if (typeof import.meta.env.PROD === "undefined") throw new Error("No env PROD found!"); -export async function GET(event: APIEvent) { +export async function GET() { const state = generateState(); const url = await discord.createAuthorizationURL(state, { scopes: ["identify", "guilds", "guilds.members.read"], }); - setCookie(event, "discord_oauth_state", state, { + setCookie("discord_oauth_state", state, { path: "/", secure: import.meta.env.PROD, httpOnly: true, diff --git a/src/routes/api/auth/logout.ts b/src/routes/api/auth/logout.ts index 0ba8a30..df26a3a 100644 --- a/src/routes/api/auth/logout.ts +++ b/src/routes/api/auth/logout.ts @@ -4,15 +4,12 @@ import { appendHeader } from "vinxi/http"; import { lucia } from "~/lib/auth"; export const GET = async (event: APIEvent) => { - if (!event.nativeEvent.context.session) { - return new Error("Unauthorized"); - } - await lucia.invalidateSession(event.nativeEvent.context.session.id); - appendHeader( - event, - "Set-Cookie", - lucia.createBlankSessionCookie().serialize(), - ); + const { session } = event.nativeEvent.context; + + if (!session) return new Error("Unauthorized"); + + await lucia.invalidateSession(session.id); + appendHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); return new Response(null, { status: httpStatus.FOUND, headers: { Location: "/" }, diff --git a/src/routes/config/[guildId].tsx b/src/routes/config/[guildId].tsx index 4f173f4..a731964 100644 --- a/src/routes/config/[guildId].tsx +++ b/src/routes/config/[guildId].tsx @@ -1,6 +1,5 @@ import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons"; import { useLocation, useNavigate, useParams } from "@solidjs/router"; -import { eq } from "drizzle-orm"; import moment from "moment-timezone"; import createClient from "openapi-fetch"; import { @@ -14,8 +13,7 @@ import { createStore } from "solid-js/store"; import { getRequestEvent } from "solid-js/web"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import Layout from "~/components/Layout"; -import db from "~/drizzle"; -import { discordTokens } from "~/drizzle/schema"; +import getAccessToken from "~/lib/accessToken"; import { paths } from "~/types/discord"; import "../../styles/pages/config.scss"; @@ -51,36 +49,39 @@ const getPayload = async ( const { user } = event.nativeEvent.context; if (!user) return { success: false, message: "User not logged in!" }; - const tokens = await db.query.discordTokens - .findFirst({ - where: eq(discordTokens.userId, user.id), - }) - .execute(); + const tokens = await getAccessToken(user.id); if (!tokens) return { success: false, message: "No discord access token!" }; const { GET } = createClient({ baseUrl: "https://discord.com/api/v10", }); - const guildsRequest = await GET("/users/@me/guilds", { + const { data: guildsData, error } = await GET("/users/@me/guilds", { headers: { Authorization: `Bearer ${tokens.accessToken}` }, }); - const channelsRequest = await GET("/guilds/{guild_id}/channels", { - params: { - path: { - guild_id: id, + const { data: channelsData, error: error2 } = await GET( + "/guilds/{guild_id}/channels", + { + params: { + path: { + guild_id: id, + }, + }, + headers: { + Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}`, }, }, - headers: { Authorization: `Bot ${import.meta.env.VITE_DISCORD_BOT_TOKEN}` }, - }); - if (guildsRequest.error || channelsRequest.error) { - console.log(guildsRequest.error, channelsRequest.error, pathname); + ); + + if (error || error2) { + console.log("Discord api error:", { error, error2 }); + console.log(error, error2, pathname); return { success: false, message: "Error on one of the discord api requests!", }; } - const guild = guildsRequest.data?.find((e) => e.id === id); + const guild = guildsData?.find((e) => e.id === id); if (!guild) return { @@ -95,7 +96,7 @@ const getPayload = async ( }; const channels: ReturnType["guild"]["channels"] = []; - channelsRequest.data?.forEach((channel) => { + channelsData?.forEach((channel) => { if (channel.type !== 0) return; channels.push({ id: channel.id, diff --git a/src/routes/config/index.tsx b/src/routes/config/index.tsx index 475651e..0c6a405 100644 --- a/src/routes/config/index.tsx +++ b/src/routes/config/index.tsx @@ -4,14 +4,12 @@ import { faPlus, } from "@fortawesome/pro-regular-svg-icons"; import { useLocation, useNavigate } from "@solidjs/router"; -import { eq } from "drizzle-orm"; import createClient from "openapi-fetch"; import { For, createResource } from "solid-js"; import { getRequestEvent } from "solid-js/web"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import Layout from "~/components/Layout"; -import db from "~/drizzle"; -import { discordTokens } from "~/drizzle/schema"; +import getAccessToken from "~/lib/accessToken"; import { paths } from "~/types/discord"; import "../../styles/pages/config.scss"; @@ -43,11 +41,7 @@ const getPayload = async (): Promise< const { user } = event.nativeEvent.context; if (!user) return { success: false, message: "User not logged in!" }; - const tokens = await db.query.discordTokens - .findFirst({ - where: eq(discordTokens.userId, user.id), - }) - .execute(); + const tokens = await getAccessToken(user.id); if (!tokens) return { success: false, message: "No discord access token!" }; const { GET } = createClient({ @@ -58,7 +52,7 @@ const getPayload = async (): Promise< }); if (error) { - console.log(error); + console.log("Discord api error:", { error }); return { success: false, message: "Error on discord api request!" }; } console.log(pathname, "success");