homedocsapi
Digital Users | Thitichaya - stock.adobe.com

Users and Environments

Howto create new users? Howto modify them?

Dec. 22, 2020|Bernhard Kauer
developerdraft

1. Environments

The users are the top-level objects of the Puzzle API. They are the containers for all currency accounts, which itself keep the transactions and their attachments.

Each user object resides inside an environment. Currently two of them are defined. First the real environment where normal money can be transfered between Puzzle accounts and outside Puzzle through the SEPA system. Secondly, the sandbox environment. It can be use for playing with the API and implementing use-cases where no real money is required.

The assignment of a user to an environment is done at creation time and cannot be changed afterwards.

1.1 The differences between the environments

Placing a user inside the sandbox has the following advantages compared to the real environment:

However, a few operations are not possible inside a sandbox:

2. Creating a new User

Every other API call needs an existing user object. Creating one is therefore often the first task one has to perform. In this section we therefore show this process step-by-step to ease the implementation of this basic functionality.

To create a new user, send a POST request to the /pzl endpoint.

$ curl -s -X POST https://$HOST/pzl

{"reason": "need JSON body"}

The content-type must be application/json.

$ curl -s -X POST -H "Content-type: application/json" https://$HOST/pzl

{"reason": "invalid JSON"}

And the body has to be valid JSON.

$ echo -n '{}' | curl -s -T - -X POST -H "Content-type: application/json" https://$HOST/pzl

{"reason": "invalid environment"}

An environment must be given where the user should reside.

$ echo -n '{"env": "sandbox"}' | curl -T - -X POST -s -H "Content-type: application/json" https://$HOST/pzl

{"reason": "invalid keytype"}

The keytype must be ed25519. Other keytypes are not supported yet.

$ echo -n '{"env": "sandbox", "keytype": "ed25519"}' | curl -T - -X POST -s -H "Content-type: application/json" https://$HOST/pzl

{"reason": "invalid pubkey"}

The public key for ed25519 is 32-bytes long and is transfered base64-encoded inside the JSON. The URL-safe variant of the encoding is prefered.

$ echo -n '{"env": "sandbox", "keytype":"ed25519", "pubkey":"TtMvY7818O7vyyXyii4fvchzrig1ZxsMlGD18S5FVqg="}' | curl -T - -X POST -s  -H "Content-type: application/json" https://$HOST/pzl

{"reason": "authorization missing"}

An authorization header for the puzzle authentication scheme must be provided. The signature is validated against the public key that is send in the body. A full trace therefore looks like:

$ puzzle-client -d -d user 2>&1

< POST /pzl HTTP/2
< authorization: pzl time=1608641178+60,sig=TRplU9GOA2yHTtQkSy-cG1w5RTs7u9X_EwUcBBUz1EF-CK9DvhqICTF_s7WThgdc6Actf7AfxP-SNQ4JvxtxDA
< content-type: application/json
< 
< {"env":"sandbox","keytype":"ed25519","pubkey":"lml7WeRunbqRp0VTeAc68YLnz6KKVHb_WoYP1GJGBwM="} 

> HTTP/2 200 
> etag: g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ
> content-type: application/json
> cache-control: no-cache
> location: /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN
> date: Tue, 22 Dec 2020 12:46:18 GMT
> content-length: 783
> 
> 
> [{"type": "user",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN",
>  "canonical": "/pzl/s3e8.g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ",
>  "event": {"date": 1608641178.51564,
>  "author": "puzzle-service api <318637@n18-1>",
>  "category": "user",
>  "name": "s3e8",
>  "operation": "init"},
>  "children": {"accounts": "g4Krr2gT1hqmVBirI5tCGY-9f3yULCRpIrJhd54Pga8G",
>  "auths": "g2AwJgwSUC0pikuVXAv70pct7sWdVFP-cdRz6WBKh8HP",
>  "info": "gzKZK5RbBgOaiYlaWdq395VMFFBa6p6h9IL5QBt6O6Yq"}},
>  {"type": "user-info",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/info",
>  "canonical": "/pzl/s3e8/info.gzKZK5RbBgOaiYlaWdq395VMFFBa6p6h9IL5QBt6O6Yq",
>  "properties": {"api": "1 20200304",
>  "current": "AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN",
>  "env": "sandbox",
>  "id": "s3e8",
>  "name": "Spoon Cloud sandbox"}}]
> 
/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN

