Skip to content

Instantly share code, notes, and snippets.

@irzhywau
Created August 9, 2022 08:29
Show Gist options
  • Save irzhywau/78eeab16f6ad9489c5c1135c83360c4b to your computer and use it in GitHub Desktop.
Save irzhywau/78eeab16f6ad9489c5c1135c83360c4b to your computer and use it in GitHub Desktop.
Sandboxing hive and did authentication flow in a backend context
const { RootIdentity, DIDStore, Logger } = require('@elastosfoundation/did-js-sdk')
class DIDEntity {
constructor(name, mnemonic, passphrase, storepass, did) {
this.name = name;
this.mnemonic = mnemonic;
this.passphrase = passphrase;
this.storepass = storepass;
this.didString = did;
this.logger = new Logger('DIDItentity');
}
async initDid(needResolve, storeRoot) {
const path = storeRoot + "/" + (this.didString);
this.didStore = await DIDStore.open(path);
const rootIdentity = await this.getRootIdentity(this.mnemonic);
await this.initDidByRootIdentity(rootIdentity, needResolve);
}
async getRootIdentity(mnemonic) {
const id = RootIdentity.getIdFromMnemonic(mnemonic, this.phrasepass);
return this.didStore.containsRootIdentity(id)
? await this.didStore.loadRootIdentity(id)
: RootIdentity.createFromMnemonic(mnemonic, this.phrasepass, this.didStore, this.storepass);
}
async initDidByRootIdentity(rootIdentity, needResolve) {
const dids = await this.didStore.listDids();
if (dids.length > 0) {
this.did = dids[0];
} else {
if (needResolve) {
const synced = await rootIdentity.synchronizeIndex(0);
this.logger.info(`${this.name}: identity synchronized result: ${synced}`);
this.did = rootIdentity.getDid(0);
} else {
const doc = await rootIdentity.newDid(this.storepass);
this.did = doc.getSubject();
this.logger.info(`[${this.name}] My new DID created: ${this.did.toString()}`);
}
}
if (!this.did) {
this.logger.error("Can not get the did from the local store.");
}
}
getDIDStore() {
return this.didStore;
}
getDid() {
return this.did;
}
async getDocument() {
return await this.didStore.loadDid(this.did);
}
getName() {
return this.name;
}
getStorePassword() {
return this.storepass;
}
toString() {
return this.did.toString();
}
}
class AppDID extends DIDEntity {
constructor(name, mnemonic, passphrase, storepass, did) {
super(name, mnemonic, passphrase, storepass, did);
}
static async create(name, mnemonic, phrasepass, storepass, storeRoot, did) {
let newInstance = new AppDID(name, mnemonic, phrasepass, storepass, did);
await newInstance.initDid(mnemonic, storeRoot);
return newInstance;
}
}
class UserDID extends DIDEntity {
constructor(name, mnemonic, passphrase, storepass, did) {
super(name, mnemonic, passphrase, storepass, did);
}
static async create(name, mnemonic, phrasepass, storepass, storeRoot, did) {
let newInstance = new UserDID(name, mnemonic, phrasepass, storepass, did);
await newInstance.initDid(mnemonic, storeRoot);
return newInstance;
}
}
module.exports = {
AppDID,
UserDID,
DIDEntity
}
const { Logger, VerifiablePresentation, JWTHeader, JWTParserBuilder, Issuer, DIDURL } = require('@elastosfoundation/did-js-sdk')
const { AppContext, Vault, HiveException } = require('@elastosfoundation/hive-js-sdk')
const didConfig = require("../../config/elastos.did");
const hiveConfig = require("../../config/elastos.hive");
const { AppDID, UserDID } = require("./entities");
class ElacityHiveContext {
constructor(subFolder = null) {
this.localDataDir = `${didConfig.FOLDER.METADATA}/${(didConfig.APP.DID).split(':').pop()}` + (subFolder ? `/${subFolder}` : '');
this.logger = new Logger('elacity.hive.context');
this.inited = false;
}
/**
* Initialize the hive context
*/
async init() {
if (!this.inited) {
AppContext.setupResolver('mainnet', this.localDataDir);
//DIDBackend.initialize(new DefaultDIDAdapter('mainnet'));
// setup user DID;
if (!this.userDID) {
this.userDID = await UserDID.create(
'Vault Owner',
hiveConfig.HIVE_USER.MNEMONIC,
'', //hiveConfig.HIVE_USER.PWD,
hiveConfig.HIVE_USER.PWD,
this.localDataDir,
hiveConfig.HIVE_USER.DID,
);
}
this.inited = true;
}
}
/**
* Setup a custom provider
*
* @returns
*/
async getAppContextProvider() {
// setup the custom provder
if (!this.appDID) {
const appDID = await AppDID.create(
'My App',
didConfig.APP.MNEMONIC,
'',
didConfig.APP.PWD,
this.localDataDir,
didConfig.APP.DID,
);
this.appDID = appDID;
}
return {
getLocalDataDir: () => this.localDataDir,
getAppInstanceDocument: async () => await this.appDID.getDocument(),
getAuthorization: (authenticationChallengeJWtCode) => {
this.logger.log('Auth', 'Hive client authentication challenge callback is being called with token:', authenticationChallengeJWtCode);
try {
return this.handleVaultAuthenticationChallenge(authenticationChallengeJWtCode);
} catch (e) {
this.logger.error('Auth.Error', 'Exception in authentication handler:', e);
return Promise.reject(e);
}
},
}
}
/**
* Retrieve the app context
*
* @param {*} userDID
* @returns
*/
async getAppContext(userDID) {
const appContext = await AppContext.build(await this.getAppContextProvider(), userDID, this.appDID.didString);
return appContext;
}
/**
* Get the issuer
*
* @returns
*/
async getIssuer() {
try {
const userDocument = await this.userDID.getDIDStore().loadDid(this.userDID.didString);
this.logger.debug("userDocument: {}", userDocument.toString(true));
return new Issuer(userDocument);
} catch (e) {
this.logger.error("error new Issuer {}", e);
return null;
}
}
/**
* Create a credential
*
* @see https://github.com/elastos/Elastos.Essentials.InAppBrowserConnector/blob/072c087a5a4a62c25a4e56c8cf8e9deb5df14b02/src/did.ts#L95
* @returns
*/
async createCredential() {
const issuer = await this.getIssuer();
if (!issuer) {
return Promise.reject(new Error('No issuer created'));
}
const subject = {
appDid: this.appDID.didString,
appInstanceDid: this.appDID.didString,
};
const cb = issuer.issueFor(this.appDID.getDid());
const vc = await cb.id(DIDURL.from('#app-id-credential', this.appDID.didString))
.type("AppIdCredential")
.properties(subject)
.seal(
this.userDID.getStorePassword()
);
this.logger.debug("VerifiableCredential IsValid: {}", await vc.isValid());
return vc;
}
/**
* JWT challenge handler
*
* @param {*} jwtChallenge
* @returns
*/
async handleVaultAuthenticationChallenge(jwtChallenge) {
return new Promise(async (resolve, reject) => {
try {
const builder = new JWTParserBuilder().setAllowedClockSkewSeconds(300).build();
const parser = await builder.parse(jwtChallenge);
const claims = parser.getBody();
if (claims == null) {
return reject(
new HiveException("Invalid jwt token as authorization.")
)
}
const vpb = await VerifiablePresentation.createFor(
this.appDID.getDid(),
null,
this.appDID.getDIDStore()
);
const vc = await this.createCredential();
const vp = await vpb.credentials(vc)
.realm(claims.getIssuer())
.nonce(claims.get('nonce'))
.seal(this.appDID.getStorePassword());
if (!vp) {
return reject(new Error('No presentation generated'))
}
const didDocument = await this.appDID.getDocument();
const jwtToken = await didDocument.jwtBuilder().addHeader(JWTHeader.TYPE, JWTHeader.JWT_TYPE)
.addHeader('version', '1.0')
.setSubject('DIDAuthResponse')
.setAudience(claims.getIssuer())
.claimsWithJson('presentation', vp.toString(true))
.sign(this.appDID.getStorePassword());
resolve(jwtToken);
} catch (e) {
reject(e);
}
});
}
async getVaultServices(userDid) {
const appContext = await this.getAppContext(userDid);
return new Vault(appContext, await appContext.getProviderAddress());
}
}
module.exports = ElacityHiveContext;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment