diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts
index cefe3a6..c804e16 100644
--- a/frontend/src/app.d.ts
+++ b/frontend/src/app.d.ts
@@ -5,15 +5,13 @@ declare global {
// interface Error {}
interface Locals {
- session: boolean;
- }
- interface PageData {
user?: {
id: string;
username: string;
avatar: string;
};
}
+ // interface PageData {}
// interface Platform {}
}
}
diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts
index 4887f85..4402452 100644
--- a/frontend/src/hooks.server.ts
+++ b/frontend/src/hooks.server.ts
@@ -1,50 +1,50 @@
+import refresh from '$lib/refreshToken';
import { redirect, type Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
const protectedRoutes = ['/'];
-const auth: Handle = async ({ resolve, event }) => {
- console.log(`handle auth for route: ${event.url.pathname}`);
-
+const handleAuth: Handle = async ({ resolve, event }) => {
const refreshToken = event.cookies.get('refresh-token');
- let accessToken = event.cookies.get('access-token');
+ const accessToken = event.cookies.get('access-token');
- console.log(`refresh token: ${refreshToken}`);
- console.log(`access token: ${accessToken}`);
+ if (refreshToken && !accessToken) await refresh(event);
- if (!accessToken && refreshToken) {
- const rsp = await event.fetch('/auth/discord', {
- method: 'PUT',
+ return await resolve(event);
+};
+
+const handleUserSession: Handle = async ({ resolve, event }) => {
+ if (event.cookies.get('access-token') && event.cookies.get('refresh-token')) {
+ const rsp = await event.fetch('https://discord.com/api/v10/users/@me', {
headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ refreshToken })
+ Authorization: `Bearer ${event.cookies.get('access-token')}`
+ }
});
- if (!rsp.ok) throw redirect(302, '/login');
+ if (!rsp.ok) console.error(`failed to get user session: ${rsp.status} ${await rsp.text()}`);
- accessToken = event.cookies.get('access-token');
+ const { id, username, avatar } = await rsp.json();
+
+ event.locals.user = {
+ id,
+ username,
+ avatar: `https://cdn.discordapp.com/avatars/${id}/${avatar}.png`
+ };
}
- // * grab the access token again, in case it was just refreshed
- event.locals.session = !!(event.cookies.get('access-token') && refreshToken);
-
return await resolve(event);
};
-const guard: Handle = async ({ resolve, event }) => {
- if (protectedRoutes.includes(event.url.pathname) && !event.locals.session) {
- console.warn(`authentication failed for: ${event.url.pathname}`);
+const handleGuard: Handle = async ({ resolve, event }) => {
+ if (!event.locals.user && protectedRoutes.includes(event.url.pathname))
throw redirect(302, '/login');
- } else if (
- (event.url.pathname === '/login' || event.url.pathname.includes('/auth')) &&
- event.locals.session
- ) {
- console.log('already authenticated. redirecting to home page');
+ else if (
+ (event.locals.user && event.url.pathname === '/login') ||
+ (event.locals.user && event.url.pathname.includes('/auth'))
+ )
throw redirect(302, '/');
- }
return await resolve(event);
};
-export const handle: Handle = sequence(auth, guard);
+export const handle: Handle = sequence(handleAuth, handleUserSession, handleGuard);
diff --git a/frontend/src/lib/refreshToken.ts b/frontend/src/lib/refreshToken.ts
new file mode 100644
index 0000000..2d21ebd
--- /dev/null
+++ b/frontend/src/lib/refreshToken.ts
@@ -0,0 +1,34 @@
+import { env } from '$env/dynamic/private';
+import { env as publicEnv } from '$env/dynamic/public';
+import { redirect, type RequestEvent } from '@sveltejs/kit';
+import type { OAuth2Response } from './types';
+
+export default async (event: RequestEvent) => {
+ const refreshToken = event.cookies.get('refresh-token');
+ if (!refreshToken) throw redirect(302, '/login');
+
+ const rsp = await fetch('https://discord.com/api/oauth2/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: new URLSearchParams({
+ client_id: env.CLIENT_ID,
+ client_secret: env.CLIENT_SECRET,
+ grant_type: 'refresh_token',
+ refresh_token: refreshToken
+ })
+ });
+
+ if (!rsp.ok) throw redirect(302, '/login');
+
+ const { access_token, expires_in }: OAuth2Response = await rsp.json();
+ event.cookies.set('access-token', access_token, {
+ domain: publicEnv.PUBLIC_ORIGIN,
+ maxAge: expires_in,
+ expires: new Date(Date.now() + expires_in),
+ httpOnly: true,
+ sameSite: true,
+ path: '/'
+ });
+};
diff --git a/frontend/src/routes/+layout.server.ts b/frontend/src/routes/+layout.server.ts
index 6e1bfb5..f325a7d 100644
--- a/frontend/src/routes/+layout.server.ts
+++ b/frontend/src/routes/+layout.server.ts
@@ -1,28 +1,5 @@
-import { toast } from '$lib/toast';
import type { LayoutServerLoad } from './$types';
-export const load: LayoutServerLoad = async ({ locals: { session }, cookies }) => {
- if (session) {
- const rsp = await fetch('https://discord.com/api/v10/users/@me', {
- headers: {
- Authorization: `Bearer ${cookies.get('access-token')}`
- }
- });
-
- if (!rsp.ok) {
- console.log("failed to fetch user's information");
- toast({ type: 'error', message: "failed to fetch user's information" });
- return {};
- }
-
- const { id, username, avatar } = await rsp.json();
-
- return {
- id,
- username,
- avatar: `https://cdn.discordapp.com/avatars/${id}/${avatar}.png`
- };
- }
-
- return {};
+export const load: LayoutServerLoad = async ({ locals: { user } }) => {
+ return { user };
};
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index b44dd28..6b8a0ee 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -8,7 +8,7 @@
export let data: PageData;
- $: ({ username, avatar, id } = data);
+ $: ({ user } = data);
setContextClient(
new Client({
@@ -27,13 +27,13 @@
- {#if id && avatar && username}
+ {#if user}
@@ -43,7 +43,7 @@
-
+
{/if}
diff --git a/frontend/src/routes/auth/callback/discord/+server.ts b/frontend/src/routes/auth/callback/discord/+server.ts
deleted file mode 100644
index a274338..0000000
--- a/frontend/src/routes/auth/callback/discord/+server.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { env } from '$env/dynamic/private';
-import { env as publicEnv } from '$env/dynamic/public';
-import type { OAuth2Response } from '$lib/types';
-import { redirect } from '@sveltejs/kit';
-import { serialize } from 'cookie';
-import type { RequestHandler } from './$types';
-
-export const GET: RequestHandler = async (event) => {
- const code = event.url.searchParams.get('code');
- if (!code) {
- console.error(`failed to get code in callback url: ${event.url}`);
- throw redirect(302, '/login');
- }
-
- const rsp = await fetch('https://discord.com/api/oauth2/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- body: new URLSearchParams({
- client_id: env.CLIENT_ID,
- client_secret: env.CLIENT_SECRET,
- grant_type: 'authorization_code',
- redirect_uri: `${publicEnv.PUBLIC_ORIGIN}/auth/callback/discord`,
- code
- })
- });
-
- if (!rsp.ok) throw redirect(302, '/login');
-
- const { access_token, refresh_token, expires_in }: OAuth2Response = await rsp.json();
-
- const headers = new Headers();
- headers.set('Location', '/');
- headers.append(
- 'Set-Cookie',
- serialize('access-token', access_token, {
- domain: publicEnv.PUBLIC_ORIGIN,
- maxAge: expires_in,
- expires: new Date(Date.now() + expires_in),
- httpOnly: true,
- sameSite: true,
- path: '/'
- })
- );
- headers.append(
- 'Set-Cookie',
- serialize('refresh-token', refresh_token, {
- domain: publicEnv.PUBLIC_ORIGIN,
- maxAge: 60 * 60 * 24 * 7,
- expires: new Date(Date.now() + 60 * 60 * 24 * 7),
- httpOnly: true,
- sameSite: true,
- path: '/'
- })
- );
-
- return new Response(null, {
- status: 302,
- headers
- });
-};
diff --git a/frontend/src/routes/auth/discord/+server.ts b/frontend/src/routes/auth/discord/+server.ts
index 1cc9186..9560c6f 100644
--- a/frontend/src/routes/auth/discord/+server.ts
+++ b/frontend/src/routes/auth/discord/+server.ts
@@ -1,61 +1,14 @@
import { env } from '$env/dynamic/private';
import { env as publicEnv } from '$env/dynamic/public';
-import type { OAuth2Response } from '$lib/types';
-import { serialize } from 'cookie';
-import type { RequestHandler } from './$types';
+import { redirect, type RequestHandler } from '@sveltejs/kit';
-export const GET: RequestHandler = () => {
+export const GET: RequestHandler = async () => {
const params = new URLSearchParams({
client_id: env.CLIENT_ID,
- redirect_uri: `${publicEnv.PUBLIC_ORIGIN}/auth/callback/discord`,
+ redirect_uri: `${publicEnv.PUBLIC_ORIGIN}/auth/discord/callback`,
response_type: 'code',
scope: 'identify'
});
- return new Response(null, {
- headers: {
- Location: `https://discord.com/api/oauth2/authorize?${params.toString()}`
- },
- status: 302
- });
-};
-
-export const PUT: RequestHandler = async ({ request, fetch }) => {
- const { refreshToken } = await request.json();
-
- const rsp = await fetch('https://discord.com/api/oauth2/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- body: new URLSearchParams({
- client_id: env.CLIENT_ID,
- client_secret: env.CLIENT_SECRET,
- grant_type: 'refresh_token',
- refresh_token: refreshToken
- })
- });
-
- if (!rsp.ok) return new Response(null, { status: 401 });
-
- const { access_token, expires_in }: OAuth2Response = await rsp.json();
-
- const headers = new Headers();
- headers.set('Location', '/');
- headers.append(
- 'Set-Cookie',
- serialize('access-token', access_token, {
- domain: publicEnv.PUBLIC_ORIGIN,
- maxAge: expires_in,
- expires: new Date(Date.now() + expires_in),
- httpOnly: true,
- sameSite: true,
- path: '/'
- })
- );
-
- return new Response(null, {
- status: 302,
- headers
- });
+ throw redirect(302, `https://discord.com/api/oauth2/authorize?${params.toString()}`);
};
diff --git a/frontend/src/routes/auth/discord/callback/+page.server.ts b/frontend/src/routes/auth/discord/callback/+page.server.ts
new file mode 100644
index 0000000..a8597df
--- /dev/null
+++ b/frontend/src/routes/auth/discord/callback/+page.server.ts
@@ -0,0 +1,47 @@
+import { env } from '$env/dynamic/private';
+import { env as publicEnv } from '$env/dynamic/public';
+import type { OAuth2Response } from '$lib/types';
+import type { PageServerLoad } from './$types';
+
+export const load: PageServerLoad = async ({ url, cookies }) => {
+ const code = url.searchParams.get('code');
+ if (!code) {
+ console.error(`failed to get code in callback url: ${url}`);
+ return { ok: false, uri: '/login', status: 302 };
+ }
+
+ const rsp = await fetch('https://discord.com/api/oauth2/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: new URLSearchParams({
+ client_id: env.CLIENT_ID,
+ client_secret: env.CLIENT_SECRET,
+ grant_type: 'authorization_code',
+ redirect_uri: `${publicEnv.PUBLIC_ORIGIN}/auth/discord/callback`,
+ code
+ })
+ });
+
+ if (!rsp.ok) return { ok: false, uri: '/login', status: 302 };
+
+ const { access_token, refresh_token, expires_in }: OAuth2Response = await rsp.json();
+
+ cookies.set('access-token', access_token, {
+ maxAge: expires_in,
+ httpOnly: true,
+ sameSite: true,
+ path: '/',
+ secure: process.env.NODE_ENV === 'production'
+ });
+ cookies.set('refresh-token', refresh_token, {
+ maxAge: expires_in * 2,
+ httpOnly: true,
+ sameSite: true,
+ path: '/',
+ secure: process.env.NODE_ENV === 'production'
+ });
+
+ return { ok: true, uri: '/', status: 302 };
+};
diff --git a/frontend/src/routes/auth/discord/callback/+page.svelte b/frontend/src/routes/auth/discord/callback/+page.svelte
new file mode 100644
index 0000000..61aed7e
--- /dev/null
+++ b/frontend/src/routes/auth/discord/callback/+page.svelte
@@ -0,0 +1,11 @@
+
diff --git a/frontend/src/routes/auth/discord/callback/+page.ts b/frontend/src/routes/auth/discord/callback/+page.ts
new file mode 100644
index 0000000..e4ecee0
--- /dev/null
+++ b/frontend/src/routes/auth/discord/callback/+page.ts
@@ -0,0 +1,5 @@
+import type { PageLoad } from './$types';
+
+export const load: PageLoad = async ({ data: { uri } }) => {
+ return { uri };
+};
diff --git a/frontend/src/routes/logout/+server.ts b/frontend/src/routes/logout/+server.ts
index b14918e..faa5b8d 100644
--- a/frontend/src/routes/logout/+server.ts
+++ b/frontend/src/routes/logout/+server.ts
@@ -1,9 +1,9 @@
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
-export const GET: RequestHandler = async (event) => {
- event.cookies.delete('access-token');
- event.cookies.delete('refresh-token');
+export const GET: RequestHandler = async ({ cookies }) => {
+ cookies.set('access-token', '', { maxAge: -1 });
+ cookies.set('refresh-token', '', { maxAge: -1 });
throw redirect(302, '/login');
};