The location header in the reply is the current path of the newly created user object. The object itself as well as the user-info is returned to get instant feedback before a potential asynchronous synchronization operation succeeds.

2.1 Duplicate Keys

Since the server cannot verify the actual strength of a private key, it is up to the client to submit only secure public keys. Deriving the private key from a user-provided password is not sufficient. The key must be generated from a cryptographically secure random number generator.

However, the server will detect duplicate and blacklisted keys. A 400 error is returned in this case. The corresponding reply body will be:

{"reason": "duplicate key"}

This error may also occur if an interrupted operation is retried, as the key might already be recorded by the backend even if the client has not received a successfull reply.

2.2 Enumerating all Users

Existing users cannot be enumerated through the API for security reasons. The client must store the location and the private keys securely. Recovery through the API is not possible if this information is lost. It is therefore advisable to use a secondary auth for backup purposes.

In the future there will be alternative recovery options for real accounts involving a potentially expensive re-identification of the user.

2.3 Deleting a User

There is no way to delete a user within the API. Owners and transactions might have to be kept for several years for legal reasons.

However, inactive users will be garbage collected and archieved after some time. To speedup this process, delete all auth objects so that external access is not possible anymore.

3. User Object

3.1 Retrieving the current version

The current version can be retrieved via a GET request to the current URL of the user object. An authorization header for the puzzle authentication scheme must be provided.

The signature in the following example is validated against the default public key that was given to the server during user creation. The return body is formated in JSON.

$ puzzle-client  -d get  /pzl/s3e8 2>&1

< GET /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN HTTP/2
< authorization: pzl time=1608641178+60,sig=fQ2dWD_xll50RdceKba83PKR59clcTUpbx8YmSniVUxCd9sNeDPt2ZPE62iZX9Fa65wnDRG1nQWecW8u8JabBA
<
> HTTP/2 200
> cache-control: no-cache
> content-length: 473
> content-type: application/json
> date: Tue, 22 Dec 2020 12:46:18 GMT
> etag: g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ
> 
> {
>  "canonical": "/pzl/s3e8.g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ",
>  "children": {
>   "accounts": "g4Krr2gT1hqmVBirI5tCGY-9f3yULCRpIrJhd54Pga8G",
>   "auths": "g2AwJgwSUC0pikuVXAv70pct7sWdVFP-cdRz6WBKh8HP",
>   "info": "gzKZK5RbBgOaiYlaWdq395VMFFBa6p6h9IL5QBt6O6Yq"
>  },
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN",
>  "event": {
>   "author": "puzzle-service api <318637@n18-1>",
>   "category": "user",
>   "date": 1608641178.51564,
>   "name": "s3e8",
>   "operation": "init"
>  },
>  "type": "user"
> }

The canonical attribute defines a path to this version that never changes. It can be seen as the version-number of the object. Requesting the canonical path returns exactly the very same object in the future. This allows one to refetch objects that where dropped at the client.

The prev attribute links to the previous version of this object. This enables backward iteration through the different versions. The prev field is not present on the very first version.

3.2 Conditional Requests

The GET and HEAD request return the etag of the object. This is a hash over the object or sub-object and guaranteed to be unique over time.

$ puzzle-client  head  /pzl/s3e8 | grep etag | cut -c 3-

etag: g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ

This can be used with a if-none-match header to detect whether the current state of an object was modifed or not. The server returns a 304 error if the etag is still current.

$ puzzle-client -d head -H "if-none-match: $(puzzle-client  head  /pzl/s3e8 | grep etag | cut -c 9-)" /pzl/s3e8 2>&1

< HEAD /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN HTTP/2
< authorization: pzl time=1608641179+60,sig=PgEx7SVxbI1K1RZ7XRlftub6SgrneU1La51BfWjy1heTTAm920DypUjtAkBnRKaYqhwwTZlEJEpdMBky3VA4DQ
< if-none-match:  g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ
<
> HTTP/2 304
> content-length: 0
> date: Tue, 22 Dec 2020 12:46:19 GMT
>

It may also be used together with a prefer header to implement long-polling.

3.3 Events

The event object inside the user looks like:

{
 "author": "puzzle-service api <267976@n18-1>",
 "category": "user",
 "date": 1608582480.928915,
 "name": "s:3e8",
 "operation": "merge",
 "subcategory": "account",
 "versions": "0-0"
}

