/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { AccessToken, HttpStatusCode, Logger, TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util"; import { LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; import { useBackendState } from "../hooks/backend.js"; import { undefinedIfEmpty } from "../utils.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js"; import { useSettingsContext } from "../context/settings.js"; const logger = new Logger("RegistrationPage"); export function RegistrationPage({ onComplete, onCancel }: { onComplete: () => void; onCancel: () => void; }): VNode { const { i18n } = useTranslationContext(); const { config } = useBankCoreApiContext(); if (!config.allow_registrations) { return (

{i18n.str`Currently, the bank is not accepting new registrations!`}

); } return ; } export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9-]*$/; export const PHONE_REGEX = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/; export const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; /** * Collect and submit registration data. */ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, onCancel: () => void }): VNode { const backend = useBackendState(); const [username, setUsername] = useState(); const [name, setName] = useState(); const [password, setPassword] = useState(); const [phone, setPhone] = useState(); const [email, setEmail] = useState(); const [repeatPassword, setRepeatPassword] = useState(); const [notification, notify, handleError] = useLocalNotification() const settings = useSettingsContext(); const { api } = useBankCoreApiContext() // const { register } = useTestingAPI(); const { i18n } = useTranslationContext(); const errors = undefinedIfEmpty({ name: !name ? i18n.str`Missing name` : undefined, username: !username ? i18n.str`Missing username` : !USERNAME_REGEX.test(username) ? i18n.str`Use letters and numbers only, and start with a lowercase letter` : undefined, phone: !phone ? undefined : !PHONE_REGEX.test(phone) ? i18n.str`Use letters and numbers only, and start with a lowercase letter` : undefined, email: !email ? undefined : !EMAIL_REGEX.test(email) ? i18n.str`Use letters and numbers only, and start with a lowercase letter` : undefined, password: !password ? i18n.str`Missing password` : undefined, repeatPassword: !repeatPassword ? i18n.str`Missing password` : repeatPassword !== password ? i18n.str`Passwords don't match` : undefined, }); async function doRegistrationAndLogin(name: string, username: string, password: string, onComplete: () => void) { await handleError(async () => { createAccount: { const resp = await api.createAccount("" as AccessToken, { name, username, password }); if (resp.type === "fail") { switch (resp.case) { case HttpStatusCode.BadRequest: return notify({ type: "error", title: i18n.str`Server replied with invalid phone or email.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({ type: "error", title: i18n.str`Registration is disabled because the bank ran out of bonus credit.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case HttpStatusCode.Unauthorized: return notify({ type: "error", title: i18n.str`No enough permission to create that account.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({ type: "error", title: i18n.str`That account id is already taken.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({ type: "error", title: i18n.str`That username is already taken.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({ type: "error", title: i18n.str`That username can't be used because is reserved.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({ type: "error", title: i18n.str`Only admin is allow to set debt limit.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({ type: "error", title: i18n.str`No information for the selected authentication channel.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return notify({ type: "error", title: i18n.str`Authentication channel is not supported.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return notify({ type: "error", title: i18n.str`Only admin can create accounts with second factor authentication.`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) default: assertUnreachable(resp) } } } login: { const resp = await api.getAuthenticationAPI(username).createAccessToken(password, { scope: "readwrite", duration: { d_us: "forever" }, refreshable: true, }) if (resp.type === "ok") { backend.logIn({ username, token: resp.body.access_token }); } else { switch (resp.case) { case "wrong-credentials": return notify({ type: "error", title: i18n.str`Wrong credentials for "${username}"`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) case "not-found": return notify({ type: "error", title: i18n.str`Account not found`, description: resp.detail.hint as TranslatedString, debug: resp.detail, }) default: assertUnreachable(resp) } } } onComplete() }) } async function doRegistrationStep() { if (!username || !password || !name) return; await doRegistrationAndLogin(name, username, password, () => { setUsername(undefined); setPassword(undefined); setRepeatPassword(undefined); onComplete(); }) } async function doRandomRegistration(tries: number = 3) { const user = getRandomUsername(); const pass = settings.simplePasswordForRandomAccounts ? "123" : getRandomPassword(); const username = `_${user.first}-${user.second}_` const name = `${user.first}, ${user.second}` await doRegistrationAndLogin(name, username, pass, onComplete) } return (

{i18n.str`Account registration`}

{ e.preventDefault(); }} autoCapitalize="none" autoCorrect="off" >
{ setUsername(e.currentTarget.value); }} />
{ setPassword(e.currentTarget.value); }} />
{ setRepeatPassword(e.currentTarget.value); }} />
{ setName(e.currentTarget.value); }} /> {/* */}
{/*
{ setPhone(e.currentTarget.value); }} />
{ setEmail(e.currentTarget.value); }} />
*/}
{settings.allowRandomAccountCreation &&

}
); } export function assertUnreachable(x: never): never { throw new Error("Didn't expect to get here"); }