Created
August 9, 2022 08:29
-
-
Save irzhywau/78eeab16f6ad9489c5c1135c83360c4b to your computer and use it in GitHub Desktop.
Sandboxing hive and did authentication flow in a backend context
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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