Browse Source

feat: improve data storage guide

feat/build-apps
Mark Hendrickson 4 years ago
parent
commit
b8f8d67c03
  1. 2
      src/pages/build-apps/guides/authentication.md
  2. 224
      src/pages/build-apps/guides/data-storage.md
  3. 2
      src/pages/build-apps/guides/transaction-signing.md

2
src/pages/build-apps/guides/authentication.md

@ -17,7 +17,7 @@ Authentication provides a way for users to identify themselves to an app while r
Users who register for your app can subsequently authenticate to any other app with support for the [Blockchain Naming System](/build-apps/references/bns) and vice versa.
See [the Todos app tutorial](/build-apps/tutorials/todos) for a concrete example of this functionality in practice.
[See the Todos app tutorial](/build-apps/tutorials/todos) for a concrete example of this functionality in practice.
## How it works

224
src/pages/build-apps/guides/data-storage.md

@ -15,199 +15,145 @@ This guide explains how to save and retrieve data for users with [Gaia](/build-a
Data storage provides a way for users to save both public and private data off-chain while retaining complete control over it.
Storing data off of the blockchain ensures that apps can provide users with high performance and high availability for data reads and writes without the involvement of centralized parties that could comprise their privacy or accessibility.
Storing data off the Stacks blockchain ensures that apps can provide users with high performance and high availability for data reads and writes without the involvement of centralized parties that could compromise their privacy or accessibility.
See [the Todos app tutorial](/build-apps/tutorials/todos) for a concrete example of this functionality in practice.
[See the Todos app tutorial](/build-apps/tutorials/todos) for a concrete example of this functionality in practice.
## Authentication
## Initiate session
TODO: Add indicator that authentication guide should be followed first
Users must authenticate to an app before the `storage` package will work to save or retrieve data on their behalf.
## How data is stored
[See the authentication guide](/build-apps/guides/authentication) before proceeding to integrate the following data storage capabilities in cases where `userSession.isUserSignedIn()` returns `true`.
Gaia storage is a key-value store.
## Save data for session user
## Creating a file
Gaia serves as a key-value store in which data is saved and retrieved as files to and from Gaia hubs owned by, or managed for, users.
Use the [Storage.putFile](https://blockstack.github.io/stacks.js/classes/storage.html#putfile) method:
The default Gaia hub for users who authenticate to apps with [the Stacks Wallet](https://blockstack.org/wallet) is run by Hiro PBC at `https://gaia.blockstack.org/`. It supports files up to 25 megabytes in size.
```tsx
const userSession = new UserSession();
const storage = new Storage({ userSession });
const options: PutFileOptions = {
encrypt: false,
};
userSession.putFile('hello.txt', 'hello world', options).then(() => {
// hello.txt exists now, and has the contents "hello world"
});
```
-> We recommend breaking data instances greater than 25 MB into several files, saving them individually, and recomposing them on retrieval.
## Creating an encrypted file
These files can comprise any type of data such as text, image, video or binary.
Use the [Storage.putFile](https://blockstack.github.io/stacks.js/classes/storage.html#putfile) method and
pass `encrypt: true` within the options object. See the [`PutFileOptions` type definition here](https://blockstack.github.io/stacks.js/interfaces/putfileoptions.html#encrypt)
Files are often saved as strings that represent stringified JSON objects and contain a variety of properties for a particular model.
```tsx
const userSession = new UserSession();
To save a file, first instantiate a `storage` object using the `userSession` object for an authenticated user. Then proceed to call its `putFile` method with relevant parameters:
const options: PutFileOptions = {
encrypt: true,
};
```js
import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';
userSession.putFile('message.txt', 'Secret hello', options).then(() => {
// message.txt exists now, and has the contents "Secret hello"
});
```
## Reading a file
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });
Use the [Storage.getFile](https://blockstack.github.io/stacks.js/classes/storage.html#getfile) method:
let fileName = 'car.json';
```tsx
const userSession = new UserSession();
const storage = new Storage({ userSession });
let fileData = {
color: 'blue'
electric: true,
purchaseDate: '2019-04-03',
};
const options: GetFileOptions = {
decrypt: false,
let options = {
encrypt: true,
};
storage.getFile('hello.txt', options).then(fileContents => {
// get the contents of the file hello.txt
assert(fileContents === 'hello world!');
storage.putFile(fileName, JSON.stringify(fileData), options).then(() => {
// Handle any execution after data has been saved
});
```
## Reading an encrypted file
The `options` parameter object contains an `encrypt` property that when set to `true` indicates that the data should be encrypted with the user's app private key before saved to their Gaia hub. All data will be encrypted as such by default if the `encrypt` property or the `options` object itself is omitted entirely.
Use the [Storage.getFile](https://blockstack.github.io/stacks.js/classes/storage.html#getfile) method and pass
`decrypt: true` within the options object. See the [`GetFileOptions` type definition here](https://blockstack.github.io/stacks.js/interfaces/getfileoptions.html#decrypt)
If the `encrypt` property is set to `false`, the data will be saved completely unencrypted and available to everyone online with public access to the user's Gaia hub.
```tsx
const userSession = new UserSession();
const storage = new Storage({ userSession });
Whereas saving privately encrypted data is possible for all authenticated apps with the [`store_write`](https://blockstack.github.io/stacks.js/enums/authscope.html#store_write) scope, the user must have previously granted the [`publish_data`](https://blockstack.github.io/stacks.js/enums/authscope.html#publish_data) scope as well during authentication for the app to save publicly unencrypted data.
const options: GetFileOptions = {
decrypt: true,
};
-> Note that you'll need to save an entirely new string of modified data using `putFile` with the same `fileName` every time you want to update a record. There is no separate update method.
storage.getFile('message.txt', options).then(fileContents => {
// get & decrypt the contents of the file /message.txt
assert(fileContents === 'Secret hello!');
});
```
## Get data for session user
To retrieve data previously saved for a user with an app, call the `getFile` method available from the `storage` object:
## Reading another user's unencrypted file
```js
import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';
In order for files to be publicly readable, the app must request
the [`publish_data` scope](https://blockstack.github.io/stacks.js/enums/authscope.html#publish_data) during authentication.
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });
let fileName = 'car.json';
```jsx
const options = {
user: 'ryan.id', // the Stacks ID of the user for which to lookup the file
app: 'https://BlockstackApp.com', // origin of the app this file is stored for
decrypt: false,
decrypt: true,
};
const userSession = new UserSession();
storage.getFile('hello.txt', options).then(fileContents => {
// get the contents of the file /message.txt
assert(fileContents === 'hello world!');
storage.getFile(fileName, options).then(fileData => {
// Handle any execution that uses decrypted fileData
});
```
## Delete a file
Note how the `decrypt` property in the `options` object here should implement the same boolean value as used for `encrypt` initially upon saving the data with `putFile`. The `decrypt` property will default to `true` if omitted.
Use the [`UserSession.deleteFile`](https://blockstack.github.io/stacks.js/classes/storage.html#deletefile) from the application's data store.
Encrypted files need `decrypt` set to `true` so the app knows to decrypt the data with the user's app private key before made available in the callback here as `fileData`.
```jsx
const userSession = new UserSession();
const storage = new Storage({ userSession });
storage.deleteFile('hello.txt').then(() => {
// hello.txt is now removed.
});
```
## Get data for other user
## Write-to and Read-from URL Guarantees
Apps can also retrieve public data saved by users other than the one with the active session, granted those users have registered usernames via the [Blockchain Naming System](/build-apps/references/bns).
Gaia is built on a driver model that supports many storage services. So, with
very few lines of code, you can interact with providers on Amazon S3, Dropbox,
and so forth. The simple `getFile()` and `putFile()` interfaces are kept simple
because Stacks assumes and wants to encourage a community of
open-source-data-management libraries.
Simply indicate the username of such a user in the `options` object:
The performance and simplicity-oriented guarantee of the Gaia specification is
that when an application submits a write-to
`https://myhub.service.org/store/foo/bar` URL, the application is guaranteed to
be able to read from the `https://myreads.com/foo/bar` URL. Note that, while the
prefix in the write-to url (for example,`myhub.service.org/store`) and the read-from URL
(`https://myreads.com`) are different, the `foo/bar` suffixes are the same.
```js
import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';
By default, `putFile()` encrypts information while `getFile()` decrypts it by default. Data stored in an
encrypted format means only the user that stored it can view it. For applications that want other users to
view data, the application should set the `encrypt` option to `false`. And, corresponding, the `decrypt`
option on `getFile()` should also be `false`.
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });
Consistent, identical suffixes allow an application to know _exactly_ where a
written file can be read from, given the read prefix. The Gaia service defines a `hub_info` endpoint to obtain
that read prefix:
let fileName = 'car.json';
```bash
GET /hub_info/
```
The endpoint returns a JSON object with a `read_url_prefix`, for example, if my service returns:
const options = {
username: 'markmhendrickson.id.blockstack',
};
```jsx
{ ...,
"read_url_prefix": "https://myservice.org/read/"
}
storage.getFile(fileName, options).then(fileData => {
// Handle any execution that uses decrypted fileData
});
```
The data be read with this `getFile()` and this address:
```
https://myservice.org/read/1DHvWDj834zPAkwMhpXdYbCYh4PomwQfzz/0/profile.json
```
This `getFile` call will retrieve data found at the given `fileName` path from the storage bucket of the Gaia hub that maps to the user who registered the given `username` and this particular app as hosted at the current domain.
The application is guaranteed that the profile is written with `putFile()` this request address:
Set an additional `app` property within `options` to retrieve data for a user as saved by an app hosted at a separate domain:
```js
const options = {
app: 'example.org'
username: 'markmhendrickson.id.blockstack',
};
```
https://myservice.org/store/1DHvWDj834zPAkwMhpXdYbCYh4PomwQfzz/0/profile.json
```
When you use the `putFile()` method it takes the user data and POSTs it to the user's Gaia storage hub.
The data POSTs directly to the hub, the blockchain is not used and no data is stored there. The limit on
file upload is currently 25mb.
## Address-based access-control
This will cause the `getFile` call to retrieve data found in a separate storage bucket for the indicated app on the user's Gaia hub.
Access control in a Gaia storage hub is performed on a per-address basis.
Writes to URLs `/store/<address>/<file>` are allowed only if the writer can
demonstrate that they control _that_ address. This is achieved via the
authentication token which is a message _signed_ by the private key associated
with that address. The message itself is a challenge text, returned via the
`/hub_info/` endpoint.
## Delete data for session user
Reads can be done by everybody. The URLs to a user's app data are in a canonical location in their profile.
For example, here's how you would get data from the [Banter](https://banter.pub/) app, stored under the
Stacks ID `gavin.id`.
Call the `deleteFile` method on `storage` to remove data found at a particular file path for the active session user:
### Step 1: Get the bucket URL
```js
import { AppConfig, UserSession } from '@stacks/connect';
import { Storage } from '@stacks/storage';
```bash
BUCKET_URL="$(curl -sL https://core.blockstack.org/v1/users/gavin.id | jq -r '."gavin.id"["profile"]["apps"]["https://banter.pub"]')" 
echo "$BUCKET_URL"  https://gaia.blockstack.org/hub/16E485MVpR3QpmjVkRgej7ya2Vnzu3jyTR/
```
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const storage = new Storage({ userSession });
### Step 2: Get the data
let fileName = 'car.json';
```bash
curl -sL "${BUCKET_URL%%/}/Message/3e866af471d0-4072-beba-06ad1e7ad4bd"
```
```bash
{"content":"Anyone here?","votes":[],"createdBy":"gavin.id",...}
storage.deleteFile(fileName).then(() => {
// Handle any execution after file has been deleted
});
```
This data is public and unencrypted. The same works for encrypted data. Only the holder of the private key used for encryption would be able to decrypt the data.
-> Apps can save and delete data only for the active session user.

2
src/pages/build-apps/guides/transaction-signing.md

@ -17,7 +17,7 @@ Transaction signing provides a way for users execute [Clarity smart contracts](/
Users can sign transactions that exchange fungible or non-fungible tokens with upfront guarantees while retaining complete control over their digital assets.
See [the public registry tutorial](/build-apps/tutorials/public-registry) for a concrete example of this functionality in practice.
[See the public registry tutorial](/build-apps/tutorials/public-registry) for a concrete example of this functionality in practice.
## How it works

Loading…
Cancel
Save