Lee Morgan пре 4 дана
родитељ
комит
d8c6e39424
3 измењених фајлова са 87 додато и 1 уклоњено
  1. 77 0
      src/lib/server/auth.js
  2. 7 0
      src/routes/workouts/+page.server.js
  3. 3 1
      src/routes/workouts/+page.svelte

+ 77 - 0
src/lib/server/auth.js

@@ -1,3 +1,5 @@
+import { redirect } from '@sveltejs/kit';
+import { ObjectId } from 'mongodb';
 import { dev } from '$app/environment';
 import { env } from '$env/dynamic/private';
 import jwt from 'jsonwebtoken';
@@ -5,6 +7,17 @@ import jwt from 'jsonwebtoken';
 export const SESSION_COOKIE = 'torus_session';
 const TOKEN_TTL = '7d';
 const COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const USERS = 'users';
+
+/**
+ * Authenticated user returned to private routes.
+ * Password is never included.
+ * @typedef {object} AuthUser
+ * @property {string} id
+ * @property {string} uuid
+ * @property {string} name
+ * @property {string} email
+ */
 
 /**
  * @returns {string}
@@ -73,3 +86,67 @@ export function setSessionCookie(cookies, token) {
 export function clearSessionCookie(cookies) {
 	cookies.delete(SESSION_COOKIE, { path: '/' });
 }
+
+/**
+ * Read the session cookie, verify the JWT, then load the user by both
+ * MongoDB id and uuid. uuid must still match the DB value so rotating it
+ * invalidates sessions on all devices.
+ *
+ * @param {import('mongodb').Db} db
+ * @param {import('@sveltejs/kit').Cookies} cookies
+ * @returns {Promise<AuthUser | null>}
+ */
+export async function getUser(db, cookies) {
+	const token = cookies.get(SESSION_COOKIE);
+	if (!token) return null;
+
+	const session = verifySessionToken(token);
+	if (!session) {
+		clearSessionCookie(cookies);
+		return null;
+	}
+
+	if (!ObjectId.isValid(session.id)) {
+		clearSessionCookie(cookies);
+		return null;
+	}
+
+	const doc = await db.collection(USERS).findOne(
+		{
+			_id: new ObjectId(session.id),
+			uuid: session.uuid
+		},
+		{
+			projection: {
+				password: 0
+			}
+		}
+	);
+
+	if (!doc) {
+		clearSessionCookie(cookies);
+		return null;
+	}
+
+	return {
+		id: String(doc._id),
+		uuid: String(doc.uuid),
+		name: String(doc.name ?? ''),
+		email: String(doc.email ?? '')
+	};
+}
+
+/**
+ * Require an authenticated user for a private route.
+ * Redirects to /login when the session is missing or invalid.
+ *
+ * @param {{ locals: { db: import('mongodb').Db }, cookies: import('@sveltejs/kit').Cookies }} event
+ * @returns {Promise<AuthUser>}
+ */
+export async function requireUser(event) {
+	const user = await getUser(event.locals.db, event.cookies);
+	if (!user) {
+		redirect(303, '/login');
+	}
+	return user;
+}

+ 7 - 0
src/routes/workouts/+page.server.js

@@ -0,0 +1,7 @@
+import { requireUser } from '$lib/server/auth.js';
+
+/** @type {import('./$types').PageServerLoad} */
+export async function load(event) {
+	const user = await requireUser(event);
+	return { user };
+}

+ 3 - 1
src/routes/workouts/+page.svelte

@@ -1,5 +1,7 @@
 <script>
 	import logoMark from '$lib/images/logo_white.svg';
+
+	let { data } = $props();
 </script>
 
 <svelte:head>
@@ -17,7 +19,7 @@
 	<main>
 		<section class="panel">
 			<h1>Workouts</h1>
-			<p class="lede">Your sessions will show up here.</p>
+			<p class="lede">Signed in as {data.user.name}. Your sessions will show up here.</p>
 		</section>
 	</main>
 </div>