Skip to main content

Connect

info

This guide is meant only as an introduction. For an exhaustive list of Hive methods, please visit the SDK documentation.

Setup

To add the Hive JS SDK dependency to your project, run the following command from your source folder:

npm i --save @elastosfoundation/hive-js-sdk

Connect to Service

import {
AppContext,
AppContextProvider,
DIDResolverAlreadySetupException,
Vault as VaultServices,
} from "@elastosfoundation/hive-js-sdk";

let vaultSubscriptionService: VaultSubscriptionService =
new VaultSubscriptionService(appContext, "[hiveNode address]");
let vaultInfo = await vaultSubscriptionService.subscribe();

The same mechanics is used by VaultService, which provides all services to interact with Hive vaults.

let vaultServices = new VaultServices(appContext, "[hiveNode address]");
let scriptingService = vaultServices.getScriptingService();
let filesService = vaultServices.getFilesService();
let databaseService = vaultServices.getDatabaseService();
tip

There is also an HTTP API available to interact with Hive nodes. Find it here.

An Example Authenticiation Setup

import {
Claims, DIDDocument, JWTHeader, JWTParserBuilder, VerifiableCredential, VerifiablePresentation, Logger,
} from '@elastosfoundation/did-js-sdk';
import { DID as ConnDID, DID } from '@elastosfoundation/elastos-connectivity-sdk-js';
import {
AppContext, AppContextProvider, DIDResolverAlreadySetupException, Vault as VaultServices,
} from '@elastosfoundation/hive-js-sdk';
import dayjs from 'dayjs';

