From 27b4b36cbfba056ecd3fb1519ca047c78d4df035 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 18 Oct 2022 01:45:46 -0700 Subject: [PATCH] Add validations, req.authUser --- src/server/middleware/authenticate-admin.ts | 2 +- src/server/middleware/authenticate.ts | 27 +++++++---- src/server/types/express/index.d.ts | 2 +- .../validations/album-artists.validation.ts | 4 +- src/server/validations/albums.validation.ts | 10 ++-- src/server/validations/artists.validation.ts | 2 +- src/server/validations/auth.validation.ts | 33 +++++++++++++ src/server/validations/index.ts | 6 +++ src/server/validations/servers.validation.ts | 13 ++++-- src/server/validations/shared.validation.ts | 46 +++++++++++++++---- src/server/validations/songs.validation.ts | 2 +- src/server/validations/users.validation.ts | 2 +- 12 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 src/server/validations/auth.validation.ts diff --git a/src/server/middleware/authenticate-admin.ts b/src/server/middleware/authenticate-admin.ts index b31f2c795..c61d0cb2d 100644 --- a/src/server/middleware/authenticate-admin.ts +++ b/src/server/middleware/authenticate-admin.ts @@ -5,7 +5,7 @@ export const authenticateAdmin = ( res: Response, next: NextFunction ) => { - if (!req.auth.isAdmin) { + if (!req.authUser.isAdmin) { return res.status(403).json({ error: { message: 'This action requires an administrator account.', diff --git a/src/server/middleware/authenticate.ts b/src/server/middleware/authenticate.ts index d3957f6bf..38a5c93a9 100644 --- a/src/server/middleware/authenticate.ts +++ b/src/server/middleware/authenticate.ts @@ -1,6 +1,7 @@ import { - ServerFolderPermissions, - ServerPermissions, + ServerCredential, + ServerFolderPermission, + ServerPermission, User, } from '@prisma/client'; import { NextFunction, Request, Response } from 'express'; @@ -9,8 +10,9 @@ import passport from 'passport'; export type AuthUser = User & { flatServerFolderPermissions: string[]; flatServerPermissions: string[]; - serverFolderPermissions: ServerFolderPermissions[]; - serverPermissions: ServerPermissions[]; + serverFolderPermissions: ServerFolderPermission[]; + serverId?: string; + serverPermissions: ServerPermission[]; }; export const authenticate = ( @@ -46,27 +48,36 @@ export const authenticate = ( } const flatServerFolderPermissions = user.serverFolderPermissions.map( - (permission: ServerFolderPermissions) => permission.serverFolderId + (permission: ServerFolderPermission) => permission.serverFolderId ); const flatServerPermissions = user.serverPermissions.map( - (permission: ServerPermissions) => permission.serverId + (permission: ServerPermission) => permission.serverId ); - const auth = { + const serverCredentials = user.serverCredentials.map( + (credential: ServerCredential) => ({ + id: credential.id, + serverId: credential.serverId, + }) + ); + + const props = { createdAt: user?.createdAt, enabled: user?.enabled, flatServerFolderPermissions, flatServerPermissions, id: user?.id, isAdmin: user?.isAdmin, + server: req.params.serverId, + serverCredentials, serverFolderPermissions: user?.serverFolderPermissions, serverPermissions: user?.serverPermissions, updatedAt: user?.updatedAt, username: user?.username, }; - req.auth = auth; + req.authUser = props; return next(); })(req, res, next); diff --git a/src/server/types/express/index.d.ts b/src/server/types/express/index.d.ts index 5e98c2f58..7471f04c1 100644 --- a/src/server/types/express/index.d.ts +++ b/src/server/types/express/index.d.ts @@ -1,5 +1,5 @@ declare namespace Express { export interface Request { - auth: any; + authUser: any; } } diff --git a/src/server/validations/album-artists.validation.ts b/src/server/validations/album-artists.validation.ts index c10e08bab..283f5033b 100644 --- a/src/server/validations/album-artists.validation.ts +++ b/src/server/validations/album-artists.validation.ts @@ -3,7 +3,7 @@ import { paginationValidation, idValidation } from './shared.validation'; export const list = { body: z.object({}), - params: z.object({}), + params: z.object({ ...idValidation('serverId') }), query: z.object({ ...paginationValidation, serverFolderIds: z.string().min(1), @@ -12,7 +12,7 @@ export const list = { export const detail = { body: z.object({}), - params: z.object({ ...idValidation }), + params: z.object({ ...idValidation('id') }), query: z.object({}), }; diff --git a/src/server/validations/albums.validation.ts b/src/server/validations/albums.validation.ts index d955e9a30..50ab707d3 100644 --- a/src/server/validations/albums.validation.ts +++ b/src/server/validations/albums.validation.ts @@ -5,23 +5,27 @@ import { orderByValidation, paginationValidation, serverFolderIdValidation, + serverUrlIdValidation, } from './shared.validation'; const list = { body: z.object({}), - params: z.object({}), + params: z.object({ ...idValidation('serverId') }), query: z.object({ ...paginationValidation, ...serverFolderIdValidation, ...orderByValidation, + ...serverUrlIdValidation, sortBy: z.nativeEnum(AlbumSort), }), }; const detail = { body: z.object({}), - params: z.object(idValidation), - query: z.object({}), + params: z.object({ ...idValidation('id') }), + query: z.object({ + ...serverUrlIdValidation, + }), }; export const albumsValidation = { diff --git a/src/server/validations/artists.validation.ts b/src/server/validations/artists.validation.ts index 3067aa4c3..97cf19f6c 100644 --- a/src/server/validations/artists.validation.ts +++ b/src/server/validations/artists.validation.ts @@ -20,7 +20,7 @@ const list = { const detail = { body: z.object({}), - params: z.object(idValidation), + params: z.object({ ...idValidation('id') }), query: z.object({}), }; diff --git a/src/server/validations/auth.validation.ts b/src/server/validations/auth.validation.ts new file mode 100644 index 000000000..b1b765542 --- /dev/null +++ b/src/server/validations/auth.validation.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +const login = { + body: z.object({ + password: z.string(), + username: z.string(), + }), + params: z.object({}), + query: z.object({}), +}; + +const register = { + body: z.object({ + password: z.string().min(6).max(255), + username: z.string().min(4).max(26), + }), + params: z.object({}), + query: z.object({}), +}; + +const refresh = { + body: z.object({ + refreshToken: z.string(), + }), + params: z.object({}), + query: z.object({}), +}; + +export const authValidation = { + login, + refresh, + register, +}; diff --git a/src/server/validations/index.ts b/src/server/validations/index.ts index 9ff392f04..5264778b1 100644 --- a/src/server/validations/index.ts +++ b/src/server/validations/index.ts @@ -1,4 +1,7 @@ +import { albumArtistsValidation } from './album-artists.validation'; import { albumsValidation } from './albums.validation'; +import { artistsValidation } from './artists.validation'; +import { authValidation } from './auth.validation'; import { serversValidation } from './servers.validation'; import { songsValidation } from './songs.validation'; import { usersValidation } from './users.validation'; @@ -6,7 +9,10 @@ import { usersValidation } from './users.validation'; export { validateRequest, TypedRequest } from './shared.validation'; export const validation = { + albumArtists: albumArtistsValidation, albums: albumsValidation, + artists: artistsValidation, + auth: authValidation, servers: serversValidation, songs: songsValidation, users: usersValidation, diff --git a/src/server/validations/servers.validation.ts b/src/server/validations/servers.validation.ts index 259e6dce9..421b50351 100644 --- a/src/server/validations/servers.validation.ts +++ b/src/server/validations/servers.validation.ts @@ -4,7 +4,7 @@ import { idValidation } from './shared.validation'; const detail = { body: z.object({}), - params: z.object(idValidation), + params: z.object({ ...idValidation('id') }), query: z.object({}), }; @@ -27,18 +27,25 @@ const create = { const scan = { body: z.object({ serverFolderId: z.string().array().optional() }), - params: z.object(idValidation), + params: z.object({ ...idValidation('id') }), query: z.object({}), }; const refresh = { body: z.object({}), - params: z.object(idValidation), + params: z.object({ ...idValidation('id') }), + query: z.object({}), +}; + +const createCredential = { + body: z.object({ credential: z.string() }), + params: z.object({ ...idValidation('id') }), query: z.object({}), }; export const serversValidation = { create, + createCredential, detail, refresh, scan, diff --git a/src/server/validations/shared.validation.ts b/src/server/validations/shared.validation.ts index 59b6593e5..6483cfea9 100644 --- a/src/server/validations/shared.validation.ts +++ b/src/server/validations/shared.validation.ts @@ -27,6 +27,16 @@ type RequestValidation = { type ErrorListItem = { errors: ZodError; type: ValidationType; + value: string; +}; + +const getErrorValue = (object: any, error: ZodError) => { + const e = error.errors[0]; + if (e.code === z.ZodIssueCode.invalid_type) { + return e.expected; + } + + return object[e.path[0]][e.path[1]]; }; export const validateRequest: ( @@ -38,29 +48,45 @@ export const validateRequest: ( if (params) { const parsed = params.safeParse(req.params); if (!parsed.success) { - errors.push({ errors: parsed.error, type: ValidationType.PARAMS }); + errors.push({ + errors: parsed.error, + type: ValidationType.PARAMS, + value: getErrorValue(req.params, parsed.error), + }); } } if (query) { const parsed = query.safeParse(req.query); if (!parsed.success) { - errors.push({ errors: parsed.error, type: ValidationType.QUERY }); + const value = getErrorValue(req.query, parsed.error); + errors.push({ + errors: parsed.error, + type: ValidationType.QUERY, + value, + }); } } if (body) { const parsed = body.safeParse(req.body); if (!parsed.success) { - errors.push({ errors: parsed.error, type: ValidationType.BODY }); + errors.push({ + errors: parsed.error, + type: ValidationType.BODY, + value: getErrorValue(req.body, parsed.error), + }); } } if (errors.length > 0) { const message = JSON.stringify( [ - `(${errors[0].type})`, - `[${errors[0].errors.issues[0].path[0]}]`, + // `(${errors[0].type})`, + `(${errors[0].type}: ${errors[0].errors.issues[0].path[0]})`, + errors[0].value && `[${errors[0].value}]`, errors[0].errors.issues[0].message, - ].join(' ') + ] + .filter((x) => x) + .join(' ') ); throw ApiError.badRequest(message); @@ -87,8 +113,12 @@ export const paginationValidation = { }), }; -export const idValidation = { - id: z.string().uuid(), +export const serverUrlIdValidation = { + serverUrlId: z.optional(z.string().uuid()), +}; + +export const idValidation = (property: string) => { + return { [property]: z.string().uuid() }; }; export const serverFolderIdValidation = { diff --git a/src/server/validations/songs.validation.ts b/src/server/validations/songs.validation.ts index 00b0becc3..bc7219eda 100644 --- a/src/server/validations/songs.validation.ts +++ b/src/server/validations/songs.validation.ts @@ -7,7 +7,7 @@ import { const list = { body: z.object({}), - params: z.object(idValidation), + params: z.object({ ...idValidation('serverId') }), query: z.object({ ...paginationValidation, ...serverFolderIdValidation, diff --git a/src/server/validations/users.validation.ts b/src/server/validations/users.validation.ts index d1cc433fa..05fcd4f23 100644 --- a/src/server/validations/users.validation.ts +++ b/src/server/validations/users.validation.ts @@ -3,7 +3,7 @@ import { idValidation } from './shared.validation'; const detail = { body: z.object({}), - params: z.object(idValidation), + params: z.object({ ...idValidation('id') }), query: z.object({}), };