The example shows a merge of certain versions from the account s:3e8 into this user object. The author field reveals the backend service that created this version. This is usefull for debugging purposes.

The date of this version is given as Unix time (UTC seconds since 1970) in micro-second resolution. The accuracy of this timestamp might be a lot lower due to clock-drifts and process interruptions on the server.

The category and subcategory specify where this event comes from.

Examples for operation done through the API are:

The name gives a hint where this operation happenend.

4. User Information Object

The user-info object details the information the backend has about a certain user. To retrieve it, request the info child from the user.

$ puzzle-client -q get  /pzl/s3e8/info

{
 "canonical":"/pzl/s3e8/info.gzKZK5RbBgOaiYlaWdq395VMFFBa6p6h9IL5QBt6O6Yq",
 "current":"/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/info",
 "properties":{
  "api":"1 20200304",
  "current":"AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN",
  "env":"sandbox",
  "id":"s3e8",
  "name":"Spoon Cloud sandbox"
 },
 "type":"user-info"
}

These are the properties for the user-info object:

4.1 Modifications

Certain properties of the user-info object are writable. An OPTIONS call reveals that currently description, icon and name fall in this class. This list will be extended in the future.

$ puzzle-client -q options /pzl/s3e8/info

{
 "optional":[
  "description",
  "icon",
  "name"
 ]
}

Modifications are performed with a POST request:

echo "info description=$(date +%s)" | puzzle-client  -d update /pzl/s3e8 2>&1

< POST /pzl/s3e8.AJjrTPfvyraFORT1SPnPOOJygikA9Qa0/info HTTP/2
< authorization: pzl time=1583943032+60,sig=MYLpiaV-s4gaFUWiuM186aFnSxq_ZRbaIQm7gwS6TYbeO46c576YZEYYN8WDak-pzwo_HktYd0vgDyRYa34FAQ
< content-type: application/json
<
< {"description":"1583943032"}

> HTTP/2 200
> content-length: 413
> content-type: application/json
> date: Wed, 11 Mar 2020 16:10:32 GMT
> etag: g3AOF-hhII3GQBbm2_QS9KmpliNnEboM_Vicr9jUtPKy
>
> {
>  "canonical": "/pzl/s3e8/info.g3AOF-hhII3GQBbm2_QS9KmpliNnEboM_Vicr9jUtPKy",
>  "current": "/pzl/s3e8.AJjrTPfvyraFORT1SPnPOOJygikA9Qa0/info",
>  "prev": "/pzl/s3e8.g23_JGQ6GIeh8u5D4AxLRYiGAg8X0fD7tgFF2jnM3A_-/info",
>  "properties": {
>   "api": "1 20200304",
>   "current": "AJjrTPfvyraFORT1SPnPOOJygikA9Qa0",
>   "description": "1583943032",
>   "env": "sandbox",
>   "id": "s3e8",
>   "name": "Sandbox 3e8"
>  },
>  "type": "user-info",
>  "version": 7
> }

This either returns a 200 and the new object or a 204 if no modification was performed.

$ echo "info foo=bar" | puzzle-client -q update /pzl/s3e8

204 /pzl/s3e8/info

5. Authorization Objects

The authorization objects contain all data needed to authorize requests. Most important are the public key for the puzzle authentication scheme. Multiple auth objects can be created per user to enable multi-device access and for backup purposes.

We have implement a fine-grained access-model, that supports read-only and time-limited access.

5.1 Listing all Auths

All authorization objects of a user can be listed with a GET request.

$ puzzle-client -q get /pzl/s3e8/auths

{
 "canonical":"/pzl/s3e8/auths.g2AwJgwSUC0pikuVXAv70pct7sWdVFP-cdRz6WBKh8HP",
 "children":{
  "x1":"g2mMkX3ksrvl-MO3efFNxtvSrkYCmpLDZyXrjIIAhOir"
 },
 "current":"/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths",
 "type":"auth-list"
}

The children lists the available authorization objects with their etag.

5.2 Retrival

A authorization object can be retrieved by combining its name with the cannonical or current path of the auth-list.

$ puzzle-client -q get /pzl/s3e8/auths/x1

