项目作者: otorno

项目描述 :
Pseudo-Decentralized Storage Middleware
高级语言: TypeScript
项目地址: git://github.com/otorno/hestia.git
创建时间: 2019-07-03T06:07:55Z
项目社区:https://github.com/otorno/hestia

开源协议:Mozilla Public License 2.0

下载


Hestia

Pseudo-Decentralized Storage Middleware, or in other words, a Multi-Backend Gaia Hub

screenshot.png

ko-fi

Feature Overview

  • Gaia Compatability
    • To use as your Gaia Hub, simply use {your hestia node url}/gaia!
    • Uses Gaia Authentication token format for simplicity and ease-of-use
  • Easily configurable with many whitelisting options
  • Uses a Database for storing metadata, indexing the files, and storing user preferences
    • This improves performance when looking up your files instead of just assuming they all
      exist.
  • Advance Drivers
    • Multi-Instance: Run more than one driver of a particular type with different
      configuration options
    • Multi-User: Run one driver that supports individual users (for remote cloud storage
      like Dropbox)
    • “Root Only”: Only writing the root folder (profile.json and avatar), to limit storage
      use and encourage users to use their own remote backends
  • Plugins
    • From making backups-on-request to providing a dashboard, it’s all possible through the
      Plugin API Interface

Doc Shortcuts:

About / Goals

Background

I am simply trying to finish what Blockstack started in regards to Gaia – or rather,
to try and fulfill the original goal in a different way. I wanted Users to be able to
use their own Dropbox without having to spin up their own node and all of the complexity
that doing so brings – so why not have a pseudo-centralized service handle it all for
them? And beyond that, why not have the ability to hook up multiple backends (as
advertised in the whitepaper) that can replicate or be given to a particular app at
the user’s choosing?

If you want to get into blockstack easily, use this; if you are concerned about
centralization but still want the ease-of-use this brings, run your own node; if you
want to go as deep as you can, run multiple of your own gaia hubs and use a browser
that supports that (if any exist currently).

  • Michael Fedora, from here.

Explanation

The goal of Hestia is to serve as a more complex
Gaia Hub. While the original software works well,
it works simply and cannot solve certain problems, such as those posed by using personal
cloud storage providers as backends while also allowing any end-user to do so.

Hestia was made so that any Blockstack user could use their personal cloud storage
(i.e. Dropbox, Google Drive, etc) as their own storage backend, i.e. to have full control
over both ends (writing the files and accessing the backend). While this node, which
serves as middleware, is still controled by a third-party, it can easily be run by
any user as well, whether for themselves, for their family, or their organization.

  1. End User -> Gaia -> Amazon S3 (Node Owned Backend)
  2. End User -> Hestia -> End User's Dropbox (User Owned Backend)

In addition, because of its inherent complexity, more features have been added to allow
extension by third party plugins and other drivers, whether locally created or imported
through npm. There are also more configuration options on the default drivers, such as
the Disk driver being able to limit how much overall storage it is using, as well as
how much each user is allowed to store. Hestia can also whitelist drivers as well as
limit a driver to only being able to be used as an identity folder — i.e. only a
user’s profile.json and avatar will be stored on the driver backend, and all other apps
that attempt to use the driver will fail.

Hestia also provides a singular Gaia interface for all backends — this means the user
manages what drivers handle what buckets. For instance, an end-user could have their Dropbox
handle Stealthy data, while their Google Drive can handle their Travelstack data, and their
One Drive gets everything. All drivers get your identity folder, however, and some drivers
allow you to use them more than once; for instance, you could attatch two dropbox instances to
your account if you so desired — but you can only have one disk driver per driver instance.

  1. User Connections:
  2. - Disk (identity only, 5mb limit)
  3. - Dropbox 1 (personal, 2gb limit, default (store everything))
  4. - Dropbox 2 (team, 2gb limit, stealthy.im only)

