Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/wallet/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"graphql-request": "^6.1.0",
"hash-wasm": "^4.12.0",
"helmet": "^7.2.0",
"i18n-iso-countries": "^7.14.0",
"ioredis": "^5.8.0",
"iron-session": "^8.0.4",
"json-canonicalize": "^1.2.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/wallet/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ export class App {
cardController.closeCard
)

//Terminal
router.post('/terminals/validation', terminalController.validation)

// Interledger cards
router.get('/cards', isAuth, interledgerCardController.list)

Expand Down
41 changes: 40 additions & 1 deletion packages/wallet/backend/src/terminal/controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Request, Response, NextFunction } from 'express'
import { TerminalService } from './service'
import { toSuccessResponse } from '@shared/backend'
import { requestSchema } from './validation'
import { WalletAddressService } from '@/walletAddress/service'
import { validate } from '@/shared/validate'

export class TerminalController {
constructor(private terminalService: TerminalService) {}
constructor(
private terminalService: TerminalService,
private walletAddressService: WalletAddressService
) {}

getOnboardingFormDefinition = async (
_req: Request,
Expand All @@ -18,4 +24,37 @@ export class TerminalController {
next(error)
}
}

validation = async (req: Request, res: Response, next: NextFunction) => {
try {
const formDefinition =
await this.terminalService.getOnboardingFormDefinition()
const schema = await requestSchema(formDefinition)
const { body } = await validate(schema, req)

// check if walletAddress exists
const paymentPointerField = formDefinition.find(
(value) => value.validation?.format === 'payment-pointer'
)

if (paymentPointerField) {
const checkWalletAddress = await this.walletAddressService.getByUrl(
req.body[paymentPointerField.key]
)
if (!checkWalletAddress) {
return res.status(400).json({
valid: false,
error: {
field: paymentPointerField.key,
message: 'Wallet Address not found!'
}
})
}
}

res.status(200).json({ valid: true, response: body })
} catch (error) {
next(error)
}
}
}
2 changes: 1 addition & 1 deletion packages/wallet/backend/src/terminal/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseModel } from '@shared/backend'
import { Model } from 'objection'

interface Validation {
export interface Validation {
minLength?: number
maxLength?: number
pattern?: string
Expand Down
174 changes: 174 additions & 0 deletions packages/wallet/backend/src/terminal/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { z } from 'zod'
import countries from 'i18n-iso-countries'
import { FieldDefinitions, Validation } from './model'

export async function requestSchema(fields: FieldDefinitions[]) {
return z.object({
body: await buildSchema(fields)
})
}

function stringValidation(
schema: z.ZodString,
validation?: Validation,
fieldName?: string
): z.ZodTypeAny {
if (!validation) {
return schema
}
if (validation.minLength) {
schema = schema.min(validation.minLength, {
message: `${fieldName} must be at least ${validation.minLength} characters`
})
}
if (validation.maxLength) {
schema = schema.max(validation.maxLength, {
message: `${fieldName} must be at most ${validation.maxLength} characters`
})
}
if (validation.pattern) {
schema = schema.regex(new RegExp(validation.pattern), {
message: `${fieldName} has an invalid format, expected - ${new RegExp(validation.pattern)}`
})
}

return schema
}

export async function buildSchema(fields: FieldDefinitions[]) {
const schemaShape: Record<string, z.ZodTypeAny> = {}
for (const field of fields) {
let schema: z.ZodTypeAny

const optionValues = field.options
? field.options?.map((option) => option.value)
: undefined

switch (field.type) {
case 'text':
schema = stringValidation(
z.string({ required_error: `${field.key} is required` }),
field.validation,
field.key
)
switch (field.validation?.format) {
case 'payment-pointer':
schema = z
.string()
.trim()
.url()
.refine((value) => value.startsWith('https://'), {
message:
'PAYMENT_POINTER must be a URL starting with https:// instead of the classic "$" format'
})

break

case 'iso-country':
schema = z
.string({ required_error: `${field.key} is required` })
.refine(
(value) => {
const isValid = countries.isValid(value)
return isValid
},
{
message: `${field.key} does not contain a valid country-code`
}
)
}

break

case 'email':
schema = stringValidation(
z.string({ required_error: `${field.key} is required` }).email(),
field.validation,
field.key
)

break

case 'number':
schema = z.number({
required_error: `${field.key} is required`
})

if (field.validation?.min !== undefined) {
schema = (schema as z.ZodNumber).min(field.validation.min as number, {
message: `${field.key} must be min ${field.validation.min}`
})
}

if (field.validation?.max !== undefined) {
schema = (schema as z.ZodNumber).max(field.validation.max as number, {
message: `${field.key} must be max ${field.validation.max}`
})
}

break

case 'tel':
schema = stringValidation(
z
.string({ required_error: `${field.key} is required` })
.regex(/^\+?[0-9]+$/, 'Invalid phone number'),
field.validation,
field.key
)

break

case 'checkbox':
schema = z
.boolean({ required_error: `${field.key} is required` })
.refine((value) => value === field.validation?.mustEqual, {
message: `${field.key} must be ${field.validation?.mustEqual}`
})

break

case 'date':
schema = z.coerce.date({ required_error: `${field.key} is required` })

if (field.validation?.min !== undefined) {
const minDate = new Date(field.validation.min)
schema = schema.refine((date) => date >= minDate, {
message: `Date must be equal or bigger than ${field.validation.min}`
})
}

if (field.validation?.max !== undefined) {
const maxDate = new Date(field.validation.max)

schema = schema.refine((date) => date <= maxDate, {
message: `Date must be equal or lower than ${field.validation.max}`
})
}

break

case 'select':
schema = z
.string({ required_error: `${field.key} is required` })
.refine((value) => optionValues?.includes(value), {
message: `Invalid value for ${field.key}`
})

break

default:
schema = z.string().refine(() => {}, {
message: `something went wrong with this key - ${field.key}`
})
}

if (!field.required) {
schema = schema.optional()
}

schemaShape[field.key] = schema
}

return z.object(schemaShape)
}
Loading
Loading