Fix: login cookies and refresh token

This commit is contained in:
Aron Malcher 2024-03-11 15:11:13 +01:00
parent 1b2673fc93
commit 76fa4872f1
Signed by: aronmal
GPG key ID: 816B7707426FC612
7 changed files with 80 additions and 55 deletions

34
src/lib/accessToken.ts Normal file
View file

@ -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;

View file

@ -2,7 +2,7 @@ import { createMiddleware } from "@solidjs/start/middleware";
import colors from "colors"; import colors from "colors";
import fs from "fs"; import fs from "fs";
import { verifyRequestOrigin } from "lucia"; import { verifyRequestOrigin } from "lucia";
import { appendHeader, getCookie, getHeader } from "vinxi/http"; import { getCookie, getHeader, setCookie } from "vinxi/http";
import { lucia } from "./lib/auth"; import { lucia } from "./lib/auth";
colors.enable(); colors.enable();
@ -12,8 +12,8 @@ let started: boolean = false;
export default createMiddleware({ export default createMiddleware({
onRequest: async (event) => { onRequest: async (event) => {
if (event.nativeEvent.node.req.method !== "GET") { if (event.nativeEvent.node.req.method !== "GET") {
const originHeader = getHeader(event.nativeEvent, "Origin") ?? null; const originHeader = getHeader("Origin") ?? null;
const hostHeader = getHeader(event.nativeEvent, "Host") ?? null; const hostHeader = getHeader("Host") ?? null;
if ( if (
!originHeader || !originHeader ||
!hostHeader || !hostHeader ||
@ -24,8 +24,7 @@ export default createMiddleware({
} }
} }
const sessionId = const sessionId = getCookie(lucia.sessionCookieName) ?? null;
getCookie(event.nativeEvent, lucia.sessionCookieName) ?? null;
if (!sessionId) { if (!sessionId) {
event.nativeEvent.context.session = null; event.nativeEvent.context.session = null;
event.nativeEvent.context.user = null; event.nativeEvent.context.user = null;
@ -34,20 +33,22 @@ export default createMiddleware({
const { session, user } = await lucia.validateSession(sessionId); const { session, user } = await lucia.validateSession(sessionId);
if (session && session.fresh) { if (session && session.fresh) {
appendHeader( const sessionCookie = lucia.createSessionCookie(session.id);
event.nativeEvent, setCookie(
"Set-Cookie", sessionCookie.name,
lucia.createSessionCookie(session.id).serialize(), sessionCookie.value,
sessionCookie.attributes,
); );
} }
if (!session) { if (!session) {
appendHeader( const sessionCookie = lucia.createBlankSessionCookie();
event.nativeEvent, setCookie(
"Set-Cookie", sessionCookie.name,
lucia.createBlankSessionCookie().serialize(), sessionCookie.value,
sessionCookie.attributes,
); );
} }
console.log(3);
event.nativeEvent.context.session = session; event.nativeEvent.context.session = session;
event.nativeEvent.context.user = user; event.nativeEvent.context.user = user;
}, },

View file

@ -96,7 +96,6 @@ export async function GET(event: APIEvent): Promise<Response> {
expiresAt: tokens.accessTokenExpiresAt, expiresAt: tokens.accessTokenExpiresAt,
refreshToken: tokens.refreshToken, refreshToken: tokens.refreshToken,
}) })
.returning()
.execute(); .execute();
const session = await lucia.createSession( const session = await lucia.createSession(
userId, userId,

View file

@ -1,4 +1,3 @@
import { APIEvent } from "@solidjs/start/server";
import { generateState } from "arctic"; import { generateState } from "arctic";
import httpStatus from "http-status"; import httpStatus from "http-status";
import { setCookie } from "vinxi/http"; import { setCookie } from "vinxi/http";
@ -7,13 +6,13 @@ import { discord } from "~/lib/auth";
if (typeof import.meta.env.PROD === "undefined") if (typeof import.meta.env.PROD === "undefined")
throw new Error("No env PROD found!"); throw new Error("No env PROD found!");
export async function GET(event: APIEvent) { export async function GET() {
const state = generateState(); const state = generateState();
const url = await discord.createAuthorizationURL(state, { const url = await discord.createAuthorizationURL(state, {
scopes: ["identify", "guilds", "guilds.members.read"], scopes: ["identify", "guilds", "guilds.members.read"],
}); });
setCookie(event, "discord_oauth_state", state, { setCookie("discord_oauth_state", state, {
path: "/", path: "/",
secure: import.meta.env.PROD, secure: import.meta.env.PROD,
httpOnly: true, httpOnly: true,

View file

@ -4,15 +4,12 @@ import { appendHeader } from "vinxi/http";
import { lucia } from "~/lib/auth"; import { lucia } from "~/lib/auth";
export const GET = async (event: APIEvent) => { export const GET = async (event: APIEvent) => {
if (!event.nativeEvent.context.session) { const { session } = event.nativeEvent.context;
return new Error("Unauthorized");
} if (!session) return new Error("Unauthorized");
await lucia.invalidateSession(event.nativeEvent.context.session.id);
appendHeader( await lucia.invalidateSession(session.id);
event, appendHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
"Set-Cookie",
lucia.createBlankSessionCookie().serialize(),
);
return new Response(null, { return new Response(null, {
status: httpStatus.FOUND, status: httpStatus.FOUND,
headers: { Location: "/" }, headers: { Location: "/" },

View file

@ -1,6 +1,5 @@
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons"; import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
import { useLocation, useNavigate, useParams } from "@solidjs/router"; import { useLocation, useNavigate, useParams } from "@solidjs/router";
import { eq } from "drizzle-orm";
import moment from "moment-timezone"; import moment from "moment-timezone";
import createClient from "openapi-fetch"; import createClient from "openapi-fetch";
import { import {
@ -14,8 +13,7 @@ import { createStore } from "solid-js/store";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; import Layout from "~/components/Layout";
import db from "~/drizzle"; import getAccessToken from "~/lib/accessToken";
import { discordTokens } from "~/drizzle/schema";
import { paths } from "~/types/discord"; import { paths } from "~/types/discord";
import "../../styles/pages/config.scss"; import "../../styles/pages/config.scss";
@ -51,36 +49,39 @@ const getPayload = async (
const { user } = event.nativeEvent.context; const { user } = event.nativeEvent.context;
if (!user) return { success: false, message: "User not logged in!" }; if (!user) return { success: false, message: "User not logged in!" };
const tokens = await db.query.discordTokens const tokens = await getAccessToken(user.id);
.findFirst({
where: eq(discordTokens.userId, user.id),
})
.execute();
if (!tokens) return { success: false, message: "No discord access token!" }; if (!tokens) return { success: false, message: "No discord access token!" };
const { GET } = createClient<paths>({ const { GET } = createClient<paths>({
baseUrl: "https://discord.com/api/v10", 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}` }, headers: { Authorization: `Bearer ${tokens.accessToken}` },
}); });
const channelsRequest = await GET("/guilds/{guild_id}/channels", { const { data: channelsData, error: error2 } = await GET(
params: { "/guilds/{guild_id}/channels",
path: { {
guild_id: id, 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) { if (error || error2) {
console.log(guildsRequest.error, channelsRequest.error, pathname); console.log("Discord api error:", { error, error2 });
console.log(error, error2, pathname);
return { return {
success: false, success: false,
message: "Error on one of the discord api requests!", 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) if (!guild)
return { return {
@ -95,7 +96,7 @@ const getPayload = async (
}; };
const channels: ReturnType<typeof initialValue>["guild"]["channels"] = []; const channels: ReturnType<typeof initialValue>["guild"]["channels"] = [];
channelsRequest.data?.forEach((channel) => { channelsData?.forEach((channel) => {
if (channel.type !== 0) return; if (channel.type !== 0) return;
channels.push({ channels.push({
id: channel.id, id: channel.id,

View file

@ -4,14 +4,12 @@ import {
faPlus, faPlus,
} from "@fortawesome/pro-regular-svg-icons"; } from "@fortawesome/pro-regular-svg-icons";
import { useLocation, useNavigate } from "@solidjs/router"; import { useLocation, useNavigate } from "@solidjs/router";
import { eq } from "drizzle-orm";
import createClient from "openapi-fetch"; import createClient from "openapi-fetch";
import { For, createResource } from "solid-js"; import { For, createResource } from "solid-js";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; import Layout from "~/components/Layout";
import db from "~/drizzle"; import getAccessToken from "~/lib/accessToken";
import { discordTokens } from "~/drizzle/schema";
import { paths } from "~/types/discord"; import { paths } from "~/types/discord";
import "../../styles/pages/config.scss"; import "../../styles/pages/config.scss";
@ -43,11 +41,7 @@ const getPayload = async (): Promise<
const { user } = event.nativeEvent.context; const { user } = event.nativeEvent.context;
if (!user) return { success: false, message: "User not logged in!" }; if (!user) return { success: false, message: "User not logged in!" };
const tokens = await db.query.discordTokens const tokens = await getAccessToken(user.id);
.findFirst({
where: eq(discordTokens.userId, user.id),
})
.execute();
if (!tokens) return { success: false, message: "No discord access token!" }; if (!tokens) return { success: false, message: "No discord access token!" };
const { GET } = createClient<paths>({ const { GET } = createClient<paths>({
@ -58,7 +52,7 @@ const getPayload = async (): Promise<
}); });
if (error) { if (error) {
console.log(error); console.log("Discord api error:", { error });
return { success: false, message: "Error on discord api request!" }; return { success: false, message: "Error on discord api request!" };
} }
console.log(pathname, "success"); console.log(pathname, "success");