Of course, with all of this complexity, there will be some downsides:

  • It is required to use an association token for every request, as the Hestia Hub needs to know
    the end-user’s address to be able to read the connection information
  • Connection information (such as dropbox tokens) are stored unencrypted within the Hestia Hub
    • These can obviously still be revoked, and this should not seem more unsecure than any other
      app requesting to use your dropbox.
  • File metadata and their paths are stored in the local database, unencrypted
    • While it could be concerning because of how easy it is to get it, reading a Gaia Hub’s
      logs (or the server’s HTTP logs) would get you the same information.

Back to top

Installation / Setup

  • npm i
  • npm run build-prod
  • Copy config.sample.json, rename to config.json and configure (see:
    Configuration below)
  • npm start

Back to Top

Configuration

A sample config file is provided in config.sample.json, but
here is an annotated configuration below:

  1. interface Config {
  2. port: number; // The port to serve the application on
  3. ip: string; // The ip (generally `0.0.0.0`) to serve the application on
  4. protocol: string; // The protocol (`http` or `https`) to serve the application on
  5. server_name: string; // The server name (e.x. `localhost:{port}` or `Hestia.otorno.cc`)
  6. valid_hub_urls?: string[]; // (optional) Other valid hub urls for apps to make requests to
  7. db_driver_path?: string; // (optional) the path where the db driver is located
  8. // (default: `default-db-drivers/sqlite3`)
  9. db_driver_config?: any; // (optional) the config for the db driver
  10. pm2?: boolean; // (optional) whether or not you are using pm2 (for logging issues)
  11. pm2InstanceVar?: string; // (optional) the instance ID if it is not default
  12. whitelist?: string[]; // (optional) A list of addresses which are whitelisted to use the node
  13. max_blob_size?: string | number; // (optional) The maximum blob size for files (i.e. "5mb", 5242880)
  14. // default is 7.5mb
  15. page_size?: number; // The pagination size for list-files
  16. root_plugin?: string; // The plugin to use as the `/` plugin, i.e. for a web interface
  17. // driver configs
  18. drivers: {
  19. [id: string]: { // this driver ID
  20. path: string; // the path where the driver is located
  21. name?: string; // (optional) a different name to use then the standard one
  22. icon_url?: string; // (optional) a url to use for the icon instead of the default provided one
  23. whitelist?: string[]; // (optional) a whitelist of users who can use the driver
  24. auto_register?: boolean; // (optional) whether or not users should be auto-registered for the
  25. // driver when their account is registered
  26. root_only?: boolean; // (optional) whether or not the driver should only be allowed for
  27. // the root (identity) directory
  28. }
  29. };
  30. // plugin configs
  31. plugins: {
  32. [id: string]: { // the plugin ID
  33. path: string; // the path to the plugin for `import({path})`
  34. }
  35. };
  36. }

Back to top

API

Token Types

Most routes need some sort of authentication token in order to work properly, but in
general there are four types of routes:

  • Those that do not need any (“None“)
  • Those that need normal gaia bucket authentication (“Bucket“)
  • Those that only partially check the token (“Partial“)
    • Based off of the Bucket token, but does not check for the user existing, or that
      the claimed Gaia-Hub in the token is the actual Hestia Hub
  • Those that need what is called a “user” authentication (“User“)
    • A Bucket token that is either authed for the Hestia Hub itself as if it were an app
      (i.e. properly issued to server_name) or one that is issued for the Id’s root bucket
      (i.e. if the issuer and signer are the same address).

Any token besides ones for which the signer and issuer are equal need to be signed with
an “Association Token,” which is a sub-token inside of the overarching token. Please see
the Gaia Hub Repo for more information on the
structure of both the normal token and the association token.

Meta Routes (/)

HTTP Method Url Token Type(s) Description
GET /env None Get the current NODE_ENV
* /plugins/{id}/... None, User Use the plugin routes. See the specific plugins for documentation.
GET /manifest.json None A computed manifest.json that is generated from the config file, for logging into the Hestia Hub with Blockstack authentication.

Gaia Routes (/gaia)

The gaia route group, of which all (should) align with the spec listed in the
Gaia Hub repo. This route is what is used in
browser configurations which need a link to your gaia hub (i.e. server_name/gaia).

