.. This file is part of Anastasis Copyright (C) 2019-2021 Anastasis SARL 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 2.1, or (at your option) any later version. 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 Anastasis; see the file COPYING. If not, see @author Christian Grothoff @author Dominik Meister @author Dennis Neufeld ----------- Reducer API ----------- This section describes the Anastasis Reducer API which is used by client applications to store or load the different states the client application can have. The reducer takes a state_ in JSON syntax and returns the new state in JSON syntax. For example a **state** may take the following structure: .. code-block:: json { "backup_state": "CONTINENT_SELECTING", "continents": [ "Europe", "North_America" ] } The new state depends on the previous one and on the transition action_ with its arguments given to the reducer. A **transition argument** also is a statement in JSON syntax: .. code-block:: json { "continent": "Europe" } The new state returned by the reducer with the state and transition argument defined above would look like following for the transition action_ ``select_continent``: .. code-block:: json { "backup_state": "COUNTRY_SELECTING", "continents": [ "Europe", "North_America" ], "selected_continent": "Europe", "countries": [ { "code": "ch", "name": "Switzerland", "continent": "Europe", "name_i18n": { "de_DE": "Schweiz", "de_CH": "Schwiiz", "fr": "Suisse", "en": "Swiss" }, "currency": "CHF" }, { "code": "de", "name": "Germany", "continent": "Europe", "continent_i18n": { "de": "Europa" }, "name_i18n": { "de_DE": "Deutschland", "de_CH": "Deutschland", "fr": "Allemagne", "en": "Germany" }, "currency": "EUR" } ] } States ^^^^^^ Overall, the reducer knows the following states: - **ERROR**: The transition led to an error. No further transitions are possible from this state, but the client may want to continue from a previous state. - **CONTINENT_SELECTING**: The user should specify the continent where they are living, so that we can show a list of countries to choose from. - **COUNTRY_SELECTING**: The user should specify the country where they are living, so that we can determine appropriate attributes, currencies and Anastasis providers. - **USER_ATTRIBUTES_COLLECTING**: The user should provide the country-specific personal attributes. - **AUTHENTICATIONS_EDITING**: The user should add authentication methods to be used during recovery. - **POLICIES_REVIEWING**: The user should review the recovery policies. - **SECRET_EDITING**: The user should edit the secret to be backed up. - **TRUTHS_PAYING**: The user needs to pay for one or more uploads of data associated with an authentication method. - **POLICIES_PAYING**: The user needs to pay for storing the recovery policy document. - **BACKUP_FINISHED**: A backup has been successfully generated. - **SECRET_SELECTING**: The user needs to select a recovery policy document with the secret that is to be recovered. - **CHALLENGE_SELECTING**: The user needs to select an authorization challenge to proceed with recovery. - **CHALLENGE_PAYING**: The user needs to pay to proceed with the authorization challenge. - **CHALLENGE_SOLVING**: The user needs to solve the authorization challenge. - **RECOVERY_FINISHED**: The secret of the user has been recovered. State names: - In SELECTING-states, the user has to choose one value out of a predefined set of values (for example a continent out of a set of continents). - In COLLECTING-states, the user has to give certain values. - In EDITING-states, the user is free to choose which values he wants to give. - In REVEIWING-states, the user may make a few choices, but primarily is expected to affirm something. - in PAYING-states, the user must make a payment. - in FINISHED-states, the operation has definitively concluded. Backup Reducer ^^^^^^^^^^^^^^ .. _state: .. _action: .. figure:: anastasis_reducer_backup.png :name: fig-anastasis_reducer_backup :alt: fig-anastasis_reducer_backup :scale: 75 % :align: center Backup states and their transitions. The illustration above shows the different states the reducer can have during a backup process. Recovery Reducer ^^^^^^^^^^^^^^^^ .. figure:: anastasis_reducer_recovery.png :name: fig-anastasis_reducer_recovery :alt: fig-anastasis_reducer_recovery :scale: 75 % :align: center Recovery states and their transitions. The illustration above shows the different states the reducer can have during a recovery process. Reducer transitions ^^^^^^^^^^^^^^^^^^^ In the following, the individual transitions will be specified in more detail. Note that we only show fields added by the reducer, typically the previous state is preserved to enable "back" transitions to function smoothly. Initial state ------------- The initial states for backup and recovery processes are: **Initial backup state:** .. code-block:: json { "backup_state": "CONTINENT_SELECTING", "continents": [ "Europe", "North America" ] } **Initial recovery state:** .. code-block:: json { "recovery_state": "CONTINENT_SELECTING", "continents": [ "Europe", "North America" ] } Here, "continents" is an array of English strings with the names of the continents which contain countries for which Anastasis could function (based on having providers that are known to operate and rules being provided for user attributes from those countries). For internationalization, another field ``continents_i18n`` may be present. This field would be a map of language names to arrays of translated continent names: .. code-block:: json { "recovery_state": "CONTINENT_SELECTING", "continents": [ "Europe", "North America" ] "continents_i18n": { "de_DE" : [ "Europa", "Nordamerika" ], "de_CH" : [ "Europa", "Nordamerika" ] } } Translations must be given in the same order as the main English array. Common transitions ------------------ **select_continent:** Here the user specifies the continent they live on. Arguments (example): .. code-block:: json { "continent": "Europe" } The continent must be given using the English name from the ``continents`` array. Using a translated continent name is invalid and may result in failure. The reducer returns an updated state with a list of countries to choose from, for example: .. code-block:: json { "backup_state": "COUNTRY_SELECTING", "selected_continent": "Europe", "countries": [ { "code": "ch", "name": "Switzerland", "continent": "Europe", "name_i18n": { "de_DE": "Schweiz", "de_CH": "Schwiiz", "fr": "Suisse", "en": "Swiss" }, "currency": "CHF" }, { "code": "de", "name": "Germany", "continent": "Europe", "continent_i18n": { "de": "Europa" }, "name_i18n": { "de_DE": "Deutschland", "de_CH": "Deutschland", "fr": "Allemagne", "en": "Germany" }, "currency": "EUR" } ] } Here ``countries`` is an array of countries on the ``selected_continent``. For each country, the ``code`` is the ISO 3166-1 alpha-2 country code. The ``continent`` is only present because some countries span continents, the information is redundant and will always match ``selected_continent``. The ``name`` is the name of the country in English, internationalizations of the name may be provided in ``name_i18n``. The ``currency`` is **an** official currency of the country, if a country has multiple currencies, it may appear multiple times in the list. In this case, the user should select the entry with the currency they intend to pay with. It is also possible for users to select a currency that does not match their country, but user interfaces should by default try to use currencies that match the user's residence. **select_country:** Selects the country (via the country code) and specifies the currency. The latter is needed as some countries have more than one currency, and some use-cases may also involve users insisting on paying with foreign currency. Arguments (example): .. code-block:: json { "country_code": "de", "currency": "EUR" } The ``country_code`` must be an ISO 3166-1 alpha-2 country code from the array of ``countries`` of the reducer's state. The ``currency`` field must be a valid currency accepted by the Taler payment system. The reducer returns a new state with the list of attributes the user is expected to provide, as well as possible authentication providers that accept payments in the selected currency: .. code-block:: json { "backup_state": "USER_ATTRIBUTES_COLLECTING", "selected_country": "de", "currency": "EUR", "required_attributes": [ { "type": "string", "name": "full_name", "label": "Full name", "label_i18n": { "de_DE": "Vollstaendiger Name", "de_CH": "Vollstaendiger. Name", "fr": "Nom complet", "en": "Full name" }, "widget": "anastasis_gtk_ia_full_name", "uuid" : "9e8f463f-575f-42cb-85f3-759559997331" }, { "type": "date", "name": "birthdate", "label": "Birthdate", "label_i18n": { "de_DE": "Geburtsdatum", "de_CH": "Geburtsdatum", "fr": "Date de naissance", "en": "Birthdate" }, "uuid" : "83d655c7-bdb6-484d-904e-80c1058c8854" "widget": "anastasis_gtk_ia_birthdate" }, { "type": "string", "name": "tax_number", "label": "Taxpayer identification number", "label_i18n":{ "de_DE": "Steuerliche Identifikationsnummer", "de_CH": "Steuerliche Identifikationsnummer", "en": "German taxpayer identification number" }, "widget": "anastasis_gtk_ia_tax_de", "uuid": "dae48f85-e3ff-47a4-a4a3-ed981ed8c3c6", "validation-regex": "^[0-9]{11}$", "validation-logic": "DE_TIN_check" }, { "type": "string", "name": "social_security_number", "label": "Social security number", "label_i18n": { "de_DE": "Sozialversicherungsnummer", "de_CH": "Sozialversicherungsnummer", "fr": "Numéro de sécurité sociale", "en": "Social security number" }, "widget": "anastasis_gtk_ia_ssn", "validation-regex": "^[0-9]{8}[[:upper:]][0-9]{3}$", "validation-logic": "DE_SVN_check" "optional" : true } ], "authentication_providers": { "http://localhost:8089/": { "http_status": 200, "methods": [ { "type" : "question", "usage_fee" : "EUR:0.0" }, { "type" : "sms", "usage_fee" : "EUR:0.5" } ], "annual_fee": "EUR:4.99", "truth_upload_fee": "EUR:4.99", "liability_limit": "EUR:1", "currency": "EUR", "truth_lifetime": { "d_ms" : 50000000 }, "storage_limit_in_megabytes": 1, "provider_name": "Anastasis 4", "salt": "CXAPCKSH9D3MYJTS9536RHJHCW" }, "http://localhost:8088/": { "http_status": 200, "methods": [ { "type" : "question", "usage_fee" : "EUR:0.01" }, { "type" : "sms", "usage_fee" : "EUR:0.55" } ], "annual_fee": "EUR:0.99", "truth_upload_fee": "EUR:3.99", "liability_limit": "EUR:1", "currency": "EUR", "truth_lifetime": { "d_ms" : 50000000 }, "storage_limit_in_megabytes": 1, "provider_name": "Anastasis 4", "salt": "CXAPCKSH9D3MYJTS9536RHJHCW" } } } The array of ``required_attributes`` contains attributes about the user that must be provided includes: - **type**: The type of the attribute, for now only ``string`` and ``date`` are supported. - **name**: The name of the attribute, this is the key under which the attribute value must be provided later. The name must be unique per response. - **label**: A human-readable description of the attribute in English. Translated descriptions may be provided under **label_i18n**. - **uuid**: A UUID that uniquely identifies identical attributes across different countries. Useful to preserve values should the user enter some attributes, and then switch to another country. Note that attributes must not be preserved if they merely have the same **name**, only the **uuid** will be identical if the semantics is identical. - **widget**: An optional name of a widget that is known to nicely render the attribute entry in user interfaces where named widgets are supported. - **validation-regex**: An optional extended POSIX regular expression that is to be used to validate (string) inputs to ensure they are well-formed. - **validation-logic**: Optional name of a function that should be called to validate the input. If the function is not known to the particular client, the respective validation can be skipped (at the expense of typos by users not being detected, possibly rendering secrets irrecoverable). - **optional**: Optional boolean field that, if ``true``, indicates that this attribute is not actually required but optional and users MAY leave it blank in case they do not have the requested information. Used for common fields that apply to some large part of the population but are not sufficiently universal to be actually required. The authentication providers are listed under a key that is the base URL of the service. For each provider, the following information is provided if the provider was successfully contacted: - **http_status**: HTTP status code, always ``200`` on success. - **methods**: Array of authentication methods supported by this provider. Includes the **type** of the authentication method and the **usage_fee** (how much the user must pay for authorization using this method during recovery). - **annual_fee**: Fee the provider charges to store the recovery policy for one year. - **truth_upload_fee**: Fee the provider charges to store a key share. - **liability_limit**: Amount the provider can be held liable for in case a key share or recovery document cannot be recovered due to provider failures. - **currency**: Currency in which the provider wants to be paid, will match all of the fees. - **storage_limit_in_megabytes**: Maximum size of an upload (for both recovery document and truth data) in megabytes. - **provider_name**: Human-readable name of the provider's business. - **salt**: Salt value used by the provider, used to derive the user's identity at this provider. Should be unique per provider, and must never change for a given provider. The salt is base32 encoded. If contacting the provider failed, the information returned is: - **http_status**: HTTP status code (if available, possibly 0 if we did not even obtain an HTTP response). - **error_code**: Taler error code, never 0. **add_provider**: This operation can be performed in state ``USER_ATTRIBUTES_COLLECTING``. It adds one or more Anastasis providers to the list of providers the reducer should henceforth consider. Note that removing providers is not possible at this time. Here, the client must provide an array with the base URLs of the providers to add, for example: .. code-block:: json { "urls": [ "http://localhost:8888/", "http://localhost:8089/" ] } Note that existing providers will remain in the state. The following is an example for an expected new state where the service on port 8089 is unreachable, the service on port 8088 was previously known, and service on port 8888 was now added: .. code-block:: json { "backup_state": "USER_ATTRIBUTES_COLLECTING", "authentication_providers": { "http://localhost:8089/": { "error_code": 11, "http_status": 0 }, "http://localhost:8088/": { "http_status": 200, "methods": [ { "type" : "question", "usage_fee" : "EUR:0.01" }, { "type" : "sms", "usage_fee" : "EUR:0.55" } ], "annual_fee": "EUR:0.99", "truth_upload_fee": "EUR:3.99", "liability_limit": "EUR:1", "currency": "EUR", "truth_lifetime": { "d_ms" : 50000000 }, "storage_limit_in_megabytes": 1, "provider_name": "Anastasis 4", "salt": "CXAPCKSH9D3MYJTS9536RHJHCW" } "http://localhost:8888/": { "methods": [ { "type" : "question", "usage_fee" : "EUR:0.01" }, { "type" : "sms", "usage_fee" : "EUR:0.55" } ], "annual_fee": "EUR:0.99", "truth_upload_fee": "EUR:3.99", "liability_limit": "EUR:1", "currency": "EUR", "truth_lifetime": { "d_ms" : 50000000 }, "storage_limit_in_megabytes": 1, "provider_name": "Anastasis 42", "salt": "BXAPCKSH9D3MYJTS9536RHJHCX" } } } Backup transitions ------------------ **enter_user_attributes:** This transition provides the user's personal attributes. The specific set of attributes required depends on the country of residence of the user. Some attributes may be optional, in which case they should be omitted entirely (that is, not simply be set to ``null`` or an empty string). Example arguments would be: .. code-block:: json { "identity_attributes": { "full_name": "Max Musterman", "social_security_number": "123456789", "birthdate": "2000-01-01", "birthplace": "Earth" } } Note that at this stage, the state machines between backup and recovery diverge and the ``recovery_state`` will begin to look very different from the ``backup_state``. For backups, if all required attributes are present, the reducer will transition to an ``AUTHENTICATIONS_EDITING`` state with the attributes added to it: .. code-block:: json { "backup_state": "AUTHENTICATIONS_EDITING", "identity_attributes": { "full_name": "Max Musterman", "social_security_number": "123456789", "birthdate": "2000-01-01", "birthplace": "Earth" } } If required attributes are missing, do not match the required regular expression, or fail the custom validation logic, the reducer SHOULD transition to an error state indicating what was wrong about the input. A reducer that does not support some specific validation logic MAY accept the invalid input and proceed anyway. The error state will include a Taler error code that is specific to the failure, and optional details. Example: .. code-block:: json { "backup_state": "ERROR", "code": 8404, "hint": "An input did not match the regular expression.", "detail": "social_security_number" } Clients may safely repeat this transition to validate the user's inputs until they satisfy all of the constraints. This way, the user interface does not have to perform the input validation directly. **add_authentication**: This transition adds an authentication method. The method must be supported by one or more providers that are included in the current state. Adding an authentication method requires specifying the ``type`` and ``instructions`` to be given to the user. The ``challenge`` is encrypted and stored at the Anastasis provider. The specific semantics of the value depend on the ``type``. Typical challenges values are a phone number (to send an SMS to), an e-mail address (to send a PIN code to) or the answer to a security question. Note that these challenge values will still be encrypted (and possibly hashed) before being given to the Anastasis providers. Note that the ``challenge`` must be given in Crockford Base32 encoding, as it MAY include binary data (such as a photograph of the user). In the latter case, the optional ``mime_type`` field must be provided to give the MIME type of the value encoded in ``challenge``. .. code-block:: json { "authentication_method": { "type": "question", "mime_type" : "text/plain", "instructions" : "What is your favorite GNU package?", "challenge" : "E1QPPS8A", } } If the information provided is valid, the reducer will add the new authentication method to the array of authentication methods: .. code-block:: json { "backup_state": "AUTHENTICATIONS_EDITING", "authentication_methods": [ { "type": "question", "mime_type" : "text/plain", "instructions" : "What is your favorite GNU package?", "challenge" : "E1QPPS8A", }, { "type": "email", "instructions" : "E-mail to user@*le.com", "challenge": "ENSPAWJ0CNW62VBGDHJJWRVFDM50" } ] } **delete_authentication**: This transition can be used to remove an authentication method from the array of authentication methods. It simply requires the index of the authentication method to remove. Note that the array is 0-indexed: .. code-block:: json { "authentication_method": 1 } Assuming we begin with the state from the example above, this would remove the ``email`` authentication method, resulting in the following response: .. code-block:: json { "backup_state": "AUTHENTICATIONS_EDITING", "authentication_methods": [ { "type": "question", "mime_type" : "text/plain", "instructions" : "What is your favorite GNU package?", "challenge" : "gdb", } ] } If the index is invalid, the reducer will instead transition into an ``ERROR`` state. **next** (from ``AUTHENTICATIONS_EDITING``): This transition confirms that the user has finished adding (or removing) authentication methods, and that the system should now automatically compute a set of reasonable recovery policies. This transition does not take any mandatory arguments. Optional arguments can be provided to upload the recovery document only to a specific subset of the providers: .. code-block:: json { "providers": [ "http://localhost:8088/", "http://localhost:8089/" ] } The resulting state provides the suggested recovery policies in a way suitable for presentation to the user: .. code-block:: javascript { "backup_state": "POLICIES_REVIEWING", "policy_providers" : [ { "provider_url" : "http://localhost:8088/" }, { "provider_url" : "http://localhost:8089/" } ], "policies": [ { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8088/" }, { "authentication_method": 1, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8087/" } ] }, { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8088/" }, { "authentication_method": 1, "provider": "http://localhost:8089/" }, { "authentication_method": 3, "provider": "http://localhost:8089/" } ] } ] } For each recovery policy, the state includes the specific details of which authentication ``methods`` must be solved to recovery the secret using this policy. The ``methods`` array specifies the index of the ``authentication_method`` in the ``authentication_methods`` array, as well as the provider that was selected to supervise this authentication. If no authentication method was provided, the reducer will transition into an ``ERROR`` state instead of suggesting policies. **add_policy**: Using this transition, the user can add an additional recovery policy to the state. The argument format is the same that is used in the existing state. An example for a possible argument would thus be: .. code-block:: javascript { "policy": [ { "authentication_method": 1, "provider": "http://localhost:8088/" }, { "authentication_method": 3, "provider": "http://localhost:8089/" } ] } Note that the specified providers must already be in the ``authentication_providers`` of the state. You cannot add new providers at this stage. The reducer will simply attempt to append the suggested policy to the "policies" array, returning an updated state: .. code-block:: json { "backup_state": "POLICIES_REVIEWING", "policies": [ { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" }, { "authentication_method": 1, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 1, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 1, "provider": "http://localhost:8088/" }, { "authentication_method": 3, "provider": "http://localhost:8089/" } ] } ] } If the new policy is invalid, for example because it adds an unknown authentication method, or the selected provider does not support the type of authentication, the reducer will transition into an ``ERROR`` state instead of adding the new policy. **update_policy**: Using this transition, the user can modify an existing recovery policy in the state. The argument format is the same that is used in **add_policy**, except there is an additional key ``policy_index`` which identifies the policy to modify. An example for a possible argument would thus be: .. code-block:: javascript { "policy_index" : 1, "policy": [ { "authentication_method": 1, "provider": "http://localhost:8088/" }, { "authentication_method": 3, "provider": "http://localhost:8089/" } ] } If the new policy is invalid, for example because it adds an unknown authentication method, or the selected provider does not support the type of authentication, the reducer will transition into an ``ERROR`` state instead of modifying the policy. **delete_policy:** This transition allows the deletion of a recovery policy. The argument simply specifies the index of the policy to delete, for example: .. code-block:: json { "policy_index": 3 } Given as input the state from the example above, the expected new state would be: .. code-block:: json { "backup_state": "POLICIES_REVIEWING", "policies": [ { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" }, { "authentication_method": 1, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 1, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8088/" } ] } ] } If the index given is invalid, the reducer will transition into an ``ERROR`` state instead of deleting a policy. **delete_challenge:** This transition allows the deletion of an individual challenge from a recovery policy. The argument simply specifies the index of the policy and challenge to delete, for example: .. code-block:: json { "policy_index": 1, "challenge_index" : 1 } Given as input the state from the example above, the expected new state would be: .. code-block:: json { "backup_state": "POLICIES_REVIEWING", "policies": [ { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" }, { "authentication_method": 1, "provider": "http://localhost:8088/" } ] }, { "methods": [ { "authentication_method": 0, "provider": "http://localhost:8089/" } ] }, { "methods": [ { "authentication_method": 1, "provider": "http://localhost:8089/" }, { "authentication_method": 2, "provider": "http://localhost:8088/" } ] } ] } If the index given is invalid, the reducer will transition into an ``ERROR`` state instead of deleting a challenge. **next** (from ``POLICIES_REVIEWING``): Using this transition, the user confirms that the policies in the current state are acceptable. The transition does not take any arguments. The reducer will simply transition to the ``SECRET_EDITING`` state: .. code-block:: json { "backup_state": "SECRET_EDITING", "upload_fees" : [ "KUDOS:42" ], "expiration" : { "t_ms" : 1245362362 } } Here, ``upload_fees`` is an array of applicable upload fees for the given policy expiration time. This is an array because fees could be in different currencies. The final cost may be lower if the user already paid for some of the time. If the array of ``policies`` is currently empty, the reducer will transition into an ``ERROR`` state instead of allowing the user to continue. **enter_secret:** This transition provides the reducer with the actual core ``secret`` of the user that Anastasis is supposed to backup (and possibly recover). The argument is simply the Crockford-Base32 encoded ``value`` together with its ``mime`` type, or a ``text`` field with a human-readable secret text. For example: .. code-block:: javascript { "secret": { "value": "EDJP6WK5EG50", "mime" : "text/plain" }, "expiration" : { "t_ms" : 1245362362 } } If the application is unaware of the format, it set the ``mime`` field to ``null``. The ``expiration`` field is optional. The reducer remains in the ``SECRET_EDITING`` state, but now the secret and updated expiration time are part of the state and the cost calculations will be updated. .. code-block:: json { "backup_state": "SECRET_EDITING", "core_secret" : { "value": "EDJP6WK5EG50", "mime" : "text/plain" }, "expiration" : { "t_ms" : 1245362362 }, "upload_fees" : [ "KUDOS:42" ] } **clear_secret:** This transition removes the core secret from the state. It is simply a convenience function to undo ``enter_secret`` without providing a new value immediately. The transition takes no arguments. The resuting state will no longer have the ``core_secret`` field, and be otherwise unchanged. Calling **clear_secret** on a state without a ``core_secret`` will result in an error. **enter_secret_name:** This transition provides the reducer with a name for the core ``secret`` of the user. This name will be given to the user as a hint when seleting a recovery policy document during recovery, prior to satisfying any of the challenges. The argument simply contains the name for the secret. Applications that have built-in support for Anastasis MUST prefix the secret name with an underscore and an application-specific identifier registered in GANA so that they can use recognize their own backups. An example argument would be: .. code-block:: javascript { "name": "_TALERWALLET_MyPinePhone", } Here, ``MyPinePhone`` might be chosen by the user to identify the device that was being backed up. The reducer remains in the ``SECRET_EDITING`` state, but now the secret name is updated: .. code-block:: json { "secret_name" : "_TALERWALLET_MyPinePhone" } **update_expiration:** This transition asks the reducer to change the desired expiration time and to update the associated cost. For example: .. code-block:: javascript { "expiration" : { "t_ms" : 1245362362 } } The reducer remains in the ``SECRET_EDITING`` state, but the expiration time and cost calculation will be updated. .. code-block:: json { "backup_state": "SECRET_EDITING", "expiration" : { "t_ms" : 1245362362 }, "upload_fees" : [ { "fee": "KUDOS:43" } ] } **next** (from ``SECRET_EDITING``): Using this transition, the user confirms that the secret and expiration settings in the current state are acceptable. The transition does not take any arguments. If the secret is currently empty, the reducer will transition into an ``ERROR`` state instead of allowing the user to continue. After adding a secret, the reducer may transition into different states depending on whether payment(s) are necessary. If payments are needed, the ``secret`` will be stored in the state under ``core_secret``. Applications should be careful when persisting the resulting state, as the ``core_secret`` is not protected in the ``PAYING`` states. The ``PAYING`` states only differ in terms of what the payments are for (key shares or the recovery document), in all cases the state simply includes an array of Taler URIs that refer to payments that need to be made with the Taler wallet. If all payments are complete, the reducer will transition into the ``BACKUP_FINISHED`` state and (if applicable) delete the ``core_secret`` as an additional safety measure. Example results are thus: .. code-block:: json { "backup_state": "TRUTHS_PAYING", "secret_name" : "$NAME", "core_secret" : { "$anything":"$anything" }, "payments": [ "taler://pay/...", "taler://pay/..." ] } .. code-block:: json { "backup_state": "POLICIES_PAYING", "secret_name" : "$NAME", "core_secret" : { "$anything":"$anything" }, "payments": [ "taler://pay/...", "taler://pay/..." ] } .. code-block:: json { "backup_state": "BACKUP_FINISHED", "success_details": { "http://localhost:8080/" : { "policy_version" : 1, "policy_expiration" : { "t_ms" : 1245362362000 } }, "http://localhost:8081/" : { "policy_version" : 3, "policy_expiration" : { "t_ms" : 1245362362000 } } } } **pay:** This transition suggests to the reducer that a payment may have been made or is immanent, and that the reducer should check with the Anastasis service provider to see if the operation is now possible. The operation takes one optional argument, which is a ``timeout`` value that specifies how long the reducer may wait (in long polling) for the payment to complete: .. code-block:: json { "timeout": { "d_ms" : 5000 }, } The specified timeout is passed on to the Anastasis service provider(s), which will wait this long before giving up. If no timeout is given, the check is done as quickly as possible without additional delays. The reducer will continue to either an updated state with the remaining payment requests, to the ``BACKUP_FINISHED`` state (if all payments have been completed and the backup finished), or into an ``ERROR`` state in case there was an irrecoverable error, indicating the specific provider and how it failed. An example for this final error state would be: .. code-block:: json { "backup_state": "ERROR", "http_status" : 500, "upload_status" : 52, "provider_url" : "https://bad.example.com/", } Here, the fields have the following meaning: - **http_status** is the HTTP status returned by the Anastasis provider. - **upload_status** is the Taler error code return by the provider. - **provider_url** is the base URL of the failing provider. In the above example, 52 would thus imply that the Anastasis provider failed to store information into its database. Recovery transitions -------------------- **enter_user_attributes:** This transition provides the user's personal attributes. The specific set of attributes required depends on the country of residence of the user. Some attributes may be optional, in which case they should be omitted entirely (that is, not simply be set to ``null`` or an empty string). The arguments are identical to the **enter_user_attributes** transition from the backup process. Example arguments would thus be: .. code-block:: json { "identity_attributes": { "full_name": "Max Musterman", "social_security_number": "123456789", "birthdate": "2000-01-01", "birthplace": "Earth" } } However, in contrast to the backup process, the reducer will attempt to retrieve the latest recovery document from all known providers for the selected currency given the above inputs. If a recovery document was found by any provider, the reducer will attempt to load it and transition to a state where the user can choose which challenges to satisfy: .. code-block:: json { "recovery_state": "CHALLENGE_SELECTING", "recovery_information": { "challenges": [ { "uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G", "cost": "TESTKUDOS:0", "type": "question", "instructions": "q1" }, { "uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "cost": "TESTKUDOS:0", "type": "email", "instructions": "e-mail address m?il@f*.bar" }, ], "policies": [ [ { "uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G" }, { "uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0" }, ], ], "provider_url": "http://localhost:8088/", "version": 1, }, "recovery_document": { "...": "..." } } The ``recovery_document`` is an internal representation of the recovery information and of no concern to the user interface. The pertinent information is in the ``recovery_information``. Here, the ``challenges`` array is a list of possible challenges the user could attempt to solve next, while ``policies`` is an array of policies, with each policy being an array of challenges. Satisfying all of the challenges of one of the policies will enable the secret to be recovered. The ``provider_url`` from where the recovery document was obtained and its ``version`` are also provided. Each challenge comes with four mandatory fields: - **uuid**: A unique identifier of the challenge; this is what the UUIDs in the policies array refer to, but also this UUID may be included in messages sent to the user. They allow the user to distinguish different PIN/TANs should say the same phone number be used for SMS-authentication with different providers. - **cost**: This is the amount the Anastasis provider will charge to allow the user to pass the challenge. - **type**: This is the type of the challenge, as a string. - **instructions**: Contains additional important hints for the user to allow the user to satisfy the challenge. It typically includes an abbreviated form of the contact information or the security question. Details depend on ``type``. If a recovery document was not found, either the user never performed a backup, entered incorrect attributes, or used a provider not yet in the list of Anastasis providers. Hence, the user must now either select a different provider, or go ``back`` and update the identity attributes. In the case a recovery document was not found, the transition fails, returning the error code and a human-readable error message together with a transition failure: .. code-block:: json { "recovery_state": "ERROR", "error_message": "account unknown to Anastasis server", "error_code": 9, } Here, the ``error_code`` is from the ``enum ANASTASIS_RecoveryStatus`` and describes precisely what failed about the download, while the ``error_message`` is a human-readable (English) explanation of the code. Applications may want to translate the message using GNU gettext; translations should be available in the ``anastasis`` text domain. However, in general it should be sufficient to display the slightly more generic Taler error code that is returned with the new state. **change_version:** Even if a recovery document was found, it is possible that the user intended to recover a different version, or recover a backup where the recovery document is stored at a different provider. Thus, the reducer allows the user to explicitly switch to a different provider or recovery document version using the ``change_version`` transition, which takes a provider URL and policy version as arguments: .. code-block:: json { "provider_url": "https://localhost:8080/", "version": 2 } Note that using a version of 0 implies fetching "the latest version". The resulting states are the same as those of the ``enter_user_attributes`` transition, except that the recovery document version is not necessarily the latest available version at the provider. **select_challenge:** Selecting a challenge takes different, depending on the state of the payment. A comprehensive example for ``select_challenge`` would be: .. code-block:: json { "uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" "timeout" : { "d_ms" : 5000 }, "payment_secret": "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" } The ``uuid`` field is mandatory and specifies the selected challenge. The other fields are optional, and are needed in case the user has previously been requested to pay for the challenge. In this case, the ``payment_secret`` identifies the previous payment request, and ``timeout`` says how long the Anastasis service should wait for the payment to be completed before giving up (long polling). Depending on the type of the challenge and the need for payment, the reducer may transition into ``CHALLENGE_SOLVING`` or ``CHALLENGE_PAYING`` states. In ``CHALLENGE_SOLVING``, the new state will primarily specify the selected challenge: .. code-block:: json { "backup_state": "CHALLENGE_SOLVING", "selected_challenge_uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" } In ``CHALLENGE_PAYING``, the new state will include instructions for payment in the ``challenge_feedback``. In general, ``challenge_feedback`` includes information about attempted challenges, with the final state being ``solved``: .. code-block:: json { "recovery_state": "CHALLENGE_SELECTING", "recovery_information": { "...": "..." } "challenge_feedback": { "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" : { "state" : "solved" } } } Challenges feedback for a challenge can have many different ``state`` values that applications must all handle. States other than ``solved`` are: - **payment**: Here, the user must pay for a challenge. An example would be: .. code-block:: json { "backup_state": "CHALLENGE_PAYING", "selected_challenge_uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30", "challenge_feedback": { "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" : { "state" : "payment", "taler_pay_uri" : "taler://pay/...", "provider" : "https://localhost:8080/", "payment_secret" : "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" } } } - **body**: Here, the server provided an HTTP reply for how to solve the challenge, but the reducer could not parse them into a known format. A mime-type may be provided and may help parse the details. .. code-block:: json { "recovery_state": "CHALLENGE_SOLVING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "body", "body": "CROCKFORDBASE32ENCODEDBODY", "http_status": 403, "mime_type" : "anything/possible" } } } - **hint**: Here, the server provided human-readable hint for how to solve the challenge. Note that the ``hint`` provided this time is from the Anastasis provider and may differ from the ``instructions`` for the challenge under ``recovery_information``: .. code-block:: json { "recovery_state": "CHALLENGE_SOLVING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "hint", "hint": "Recovery TAN send to email mail@DOMAIN", "http_status": 403 } } } - **details**: Here, the server provided a detailed JSON status response related to solving the challenge: .. code-block:: json { "recovery_state": "CHALLENGE_SOLVING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "details", "details": { "code": 8111, "hint": "The client's response to the challenge was invalid.", "detail" : null }, "http_status": 403 } } } - **redirect**: To solve the challenge, the user must visit the indicated Web site at ``redirect_url``, for example to perform video authentication: .. code-block:: json { "recovery_state": "CHALLENGE_SOLVING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "redirect", "redirect_url": "https://videoconf.example.com/", "http_status": 303 } } } - **server-failure**: This indicates that the Anastasis provider encountered a failure and recovery using this challenge cannot proceed at this time. Examples for failures might be that the provider is unable to send SMS messages at this time due to an outage. The body includes details about the failure. The user may try again later or continue with other challenges. .. code-block:: json { "recovery_state": "CHALLENGE_SELECTING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "server-failure", "http_status": "500", "error_code": 52 } } } - **truth-unknown**: This indicates that the Anastasis provider is unaware of the specified challenge. This is typically a permanent failure, and user interfaces should not allow users to re-try this challenge. .. code-block:: json { "recovery_state": "CHALLENGE_SELECTING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "truth-unknown", "error_code": 8108 } } } - **rate-limit-exceeded**: .. code-block:: json { "recovery_state": "CHALLENGE_SELECTING", "recovery_information": { "...": "..." } "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", "challenge_feedback": { "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { "state": "rate-limit-exceeded", "error_code": 8121 } } } **pay:** With a ``pay`` transition, the application indicates to the reducer that a payment may have been made. Here, it is again possible to specify an optional ``timeout`` argument for long-polling, for example: .. code-block:: json { "payment_secret": "ABCDADF242525AABASD52525235ABABFDABABANALASDAAKASDAS" "timeout" : { "d_ms" : 5000 }, } Depending on the type of the challenge and the result of the operation, the new state may be ``CHALLENGE_SOLVING`` (if say the SMS was now sent to the user), ``CHALLENGE_SELECTING`` (if the answer to the security question was correct), ``RECOVERY_FINISHED`` (if this was the last challenge that needed to be solved) or still ``CHALLENGE_PAYING`` (if the challenge was not actually paid for). For sample messages, see the different types of ``challenge_feedback`` in the section about ``select_challenge``. **solve_challenge:** Solving a challenge takes various formats, depending on the type of the challenge and what is known about the answer. The different supported formats are: .. code-block:: json { "answer": "answer to security question" } .. code-block:: json { "pin": 1234 } .. code-block:: json { "hash": "SOMEBASE32ENCODEDHASHVALUE" }