/* This file is part of GNU Anastasis (C) 2021-2022 Anastasis SARL GNU Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Anastasis 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with GNU Anastasis; see the file COPYING. If not, see */ import { UserAttributeSpec, validators } from "@gnu-taler/anastasis-core"; import { isAfter, parse } from "date-fns"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { DateInput } from "../../components/fields/DateInput.js"; import { PhoneNumberInput } from "../../components/fields/NumberInput.js"; import { TextInput } from "../../components/fields/TextInput.js"; import { useAnastasisContext } from "../../context/anastasis.js"; import { ConfirmModal } from "./ConfirmModal.js"; import { AnastasisClientFrame, withProcessLabel } from "./index.js"; export function AttributeEntryScreen(): VNode { const reducer = useAnastasisContext(); const state = reducer?.currentReducerState; const currentIdentityAttributes = state && "identity_attributes" in state ? state.identity_attributes || {} : {}; const [attrs, setAttrs] = useState>( currentIdentityAttributes, ); const isBackup = state?.reducer_type === "backup"; const [askUserIfSure, setAskUserIfSure] = useState(false); if (!reducer) { return
no reducer in context
; } if ( !reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState) ) { return
invalid state
; } const reqAttr = reducer.currentReducerState.required_attributes || []; let hasErrors = false; const fieldList: VNode[] = reqAttr.map((spec, i: number) => { const value = attrs[spec.name]; const error = checkIfValid(value, spec); function addAutocomplete(newValue: string): string { const ac = spec.autocomplete; if (!ac || ac.length <= newValue.length || ac[newValue.length] === "?") return newValue; if (!value || newValue.length < value.length) { return newValue.slice(0, -1); } return newValue + ac[newValue.length]; } hasErrors = hasErrors || error !== undefined; return ( setAttrs({ ...attrs, [spec.name]: addAutocomplete(v) }) } spec={spec} errorMessage={error} onConfirm={() => { if (!hasErrors) { setAskUserIfSure(true); } }} value={value} /> ); }); const doConfirm = async () => { await reducer.transition("enter_user_attributes", { identity_attributes: { application_id: "anastasis-standalone", ...attrs, }, }); }; function saveAsPDF(): void { const printWindow = window.open("", "", "height=400,width=800"); const divContents = document.getElementById("printThis"); if (!printWindow || !divContents) return; printWindow.document.write( `Anastasis Recovery Document "); printWindow.document.close(); printWindow.document.body.appendChild(divContents.cloneNode(true)); printWindow.addEventListener("load", () => { printWindow.print(); printWindow.close(); }); } return ( (isBackup ? setAskUserIfSure(true) : doConfirm())} > {askUserIfSure ? ( setAskUserIfSure(false)} description="The values in the form must be correct" label="I am sure" cancelLabel="Wait, I want to check" onConfirm={() => doConfirm().then(() => setAskUserIfSure(false))} > You personal information is used to define the location where your secret will be safely stored. If you forget what you have entered or if there is a misspell you will be unable to recover your secret.

{/* TODO: make this actually work reliably cross-browser lol (opens about:blank for me) */} Save the personal information as PDF

) : undefined}
{fieldList}

This personal information will help to locate your secret.

This stays private

The information you have entered here:

  • Will be hashed, and therefore unreadable
  • The non-hashed version is not shared
); } interface AttributeEntryFieldProps { isFirst: boolean; value: string; setValue: (newValue: string) => void; spec: UserAttributeSpec; errorMessage: string | undefined; onConfirm: () => void; } const possibleBirthdayYear: Array = []; for (let i = 0; i < 100; i++) { possibleBirthdayYear.push(2020 - i); } function AttributeEntryField(props: AttributeEntryFieldProps): VNode { return (
{props.spec.type === "date" && ( )} {props.spec.type === "number" && ( )} {props.spec.type === "string" && ( )} {props.spec.type === "string" && (
This field is case-sensitive. You must enter exactly the same value during recovery.
)} {props.spec.name === "full_name" && (
If possible, use "LASTNAME, Firstname(s)" without abbreviations.
)}
This stays private
); } const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/; function checkIfValid( value: string, spec: UserAttributeSpec, ): string | undefined { const pattern = spec["validation-regex"]; if (pattern) { const re = new RegExp(pattern); if (!re.test(value)) return "The value is invalid"; } const logic = spec["validation-logic"]; if (logic) { const func = (validators as any)[logic]; if (func && typeof func === "function" && !func(value)) return "Please check the value"; } const optional = spec.optional; if (!optional && !value) { return "This value is required"; } if ("date" === spec.type) { if (!YEAR_REGEX.test(value)) { return "The date doesn't follow the format"; } try { const v = parse(value, "yyyy-MM-dd", new Date()); if (Number.isNaN(v.getTime())) { return "Some numeric values seems out of range for a date"; } if ("birthdate" === spec.name && isAfter(v, new Date())) { return "A birthdate cannot be in the future"; } } catch (e) { return "Could not parse the date"; } } return undefined; }