HTTP Method Url Token Type(s) Description
GET /gaia/hub_info None Read the hub info.
GET /gaia/read/{address}/{path} None Read a file from the given bucket address and file path.
POST /gaia/store/{address}/{path} Bucket, User Store a file to the given bucket address and path. Post the contents as a body or urlencoded stream, preferably with a content-type and content-length.
DELETE /gaia/delete/{address}/{path} Bucket, User Delete the file given by the bucket address and path.
POST /gaia/list-files/{address} Bucket, User List the files in the given bucket. Within the body, put a JSON object with { state?: boolean, page?: number } to specify whether or not to return “state” info, and the page number. See below for return types.
POST /gaia/revoke-all/{address} Bucket, User Revoke all tokens in the given bucket up to a value (in seconds) in the body which is formatted as so: { oldestValidTimestamp: number }.

Return type for /gaia/list-files/{address} with state as false or undefined:

  1. {
  2. entries: string[]; // full-length paths of files
  3. page?: number; // the next page, if it exists
  4. }

Return type for /gaia/list-files/{address} with state as true:

  1. {
  2. entries: {
  3. name: string; // full-length path of the file
  4. contentLength: number; // the size of the file
  5. lastModifiedDate: number; // the unix timestamp of when the file was last modified
  6. }[];
  7. page?: number; // the next page, if it exists
  8. }

API Routes (/api/v1)

