Managing Stores
At the core, Stores provide a secure way for assigned members to upload encrypted files.
- Stores allow users to upload files to separate file containers.
- Each Context can contain any number of Stores with a unique identifier (
storeId
) used to distinguish them. - Stores do not need to have unique names or assigned public keys.
Permissions
Stores differentiate two types of users: Managers and Regular Users. The table below shows the differences in their permissions:
Activity | User | Manager |
---|---|---|
Uploading files | yes | yes |
Editing files | yes | yes |
Editing Store | no | yes |
Deleting files | only their own | all files |
The values above are the default policy values defined by PrivMX. To read more about Policies and learn how to modify them, go to Policies.
Initial Assumptions
The initial assumptions for all the code examples below are as follows:
#include <privmx/endpoint/core/Connection.hpp>
#include <privmx/endpoint/store/StoreApi.hpp>
#include <privmx/endpoint/core/Buffer.hpp>
// ...
/*
All the values below like BRIDGE_URL, SOLUTION_ID, CONTEXT_ID
should be replaced by the ones corresponding to your Bridge Server instance.
The private keys here are for demonstration purposes only.
Normally, they should be kept separately by each user and stored in a safe place,
or generated from a password (see the derivePrivateKey() method in the Crypto API)
*/
std::string BRIDGE_URL {"http://localhost:9111"};
std::string SOLUTION_ID {"YOUR_SOLUTION_ID"};
std::string CONTEXT_ID {"YOUR_CONTEXT_ID"};
std::string USER1_ID {"user_1"};
std::string USER1_PUBLIC_KEY {"PUBLIC_KEY_1"};
std::string USER1_PRIV {"PRIVATE_KEY_1"};
std::string USER2_ID {"user_2"};
std::string USER2_PUBLIC_KEY {"PUBLIC_KEY_2"};
std::string USER3_ID {"user_3"};
std::string USER3_PUBLIC_KEY {"PUBLIC_KEY_3"};
// initialize Endpoint connection and Threads API
auto connection {core::Connection::connect(USER1_PRIV, SOLUTION_ID, BRIDGE_URL)};
auto storesApi {store::StoreApi::create(connection)};
Creating Stores
To create a Store, you need a name and a list of public key - userID pairs. See how to get them here. Due to the fact that each Store is inside a Context, all the public keys have to be registered inside the given Context. You can do it using Bridge API.
After creating a Store, all the users with management rights will be able to edit the Store. Skip to Modifying Stores for more info.
Below you can see some examples of creating Stores:
// create a new Store with access for user_1 as manager and user_2 as regular user
auto storeId {storesApi.createStore(CONTEXT_ID, users, managers,
core::Buffer::from("some store's public meta-data"),
core::Buffer::from("some store's private meta-data")
)};
Important note: Managers declared while creating the Store, also have to be included in the regular user list.
The user list in Stores is designed to be flexible, accommodating a wide range of use cases.
Listing Stores
Your application may include multiple Stores, each associated with different Contexts. You can retrieve a list of all Stores within a given Context. This list will include useful metadata about the Stores, such as the creation date, last file upload date, user list, and information about the last modification. However, to optimize performance, the list will only include the total number of files in each Store, not the full file content.
Here's an example of how to download the last 30 Stores created within a Context:
// list stores
core::PagingQuery query = {.skip = 0, .limit = 30, .sortOrder = "desc"};
auto result {storesApi.listStores(CONTEXT_ID, query)};
for (auto store: result.readItems) {
std::cout << "Store ID: " << store.storeId << std::endl;
}
To limit collecting too much data when downloading Stores, specify the page index (starting from 0) and the number of items to be included on each page.
Modifying Stores
Depending on your project's specification, it may be necessary to modify a Store. It could be e.g. changing the name or adding/removing users. Each user with management rights is able to modify Stores, delete them as a whole or only particular files.
Updating a Store means overwriting it with the provided data.
To successfully update a Store, you must specify its current version
.
The version
field is mandatory to handle multiple updates on the server and it is incremented by 1 with each update.
Below there is an example of modifying a Store:
std::vector<core::UserWithPubKey> managers {};
managers.push_back({.userId = USER1_ID, .pubKey = USER1_PUBLIC_KEY});
// Let's add a new user to the existing ones in the Store
std::vector<core::UserWithPubKey> newUsersList {};
newUsersList.push_back({.userId = USER1_ID, .pubKey = USER1_PUBLIC_KEY});
newUsersList.push_back({.userId = USER2_ID, .pubKey = USER2_PUBLIC_KEY});
newUsersList.push_back({.userId = USER3_ID, .pubKey = USER3_PUBLIC_KEY});
auto storeInfo {storesApi.getStore(storeId)};
storesApi.updateStore(storeId, newUsersList, managers,
core::Buffer::from("some new store's public meta-data"),
core::Buffer::from("some new store's private meta-data"), storeInfo.version, false, false
);
Three additional options are available when changing the list of users inside a Store:
force
- applies an update, without checking the current version;forceGenerateNewKey
- re-encrypts messages in the Thread. It's useful when a user is removed and we want to prevent them from accessing the Thread.policies
- allow you to manage access to Stores and files. Read more about Policies.