Working Draft (Version 0.4)
A management server can manage profiles with various profile URIs, not just profiles on the same domain. To give the implementers flexibility on the layout of the URI hierarchy on this domain, this spec defines the API relative to a base URI.
This API uses an OAuth like authentication scheme.
Each device is granted a long living device token. Each device can choose a random unique device ID.
Endpoint: <baseUri>/auth/device
Method: POST
Content-Type: application/json
Body:
Name | Type | Mandatory | Description |
---|---|---|---|
profile_uri |
String | required | Social profile URI as specified by SPXP of the profile for which a device should be registered |
device_id |
String | required | A random ID uniquely identifying this device |
timestamp |
timestamp | required | Timestamp when the client created this request |
The device registration request must always be signed by the profile owner as specified by SPXP.
Example:
{
"profile_uri" : "https://example.com/spxp/alice",
"device_id" : "my-fancy-phone",
"timestamp" : "2020-01-15T10:39:15.437",
"signature" : {
"key" : "C8xSIBPKRTcXxFix",
"sig" : "htQvu861MyGldtDEaDfOBydlijO_FaZYnpgXuvgcO0H5HNCIkQ_VIOWKZL9peZ19PjPjvpWtQbZEOvNh05o6Bw"
}
}
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
token_type |
String | required | Fixed text string device_token |
device_token |
String | required | The device token registered by the server |
Example:
{
"token_type" : "device_token",
"device_token" : "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3"
}
Failure response code: 403
This operation will invalidate all device tokens previously granted for the same device_id
. A server can choose to
limit the lifetime of a device token or a user can choose to invalidate a device token (lost device). In either case,
clients must be prepared to re-register once their device token becomes invalid.
The device token grants access to the profile and needs to be stored by clients in a secure location.
Device Tokens need to be exchanged into short living access tokens before being used on API endpoints.
Endpoint: <baseUri>/auth/access_token
Method: POST
Content-Type: application/json
Body:
Name | Type | Mandatory | Description |
---|---|---|---|
device_token |
String | required | Device token registered by the server |
timestamp |
timestamp | required | Timestamp when the client created this request |
The access token request must always be signed by the profile owner as specified by SPXP.
Example:
{
"device_token" : "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"timestamp" : "2020-01-15T10:39:17.164",
"signature" : {
"key" : "C8xSIBPKRTcXxFix",
"sig" : "sfdzUrSIjZA2ZTFAQSO8NHqDz1LlhAbRbcmiOlIcEpz9ezlNoxYAhYHIctCpa4eG3NGaVGruMVVzQE0Y6ez6BQ"
}
}
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
token_type |
String | required | Fixed text string access_token |
access_token |
String | required | The access token issued by the server |
expires_in |
Integer | required | Lifetime in seconds of the access token |
Example:
{
"token_type" : "access_token",
"access_token" : "jT4k9ZgkRwHIctzlNoxYAhY4eG3NCpaVVzQEGaVGruM0Y6ez6BQ",
"expires_in" : 3600
}
Failure response code: 403
Every API request listed here, except for authentication requests, must be authorised by sending the Authorization
http request header field containing the Access Token using the Bearer
authentication scheme:
Authorization: Bearer <access_token>
If the access token is invalid, the server responds with a 401
status code. Clients need to refresh the access token
in this case or re-register the device.
The server implementation is providing the backend behind the various endpoints listed in the profile root document.
Most likely, this implementation has some constraints on the endpoint URIs so that they cannot be freely chosen by the
profile owner. The service info API provides the information needed by the profile owner to craft a profile root
document. It further exposes information about the capabilities of this implementation.
Endpoint: <baseUri>/service/info
Method: GET
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
server |
Object | required | Information about the server as defined below |
server.vendor |
String | optional | Vendor of the server product |
server.product |
String | required | Product name of the server product |
server.version |
String | optional | Version string of the server product |
server.helpUri |
String | optional | Absolute URI of user facing help pages of the server product |
endpoints |
Object | required | Collection of endpoint information to be used for the profile root document and in the connection process as defined below |
endpoints.friendsEndpoint |
String | optional | friendsEndpoint to be used in the profile root document |
endpoints.postsEndpoint |
String | optional | postsEndpoint to be used in the profile root document |
endpoints.keysEndpoint |
String | optional | keysEndpoint to be used in the profile root document |
endpoints.connectEndpoint |
String | optional | connectEndpoint to be used in the profile root document |
endpoints.connectResponseEndpoint |
String | optional | responseEndpoint to be used in connection requests |
endpoints.publishEndpoint |
String | optional | publishEndpoint to be used in the profile root document |
limits |
Object | required | Information about service limits as defined below |
limits.maxMediaSize |
Integer | optional | Maximum size in bytes of media objects accepted by this server |
Example:
{
"server" : {
"vendor" : "ACME Corp.",
"product" : "Fancy SPXP Server (tm)",
"version" : "1.0",
"helpUri" : "http://acme.example.com/fancy-server/user-help.html"
},
"endpoints" : {
"friendsEndpoint" : "friends/alice",
"postsEndpoint" : "posts?profile=alice",
"keysEndpoint" : "keys/alice",
"connectEndpoint" : "connect/alice",
"connectResponseEndpoint" : "connectResponse/alice",
"publishEndpoint" : "publish/alice"
},
"limits" : {
"maxMediaSize" : 10485760
}
}
Failure response code: 4xx
or 5xx
There are situations where a server needs to communicate with the profile owner, for example to inform the owner about
a new service agreement that needs to be accepted or to relay connection requests received from other profiles.
These messages use a similar approach as posts in SPXP.
We use the same basic approach and paging mechanism as in SPXP.
Endpoint: <baseUri>/service/messages
Method: GET
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
data |
Array | required | List of service message objects |
more |
Boolean | required | True if and only if there are more service messages before the oldest post in the data array |
Each single service message in the data array is a JSON object with these members:
Name | Type | Mandatory | Description |
---|---|---|---|
seqts |
timestamp | required | Sequence timestamp of service message generated by the server |
type |
String | required | Type of service message. One of provider_message , connection_request or connection_package |
Depending on the type
, additional members are defined as follows:
Name | Type | Mandatory | Description |
---|---|---|---|
message |
String | required | Text message to be displayed to the user |
link |
String | optional | Absolute URI of a link to be displayed to the user |
Name | Type | Mandatory | Description |
---|---|---|---|
received |
Timestamp | required | Timestamp when the server received the connection request |
ver |
String | required | ver field as received on the connect endpoint |
msg |
Object | required | msg field as received on the connect endpoint |
Name | Type | Mandatory | Description |
---|---|---|---|
received |
Timestamp | required | Timestamp of the package exchange |
ver |
String | required | ver field as received on the package exchange endpoint |
package |
Object | required | package field as received on the package exchange endpoint |
Supported query parameters:
Name | Type | Description |
---|---|---|
max |
Integer | Maximum number of items the client can handle in one response. The server can return fewer items, but must not return more items. |
before |
timestamp | Only include items with a timestamp before this date |
after |
timestamp | Only include items with a timestamp after this date |
Example request: <baseUri>/service/messages?max=5&after=2018-09-17T14:04:27.373&before=2018-09-19T15:45:37.735
Example:
{
"data" : [
{
"seqts" : "2018-09-17T14:04:27.373",
"type" : "provider_message",
"message" : "Hello, world!",
"link" : "https://example.com"
}, {
"seqts" : "2018-09-15T12:35:47.735",
"type" : "connection_request",
"received" : "2018-09-15T12:35:46.123",
"ver" : "0.3",
"msg" : {
"..." : "..."
}
}, {
"seqts" : "2018-09-13T02:53:12.154",
"type" : "connection_package",
"received" : "2018-09-13T02:53:11.856",
"ver" : "0.3",
"establishId" : "",
"package" : {
"..." : "..."
}
}
],
"more" : true
}
Failure response code: 4xx
or 5xx
Messages are hold by the server until they are cleaned up. Clients can decide if they delete messages on the server
immediately after having received them, or only after having shown them to the user.
Endpoint: <baseUri>/service/messages/<seqts>
Parameter: <seqts>
value of the seqts
field in the service message object to delete
Method: DELETE
Success response code: 204
Failure response code: 4xx
or 5xx
To publish new data for the profile root document or the “friends endpoint“, the client simply PUTs the JSON objects
to the server, including all private data. There is no possibility yet to selectively update individual elements in the
private array or the main object.
The server does not need to validate any signatures or availability of decryption keys.
Endpoint: <baseUri>/profile/root
Endpoint: <baseUri>/profile/friends
Method: PUT
Content-Type: application/json
Body: the entire JSON object as specified in the SPXP specification
Success response: 201
or 204
without body
Failure response code: 4xx
or 5xx
Clients have to use the normal “posts endpoint” in SPXP to enumerate all posts. This requires the profile owner to have a reader key (or set of keys) that is able to read all posts.
To publish a new post, the client POSTs the entire post object to the server.
The server does not need to validate any signatures or availability of decryption keys.
Endpoint: <baseUri>/posts
Method: POST
Content-Type: application/json
Body: the entire JSON object of one single post object as specified in the SPXP specification
Example:
{
"createts" : "2018-09-16T12:23:18.751",
"type" : "text",
"message" : "Hello, world!",
"signature" : {
"key" : "C8xSIBPKRTcXxFix",
"sig" : "bDOgcT4uxTKYMTuOJXDbAPc1UA2p-aGdxwplUWNStzyDRIRPu9UxaTU1IoZ1ELjBY5iRf4FEBPV09Uw9TOYuCA"
}
}
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
seqts |
String | required | The seqts assigned to this post by the server |
Example:
{
"seqts" : "2018-09-17T14:04:27.373"
}
The server will assign a unique seqts, store the post and return the assigned seqts.
Failure response code: 4xx
or 5xx
Post objects can be removed with a DELETE HTTP request using the unique seqts of the post object.
Endpoint: <baseUri>/posts/<seqts>
Parameter: <seqts>
value of the seqts
field in the post object to delete
Method: DELETE
Success response code: 204
Failure response code: 4xx
or 5xx
Profiles can include links to other media files, like images and videos, in their posts and the profile. These media
files can also be managed through this extension.
Media files are handled as opaque binary files by this protocol, uniquely identified by a media ID.
Binary files can be POSTed directly to the server as multipart/form-data
.
Endpoint: <baseUri>/media
Method: POST
Content-Type: multipart/form-data
Form Data:
Field | Content |
---|---|
source | binary media as application/octet-stream , images/jpeg or images/png |
Success response code: 200
Content-Type: application/json
Success response body:
Name | Type | Mandatory | Description |
---|---|---|---|
mediaid |
String | required | The ID of this media object used to address this object with this API |
uri |
String | required | Absolute URI of this media object (public accessible) |
Example:
{
"mediaId" : "3d3af028-8c61-4cdc-8b96-6012a85faa99",
"uri" : "https://cdn.example.com/3d3af028-8c61-4cdc-8b96-6012a85faa99"
}
Failure response code: 4xx
or 5xx
Endpoint: <baseUri>/media/<mediaId>
Parameter: <mediaId>
ID of media object to be deleted as returned by the upload media endpoint
Method: DELETE
Success response code: 204
Failure response code: 4xx
or 5xx
Maintaining the tree of keys and ensuring that actors have access to all the content they are entitled for is the sole
responsibility of the profile owner. This management extension only provides endpoints to publish new keys to the server
and to delete existing keys. The server will maintain this set of keys and use it to compute the result set on the “keys
endpoint” as well as to select the elements exposed as private
data in objects.
The client transfers a set of keys to the server. Each key is processed independently, and the outcome of each operation
is reported individually. A ”Success” response code only means that the entire request has been processed, but it does
not indicate that any of these operations has been successful. The request is structured similar to the response on the
“keys endpoint” in SPXP.
Endpoint: <baseUri>/keys
Method: POST
Content-Type: application/json
Request body: The same 3 level JSON structure as specified for the keys endpoint in SPXP
Example:
{
"audience1" : {
"group1" : {
"round1" : "<JWK1>",
"round2" : "<JWK>",
"round3" : "<JWK>"
},
"group2" : {
"round1" : "<JWK>",
"round2" : "<JWK>",
"round3" : "<JWK>"
}
},
"audience2" : {
}
}
Success response code: 200
Content-Type: application/json
Success body:
{
"audience1" : {
"group1" : {
"round1" : "<outcome>",
"round2" : "<outcome>",
"round3" : "<outcome>"
},
"group2" : {
"round1" : "<outcome>",
"round2" : "<outcome>",
"round3" : "<outcome>"
}
},
"audience2" : {
}
}
With <outcome>
one of ok
, err_exists
, err_invalid_jwk
, err_invalid_jwk: <description>
, err_retry
or error: <description>
Failure response code: 4xx
or 5xx
Keys can be deleted individually or in groups. The server does not perform any cascading delete. If a key can no longer
be decrypted, it is still hold on the server. It is possible that a client is going to publish new decryption keys in a
later operation.
Endpoint: <baseUri>/keys/<audience>/<group>/<round>
Endpoint: <baseUri>/keys/<audience>/<group>
Endpoint: <baseUri>/keys/<audience>
Method: DELETE
Success response code: 204
Failure response code: 4xx
or 5xx
To add or remove keys that are associated with a prepared package, the client can add the establishId
of the prepared
package as suffix to the audience, separated by the @
character.
See 9.3 for details.
When a peer profile accepts a connection request, the server has to perform the package exchange on behalf of the user.
In this process, the server has to activate a set of keys so that the peer profile can start to read private
information, accept and store the package provided by the peer profile and then hand out the connection package that
has been prepared for the peer. Please see the main SPXP specification for details on this process.
The profile management extension defines the following operations to manage this process.
The server needs to be prepared for the connection package exchange with other profiles.
Endpoint: <baseUri>/connect/packages
Method: POST
Content-Type: application/json
Body:
Name | Type | Mandatory | Description |
---|---|---|---|
establishId |
String | required | The establish ID as defined in the connection process in SPXP |
expires |
timestamp | required | Timestamp after which the server must no longer accept package exchanges |
package |
Object | required | The connection package to be exchanged on the connection response endpoint for this establish ID |
keys |
Object | required | set of keys to be activated on the package exchange. Same object structure as on the publish keys endpoint. |
Example:
{
"establishId" : "K4dwfD4wA67xaD-t",
"expires" : "2020-07-12T09:40:17.734",
"package" : {
"..." : "..."
},
"keys" : {
"audience1" : {
"group1" : {
"round1" : "<JWK>",
"round2" : "<JWK>",
"round3" : "<JWK>"
},
"group2" : {
"round1" : "<JWK>",
"round2" : "<JWK>",
"round3" : "<JWK>"
}
},
"audience2" : {
}
}
}
Success response code: 204
Failure response code: 4xx
or 5xx
The client can revoke a prepared connection package based on the establishId.
Endpoint: <baseUri>/connect/packages/<establishId>
Method: DELETE
Success response code: 204
Failure response code: 4xx
or 5xx
It is possible that the client needs to extend or even replace cryptographic material associated with a connection
process. If either the reader key or certificate are effected, the client needs to revoke the entire package and issue
a new one with the same establishId.
If the change is limited to the round keys that are associated with a connection, which is most likely, then the client
can use the normal key management operations using this special audience:
<audince>'@'<establishId>
While a connection request ages, it is likely that new round keys need to be generated in the assigned group. In this
case, these new round keys need to be added to the prepared package.
Example body on the keys endpoint:
{
"audience1@establish1" : {
"group1" : {
"round1" : "<JWK1>",
"round2" : "<JWK>",
"round3" : "<JWK>"
}
},
"audience2" : {
}
}
To be able to authenticate requests for publishing tokens on the publishing endpoint,
the server needs to know the set of keys which are authorized to post publicly and those authorized to post privately.
Although fundamentally different from reader keys, the lifecycle is similar: They are exchanged as part of connection
packages and either need to be activated during the package exchange or they get published most commonly together with
a set of reader keys when accepting connections. Hence, we treat these keys like reader keys and use the usual
key management endpoints and package
preparation endpoints with
the following 3 level JSON object structure:
Level | Content |
---|---|
Outermost level | Fixed text string @publish@ |
Middle level | key id |
Inner level | Fixed text string public or private depending on allowed key use |
To be compatible with round keys, the JWK of the public signing key is transferred as a String containing the JSON serialisation of the JWK.
Example on the <baseUri>/keys
endpoint:
{
"@publish@" : {
"QcUQRaiTiOuchvSy" : {
"private" : "{\"kid\":\"QcUQRaiTiOuchvSy\",\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"rHdyo3zVbl50ufXSajF71HjidGdBwk-YQSKDM2hS5Yc\"}"
},
"czlHMPEJcLb7jMUI" : {
"public" : "{\"kid\":\"czlHMPEJcLb7jMUI\",\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"vg42ogNHigJnwZ0pwwMzUtaXZA49eqcfGYl2u9GR8vg\"}"
}
}
}
Example on the <baseUri>/connect/packages
endpoint:
{
"establishId" : "K4dwfD4wA67xaD-t",
"expires" : "2020-07-12T09:40:17.734",
"package" : {
"..." : "..."
},
"keys" : {
"audience1" : {
"group1" : {
"round1" : "<JWK>",
"round2" : "<JWK>",
"round3" : "<JWK>"
},
"group2" : {
"round1" : "<JWK>",
"round2" : "<JWK>",
"round3" : "<JWK>"
}
},
"@publish@" : {
"QcUQRaiTiOuchvSy" : {
"private" : "{\"kid\":\"QcUQRaiTiOuchvSy\",\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"rHdyo3zVbl50ufXSajF71HjidGdBwk-YQSKDM2hS5Yc\"}"
},
"czlHMPEJcLb7jMUI" : {
"public" : "{\"kid\":\"czlHMPEJcLb7jMUI\",\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"vg42ogNHigJnwZ0pwwMzUtaXZA49eqcfGYl2u9GR8vg\"}"
}
}
}
}