-
Notifications
You must be signed in to change notification settings - Fork 0
221 add validation to patch #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; | ||
| import db from './db' | ||
| import { authenticateRequest, checkAuthorization, AuthContext } from './auth'; | ||
| import { UserValidationUtils } from './validation-utils'; | ||
|
|
||
| function requireAuth(authContext: AuthContext, level: Parameters<typeof checkAuthorization>[1], resourceUserId?: number | string): APIGatewayProxyResult | undefined { | ||
| const authCheck = checkAuthorization(authContext, level, resourceUserId); | ||
|
|
@@ -124,15 +125,31 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => { | |
| let user = await db.selectFrom("branch.users").where("user_id", "=", Number(userId)).selectAll().executeTakeFirst(); | ||
| if (!user) return json(404, { message: 'User not found' }); | ||
|
|
||
| // vars to update | ||
| let email = body.email as string; | ||
| let name = body.name as string; | ||
| let isAdmin = body.isAdmin as boolean; | ||
| let profileImage = body.profileImage as string | undefined; | ||
| const updates: { email?: string; name?: string; is_admin?: boolean; profile_image?: string } = {}; | ||
|
|
||
| const emailResult = UserValidationUtils.validateEmail(body.email); | ||
| if (!emailResult.isValid) return json(400, { message: emailResult.error }); | ||
| if (emailResult.value != null) updates.email = emailResult.value; | ||
|
|
||
| const nameResult = UserValidationUtils.validateName(body.name); | ||
| if (!nameResult.isValid) return json(400, { message: nameResult.error }); | ||
| if (nameResult.value != null) updates.name = nameResult.value; | ||
|
|
||
| const isAdminResult = UserValidationUtils.validateIsAdmin(body.isAdmin); | ||
| if (!isAdminResult.isValid) return json(400, { message: isAdminResult.error }); | ||
| if (isAdminResult.value != null) updates.is_admin = isAdminResult.value; | ||
|
|
||
| const profileImageResult = UserValidationUtils.validateProfileImage(body.profileImage); | ||
| if (!profileImageResult.isValid) return json(400, { message: profileImageResult.error }); | ||
| if (profileImageResult.value != null) updates.profile_image = profileImageResult.value; | ||
|
|
||
| if (Object.keys(updates).length === 0) { | ||
| return json(400, { message: 'No valid fields provided to update' }); | ||
| } | ||
|
|
||
| // update | ||
| await db.updateTable('branch.users') | ||
| .set({ email, name, is_admin: isAdmin, profile_image: profileImage }) | ||
| .set(updates) | ||
| .where('user_id', '=', Number(userId)) | ||
| .execute(); | ||
|
|
||
|
|
@@ -168,14 +185,28 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => { | |
| ? (JSON.parse(event.body) as Record<string, unknown>) | ||
| : {}; | ||
|
|
||
| // extract fields to create user | ||
| let email = body.email as string; | ||
| let name = body.name as string; | ||
| let isAdmin = body.isAdmin as boolean; | ||
| if (!email || !name || typeof isAdmin !== 'boolean') { | ||
| // email, name, and isAdmin are required on create | ||
| if (!body.email || !body.name || body.isAdmin === undefined || body.isAdmin === null) { | ||
| return json(400, { message: 'email, name, and isAdmin are required' }); | ||
| } | ||
| let profile_image = body.profileImage as string; | ||
|
|
||
| // validate the type/format of each field | ||
| const emailResult = UserValidationUtils.validateEmail(body.email); | ||
| if (!emailResult.isValid) return json(400, { message: emailResult.error }); | ||
|
|
||
| const nameResult = UserValidationUtils.validateName(body.name); | ||
| if (!nameResult.isValid) return json(400, { message: nameResult.error }); | ||
|
|
||
| const isAdminResult = UserValidationUtils.validateIsAdmin(body.isAdmin); | ||
| if (!isAdminResult.isValid) return json(400, { message: isAdminResult.error }); | ||
|
|
||
| const profileImageResult = UserValidationUtils.validateProfileImage(body.profileImage); | ||
| if (!profileImageResult.isValid) return json(400, { message: profileImageResult.error }); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be missing something obvious but do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added checking isAdmin against validation utils! |
||
| const email = emailResult.value as string; | ||
| const name = nameResult.value as string; | ||
| const isAdmin = isAdminResult.value as boolean; | ||
| const profile_image = profileImageResult.value ?? undefined; | ||
|
|
||
| // Check if user with this email already exists | ||
| const existingUser = await db | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Result type for input validation operations | ||
| export type ValidationResult<T> = { | ||
| isValid: boolean; | ||
| value?: T; | ||
| error?: string; | ||
| }; | ||
|
|
||
| // Utility class for validating user-related input fields. | ||
| export class UserValidationUtils { | ||
| static readonly EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
|
|
||
| // Validates email - if provided, must be a correctly formatted string | ||
| static validateEmail(input: unknown): ValidationResult<string | null> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think email, name, isAdmin should be required. profile image optional makes sense |
||
| if (input === undefined || input === null || input === '') { | ||
| return { isValid: true, value: null }; | ||
| } | ||
| if (typeof input !== 'string') { | ||
| return { isValid: false, error: 'email must be a string' }; | ||
| } | ||
| if (!this.EMAIL_REGEX.test(input)) { | ||
| return { isValid: false, error: 'Invalid email format' }; | ||
| } | ||
| return { isValid: true, value: input }; | ||
| } | ||
|
|
||
| // Validates name - if provided, must be a non-empty string | ||
| static validateName(input: unknown): ValidationResult<string | null> { | ||
| if (input === undefined || input === null) { | ||
| return { isValid: true, value: null }; | ||
| } | ||
| if (typeof input !== 'string' || input.trim().length === 0) { | ||
| return { isValid: false, error: 'name must be a non-empty string' }; | ||
| } | ||
| return { isValid: true, value: input }; | ||
| } | ||
|
|
||
| // Validates isAdmin - if provided, must be a boolean | ||
| static validateIsAdmin(input: unknown): ValidationResult<boolean | null> { | ||
| if (input === undefined || input === null) { | ||
| return { isValid: true, value: null }; | ||
| } | ||
| if (typeof input !== 'boolean') { | ||
| return { isValid: false, error: 'isAdmin must be a boolean' }; | ||
| } | ||
| return { isValid: true, value: input }; | ||
| } | ||
|
|
||
| // Validates profileImage - if provided, must be a string | ||
| static validateProfileImage(input: unknown): ValidationResult<string | null> { | ||
| if (input === undefined || input === null) { | ||
| return { isValid: true, value: null }; | ||
| } | ||
| if (typeof input !== 'string') { | ||
| return { isValid: false, error: 'profileImage must be a string' }; | ||
| } | ||
| return { isValid: true, value: input }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very much a nit but I wonder if we should just throw the whole body into the validation utils and let it handle all these conditionals 👀 I don't feel super strongly about this just an idea