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:
{
"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:
{
"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
:
{
"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"
}
]
}
An action may also result into an error response instead of a new state. Clients should then render this error response to the user and allow the user to continue from the old state. An error response looks like this:
{
"code": 123,
"hint": "something went wrong",
"details": "parameter foo failed to frobnify"
}
Overall, the reducer knows the following states:
- 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.
The illustration above shows the different states the reducer can have during a backup process.
The illustration above shows the different states the reducer can have during a recovery process.
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.
The initial states for backup and recovery processes are:
Initial backup state:
{
"backup_state": "CONTINENT_SELECTING",
"continents": [
"Europe",
"North America"
]
}
Initial recovery state:
{
"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:
{
"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.
select_continent:
Here the user specifies the continent they live on. Arguments (example):
{
"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:
{
"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):
{
"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:
{
"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
anddate
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 object with the base URLs of the providers to add or disable. The object maps the URLs to status information about the provider to use. For example:
{
"http://localhost:8088/" : { "disabled" : false },
"http://localhost:8089/" : { "disabled" : false },
"http://localhost:8090/" : { "disabled" : true }
}
Note that existing providers will remain in the state they were in. The following is an example for an expected new state where the service on port 8089 is unreachable, the services on port 8088 and 8888 were previously known, and service on port 8088 was now added, and on 8090 is disabled:
{
"backup_state": "USER_ATTRIBUTES_COLLECTING",
"authentication_providers": {
"http://localhost:8089/": {
"disabled": false,
"error_code": 11,
"http_status": 0
},
"http://localhost:8090/": {
"disabled": true
},
"http://localhost:8088/": {
"disabled": false,
"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"
}
}
}
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:
{
"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:
{
"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 return an error response indicating that the transition has failed and what is wrong about the input and not transition to a new state. 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": 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
.
{
"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:
{
"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:
{
"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:
{
"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 return an error response instead of making a transition.
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:
{
"providers": [
"http://localhost:8088/",
"http://localhost:8089/"
]
}
The resulting state provides the suggested recovery policies in a way suitable for presentation to the user:
{
"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 return an error response instead of making a transition.
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:
{
"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:
{
"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 return an error response 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:
{
"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 return an error response 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:
{
"policy_index": 3
}
Given as input the state from the example above, the expected new state would be:
{
"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 return an error response 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:
{
"policy_index": 1,
"challenge_index" : 1
}
Given as input the state from the example above, the expected new state would be:
{
"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 return an error response 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:
{
"backup_state": "SECRET_EDITING",
"upload_fees" : [ { "fee": "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
return an error response 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:
{
"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.
{
"backup_state": "SECRET_EDITING",
"core_secret" : {
"value": "EDJP6WK5EG50",
"mime" : "text/plain"
},
"expiration" : { "t_ms" : 1245362362 },
"upload_fees" : [ { "fee": "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:
{
"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:
{
"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:
{
"expiration" : { "t_ms" : 1245362362 }
}
The reducer remains in the SECRET_EDITING
state, but the
expiration time and cost calculation will be updated.
{
"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 return an error response 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:
{
"backup_state": "TRUTHS_PAYING",
"secret_name" : "$NAME",
"core_secret" : { "$anything":"$anything" },
"payments": [
"taler://pay/...",
"taler://pay/..."
]
}
{
"backup_state": "POLICIES_PAYING",
"secret_name" : "$NAME",
"core_secret" : { "$anything":"$anything" },
"payments": [
"taler://pay/...",
"taler://pay/..."
]
}
{
"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:
{
"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 return an error response in case there was an irrecoverable error,
indicating the specific provider and how it failed. An example for this
final error state would be:
{
"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.
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:
{
"identity_attributes": {
"full_name": "Max Musterman",
"social_security_number": "123456789",
"birthdate": "2000-01-01",
"birthplace": "Earth"
}
}
Afterwards, the reducer transitions into the SECRET_SELECTING
state:
{
"recovery_state": "SECRET_SELECTING",
"identity_attributes": {
"full_name": "Max Musterman",
"social_security_number": "123456789",
"birthdate": "2000-01-01",
"birthplace": "Earth"
}
}
Typically, the special policy discovery process (outside of the state
machine) is expected to be run in this state. The discovery process
will use the state (and in particular the identity attributes and the
list of active providers) to discover a set of possible recovery
documents with their respective provider URLs, policy version and
identity attribute mask. An identity attribute mask is a bitmask that
describes which of the optional attributes from the identity
attributes should be omitted to recover this backup. Once the user
has selected a backup providing this triplet, it is possible to
proceed using next
.
Especially if the discovered policies are inadequate, it is again
possible to add providers using add_provider
.
add_provider:
This operation can be performed in state SECRET_SELECTING
. It
adds one additional Anastasis provider to the list of providers that
the discovery process should henceforth consider. Note that removing
providers is not possible at this time.
Here, the client must provide an object with the base URL of the providers to add, for example:
{
"provider_url" : "http://localhost:8088/"
}
select_version:
Using the select_version
transition in the SECRET_SELECTING
state,
it is possible to trigger the download and decryption of a recovery
policy document. Here, the arguments specify which provider, version
and mask should be used to download the document:
{
"providers" : [ {
"url": "https://localhost:8088/",
"version": 0
} ],
"attribute_mask": 0
}
The reducer will attempt to retrieve the specified recovery document from that provider. If a recovery document was found, the reducer will attempt to load it and transition to a state where the user can choose which challenges to satisfy:
{
"recovery_state": "CHALLENGE_SELECTING",
"recovery_information": {
"challenges": [
{
"uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G",
"uuid-display": "MW2R3RC",
"type": "question",
"instructions": "q1"
},
{
"uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"uuid-display": "TXYKGE",
"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.
- uuid-display: Shortened idenfier which is included in messages send to the user. Allows the user to distinguish different PIN/TANs should say the same phone number be used for SMS-authentication with different providers.
- 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:
{
"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.
sync_providers
The downloaded policy may include secrets from providers for which
we do not (yet) have the cost structure or even the salt. So here
an application can use the sync_providers
request to download
/config
from providers that are in the challenge list but not
yet known with their salt and other attributes in the provider list.
The transition fails if all providers relevant for the selected
policy are already downloaded. Applications may either internally
check the state for this, or call sync_providers
until it fails
with this error:
{
"detail": "already in sync",
"code": 8400,
"hint": "The given action is invalid for the current state of the reducer."
}
As providers may fail to respond, this action may need to be called
repeatedly. The action will block until progress is made on any provider.
As some providers may never respond, the application should disable
challenge buttons for challenges where providers are down. However,
users should be able to solve challenges where the provider is up while
the reducer is polling for /config
in the background.
select_challenge:
Selecting a challenge takes different, depending on the state of the payment.
A comprehensive example for select_challenge
would be:
{
"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:
{
"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
:
{
"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:
{
"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.
{
"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
:
{
"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:
{
"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:
{
"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.
{
"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.
{
"recovery_state": "CHALLENGE_SELECTING",
"recovery_information": {
"...": "..."
}
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
"state": "truth-unknown",
"error_code": 8108
}
}
}
rate-limit-exceeded: This indicates that the user has made too many invalid attempts in too short an amount of time.
{
"recovery_state": "CHALLENGE_SELECTING",
"recovery_information": {
"...": "..."
}
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
"state": "rate-limit-exceeded",
"error_code": 8121
}
}
}
authentication-timeout: This indicates that the challenge is awaiting for some external authentication process to complete. The application should poll
for it to complete, or proceed with selecting other challenges.
{
"recovery_state": "CHALLENGE_SELECTING",
"recovery_information": {
"...": "..."
}
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
"state": "authentication-timeout",
"error_code": 8122
}
}
}
external-instructions: This indicates that the challenge requires the user to perform some authentication method-specific actions. Details about what the user should do are provided.
{
"recovery_state": "CHALLENGE_SELECTING",
"recovery_information": {
"...": "..."
}
"selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
"challenge_feedback": {
"TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
"state": "external-instructions",
"method": "iban",
"async": true, // optional
"answer_code": 987654321, // optional
"details": {
"...": "..."
}
}
}
}
If “async” is “true”, then the client should poll for the challenge being satisfied using the “answer_code” that has been provided.
The specific instructions on how to satisfy
the challenge depend on the method
.
They include:
iban: The user must perform a wire transfer from their account to the Anastasis provider.
{
"challenge_amount": "EUR:1",
"credit_iban": "DE12345789000",
"business_name": "Data Loss Incorporated",
"wire_transfer_subject": "Anastasis 987654321"
}
Note that the actual wire transfer subject must contain both
the numeric answer_code
as well as
the string Anastasis
.
poll:
With a poll
transition, the application indicates that it wants to wait longer for one or more of the challenges that are awaiting some external authentication (state external-instructions
) or experienced some kind of timeout (state authentication-timeout
) to possibly complete. While technically optional, the timeout
argument should really be provided to enable long-polling, for example:
{
"timeout" : { "d_ms" : 5000 },
}
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:
{
"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:
{
"answer": "answer to security question"
}
{
"pin": 1234
}
{
"hash": "SOMEBASE32ENCODEDHASHVALUE"
}