{
 "canonical":"/pzl/s3e8/auths/x1.g2mMkX3ksrvl-MO3efFNxtvSrkYCmpLDZyXrjIIAhOir",
 "current":"/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1",
 "properties":{
  "description":"default",
  "keytype":"ed25519",
  "name":"x1",
  "policies":[
   {
    "until":1671540378
   }
  ],
  "pubkey":"lml7WeRunbqRp0VTeAc68YLnz6KKVHb_WoYP1GJGBwM="
 },
 "type":"auth"
}

The object keeps the pubkey and type as well as a description to let the user distinguish different authorizations. The default policies grants full access until the object is valid.

5.3 Creation

To create a new auth object, one sends a new public key to the auth-list. An OPTIONS call to the URL reveals the supported parameters.

$ puzzle-client -q options /pzl/s3e8/auths

{
 "choice":{
  "keytype":[
   "ed25519"
  ]
 },
 "mandatory":[
  "keytype",
  "pubkey"
 ],
 "optional":[
  "description",
  "policies"
 ]
}

Both keytype and pubkey must be given. A human-readable description is optional.

The creation is done throught the POST method.

$ puzzle-client -d -d auth /pzl/s3e8 2>&1

< POST /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths HTTP/2
< authorization: pzl time=1608641180+60,sig=E95mGNgvt0ghnTKYpb0dmA_Uk3IHAO2NpCuMHCGCqfC7safVc_khQvhltbZ9Ohfk5dqIBNCvC5JxmljniGJ5CQ
< content-type: application/json
< 
< {"description":"default","keytype":"ed25519","pubkey":"Kvmfyj78jlsQp08GJ3NrzXdBybOuz0osMlWctwymQms="} 

> HTTP/2 200 
> content-type: application/json
> cache-control: no-cache
> location: /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x2
> date: Tue, 22 Dec 2020 12:46:20 GMT
> content-length: 709
> 
> 
> [{"type": "auth-list",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths",
>  "canonical": "/pzl/s3e8/auths.gwPhGzUedeMhKjp0rsuxm8ikqcHBwDLrCtsb83dZzGGa",
>  "prev": "/pzl/s3e8.g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ/auths",
>  "version": 1,
>  "children": {"x1": "g2mMkX3ksrvl-MO3efFNxtvSrkYCmpLDZyXrjIIAhOir",
>  "x2": "gwfMOq4MZP-PsUKzpVzaah8Nyd6ItxVsAciAszu7Elg7"}},
>  {"type": "auth",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x2",
>  "canonical": "/pzl/s3e8/auths/x2.gwfMOq4MZP-PsUKzpVzaah8Nyd6ItxVsAciAszu7Elg7",
>  "properties": {"description": "default",
>  "keytype": "ed25519",
>  "name": "x2",
>  "policies": [{"until": 1671540380}],
>  "pubkey": "Kvmfyj78jlsQp08GJ3NrzXdBybOuz0osMlWctwymQms="}}]
> 
/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x2

The location header in the reply is the current path of the newly created authorization object.

5.4 Modification

An existing auth object can be modified. An OPTIONS call to the URL reveals the supported parameters.

$ puzzle-client -q options /pzl/s3e8/auths/x1

{
 "choice":{
  "keytype":[
   "ed25519"
  ]
 },
 "optional":[
  "description",
  [
   "keytype",
   "pubkey"
  ],
  "policies"
 ]
}

This means description and the public key are both optional. If the later is changed both keytype and pubkey must be provided.

Unknown fields and non-modifying updates are ignored.

$ echo 'auths/x1 description=default foo=bar' | puzzle-client -q update /pzl/s3e8

204 /pzl/s3e8/auths/x1

Updates are better performed on the canonical URL, as this allows to detect race conditions. Any succesfull modification returns the new object version.

$ echo "$(puzzle-client -q get /pzl/s3e8/auths/x1 | grep canonical | cut -d'"' -f 4) description=first" |  puzzle-client -d update / 2>&1

< POST /pzl/s3e8/auths/x1.g2mMkX3ksrvl-MO3efFNxtvSrkYCmpLDZyXrjIIAhOir HTTP/2
< authorization: pzl time=1608641180+60,key=x2,sig=6U-gc1_l7-sy7R-STeGAo0v8q2BV0Jh5LuEdOHZrOuntSQePfx3gjliPare3IXqMOrNAyeM3BxQWGDVEOOLCAg
< content-type: application/json
< 
< {"description":"first"} 