/**
* This is a sample hive auth helper that makes everything automatic for apps using hive to
* authenticate on hive vaults.
*
* This authentication flow is for the connectivity SDK.
*/
export class BrowserConnectivitySDKHiveAuthHelper {
private didAccess: DID.DIDAccess;

private logger = new Logger('hiveauthhelper');

private dataDir = '/data/userDir/data/store/user'

constructor(didResolverUrl: string, protected appDid: string) {
try {
AppContext.setupResolver(didResolverUrl, this.dataDir);
} catch (e) {
if (e instanceof DIDResolverAlreadySetupException) {
// silent error, it's ok
} else {
this.logger.error('AppContext.setupResolver() exception:', e);
}
}

this.didAccess = new ConnDID.DIDAccess();
}

use(didAccess: ConnDID.DIDAccess): BrowserConnectivitySDKHiveAuthHelper {
this.didAccess = didAccess;

return this;
}

public async getAppContext(userDid: string, onAuthError?: (e: Error) => void): Promise<AppContext> {
const appInstanceDIDInfo = await this.didAccess.getOrCreateAppInstanceDID();

this.logger.log('hiveauthhelper', 'Getting app instance DID document');
const didDocument = await appInstanceDIDInfo.didStore.loadDid(appInstanceDIDInfo.did.toString());
this.logger.log('hiveauthhelper', 'Got app instance DID document. Now creating the Hive client', didDocument.toJSON());

const appContextProvider: AppContextProvider = {
getLocalDataDir: (): string => this.dataDir,
getAppInstanceDocument: (): Promise<DIDDocument> => Promise.resolve(didDocument),
getAuthorization: (authenticationChallengeJWtCode: string): Promise<string> => {
/**
* Called by the Hive plugin when a hive backend needs to authenticate the user and app.
* The returned data must be a verifiable presentation, signed by the app instance DID, and
* including a appid certification credential provided by the identity application.
*/
this.logger.log('hiveauthhelper', 'Hive client authentication challenge callback is being called with token:', authenticationChallengeJWtCode);
try {
return this.handleVaultAuthenticationChallenge(authenticationChallengeJWtCode);
} catch (e) {
this.logger.error('hiveauthhelper', 'Exception in authentication handler:', e);
if (onAuthError) onAuthError(e);
return null;
}
},
};

const appContext = await AppContext.build(appContextProvider, userDid, this.appDid);
return appContext;
}

public async getVaultServices(userDid: string, providerAddress: string = null, onAuthError?: (e: Error) => void): Promise<VaultServices> {
const appContext = await this.getAppContext(userDid, onAuthError);
if (!providerAddress) { providerAddress = await AppContext.getProviderAddressByUserDid(userDid); } // TODO: cache, don't resolve every time
return new VaultServices(appContext, providerAddress);
}

/*
- auth challenge: JWT (iss, nonce)
- hive sdk:
- verify jwt
- extract iss and nonce
- consumer dapp:
- generate app instance presentation including nonce=nonce, realm=iss, app id credential
- embed presentation as JWT and return to the hive auth handler
- server side:
- verify jwt (using local app instance did public key provided before)
- generate access token
*/
public handleVaultAuthenticationChallenge(jwtToken: string): Promise<string> {
return this.generateAuthPresentationJWT(jwtToken);
}

/**
* Generates a JWT token needed by hive vaults to authenticate users and app.
* That JWT contains a verifiable presentation that contains server challenge info, and the app id credential
* issued by the end user earlier.
*/
private generateAuthPresentationJWT(authChallengeJwttoken: string): Promise<string> {
this.logger.log('hiveauthhelper', 'Starting process to generate hive auth presentation JWT');

return new Promise(async (resolve, reject) => {
// Parse, but verify on chain that this JWT is valid first
try {
const claims: Claims = (await new JWTParserBuilder().setAllowedClockSkewSeconds(300).build().parse(authChallengeJwttoken)).getBody();
if (claims == null) throw new Error('Invalid jwt token as authorization.');

// The request JWT must contain iss and nonce fields
if (!claims.getIssuer() || !claims.get('nonce')) {
reject('The received authentication JWT token does not contain iss or nonce');
return;
}

// Generate a hive authentication presentation and put the credential + back-end info such as nonce inside
const nonce = claims.get('nonce') as string;
const realm = claims.getIssuer() as string;

this.logger.log('hiveauthhelper', 'Getting app instance DID');
const appInstanceDIDResult = await this.didAccess.getOrCreateAppInstanceDID();
const appInstanceDID = appInstanceDIDResult.did;

const appInstanceDIDInfo = await this.didAccess.getExistingAppInstanceDIDInfo();

this.logger.log('hiveauthhelper', 'Getting app identity credential');
let appIdCredential = await this.didAccess.getExistingAppIdentityCredential();

if (!appIdCredential) {
this.logger.log('hiveauthhelper', 'Empty app id credential. Trying to generate a new one');

appIdCredential = await this.generateAppIdCredential();
if (!appIdCredential) {
this.logger.warn('hiveauthhelper', 'Failed to generate a new App ID credential');
resolve(null);
return;
}
}

// Create the presentation that includes hive back end challenge (nonce) and the app id credential.
this.logger.log('hiveauthhelper', 'Creating DID presentation response for Hive authentication challenge');
const builder = await VerifiablePresentation.createFor(appInstanceDID.toString(), null, appInstanceDIDResult.didStore);
const presentation = await builder
.credentials(appIdCredential)
.realm(realm)
.nonce(nonce)
.seal(appInstanceDIDInfo.storePassword);

if (presentation) {
// Generate the hive back end authentication JWT
this.logger.log('hiveauthhelper', 'Opening DID store to create a JWT for presentation:', presentation.toJSON());
const didStore = await DID.DIDHelper.openDidStore(appInstanceDIDInfo.storeId);

this.logger.log('hiveauthhelper', 'Loading DID document');
try {
const didDocument = await didStore.loadDid(appInstanceDIDInfo.didString);
this.logger.log('hiveauthhelper', 'App instance DID document', didDocument.toJSON());
this.logger.log('hiveauthhelper', 'Creating JWT');

try {
// Create JWT token with presentation.
// let info = await new ConDID.DIDAccess().getExistingAppInstanceDIDInfo();
const jwtToken = await didDocument.jwtBuilder().addHeader(JWTHeader.TYPE, JWTHeader.JWT_TYPE)
.addHeader('version', '1.0')
.setSubject('DIDAuthResponse')
.setAudience(claims.getIssuer())
.setIssuedAt(dayjs().unix())
.setExpiration(dayjs().add(3, 'month').unix())
.setNotBefore(dayjs().unix())
.claimsWithJson('presentation', presentation.toString(true))
.sign(appInstanceDIDInfo.storePassword);

this.logger.log('hiveauthhelper', 'JWT created for presentation:', jwtToken);
resolve(jwtToken);
} catch (err) {
reject(err);
}
} catch (err) {
reject(err);
}
} else {
reject('No presentation generated');
}
} catch (e) {
// Verification error?
// Could not verify the received JWT as valid - reject the authentication request by returning a null token
reject(`The received authentication JWT token signature cannot be verified or failed to verify: ${e.message}. Is the hive back-end DID published? Are you on the right network?`);
}
});
}

private generateAppIdCredential(): Promise<VerifiableCredential> {
// eslint-disable-next-line no-async-promise-executor, @typescript-eslint/no-misused-promises
return new Promise(async (resolve) => {
const storedAppInstanceDID = await this.didAccess.getOrCreateAppInstanceDID();
if (!storedAppInstanceDID) {
resolve(null);
return;
}

// No such credential, so we have to create one. Send an intent to get that from the did app
this.logger.log('hiveauthhelper', 'Starting to generate a new App ID credential.');

// Ask the identity wallet (eg: Essentials) to generate an app id credential.
const didAccess = new ConnDID.DIDAccess();
const appIdCredential = await didAccess.generateAppIdCredential();

// Save this issued credential for later use.
await storedAppInstanceDID.didStore.storeCredential(appIdCredential);

resolve(appIdCredential);
});
}
}