diff --git a/api/src/app/app.ts b/api/src/app/app.ts index bdaee04..433b8a5 100644 --- a/api/src/app/app.ts +++ b/api/src/app/app.ts @@ -22,8 +22,8 @@ export async function app(fastify: FastifyInstance, opts: AppOptions) { }); } - // Log unauthorized access attempts - if (error.statusCode === 401 || error.statusCode === 403) { + // Log unauthorized access attempts (exclude /api/auth/me as 401s there are expected during token refresh) + if ((error.statusCode === 401 || error.statusCode === 403) && request.url !== '/api/auth/me') { await AuditService.log({ action: AuditAction.unauthorizedAccess, category: AuditCategory.security, diff --git a/api/src/app/services/book.service.ts b/api/src/app/services/book.service.ts index 638d302..5fcf34c 100644 --- a/api/src/app/services/book.service.ts +++ b/api/src/app/services/book.service.ts @@ -36,10 +36,6 @@ export class BookService { if (!validateStringLength(data.notes, MAX_LENGTHS.NOTES)) { throw new Error(`Notes must be ${MAX_LENGTHS.NOTES} characters or less.`); } - if (!validateStringLength(data.coverImage, MAX_LENGTHS.URL)) { - throw new Error(`Cover image URL must be ${MAX_LENGTHS.URL} characters or less.`); - } - // Validate rating if (!validateRating(data.rating)) { throw new Error("Rating must be an integer between 0 and 10."); @@ -47,7 +43,11 @@ export class BookService { if (data.coverImage) { if (data.coverImage.startsWith("data:")) { - const sizeInBytes = data.coverImage.length * 0.75; + const base64Data = data.coverImage.split(",")[1]; + if (!base64Data) { + throw new Error("Invalid image data URL format."); + } + const sizeInBytes = base64Data.length * 0.75; if (sizeInBytes > MAX_LENGTHS.DATA_URL) { throw new Error("Cover image must be under 5MB."); } diff --git a/api/src/app/services/manga.service.ts b/api/src/app/services/manga.service.ts index 1fa19b5..40beb10 100644 --- a/api/src/app/services/manga.service.ts +++ b/api/src/app/services/manga.service.ts @@ -33,10 +33,6 @@ export class MangaService { if (!validateStringLength(data.notes, MAX_LENGTHS.NOTES)) { throw new Error(`Notes must be ${MAX_LENGTHS.NOTES} characters or less.`); } - if (!validateStringLength(data.coverImage, MAX_LENGTHS.URL)) { - throw new Error(`Cover image URL must be ${MAX_LENGTHS.URL} characters or less.`); - } - // Validate rating if (!validateRating(data.rating)) { throw new Error("Rating must be an integer between 0 and 10."); @@ -45,7 +41,11 @@ export class MangaService { // Validate cover image URL if (data.coverImage) { if (data.coverImage.startsWith("data:")) { - const sizeInBytes = data.coverImage.length * 0.75; + const base64Data = data.coverImage.split(",")[1]; + if (!base64Data) { + throw new Error("Invalid image data URL format."); + } + const sizeInBytes = base64Data.length * 0.75; if (sizeInBytes > MAX_LENGTHS.DATA_URL) { throw new Error("Cover image must be under 5MB."); } diff --git a/api/src/app/services/music.service.ts b/api/src/app/services/music.service.ts index c0480d8..0652c0b 100644 --- a/api/src/app/services/music.service.ts +++ b/api/src/app/services/music.service.ts @@ -33,10 +33,6 @@ export class MusicService { if (!validateStringLength(data.notes, MAX_LENGTHS.NOTES)) { throw new Error(`Notes must be ${MAX_LENGTHS.NOTES} characters or less.`); } - if (!validateStringLength(data.coverArt, MAX_LENGTHS.URL)) { - throw new Error(`Cover art URL must be ${MAX_LENGTHS.URL} characters or less.`); - } - // Validate rating if (data.rating !== undefined && !validateRating(data.rating)) { throw new Error("Rating must be an integer between 0 and 10."); @@ -45,7 +41,11 @@ export class MusicService { // Validate cover art URL if (data.coverArt) { if (data.coverArt.startsWith("data:")) { - const sizeInBytes = data.coverArt.length * 0.75; + const base64Data = data.coverArt.split(",")[1]; + if (!base64Data) { + throw new Error("Invalid image data URL format."); + } + const sizeInBytes = base64Data.length * 0.75; if (sizeInBytes > MAX_LENGTHS.DATA_URL) { throw new Error("Cover image must be under 5MB."); } diff --git a/api/src/app/services/show.service.ts b/api/src/app/services/show.service.ts index cda0ec9..5d5aed9 100644 --- a/api/src/app/services/show.service.ts +++ b/api/src/app/services/show.service.ts @@ -30,10 +30,6 @@ export class ShowService { if (!validateStringLength(data.notes, MAX_LENGTHS.NOTES)) { throw new Error(`Notes must be ${MAX_LENGTHS.NOTES} characters or less.`); } - if (!validateStringLength(data.coverImage, MAX_LENGTHS.URL)) { - throw new Error(`Cover image URL must be ${MAX_LENGTHS.URL} characters or less.`); - } - // Validate rating if (!validateRating(data.rating)) { throw new Error("Rating must be an integer between 0 and 10."); @@ -42,7 +38,11 @@ export class ShowService { // Validate cover image URL if (data.coverImage) { if (data.coverImage.startsWith("data:")) { - const sizeInBytes = data.coverImage.length * 0.75; + const base64Data = data.coverImage.split(",")[1]; + if (!base64Data) { + throw new Error("Invalid image data URL format."); + } + const sizeInBytes = base64Data.length * 0.75; if (sizeInBytes > MAX_LENGTHS.DATA_URL) { throw new Error("Cover image must be under 5MB."); } diff --git a/apps/frontend/src/app/services/global-error-handler.service.ts b/apps/frontend/src/app/services/global-error-handler.service.ts index d798e38..7270dea 100644 --- a/apps/frontend/src/app/services/global-error-handler.service.ts +++ b/apps/frontend/src/app/services/global-error-handler.service.ts @@ -12,6 +12,11 @@ export class GlobalErrorHandler implements ErrorHandler { private toast = inject(ToastService); handleError(error: Error): void { + if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) { + window.location.reload(); + return; + } + console.error('Global error caught:', error); // Show user-friendly error message