From 3cffcbb59b6ffcc66bb327a57e1956aa8fc37171 Mon Sep 17 00:00:00 2001 From: Adrian Boros Date: Mon, 16 Sep 2024 11:42:49 +0300 Subject: [PATCH 1/5] moved chared zod schemas, updated password generation for tests --- .../wallet/backend/src/asset/validation.ts | 8 -- .../wallet/backend/src/auth/controller.ts | 12 +-- .../wallet/backend/src/auth/validation.ts | 34 ++------- .../wallet/backend/src/rafiki/controller.ts | 4 +- .../wallet/backend/src/rafiki/validation.ts | 9 --- packages/wallet/backend/src/rapyd/schemas.ts | 9 --- .../wallet/backend/src/rates/validation.ts | 10 --- packages/wallet/backend/tests/mocks.ts | 14 ++-- packages/wallet/frontend/src/lib/api/user.ts | 76 ++----------------- .../wallet/frontend/src/pages/auth/login.tsx | 3 +- .../wallet/frontend/src/pages/auth/signup.tsx | 3 +- packages/wallet/shared/src/types/rate.ts | 11 +++ packages/wallet/shared/src/types/user.ts | 65 ++++++++++++++++ 13 files changed, 112 insertions(+), 146 deletions(-) delete mode 100644 packages/wallet/backend/src/asset/validation.ts delete mode 100644 packages/wallet/backend/src/rates/validation.ts diff --git a/packages/wallet/backend/src/asset/validation.ts b/packages/wallet/backend/src/asset/validation.ts deleted file mode 100644 index 770bbe0d9..000000000 --- a/packages/wallet/backend/src/asset/validation.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { z } from 'zod' - -export const assetSchema = z.object({ - body: z.object({ - code: z.string(), - scale: z.number() - }) -}) diff --git a/packages/wallet/backend/src/auth/controller.ts b/packages/wallet/backend/src/auth/controller.ts index c94213379..cff4b6147 100644 --- a/packages/wallet/backend/src/auth/controller.ts +++ b/packages/wallet/backend/src/auth/controller.ts @@ -2,9 +2,9 @@ import { validate } from '@/shared/validate' import { NextFunction, Request } from 'express' import { AuthService } from './service' import { - logInSchema, - signUpSchema, - resendVerifyEmailSchema + logInBodySchema, + signUpBodySchema, + emailBodySchema } from './validation' import { UserService } from '@/user/service' import { Controller, toSuccessResponse, Unauthorized } from '@shared/backend' @@ -27,7 +27,7 @@ export class AuthController implements IAuthController { try { const { body: { email, password } - } = await validate(signUpSchema, req) + } = await validate(signUpBodySchema, req) await this.authService.signUp({ email, password }) @@ -43,7 +43,7 @@ export class AuthController implements IAuthController { try { const { body: { email, password } - } = await validate(logInSchema, req) + } = await validate(logInBodySchema, req) const { user, session } = await this.authService.authorize({ email, @@ -109,7 +109,7 @@ export class AuthController implements IAuthController { try { const { body: { email } - } = await validate(resendVerifyEmailSchema, req) + } = await validate(emailBodySchema, req) await this.authService.resendVerifyEmail({ email }) diff --git a/packages/wallet/backend/src/auth/validation.ts b/packages/wallet/backend/src/auth/validation.ts index 5a5759248..7a787ae5e 100644 --- a/packages/wallet/backend/src/auth/validation.ts +++ b/packages/wallet/backend/src/auth/validation.ts @@ -1,34 +1,14 @@ import { z } from 'zod' +import { emailSchema, loginSchema, signUpSchema } from '@wallet/shared' -export const signUpSchema = z.object({ - body: z - .object({ - email: z.string().email({ message: 'Email is required' }), - password: z - .string() - .min(6, { message: 'Password should be at least 6 characters long' }), - confirmPassword: z.string() - }) - .superRefine(({ password, confirmPassword }, ctx) => { - if (password !== confirmPassword) { - ctx.addIssue({ - code: 'custom', - message: `Passwords do not match`, - path: ['confirmPassword'] - }) - } - }) +export const signUpBodySchema = z.object({ + body: signUpSchema }) -export const logInSchema = z.object({ - body: z.object({ - email: z.string().email(), - password: z.string() - }) +export const logInBodySchema = z.object({ + body: loginSchema }) -export const resendVerifyEmailSchema = z.object({ - body: z.object({ - email: z.string().email() - }) +export const emailBodySchema = z.object({ + body: emailSchema }) diff --git a/packages/wallet/backend/src/rafiki/controller.ts b/packages/wallet/backend/src/rafiki/controller.ts index c426204bd..ebfb986f6 100644 --- a/packages/wallet/backend/src/rafiki/controller.ts +++ b/packages/wallet/backend/src/rafiki/controller.ts @@ -3,8 +3,8 @@ import { Logger } from 'winston' import { RatesService } from '@/rates/service' import { validate } from '@/shared/validate' import { RafikiService } from './service' -import { ratesSchema, webhookSchema } from './validation' -import { RatesResponse } from '@wallet/shared' +import { webhookSchema } from './validation' +import { ratesSchema, RatesResponse } from '@wallet/shared' interface IRafikiController { getRates: ( diff --git a/packages/wallet/backend/src/rafiki/validation.ts b/packages/wallet/backend/src/rafiki/validation.ts index 7e4a92905..82542c7f6 100644 --- a/packages/wallet/backend/src/rafiki/validation.ts +++ b/packages/wallet/backend/src/rafiki/validation.ts @@ -1,15 +1,6 @@ import { z } from 'zod' import { EventType, PaymentType } from './service' -export const ratesSchema = z.object({ - query: z.object({ - base: z - .string() - .length(3) - .transform((v) => v.toLocaleUpperCase()) - }) -}) - export const webhookSchema = z.object({ body: z.object({ id: z.string({ required_error: 'id is required' }), diff --git a/packages/wallet/backend/src/rapyd/schemas.ts b/packages/wallet/backend/src/rapyd/schemas.ts index 0e8e50c79..34936ff12 100644 --- a/packages/wallet/backend/src/rapyd/schemas.ts +++ b/packages/wallet/backend/src/rapyd/schemas.ts @@ -37,15 +37,6 @@ export const walletSchema = z.object({ }) }) -export const ratesSchema = z.object({ - query: z.object({ - base: z - .string() - .length(3) - .transform((v) => v.toLocaleUpperCase()) - }) -}) - export interface RapydResponse { status: Status data: T diff --git a/packages/wallet/backend/src/rates/validation.ts b/packages/wallet/backend/src/rates/validation.ts deleted file mode 100644 index 7ce574063..000000000 --- a/packages/wallet/backend/src/rates/validation.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { z } from 'zod' - -export const ratesSchema = z.object({ - query: z.object({ - base: z - .string() - .length(3) - .transform((v) => v.toLocaleUpperCase()) - }) -}) diff --git a/packages/wallet/backend/tests/mocks.ts b/packages/wallet/backend/tests/mocks.ts index d54212fea..d41b1eb2a 100644 --- a/packages/wallet/backend/tests/mocks.ts +++ b/packages/wallet/backend/tests/mocks.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker' -import { logInSchema, signUpSchema } from '@/auth/validation' +import { logInBodySchema, signUpBodySchema } from '@/auth/validation' import z from 'zod' import { PartialModelObject } from 'objection' import { Transaction } from '../src/transaction/model' @@ -11,15 +11,16 @@ import { RapydProfile, walletSchema } from '@/rapyd/schemas' -import { ratesSchema, webhookSchema } from '@/rafiki/validation' +import { webhookSchema } from '@/rafiki/validation' import { EventType, WebHook } from '@/rafiki/service' import { incomingPaymentSchema, paymentDetailsSchema } from '@/incomingPayment/validation' import { outgoingPaymentSchema } from '@/outgoingPayment/validation' +import { ratesSchema } from '@wallet/shared' -export type LogInRequest = z.infer +export type LogInRequest = z.infer export type GetRatesRequest = z.infer export type OnWebHook = z.infer export type IncomingPaymentCreated = z.infer @@ -29,7 +30,10 @@ export type GetPaymentDetailsByUrl = z.infer export const fakeLoginData = () => { return { email: faker.internet.email(), - password: faker.internet.password() + password: faker.internet.password({ + length: 20, + pattern: /[0-9a-zA-Z`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/ + }) } } @@ -42,7 +46,7 @@ export const mockLogInRequest = ( } }) -type SignUpRequest = z.infer +type SignUpRequest = z.infer export const mockSignUpRequest = ( overrides?: Partial ): SignUpRequest => { diff --git a/packages/wallet/frontend/src/lib/api/user.ts b/packages/wallet/frontend/src/lib/api/user.ts index 6b96826bc..1d25c6260 100644 --- a/packages/wallet/frontend/src/lib/api/user.ts +++ b/packages/wallet/frontend/src/lib/api/user.ts @@ -7,74 +7,14 @@ import { } from '../httpClient' import { ACCEPTED_IMAGE_TYPES } from '@/utils/constants' import { SelectOption } from '@/ui/forms/Select' -import { UserResponse, ValidTokenResponse } from '@wallet/shared' -import { emailSchema } from '@wallet/shared/src' - -const isValidPassword = (password: string): boolean => { - if (typeof password !== 'string') return false - if (password.length < 8) return false - - const containsUppercase = (ch: string) => /[A-Z]/.test(ch) - const containsLowercase = (ch: string) => /[a-z]/.test(ch) - const containsSpecialChar = (ch: string) => - // eslint-disable-next-line no-useless-escape - /[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(ch) - let countOfUpperCase = 0, - countOfLowerCase = 0, - countOfNumbers = 0, - countOfSpecialChar = 0 - for (let i = 0; i < password.length; i++) { - const ch = password.charAt(i) - if (!isNaN(+ch)) countOfNumbers++ - else if (containsUppercase(ch)) countOfUpperCase++ - else if (containsLowercase(ch)) countOfLowerCase++ - else if (containsSpecialChar(ch)) countOfSpecialChar++ - } - - if ( - countOfLowerCase < 1 || - countOfUpperCase < 1 || - countOfSpecialChar < 1 || - countOfNumbers < 1 - ) { - return false - } - - return true -} - -export const signUpSchema = z - .object({ - email: z.string().email({ message: 'Email is required' }), - password: z - .string() - .min(8, { message: 'Password should be at least 8 characters long' }), - confirmPassword: z.string() - }) - .superRefine(({ password }, ctx) => { - if (!isValidPassword(password)) { - ctx.addIssue({ - code: 'custom', - message: - 'Password must contain at least one number and one special character and have a mixture of uppercase and lowercase letters', - path: ['password'] - }) - } - }) - .superRefine(({ confirmPassword, password }, ctx) => { - if (confirmPassword !== password) { - ctx.addIssue({ - code: 'custom', - message: 'Passwords must match', - path: ['confirmPassword'] - }) - } - }) - -export const loginSchema = z.object({ - email: z.string().email({ message: 'Email is required' }), - password: z.string().min(1, { message: 'Password is required' }) -}) +import { + UserResponse, + ValidTokenResponse, + emailSchema, + isValidPassword, + signUpSchema, + loginSchema +} from '@wallet/shared' export const personalDetailsSchema = z.object({ firstName: z.string().min(1, { message: 'First name is required' }), diff --git a/packages/wallet/frontend/src/pages/auth/login.tsx b/packages/wallet/frontend/src/pages/auth/login.tsx index 9b2bc45f4..8f2820fb7 100644 --- a/packages/wallet/frontend/src/pages/auth/login.tsx +++ b/packages/wallet/frontend/src/pages/auth/login.tsx @@ -7,13 +7,14 @@ import { Link } from '@/ui/Link' import { Play } from '@/components/icons/Play' import { useRouter } from 'next/router' import Image from 'next/image' -import { loginSchema, userService } from '@/lib/api/user' +import { userService } from '@/lib/api/user' import { useDialog } from '@/lib/hooks/useDialog' import { SuccessDialog } from '@/components/dialogs/SuccessDialog' import { ErrorDialog } from '@/components/dialogs/ErrorDialog' import { NextPageWithLayout } from '@/lib/types/app' import { useEffect } from 'react' import { useTheme } from 'next-themes' +import { loginSchema } from '@wallet/shared' const LoginPage: NextPageWithLayout = () => { const [openDialog, closeDialog] = useDialog() diff --git a/packages/wallet/frontend/src/pages/auth/signup.tsx b/packages/wallet/frontend/src/pages/auth/signup.tsx index 0add322c7..a209ff9b2 100644 --- a/packages/wallet/frontend/src/pages/auth/signup.tsx +++ b/packages/wallet/frontend/src/pages/auth/signup.tsx @@ -5,7 +5,7 @@ import { useZodForm } from '@/lib/hooks/useZodForm' import { Input } from '@/ui/forms/Input' import { Link } from '@/ui/Link' import { Play } from '@/components/icons/Play' -import { signUpSchema, userService } from '@/lib/api/user' +import { userService } from '@/lib/api/user' import { useDialog } from '@/lib/hooks/useDialog' import { SuccessDialog } from '@/components/dialogs/SuccessDialog' import { getObjectKeys } from '@/utils/helpers' @@ -13,6 +13,7 @@ import { NextPageWithLayout } from '@/lib/types/app' import { useEffect } from 'react' import { cx } from 'class-variance-authority' import { useTheme } from 'next-themes' +import { signUpSchema } from '@wallet/shared' const SignUpPage: NextPageWithLayout = () => { const [openDialog, closeDialog] = useDialog() diff --git a/packages/wallet/shared/src/types/rate.ts b/packages/wallet/shared/src/types/rate.ts index fbf0faa5c..0fa013729 100644 --- a/packages/wallet/shared/src/types/rate.ts +++ b/packages/wallet/shared/src/types/rate.ts @@ -1,4 +1,15 @@ +import { z } from 'zod' + export interface RatesResponse { base: string rates: Record } + +export const ratesSchema = z.object({ + query: z.object({ + base: z + .string() + .length(3) + .transform((v) => v.toLocaleUpperCase()) + }) +}) diff --git a/packages/wallet/shared/src/types/user.ts b/packages/wallet/shared/src/types/user.ts index b2855d3d2..e12e83595 100644 --- a/packages/wallet/shared/src/types/user.ts +++ b/packages/wallet/shared/src/types/user.ts @@ -16,3 +16,68 @@ export type ValidTokenResponse = { export const emailSchema = z.object({ email: z.string().email({ message: 'Email is required' }) }) + +export const isValidPassword = (password: string): boolean => { + if (typeof password !== 'string') return false + if (password.length < 8) return false + + const containsUppercase = (ch: string) => /[A-Z]/.test(ch) + const containsLowercase = (ch: string) => /[a-z]/.test(ch) + const containsSpecialChar = (ch: string) => + // eslint-disable-next-line no-useless-escape + /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(ch) + let countOfUpperCase = 0, + countOfLowerCase = 0, + countOfNumbers = 0, + countOfSpecialChar = 0 + for (let i = 0; i < password.length; i++) { + const ch = password.charAt(i) + if (!isNaN(+ch)) countOfNumbers++ + else if (containsUppercase(ch)) countOfUpperCase++ + else if (containsLowercase(ch)) countOfLowerCase++ + else if (containsSpecialChar(ch)) countOfSpecialChar++ + } + + if ( + countOfLowerCase < 1 || + countOfUpperCase < 1 || + countOfSpecialChar < 1 || + countOfNumbers < 1 + ) { + return false + } + return true +} + +export const signUpSchema = z + .object({ + email: z.string().email({ message: 'Email is required' }), + password: z + .string() + .min(8, { message: 'Password should be at least 8 characters long' }), + confirmPassword: z.string() + }) + .superRefine(({ password }, ctx) => { + if (!isValidPassword(password)) { + ctx.addIssue({ + code: 'custom', + message: + 'Password must contain at least one number and one special character and have a mixture of uppercase and lowercase letters', + path: ['password'] + }) + } + }) + .superRefine(({ confirmPassword, password }, ctx) => { + if (confirmPassword !== password) { + ctx.addIssue({ + code: 'custom', + message: 'Passwords must match', + path: ['confirmPassword'] + }) + } + }) + +export const loginSchema = z.object({ + email: z.string().email({ message: 'Email is required' }), + password: z.string().min(1, { message: 'Password is required' }) +}) From 1b2cf610f4ff5e51353284595004ffc048ddc1b4 Mon Sep 17 00:00:00 2001 From: Adrian Boros Date: Mon, 16 Sep 2024 11:50:13 +0300 Subject: [PATCH 2/5] add // eslint-disable-next-line no-useless-escape --- packages/wallet/backend/tests/mocks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wallet/backend/tests/mocks.ts b/packages/wallet/backend/tests/mocks.ts index d41b1eb2a..bf27ee887 100644 --- a/packages/wallet/backend/tests/mocks.ts +++ b/packages/wallet/backend/tests/mocks.ts @@ -32,6 +32,7 @@ export const fakeLoginData = () => { email: faker.internet.email(), password: faker.internet.password({ length: 20, + // eslint-disable-next-line no-useless-escape pattern: /[0-9a-zA-Z`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/ }) } From aa2c0887ac94c03715ca13116d65c035b0ccc475 Mon Sep 17 00:00:00 2001 From: Adrian Boros Date: Mon, 16 Sep 2024 12:44:07 +0300 Subject: [PATCH 3/5] ratesSchema revert and use only one definition in wallet backend --- packages/wallet/backend/src/rafiki/controller.ts | 4 ++-- packages/wallet/backend/src/rates/valitation.ts | 10 ++++++++++ packages/wallet/backend/tests/mocks.ts | 2 +- packages/wallet/shared/src/types/rate.ts | 11 ----------- 4 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 packages/wallet/backend/src/rates/valitation.ts diff --git a/packages/wallet/backend/src/rafiki/controller.ts b/packages/wallet/backend/src/rafiki/controller.ts index ebfb986f6..1f00d2e8d 100644 --- a/packages/wallet/backend/src/rafiki/controller.ts +++ b/packages/wallet/backend/src/rafiki/controller.ts @@ -1,11 +1,11 @@ import { NextFunction, Request, Response } from 'express' import { Logger } from 'winston' import { RatesService } from '@/rates/service' +import { ratesSchema } from '@/rates/valitation' import { validate } from '@/shared/validate' import { RafikiService } from './service' import { webhookSchema } from './validation' -import { ratesSchema, RatesResponse } from '@wallet/shared' - +import { RatesResponse } from '@wallet/shared' interface IRafikiController { getRates: ( req: Request, diff --git a/packages/wallet/backend/src/rates/valitation.ts b/packages/wallet/backend/src/rates/valitation.ts new file mode 100644 index 000000000..7ce574063 --- /dev/null +++ b/packages/wallet/backend/src/rates/valitation.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ratesSchema = z.object({ + query: z.object({ + base: z + .string() + .length(3) + .transform((v) => v.toLocaleUpperCase()) + }) +}) diff --git a/packages/wallet/backend/tests/mocks.ts b/packages/wallet/backend/tests/mocks.ts index bf27ee887..ea47f3def 100644 --- a/packages/wallet/backend/tests/mocks.ts +++ b/packages/wallet/backend/tests/mocks.ts @@ -18,7 +18,7 @@ import { paymentDetailsSchema } from '@/incomingPayment/validation' import { outgoingPaymentSchema } from '@/outgoingPayment/validation' -import { ratesSchema } from '@wallet/shared' +import { ratesSchema } from '@/rates/validation' export type LogInRequest = z.infer export type GetRatesRequest = z.infer diff --git a/packages/wallet/shared/src/types/rate.ts b/packages/wallet/shared/src/types/rate.ts index 0fa013729..fbf0faa5c 100644 --- a/packages/wallet/shared/src/types/rate.ts +++ b/packages/wallet/shared/src/types/rate.ts @@ -1,15 +1,4 @@ -import { z } from 'zod' - export interface RatesResponse { base: string rates: Record } - -export const ratesSchema = z.object({ - query: z.object({ - base: z - .string() - .length(3) - .transform((v) => v.toLocaleUpperCase()) - }) -}) From 616205f21c4df87b96693e33e87269a6a64cf08e Mon Sep 17 00:00:00 2001 From: Adrian Boros Date: Mon, 16 Sep 2024 12:50:36 +0300 Subject: [PATCH 4/5] typo --- packages/wallet/backend/tests/mocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet/backend/tests/mocks.ts b/packages/wallet/backend/tests/mocks.ts index ea47f3def..12afadbbb 100644 --- a/packages/wallet/backend/tests/mocks.ts +++ b/packages/wallet/backend/tests/mocks.ts @@ -18,7 +18,7 @@ import { paymentDetailsSchema } from '@/incomingPayment/validation' import { outgoingPaymentSchema } from '@/outgoingPayment/validation' -import { ratesSchema } from '@/rates/validation' +import { ratesSchema } from '@/rates/valitation' export type LogInRequest = z.infer export type GetRatesRequest = z.infer From 65a9209773a30743c035b49e9cdeec6da63bfe16 Mon Sep 17 00:00:00 2001 From: Adrian Boros Date: Mon, 16 Sep 2024 13:01:10 +0300 Subject: [PATCH 5/5] file name typo --- packages/wallet/backend/src/rafiki/controller.ts | 2 +- .../wallet/backend/src/rates/{valitation.ts => validation.ts} | 0 packages/wallet/backend/tests/mocks.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/wallet/backend/src/rates/{valitation.ts => validation.ts} (100%) diff --git a/packages/wallet/backend/src/rafiki/controller.ts b/packages/wallet/backend/src/rafiki/controller.ts index 1f00d2e8d..8ddd3c719 100644 --- a/packages/wallet/backend/src/rafiki/controller.ts +++ b/packages/wallet/backend/src/rafiki/controller.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from 'express' import { Logger } from 'winston' import { RatesService } from '@/rates/service' -import { ratesSchema } from '@/rates/valitation' +import { ratesSchema } from '@/rates/validation' import { validate } from '@/shared/validate' import { RafikiService } from './service' import { webhookSchema } from './validation' diff --git a/packages/wallet/backend/src/rates/valitation.ts b/packages/wallet/backend/src/rates/validation.ts similarity index 100% rename from packages/wallet/backend/src/rates/valitation.ts rename to packages/wallet/backend/src/rates/validation.ts diff --git a/packages/wallet/backend/tests/mocks.ts b/packages/wallet/backend/tests/mocks.ts index 12afadbbb..ea47f3def 100644 --- a/packages/wallet/backend/tests/mocks.ts +++ b/packages/wallet/backend/tests/mocks.ts @@ -18,7 +18,7 @@ import { paymentDetailsSchema } from '@/incomingPayment/validation' import { outgoingPaymentSchema } from '@/outgoingPayment/validation' -import { ratesSchema } from '@/rates/valitation' +import { ratesSchema } from '@/rates/validation' export type LogInRequest = z.infer export type GetRatesRequest = z.infer