> HTTP/2 200
> content-length: 501
> content-type: application/json
> date: Tue, 22 Dec 2020 12:46:20 GMT
> etag: g_L3O1knFl2u9xMQVY54DfojgRQZusPMbAx97kiUouKq
> 
> {
>  "ancient": "/pzl/s3e8.g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ/auths/x1",
>  "canonical": "/pzl/s3e8/auths/x1.g_L3O1knFl2u9xMQVY54DfojgRQZusPMbAx97kiUouKq",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1",
>  "prev": "/pzl/s3e8.g-Rf0fttdUnl88btdmxnBo0aSiFneLsRoJ-1y71XNHBI/auths/x1",
>  "properties": {
>   "description": "first",
>   "keytype": "ed25519",
>   "name": "x1",
>   "policies": [
>    {
>     "until": 1671540378
>    }
>   ],
>   "pubkey": "lml7WeRunbqRp0VTeAc68YLnz6KKVHb_WoYP1GJGBwM="
>  },
>  "type": "auth",
>  "version": 2
> }

Performing the very same operation on an older version leads to a 412 precondition failed as the given URL is not current anymore.

$ echo "$(puzzle-client -q get /pzl/s3e8/auths/x1 | grep prev | cut -d'"' -f 4) description=second" |  puzzle-client  update /

412 /pzl/s3e8.g-Rf0fttdUnl88btdmxnBo0aSiFneLsRoJ-1y71XNHBI/auths/x1

In this case one has to refetch the current state of the auth object before performing the update again.

5.5 Rolling Key Updates

Modifying a key directly is tricky to implement at the client, as the reply might not reach the client due to limited internet availability. If this happens the client may only guess which key the server will accept in the future. We therefore support a rolling update scheme where the old key is retained until the new key is confirmed.

The first step in the process is to register a new key:

$ puzzle-client -d auth --postfix=/auths/x1 /pzl/s3e8 2>&1

< POST /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1 HTTP/2
< authorization: pzl time=1608641181+60,key=x2,sig=OuwAryoQozSXUdMGdhyYXWk-AOUnNKDvhUMGafqcOkO2EnDMpHIc_KPo9uTnuCILk6hvkGsMu1EJ6bm8J9dUBA
< content-type: application/json
< 
< {"description":"default","keytype":"ed25519","pubkey":"I30_pyo0YBFqAr-NAEI97ihxcQeb3DSbDFUEsWANDK0="} 

{"type": "auth",
 "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1",
 "canonical": "/pzl/s3e8/auths/x1.gxIYL3PVOB3MZoqCazjKuWM_FB7cLvtpMl9gF4pNNFp_",
 "properties": {"description": "default",
 "keytype": "ed25519",
 "keytype2": "ed25519",
 "name": "x1",
 "policies": [{"until": 1671540378}],
 "pubkey": "lml7WeRunbqRp0VTeAc68YLnz6KKVHb_WoYP1GJGBwM=",
 "pubkey2": "I30_pyo0YBFqAr-NAEI97ihxcQeb3DSbDFUEsWANDK0="},
 "prev": "/pzl/s3e8.g6Wgeojhq9jl9WL_9IGhSC0hlHr-F6bVmT3lMIkmg5UL/auths/x1",
 "version": 3,
 "ancient": "/pzl/s3e8.g8scu7crfIr2mCU8dL41XC0EiCMkJ9fE27py_DfjSzrQ/auths/x1"}

This installs a pubkey2 of keytype2 that can be used in parallel to the previous key. To confirm the new key, issue the operation with the very same public key again.

$ echo -n x1 keytype=ed25519 pubkey=$(puzzle-client -q get /pzl/s3e8/auths/x1 | grep pubkey2 | cut -d'"' -f4) | puzzle-client -d update /pzl/s3e8/auths 2>&1

< POST /pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1 HTTP/2
< authorization: pzl time=1608641181+60,key=x2,sig=rNrjiHlBx2sDjpakyLlMqLUEpHlobSjwxbDJDNhsY0zZdYjOfvpNlgDIFycbFnOQjlfBdXTz2rFKgrJvu42PCA
< content-type: application/json
< 
< {"keytype":"ed25519","pubkey":"I30_pyo0YBFqAr-NAEI97ihxcQeb3DSbDFUEsWANDK0="} 

