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.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import createClient from 'openapi-fetch';
|
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 { paths } from '$lib/api/schema';
|
||||||
|
import type { SessionUser } from '$lib/types';
|
||||||
import { apiConfig } from '$lib/server/config';
|
import { apiConfig } from '$lib/server/config';
|
||||||
|
|
||||||
interface ApiClientOptions {
|
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 {
|
export function requireAccessToken(locals: App.Locals, message = 'Inicia sesión.'): string {
|
||||||
if (!locals.accessToken) error(401, message);
|
if (!locals.accessToken) error(401, message);
|
||||||
return locals.accessToken;
|
return locals.accessToken;
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { fail } from '@sveltejs/kit';
|
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;
|
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 api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
const result = await api.GET('/tracks/users/{userId}', {
|
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.') };
|
return { tracks: unwrapOrError(result, 'No se pudo cargar tu biblioteca.') };
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import {
|
|||||||
createApiClient,
|
createApiClient,
|
||||||
playlistCoverTrackIds,
|
playlistCoverTrackIds,
|
||||||
requireAccessTokenAction,
|
requireAccessTokenAction,
|
||||||
|
requireUser,
|
||||||
unwrapOrError
|
unwrapOrError
|
||||||
} from '$lib/server/api';
|
} from '$lib/server/api';
|
||||||
import { uploadPresignedImage } from '$lib/server/upload';
|
import { uploadPresignedImage } from '$lib/server/upload';
|
||||||
|
|
||||||
const MAX_NAME = 100;
|
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 api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
const result = await api.GET('/playlists/users/{userId}', {
|
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.');
|
const data = unwrapOrError(result, 'No se pudieron cargar tus playlists.');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { fail, redirect } from '@sveltejs/kit';
|
|||||||
import {
|
import {
|
||||||
createApiClient,
|
createApiClient,
|
||||||
requireAccessTokenAction,
|
requireAccessTokenAction,
|
||||||
|
requireUser,
|
||||||
unwrapOrError,
|
unwrapOrError,
|
||||||
unwrapOrFail
|
unwrapOrFail
|
||||||
} from '$lib/server/api';
|
} from '$lib/server/api';
|
||||||
@@ -14,7 +15,8 @@ import type { YouTubeSong } from '$lib/types';
|
|||||||
const MAX_NAME = 100;
|
const MAX_NAME = 100;
|
||||||
const SUGGESTIONS_LIMIT = 20;
|
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 api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
|
|
||||||
const [playlistRes, tracksRes, libraryRes, playlistsRes] = await Promise.all([
|
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 } }
|
params: { path: { playlistId: params.id }, query: { pageNumber: 1, pageSize: 200 } }
|
||||||
}),
|
}),
|
||||||
api.GET('/tracks/users/{userId}', {
|
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}', {
|
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 } }
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import type { Actions } from './$types';
|
import type { Actions, PageServerLoad } from './$types';
|
||||||
import { fail } from '@sveltejs/kit';
|
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';
|
import { putPresigned, extOf, contentTypeOf, AUDIO_TYPES, IMAGE_TYPES } from '$lib/server/upload';
|
||||||
|
|
||||||
const MAX_TITLE = 100;
|
const MAX_TITLE = 100;
|
||||||
|
|
||||||
|
export const load: PageServerLoad = ({ locals, url }) => {
|
||||||
|
requireUser(locals, url);
|
||||||
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async ({ request, locals, fetch }) => {
|
default: async ({ request, locals, fetch }) => {
|
||||||
const accessToken = requireAccessTokenAction(locals, 'Inicia sesión para subir música.');
|
const accessToken = requireAccessTokenAction(locals, 'Inicia sesión para subir música.');
|
||||||
|
|||||||
Reference in New Issue
Block a user