taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 01e654fb51de21d40e33dcc0166a3dd172c26bf3
parent fb0b62a145694a0a05fa22731a9e9634ad965cde
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  6 Feb 2025 16:33:27 -0300

validation defined on design

Diffstat:
Mpackages/aml-backoffice-ui/src/pages/CreateAccount.tsx | 59+++++++++++++++++++++++++++--------------------------------
Mpackages/aml-backoffice-ui/src/pages/NewMeasure.tsx | 96+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/aml-backoffice-ui/src/pages/Search.tsx | 172++++++++++++++-----------------------------------------------------------------
Mpackages/aml-backoffice-ui/src/pages/UnlockAccount.tsx | 10+++++-----
4 files changed, 124 insertions(+), 213 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx @@ -36,38 +36,11 @@ type FormType = { password: string; repeat: string; }; -function createFormValidator( + +const createAccountForm = ( i18n: InternationalizationAPI, allowInsecurePassword: boolean, -) { - return function check( - state: RecursivePartial<FormValues<FormType>>, - ): FormErrors<FormType> | undefined { - return undefinedIfEmpty<FormErrors<FormType>>({ - password: !state.password - ? i18n.str`required` - : allowInsecurePassword - ? undefined - : state.password.length < 8 - ? i18n.str`should have at least 8 characters` - : !state.password.match(/[a-z]/) && state.password.match(/[A-Z]/) - ? i18n.str`should have lowercase and uppercase characters` - : !state.password.match(/\d/) - ? i18n.str`should have numbers` - : !state.password.match(/[^a-zA-Z\d]/) - ? i18n.str`should have at least one character which is not a number or letter` - : undefined, - - repeat: !state.repeat - ? i18n.str`required` - : state.password !== state.repeat - ? i18n.str`doesn't match` - : undefined, - }); - }; -} - -const createAccountForm = (i18n: InternationalizationAPI): FormDesign => ({ +): FormDesign => ({ type: "single-column", fields: [ { @@ -75,12 +48,34 @@ const createAccountForm = (i18n: InternationalizationAPI): FormDesign => ({ type: "secret", label: i18n.str`Password`, required: true, + validator(value) { + return !value + ? i18n.str`required` + : allowInsecurePassword + ? undefined + : value.length < 8 + ? i18n.str`should have at least 8 characters` + : !value.match(/[a-z]/) && value.match(/[A-Z]/) + ? i18n.str`should have lowercase and uppercase characters` + : !value.match(/\d/) + ? i18n.str`should have numbers` + : !value.match(/[^a-zA-Z\d]/) + ? i18n.str`should have at least one character which is not a number or letter` + : undefined; + }, }, { id: "repeat" as UIHandlerId, type: "secret", label: i18n.str`Repeat password`, required: true, + validator(value) { + return value + ? i18n.str`required` + : // : state.password !== value + // ? i18n.str`doesn't match` + undefined; + }, }, ], }); @@ -92,7 +87,7 @@ export function CreateAccount(): VNode { const [notification, withErrorHandler] = useLocalNotificationHandler(); - const design = createAccountForm(i18n); + const design = createAccountForm(i18n, settings.allowInsecurePassword); const { handler, status } = useForm<FormType>( design, @@ -100,7 +95,7 @@ export function CreateAccount(): VNode { password: undefined, repeat: undefined, }, - createFormValidator(i18n, settings.allowInsecurePassword), + // createFormValidator(i18n, settings.allowInsecurePassword), ); const createAccountHandler = diff --git a/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx b/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx @@ -51,13 +51,13 @@ export function NewMeasure({}: {}): VNode { })), }; - const design = formDesign(i18n, names.programs, names.checks); - const summary = !measures || measures instanceof TalerError || measures.type === "fail" ? undefined : measures.body; + const design = formDesign(i18n, names.programs, names.checks, summary); + const form = useForm<FormType>( design, { @@ -71,38 +71,38 @@ export function NewMeasure({}: {}): VNode { }, ], }, - (f) => { - if (!summary) return undefined; - return undefinedIfEmpty<FormErrors<FormType>>({ - name: !f.name - ? i18n.str`required` - : summary.roots[f.name] - ? i18n.str`already exist` - : undefined, - program: !f.program - ? i18n.str`required` - : programAndCheckMatch(i18n, summary, f.program, f.check) ?? - undefined, - check: checkAndcontextMatch( - i18n, - summary, - f.check, - (f.context ?? []) as { - key: string; - value: string; - }[], - ), - context: checkAndcontextMatch( - i18n, - summary, - f.check, - (f.context ?? []) as { - key: string; - value: string; - }[], - ) as any, - }); - }, + // (f) => { + // if (!summary) return undefined; + // return undefinedIfEmpty<FormErrors<FormType>>({ + // // name: !f.name + // // ? i18n.str`required` + // // : summary.roots[f.name] + // // ? i18n.str`already exist` + // // : undefined, + // // program: !f.program + // // ? i18n.str`required` + // // : programAndCheckMatch(i18n, summary, f.program, f.check) ?? + // // undefined, + // // check: checkAndcontextMatch( + // // i18n, + // // summary, + // // f.check, + // // (f.context ?? []) as { + // // key: string; + // // value: string; + // // }[], + // // ), + // // context: checkAndcontextMatch( + // // i18n, + // // summary, + // // f.check, + // // (f.context ?? []) as { + // // key: string; + // // value: string; + // // }[], + // // ) as any, + // }); + // }, ); function addNewRule(nr: FormType) { @@ -330,6 +330,7 @@ const formDesign = ( i18n: InternationalizationAPI, programs: { key: string; value: AmlProgramRequirement }[], checks: { key: string; value: KycCheckInformation }[], + summary: AvailableMeasureSummary | undefined, ): FormDesign<KycRule> => ({ type: "single-column", fields: [ @@ -338,6 +339,13 @@ const formDesign = ( type: "text", required: true, label: i18n.str`Name`, + validator(value) { + return !value + ? i18n.str`required` + : summary && summary.roots[value] + ? i18n.str`already exist` + : undefined; + }, }, { type: "selectOne", @@ -349,6 +357,13 @@ const formDesign = ( label: m.key, }; }), + validator(value) { + // FIXME: cross validation + return !value + ? i18n.str`required` + : // : !summary ? undefined : programAndCheckMatch(i18n, summary, value, f.check) ?? + undefined; + }, }, { type: "selectOne", @@ -360,6 +375,19 @@ const formDesign = ( label: m.key, }; }), + validator(value) { + // FIXME: cross validation + return undefined; + // return checkAndcontextMatch( + // i18n, + // summary!, + // f.check, + // (f.context ?? []) as { + // key: string; + // value: string; + // }[], + // ) + }, }, { type: "array", diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx @@ -67,7 +67,7 @@ export function Search() { const paytoForm = useForm<FormPayto>( design, { paytoType: "iban" }, - createFormValidator(i18n), + // createFormValidator(i18n), ); if (officer.state !== "ready") { @@ -307,7 +307,7 @@ function XTalerBankForm({ const form = useForm<PaytoUriTalerBankForm>( design, {}, - createTalerBankPaytoValidator(i18n), + // createTalerBankPaytoValidator(i18n), ); const paytoUri = form.status.status === "fail" @@ -340,6 +340,7 @@ function XTalerBankForm({ </form> ); } + function IbanForm({ onSearch, }: { @@ -353,7 +354,7 @@ function IbanForm({ const form = useForm<PaytoUriIBANForm>( design, {}, - createIbanPaytoValidator(i18n), + // createIbanPaytoValidator(i18n), ); const paytoUri = form.status.status === "fail" @@ -398,7 +399,7 @@ function WalletForm({ { exchange: getURLHostnamePortPath(config.keys.base_url), }, - createTalerPaytoValidator(i18n), + // createTalerPaytoValidator(i18n), ); const paytoUri = form.status.status === "fail" @@ -445,7 +446,7 @@ function GenericForm({ const form = useForm<PaytoUriGenericForm>( design, {}, - createGenericPaytoValidator(i18n), + // createGenericPaytoValidator(i18n), ); const paytoUri = form.status.status === "fail" @@ -477,80 +478,23 @@ interface FormPayto { paytoType: "generic" | "iban" | "x-taler-bank" | "wallet"; } -function createFormValidator(i18n: InternationalizationAPI) { - return function check( - state: RecursivePartial<FormValues<FormPayto>>, - ): FormErrors<FormPayto> | undefined { - return undefinedIfEmpty<FormErrors<FormPayto>>({ - paytoType: !state?.paytoType ? i18n.str`required` : undefined, - }); - }; -} - interface PaytoUriGenericForm { payto: string; } -function createGenericPaytoValidator(i18n: InternationalizationAPI) { - return function check( - state: RecursivePartial<FormValues<PaytoUriGenericForm>>, - ): FormErrors<PaytoUriGenericForm> | undefined { - return undefinedIfEmpty<FormErrors<PaytoUriGenericForm>>({ - payto: !state.payto - ? i18n.str`required` - : parsePaytoUri(state.payto) === undefined - ? i18n.str`invalid` - : undefined, - }); - }; -} - interface PaytoUriIBANForm { account: string; } -function createIbanPaytoValidator(i18n: InternationalizationAPI) { - return function check( - state: RecursivePartial<FormValues<PaytoUriIBANForm>>, - ): FormErrors<PaytoUriIBANForm> | undefined { - return undefinedIfEmpty<FormErrors<PaytoUriIBANForm>>({ - account: !state.account ? i18n.str`required` : undefined, - }); - }; -} interface PaytoUriTalerBankForm { hostname: string; account: string; } -function createTalerBankPaytoValidator(i18n: InternationalizationAPI) { - return function check( - state: RecursivePartial<FormValues<PaytoUriTalerBankForm>>, - ): FormErrors<PaytoUriTalerBankForm> | undefined { - return undefinedIfEmpty<FormErrors<PaytoUriTalerBankForm>>({ - account: !state.account ? i18n.str`required` : undefined, - hostname: !state.hostname ? i18n.str`required` : undefined, - }); - }; -} interface PaytoUriTalerForm { exchange: string; reservePub: string; } -function createTalerPaytoValidator(i18n: InternationalizationAPI) { - return function check( - state: RecursivePartial<FormValues<PaytoUriTalerForm>>, - ): FormErrors<PaytoUriTalerForm> | undefined { - return undefinedIfEmpty<FormErrors<PaytoUriTalerForm>>({ - exchange: !state.exchange ? i18n.str`required` : undefined, - reservePub: !state.reservePub - ? i18n.str`required` - : state.reservePub.length !== 16 - ? i18n.str`Should be 16 charaters` - : undefined, - }); - }; -} const paytoTypeField: ( i18n: InternationalizationAPI, @@ -581,17 +525,6 @@ const paytoTypeField: ( }, ]; -const receiverName: (i18n: InternationalizationAPI) => UIFormElementConfig = ( - i18n, -) => ({ - id: "name" as UIHandlerId, - type: "text", - required: true, - label: i18n.str`Owner's name`, - help: i18n.str`It should match the bank account name.`, - placeholder: i18n.str`John Doe`, -}); - const genericFields: ( i18n: InternationalizationAPI, ) => UIFormElementConfig[] = (i18n) => [ @@ -602,6 +535,9 @@ const genericFields: ( label: i18n.str`Payto URI`, help: i18n.str`As defined by RFC 8905`, placeholder: i18n.str`payto://`, + validator(value) { + return parsePaytoUri(value) === undefined ? i18n.str`invalid` : undefined; + }, }, ]; const ibanFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = ( @@ -614,39 +550,8 @@ const ibanFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = ( label: i18n.str`IBAN`, help: i18n.str`International Bank Account Number`, placeholder: i18n.str`DE1231231231`, - // validator: (value) => validateIBAN(value, i18n), + validator: (value) => validateIBAN(value, i18n), }, - // receiverName(i18n), - // { - // id: "bic" as UIHandlerId, - // type: "text", - // label: i18n.str`Bank`, - // help: i18n.str`Business Identifier Code`, - // placeholder: i18n.str`GENODEM1GLS`, - // // validator: (value) => validateIBAN(value, i18n), - // }, -]; -const paytoHashFields: ( - i18n: InternationalizationAPI, -) => UIFormElementConfig[] = (i18n) => [ - { - id: "account" as UIHandlerId, - type: "text", - required: true, - label: i18n.str`ID`, - help: i18n.str`Normalized payto:// hash`, - placeholder: i18n.str`ABC123`, - // validator: (value) => validateIBAN(value, i18n), - }, - // receiverName(i18n), - // { - // id: "bic" as UIHandlerId, - // type: "text", - // label: i18n.str`Bank`, - // help: i18n.str`Business Identifier Code`, - // placeholder: i18n.str`GENODEM1GLS`, - // // validator: (value) => validateIBAN(value, i18n), - // }, ]; const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = ( @@ -659,7 +564,9 @@ const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = ( label: i18n.str`Host`, help: i18n.str`Exchange hostname`, placeholder: i18n.str`exchange.taler.net`, - // validator: (value) => validateIBAN(value, i18n), + validator(value) { + return !DOMAIN_REGEX.test(value) ? i18n.str`Invalid hostname` : undefined; + }, }, { id: "reservePub" as UIHandlerId, @@ -668,17 +575,12 @@ const walletFields: (i18n: InternationalizationAPI) => UIFormElementConfig[] = ( label: i18n.str`ID`, help: i18n.str`Wallet reserve public key`, placeholder: i18n.str`abcdef1235`, - // validator: (value) => validateIBAN(value, i18n), + validator(value) { + return value.length !== 16 + ? i18n.str`Should be 16 characters` + : undefined; + }, }, - // receiverName(i18n), - // { - // id: "bic" as UIHandlerId, - // type: "text", - // label: i18n.str`Bank`, - // help: i18n.str`Business Identifier Code`, - // placeholder: i18n.str`GENODEM1GLS`, - // // validator: (value) => validateIBAN(value, i18n), - // }, ]; const talerBankFields: ( @@ -699,9 +601,9 @@ const talerBankFields: ( label: i18n.str`Hostname`, help: i18n.str`Without the scheme, may include subpath: bank.com, bank.com/path/`, placeholder: i18n.str`bank.demo.taler.net`, - // validator: (value) => validateTalerBank(value, i18n), + validator: (value) => + !DOMAIN_REGEX.test(value) ? i18n.str`Invalid hostname` : undefined, }, - // receiverName(i18n), ]; function validateIBAN( @@ -733,35 +635,21 @@ function validateIBAN( }) .join(""); - function calculate_iban_checksum(str: string): number { - const numberStr = str.substr(0, 5); - const rest = str.substr(5); - const number = parseInt(numberStr, 10); - const result = number % 97; - if (rest.length > 0) { - return calculate_iban_checksum(`${result}${rest}`); - } - return result; - } - const checksum = calculate_iban_checksum(step3); if (checksum !== 1) return i18n.str`IBAN number is invalid, checksum is wrong`; return undefined; } +function calculate_iban_checksum(str: string): number { + const numberStr = str.substr(0, 5); + const rest = str.substr(5); + const number = parseInt(numberStr, 10); + const result = number % 97; + if (rest.length > 0) { + return calculate_iban_checksum(`${result}${rest}`); + } + return result; +} const DOMAIN_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9-_](?:\.[a-zA-Z0-9-_]{2,})+(:[0-9]+)?(\/[a-zA-Z0-9-.]+)*\/?$/; - -function validateTalerBank( - addr: string, - i18n: InternationalizationAPI, -): TranslatedString | undefined { - try { - const valid = DOMAIN_REGEX.test(addr); - if (valid) return undefined; - } catch (e) { - console.log(e); - } - return i18n.str`This is not a valid host.`; -} diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx @@ -58,11 +58,11 @@ export function UnlockAccount(): VNode { { password: undefined, }, - (state) => { - return undefinedIfEmpty<FormErrors<FormType>>({ - password: !state.password ? i18n.str`required` : undefined, - }); - }, + // (state) => { + // return undefinedIfEmpty<FormErrors<FormType>>({ + // password: !state.password ? i18n.str`required` : undefined, + // }); + // }, ); const unlockHandler =