This server acts as middleware between web applications built using the Verida Datastore and the underlying databases storing user data.
Key features:
- Ensuring all API requests come from verified Verida network users (via user signed messages)
- Managing database users, linking them to valid DID's
- Managing permissions for individual databases
- Adding a second layer of security by managing per-database ACL validation rules
- Providing applications with user's database connection strings
This is the login flow:
- The Verida Account makes a request to the storage node API to obtain an auth JWT to be signed (
/auth/generateAuthJwt). This prevents replay attacks. - The Verida Account signs a consent message using their private key. This consent message proves the user wants to unlock a specific application context
- The Verida Account submits the signed authorization request (
/auth/authenticate). Assuming the signed AuthJWT is valid, the storage node returns a refresh token and an access token - The Verida Account can then use the access token to either; 1) make storage node requests (ie: create database) or 2) directly access CouchDB as an authenticated user (using
Bearertoken auth) - When the access token expires, the Verida Account can use the refresh token to request a new access token (
/auth/connect) - If a refresh token is close to expiry, the Verida Account can use the active refresh token to obtain a new refresh token (
/auth/regenerateRefreshToken)
When a Verida Account authenticates, it can designate an authenticate requst to be linked to a particular device by specifying the deviceId in the request.
This allows a specific device to be linked to a refresh token. A call to /auth/invalidateDeviceId can be used to invalidate any refresh tokens linked to the specified deviceId. This allows the Verida Vault to remotely log out an application that previously logged in.
Note: This only invalidates the refresh token. The access token will remain valid until it expires. It's for this reason that access tokens are configured to have a short expiry (5 minutes by default). CouchDB does not support manually invalidating access tokens, so we have to take this timeout approach to invalidation.
yarn install
yarn build
yarn serveA sample.env is included. Copy this to .env and update the configuration:
VERIDA_NETWORK: Verida network to use. See https://developers.verida.network/docs/infrastructure/networks for valid networks. (ie:banksia)DB_PROTOCOL: Protocol to use when connecting to CouchDB (httporhttps).DB_USER: Username of CouchDB Admin (has access to create users and databases).DB_PASS: Password of CouchDB Admin.DB_HOST: Hostname of CouchDB Admin.DB_PORT: Port of CouchDB server (5984).DB_REPLICATION_USER: Replication username (for replicating data to other nodes). MUST be set to something random. MUST not change once the node is operational.DB_REPLICATION_PASS: Replication password (for replicating data to other nodes). MUST be set to something random. MUST not change once the node is operational.DB_REJECT_UNAUTHORIZED_SSL: Boolean indicating if unauthorized SSL certificates should be rejected (trueorfalse). Defaults tofalsefor development testing. Must betruefor production environments otherwise SSL certificates won't be verified.DB_PUBLIC_USER: Alphanumeric string for a public database user. These credentials can be requested by anyone and provide access to all databases where the permissions have been set topublic.DB_PUBLIC_PASS: Alphanumeric string for a public database password.DB_REFRESH_TOKENS: Internal CouchDB database that stores refresh tokens (ie:verida_refresh_tokens)ACCESS_JWT_SIGN_PK: The access token private key. The base64 version of this must be specified in the CouchDB configuration underjwt_keys/hmac:_defaultREFRESH_JWT_SIGN_PK: The refresh token private keyDB_PROTOCOL_INTERNAL: Internal database protocol (httporhttps).DB_HOST_INTERNAL: Internal database hostname (ie:localhost)DB_PORT_INTERNAL: Internal database port (ie:5984)DB_PROTOCOL_INTERNAL: External database protocol (httporhttps).DB_HOST_INTERNAL: External database hostname (ie:mydomain.com)DB_PORT_INTERNAL: External database port (ie:5984)ENDPOINT_URI: The public URI of this storage node server (Will match what is stored in DID Documents). Note: Must include the port and have NO trailing slash. (ie:"http://localhost:5000")VDA_PRIVATE_KEY: Verida network private key as a hex string. Including leading 0x. This is used to sign server responses and in the future, prove VDA tokens are staked for this node. (ie:0xaaaabbbb...)MAX_USERS: Maximum number of users supported by this node (ie:10000)PORT: Port this server runs on (ie:5151)
- On a powershell execute the following ( replica of
.env)
$env:DID_CACHE_DURATION=3600
$env:DB_PROTOCOL="http"
$env:DB_USER="admin"
$env:DB_PASS="admin"
$env:DB_HOST="localhost"
$env:DB_PORT=5984
$env:DB_REJECT_UNAUTHORIZED_SSL=false
$env:DB_PUBLIC_USER="784c2n780c9cn0789"
$env:DB_PUBLIC_PASS="784c2n780c9cn0789"- CORS must be enabled so that database requests can come from any domain name
- A valid user must be enforced for security reasons
[couchdb]
single_node=true
[chttpd]
authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler}
enable_cors = true
[chttpd_auth]
require_valid_user = true
[jwt_auth]
required_claims = exp
[jwt_keys]
hmac:_default = <base64 secret key>
[cors]
origins = *
credentials = true
methods = GET, PUT, POST, HEAD, DELETE
headers = accept, authorization, content-type, origin, referer, x-csrf-token
The hmac:_default key is a base64 encoded representation of the access token JWT private key
Note: A secret key (string) suitable for jwt_keys can be base64 encoded with the following:
const secretKey = 'secretKey'
const encodedKey = Buffer.from(secretKey).toString('base64')You can generate the base64 encoced string with:
node src/base64key.js <yourkey>This can be tested via curl:
curl -H "Host: localhost:5984" \
-H "accept: application/json, text/plain, */*" \
-H "authorization: Bearer <bearer_token>" \
"http://localhost:5984/_session"Where:
bearer_token- A bearer token generated via thetest/jwtunit testlocalhost- Replace this with the hostname of the server being tested
You can spin up storage node API on your machine with Docker:
docker run --init --env-file=.env verida/storage-node:latestNote that this uses the experimental buildx command to build both AMD64 (Intel/AMD servers) and ARM64 (Mac) images.
- Login (details in BitWarden)
docker buildx build --platform linux/amd64,linux/arm64 --push -t verida/storage-node:latest .
Run tests with yarn run tests
You will need to update /test/config.js with at least the VDA_PRIVATE_KEY, DID_CLIENT_CONFIG.web3Config.privateKey
and possibily ENDPOINTS and SERVER_URL.
Common issues when running tests:
Bad key: The key in CouchDB configuration forjwt_keys/hmac:_defaultis not a valid Base64 encoded keyHMAC error: The key in CouchDB configuration forjwt_keys/hmac:_defaultdoes not matchACCESS_JWT_SIGN_PKin.env
To test a deployed node, do the following
- Modify
test/config.jswith the correct endpoint URLs - Run
yarn test test/server.js
When deploying behind a HTTP load balancer it is important to make sure it doesn't close the connection during a long-poll call. The server will keep pushing data through this connection, but some load balancers (eg the Google Cloud loadbalancer) will close anyway. In these case configure the load balancer with a long timeout (we recommend 3600 seconds).
The Verida team is currently operating nodes with the following hardware requirements:
- 16GB RAM
- 4vCPUs
- 1TB storage (20,000 storage slots)
These cost approx $US250 / month on AWS / GCP / Azure.
Ideally nodes will have burstable CPU usage as the number of connected users is CPU bound and can fluxuate depending on active demand from end users.