> HTTP/2 200
> content-length: 503
> content-type: application/json
> date: Tue, 22 Dec 2020 12:46:21 GMT
> etag: g6UI_ELkHDLxCEM84k-w_v2TIltwIKfY0yV1rp_NTa1A
> 
> {
>  "ancient": "/pzl/s3e8.g6Wgeojhq9jl9WL_9IGhSC0hlHr-F6bVmT3lMIkmg5UL/auths/x1",
>  "canonical": "/pzl/s3e8/auths/x1.g6UI_ELkHDLxCEM84k-w_v2TIltwIKfY0yV1rp_NTa1A",
>  "current": "/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x1",
>  "prev": "/pzl/s3e8.g6tClDljNyf4UthkNwXRiNo0UTQHYI3ceid9gyeXIRQV/auths/x1",
>  "properties": {
>   "description": "default",
>   "keytype": "ed25519",
>   "name": "x1",
>   "policies": [
>    {
>     "until": 1671540378
>    }
>   ],
>   "pubkey": "I30_pyo0YBFqAr-NAEI97ihxcQeb3DSbDFUEsWANDK0="
>  },
>  "type": "auth",
>  "version": 4
> }

One may also drop the new key by sending the older one instead. To avoid race conditions, one should use the canonical path for both operations.

5.6 Access Control Policies

Fine-grained access control is possible by defining a policy that decides which operations are allowed for a given auth object. This policy is checked only for requests with valid signatures to avoid information leaks. The policy itself is a list of entries that can be specified when creating or modifying an auth object. At least one of the entries must match for any request.

The fields of the entries are as follows:

The until field is always set. This enables timely replacement of unecessary rights and deprecated cryptograpic methods. If no expiration date is given, the maximum duration (currently two years) is choosen.

One can set a read-only policy by allowing only the GET method:

$ echo -n 'x2 policies=[{"method":"GET"}]' | puzzle-client update /pzl/s3e8/auths

{
 "canonical":"/pzl/s3e8/auths/x2.g2vqNv01IHzpQZMGitFNXaMw0MhmT93lvi0RgcCEwzm5",
 "current":"/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths/x2",
 "prev":"/pzl/s3e8.g0w5tKJ80gb-Uv3XxXhyv4gGtEsCbYoYuJOYN0LzhPQr/auths/x2",
 "properties":{
  "description":"default",
  "keytype":"ed25519",
  "name":"x2",
  "policies":[
   {
    "method":"GET",
    "until":1671540381
   }
  ],
  "pubkey":"Kvmfyj78jlsQp08GJ3NrzXdBybOuz0osMlWctwymQms="
 },
 "type":"auth",
 "version":5
}

If the policy check fails on any method, a 401 error is returned.

$ echo -n 'x2 policies=[{"method":"GET"}]' | puzzle-client  --keyname=x2 update /pzl/s3e8/auths

401 /pzl/s3e8/auths/x2

5.7 Deletion

An existing auth object can be deleted.

$ puzzle-client --keyname=x1 -q delete /pzl/s3e8/auths/x2

{
 "ancient":"/pzl/s3e8.g0w5tKJ80gb-Uv3XxXhyv4gGtEsCbYoYuJOYN0LzhPQr/auths",
 "canonical":"/pzl/s3e8/auths.g3-GqC0y53_LQ76Yut8dOBvrZADUvSlZZy2fxjO8Urhm",
 "children":{
  "x1":"g6UI_ELkHDLxCEM84k-w_v2TIltwIKfY0yV1rp_NTa1A"
 },
 "current":"/pzl/s3e8.AGPyrPuKeB_kFgCB2b-uL35EqLKrwZyN/auths",
 "prev":"/pzl/s3e8.g5i9_Yu0nELFyKaw0exCFIKU9FJ2Q1ybVfeH8ihwhnxa/auths",
 "type":"auth-list",
 "version":6
}

Further access with this key is prohibited.

$ puzzle-client -q --keyname=x2 get /pzl/s3e8 2>&1

{
 "reason":"key not found"
}

6. Further Questions

6.1 Where can I try this API? Is there a demo instance of Puzzle?

There is no special demo instance of Puzzle. For testing purposes create a user inside the sandbox environment.

6.2 How do I know in which environment a user resides?

The user and account names start with a different prefix. A user named s3f5 would be in the sandbox.

Concerns?
tell@puzzle2pay.com