From c8bc26a5e0d1f7975604e0f7a77dc8b7a484f702 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Mon, 20 Jul 2026 07:28:04 +0200 Subject: [PATCH] fix(web): guard authenticated routes instead of asserting the session user library, playlists and playlists/[id] read locals.user!.sub with no check, so an anonymous request threw a TypeError and returned 500 instead of redirecting to login. Adds requireUser(), which redirects to /login?returnTo=... and returns a non-null SessionUser, and drops the five non-null assertions. /upload had no load at all: the page rendered for anonymous users and only failed on submit. It now redirects up front. --- frontend/src/lib/server/api.ts | 8 +++++++- frontend/src/routes/(app)/library/+page.server.ts | 13 ++++++++++--- frontend/src/routes/(app)/playlists/+page.server.ts | 6 ++++-- .../src/routes/(app)/playlists/[id]/+page.server.ts | 8 +++++--- frontend/src/routes/(app)/upload/+page.server.ts | 13 +++++++++++-- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/server/api.ts b/frontend/src/lib/server/api.ts index ea6c451..b62099c 100644 --- a/frontend/src/lib/server/api.ts +++ b/frontend/src/lib/server/api.ts @@ -1,6 +1,7 @@ import createClient from 'openapi-fetch'; -import { error, fail, type ActionFailure } from '@sveltejs/kit'; +import { error, fail, redirect, type ActionFailure } from '@sveltejs/kit'; import type { paths } from '$lib/api/schema'; +import type { SessionUser } from '$lib/types'; import { apiConfig } from '$lib/server/config'; interface ApiClientOptions { @@ -16,6 +17,11 @@ export function createApiClient({ fetch, accessToken }: ApiClientOptions) { }); } +export function requireUser(locals: App.Locals, url: URL): SessionUser { + if (!locals.user) redirect(302, `/login?returnTo=${encodeURIComponent(url.pathname + url.search)}`); + return locals.user; +} + export function requireAccessToken(locals: App.Locals, message = 'Inicia sesión.'): string { if (!locals.accessToken) error(401, message); return locals.accessToken; diff --git a/frontend/src/routes/(app)/library/+page.server.ts b/frontend/src/routes/(app)/library/+page.server.ts index 8469f60..3a49811 100644 --- a/frontend/src/routes/(app)/library/+page.server.ts +++ b/frontend/src/routes/(app)/library/+page.server.ts @@ -1,13 +1,20 @@ import type { PageServerLoad, Actions } from './$types'; import { fail } from '@sveltejs/kit'; -import { createApiClient, requireAccessTokenAction, unwrapOrError, unwrapOrFail } from '$lib/server/api'; +import { + createApiClient, + requireAccessTokenAction, + requireUser, + unwrapOrError, + unwrapOrFail +} from '$lib/server/api'; const PAGE_SIZE = 50; -export const load: PageServerLoad = async ({ locals, fetch }) => { +export const load: PageServerLoad = async ({ locals, url, fetch }) => { + const user = requireUser(locals, url); const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined }); const result = await api.GET('/tracks/users/{userId}', { - params: { path: { userId: locals.user!.sub }, query: { pageNumber: 1, pageSize: PAGE_SIZE } } + params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: PAGE_SIZE } } }); return { tracks: unwrapOrError(result, 'No se pudo cargar tu biblioteca.') }; diff --git a/frontend/src/routes/(app)/playlists/+page.server.ts b/frontend/src/routes/(app)/playlists/+page.server.ts index 4724809..9fe4e9f 100644 --- a/frontend/src/routes/(app)/playlists/+page.server.ts +++ b/frontend/src/routes/(app)/playlists/+page.server.ts @@ -4,16 +4,18 @@ import { createApiClient, playlistCoverTrackIds, requireAccessTokenAction, + requireUser, unwrapOrError } from '$lib/server/api'; import { uploadPresignedImage } from '$lib/server/upload'; const MAX_NAME = 100; -export const load: PageServerLoad = async ({ locals, fetch }) => { +export const load: PageServerLoad = async ({ locals, url, fetch }) => { + const user = requireUser(locals, url); const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined }); const result = await api.GET('/playlists/users/{userId}', { - params: { path: { userId: locals.user!.sub }, query: { pageNumber: 1, pageSize: 50 } } + params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } } }); const data = unwrapOrError(result, 'No se pudieron cargar tus playlists.'); diff --git a/frontend/src/routes/(app)/playlists/[id]/+page.server.ts b/frontend/src/routes/(app)/playlists/[id]/+page.server.ts index 362e4ce..02b0d1a 100644 --- a/frontend/src/routes/(app)/playlists/[id]/+page.server.ts +++ b/frontend/src/routes/(app)/playlists/[id]/+page.server.ts @@ -3,6 +3,7 @@ import { fail, redirect } from '@sveltejs/kit'; import { createApiClient, requireAccessTokenAction, + requireUser, unwrapOrError, unwrapOrFail } from '$lib/server/api'; @@ -14,7 +15,8 @@ import type { YouTubeSong } from '$lib/types'; const MAX_NAME = 100; const SUGGESTIONS_LIMIT = 20; -export const load: PageServerLoad = async ({ params, locals, fetch }) => { +export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { + const user = requireUser(locals, url); const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined }); const [playlistRes, tracksRes, libraryRes, playlistsRes] = await Promise.all([ @@ -23,10 +25,10 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => { params: { path: { playlistId: params.id }, query: { pageNumber: 1, pageSize: 200 } } }), api.GET('/tracks/users/{userId}', { - params: { path: { userId: locals.user!.sub }, query: { pageNumber: 1, pageSize: 50 } } + params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } } }), api.GET('/playlists/users/{userId}', { - params: { path: { userId: locals.user!.sub }, query: { pageNumber: 1, pageSize: 50 } } + params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } } }) ]); diff --git a/frontend/src/routes/(app)/upload/+page.server.ts b/frontend/src/routes/(app)/upload/+page.server.ts index e4ee66e..bb0e6ba 100644 --- a/frontend/src/routes/(app)/upload/+page.server.ts +++ b/frontend/src/routes/(app)/upload/+page.server.ts @@ -1,10 +1,19 @@ -import type { Actions } from './$types'; +import type { Actions, PageServerLoad } from './$types'; import { fail } from '@sveltejs/kit'; -import { createApiClient, requireAccessTokenAction, unwrapOrFail } from '$lib/server/api'; +import { + createApiClient, + requireAccessTokenAction, + requireUser, + unwrapOrFail +} from '$lib/server/api'; import { putPresigned, extOf, contentTypeOf, AUDIO_TYPES, IMAGE_TYPES } from '$lib/server/upload'; const MAX_TITLE = 100; +export const load: PageServerLoad = ({ locals, url }) => { + requireUser(locals, url); +}; + export const actions: Actions = { default: async ({ request, locals, fetch }) => { const accessToken = requireAccessTokenAction(locals, 'Inicia sesión para subir música.');