HTTP Method Url Token Type(s) Description
GET /api/v1/plugins None Returns the list of plugins and their IDs, as well as whatever info they want to include in the getInfo function.
GET /api/v1/drivers None, User Return the available drivers and, if logged in, the current connections like so: { current?: { id: string, name: string, longId: string, driver: string, rootOnly?: boolean }[], available: { id: string, name: string, longId: string, rootOnly?: boolean, multiUser?: boolean }[].
GET /api/v1/drivers/{id}/icon None Get the icon for a particular driver.
GET /api/v1/drivers/{id}/register User Register for a particular driver (use in a new window) — start the auth flow by putting the auth token the query parameter ?authorizationBearer={token}.

User Routes (/api/v1/user)

HTTP Method Url Token Type(s) Description
POST /api/v1/user/validate-token User Validates the token used to see if it is valid in regards to using the API with it.
POST /api/v1/user/register Partial* Registers the user to the Hestia Hub and auto-registers them for any drivers that are labeled as such. While being partial, it does require the claimed hub to be the Hestia Hub.
POST /api/v1/user/unregister User Unregisters the user from all drivers and then deletes the user from the database.
GET /api/v1/user/list-files?global={boolean}&hash={boolean} User Lists all files and metadata; if the global query is set to true, it will include every conneciton (not just the up-to-date ones), and if hash is specified to true, it will simply return a hash of the table to reduce traffic if nothing has changed. See return type below this table.
GET /api/v1/user/gdpr User Gets a JSON’d file of the user object stored in the database. Be careful, as this includes any and all tokens and keys generated for the driver connections, as well as the metadata indexes.

Return type of /api/v1/user/list-files

  1. {
  2. [path: string]: {
  3. contentType: string;
  4. size: number;
  5. hash: string;
  6. lastModified: string;
  7. connIds: string[]
  8. }
  9. }

Return type of /api/v1/user/list-files?global=true

  1. {
  2. [path: string]: {
  3. [connId: string] {
  4. contentType: string;
  5. size: number;
  6. hash: string;
  7. lastModified: string;
  8. }
  9. }
  10. }

Connection Routes (/api/v1/connections)

All routes within the /api/v1/connections group require an authed user token, and some of
them are very similar to the routes found under /gaia. One trait they do all share in
common, however, is that the very first parameter is the {id} field, which is the connection
ID itself — you can get this from the current sub-object in the /drivers return object
(when requested with a valid token).

HTTP Method Url Token Type(s) Description
POST /api/v1/connections/{id}/set-default User Set this connection to be the “default”, which means that all new store requests will be forwarded to this address if they are not already configured to go to a specific one.
GET /api/v1/connections/{id}/info User Get the info for a particular connection; returns { spaceUsed?: number, spaceAvailable?: number }.
DELETE /api/v1/connections/{id} User Delete the connection — NOTE: this may or may not clear the data in the backend, and all data within the connection is assumed to be lost.
POST /api/v1/connections/{id}/set-buckets User Set the bucket addresses for the connection; put a string[] in the body, in JSON format.
POST /api/v1/connections/{id}/store/{address}/{path} User See /gaia/store.
GET /api/v1/connections/{id}/read/{address}/{path} User See /gaia/read. This one still requires the user to be authenticated because connection IDs are not unique across all users.
DELETE /api/v1/connections/{id}/delete/{address}/{path} User See /gaia/delete.
POST /api/v1/connections/{id}/list-files/{bucket?} User Similar to the /gaia/list-files except that it doesn’t require an address (though it can be provided), as it can list all files and also includes metadata; returns { entries: { path: string, size: number, hash: string, lastModified: string }[], page?: number }

Back to top

DB Drivers

Database drivers can be selected by setting the db_driver_path entry in the config.json
to a path that can be used to import it; default-db-drivers is the prefix for using default
drivers, otherwise you can use a relative import (from the root directory where hestia.js is
run from — i.e when using npm start it will be the root repository directory), or an import
from node_modules as you would normally do from a script (i.e. npm i my-hestia-db-driver
and then, in the config.json, "path": would be "my-hestia-db-driver" and that’s it).

Default database drivers are the Sqlite3 driver (default-drivers/sqlite3), which is the one
Hestia will use if none is specified, and the RethinkDB driver default-drivers/rethinkdb.

DB Driver Configuration

View each database driver’s docs to see how it needs to be configured specifically.

For the default database drivers, see below:

  1. // path: `default-db-drivers/sqlite3`
  2. interface SQLite3Config {
  3. filename?: string; // (optional) the filename of the database to use
  4. // default is `hestia-db.sqlite`
  5. }
  6. // path: `default-db-drivers/sqlite3`
  7. interface RethinkDBConfig {
  8. host?: string; // (optional) the RethinkDB host (default: `127.0.0.1`)
  9. port?: number; // (optional) the RethinkDB port (default: `28015`)
  10. }

Back to top

Drivers

Drivers can be included by adding them in the config.json. The path field is used to
import them; default-drivers is the prefix for using default drivers, otherwise you can use
a relative import (from the root directory where hestia.js is run from — i.e when using
npm start it will be the root repository directory), or an import from node_modules as you
would normally do from a script (i.e. npm i my-hestia-driver and then, in the config.json,
"path": would be "my-hestia-driver" and that’s it).

Default drivers are the Disk driver (default-drivers/disk), which allows you to use the local
harddisk and the User-Dropbox driver (default-drivers/user-dropbox), which allows users to use
their own Dropbox account (and also fulfills one of the goals of this project).

Driver Configuration

Every driver can take the following options:

  1. export interface DriverConfig {
  2. path: string; // the path where the driver is located
  3. name?: string; // (optional) a different name to use then the standard one
  4. icon_url?: string; // (optional) a url to use for the icon instead of the default provided one
  5. whitelist?: string[]; // (optional) a whitelist of users who can use the driver
  6. auto_register?: boolean; // (optional) whether or not users should be auto-registered for the
  7. // driver when their account is registered
  8. root_only?: boolean; // (optional) whether or not the driver should only be allowed for
  9. // the root (identity) directory
  10. }

Tip: root_only is a fantastic option so that users can automatically register for your
Hestia Hub without going through the hoops to register their own storage backend first. For
instance, if you are limited on Disk space, but want to make it easy for users to get started
using your hub, simply use the disk driver (below), limit it to 5mb or less for each user,
and make it root only — Users will automatically be able to register to the Hestia Hub, but
will have to register another driver in order to actually use it for apps.

View each drivers’s docs to see how it needs to be configured.

For the default drivers, see below:

  1. // path: `default-drivers/disk`
  2. interface DiskDriverConfigType {
  3. storage_root_directory: string; // the directory to put the files (default: `./Hestia-storage`)
  4. // for storage caps (below), use a number of bytes or a string representation (i.e. "5mb")
  5. max_user_storage: string | number; // the storage cap for each user (default: unlimited)
  6. max_total_storage: string | number; // the overall storage cap for Hestia (default: unlimited)
  7. }
  8. // path: `default-drivers/user-dropbox`
  9. interface UserDropboxDriverConfig {
  10. client_id: string; // the client Id for the http dropbox API
  11. secret: string; // the client secret for the http dropbox API
  12. }
  13. // **NOTE**: When using this driver, make sure your Hestia Hub's max_blob_size is LESS THAN OR
  14. // EQUAL TO the Gaia Buckets you are connected to -- otherwise syncing will fail because the
  15. // remote gaia server will fail repeatedly. It WILL NOT separate files into parts for you.
  16. //
  17. // path: `default-drivers/gaia`
  18. interface GaiaDriverConfig {
  19. token?: string; // (optional - required for hub-backend) the authorization token
  20. // if not provided, this will be a per-user backend
  21. }

Back to top

Plugins

Plugins can be included by adding them in the config.json. The path field is used to
import them; default-plugins is the prefix for using default plugins, otherwise you can use
a relative import (from the root directory where hestia.js is run from — i.e when using
npm start it will be the root repository directory), or a import from node_modules as you
would normally do from a script (i.e. npm i my-hestia-plugin and then, in the config.json,
"path": would be "my-hestia-plugin" and that’s it).

Default plugins are the Dashboard plugin (default-plugins/dashboard), which adds the Hestia
frontend to the server (and must be the root_plugin in order to work properly), and the
Backup plugin (default-plugins/backup), which adds backup functionality to the Hestia Hub.
Currently there are a couple of other plugins in the directory but they are still under
construction.

There are no routes added by the Dashboard plugin (besides the root route, which is the
dashboard webpage itself), but the Backup plugin adds three, all of which require an authenticated user
(replace /plugins/backup with /plugins/{your chosen id} for your own instance):

HTTP Method Url Token Type(s) Description
POST /plugins/backup/request-backup User Requests a backup to be started.
GET /plugins/backup/status User Checks the status of the backup for the user; returns `{ status: ‘ready’ ‘working’ ‘not started’ }, whereready` means the download is ready.
GET /plugins/backup/download User Acts as a download link to the (large) backup zip file.

Plugin Configuration

Every plugin config requires a path field to specify where it is located at:

  1. interface PluginConfig {
  2. path: string; // the path where the plugin is located
  3. }

View each plugin’s docs to see how it needs to be configured specifically.

For the default plugins, see below:

  1. // path: `default-plugins/dashboard`
  2. interface DashboardPluginConfig {
  3. root_directory?: string; // defaults to `__dirname/../../frontend"`
  4. // (i.e. `./build/frontend` or `./build-prod/frontend`)
  5. }
  6. // path: `default-plugins/backup`
  7. interface BackupPluginConfig {
  8. temp_directory?: string; // default: `__dirname/backups`
  9. // (i.e. `./build/backend/default-plugins/backups`)
  10. }

Back to top

Building & Testing

  • npm run build - build debug, output to ./build
    • npm run build:backend- just build (debug) the backend
    • npm run build:frontend- just build (debug) the frontend
  • npm test - start the debug build (./build)

  • npm run build-prod - build prod, output to ./build-prod

  • npm start - start the prod build (./build-prod)

Back to top

License

Released under Mozilla Public License 2.0, with graphics under
CC BY-SA 4.0.

What the code license means is that you are free to fork and modify the project,
but any changes to this code should be given back via pull requests. Snippets
are generally ok to take, and your own plugins, drivers, and db drivers do not
need to be added to this repo — those are yours to license as you will. Of course,
I am not a lawyer, so go ask one if you need further help.

The graphics license means that if you take my graphics, you can derive or republish
them at will, as long as you give me attribution and share under the same license.

Back to top