Skip to content
This repository has been archived by the owner on Apr 11, 2023. It is now read-only.

Commit

Permalink
fix: addressing comments for gnap integration and cleanup
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Biriukov <anton.biriukov@avast.com>
  • Loading branch information
birtony committed Jul 15, 2022
1 parent 8babd2a commit ae1d4a0
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 247 deletions.
56 changes: 55 additions & 1 deletion cmd/wallet-web/src/mixins/gnap/gnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

import axios from 'axios';
import { GNAPClient } from '@trustbloc/wallet-sdk';
import { getGnapKeyPair } from '@/mixins';

export async function gnapRequestAccess(
signer,
Expand Down Expand Up @@ -46,3 +46,57 @@ export async function gnapContinue(signer, gnapAuthServerURL, interactRef, acces
const resp = await gnapClient.continue(gnapContinueReq, accessToken);
return resp;
}

export async function getBootstrapData(agentOpts, hubAuthURL, dispatch, accessToken) {
// Prop Validation
if (!agentOpts) throw new Error(`Error getting bootstrap data: agentOpts can't be empty`);
if (!hubAuthURL) throw new Error(`Error getting bootstrap data: hubAuthURL can't be empty`);
if (!dispatch) throw new Error(`Error getting bootstrap data: dispatch is undefined`);
if (!accessToken) throw new Error(`Error getting bootstrap data: accessToken can't be empty`);

const newAgentOpts = {};

const newOpts = await axios
.get(hubAuthURL + '/gnap/bootstrap', {
headers: { Authorization: `GNAP ${accessToken}` },
})
.then((resp) => {
const { data } = resp;

// TODO to be removed after universal wallet migration
if (agentOpts.storageType === 'edv') {
Object.assign(newAgentOpts, { edvVaultID: data?.data?.userEDVVaultID });
// TODO this property is not returned from the bootstrap data - remove if not needed
Object.assign(newAgentOpts, { edvCapability: data?.data?.edvCapability });
}

// TODO to be removed after universal wallet migration
if (agentOpts.kmsType === 'webkms') {
Object.assign(newAgentOpts, { opsKeyStoreURL: data?.data?.opsKeyStoreURL });
Object.assign(newAgentOpts, { edvOpsKIDURL: data?.data?.edvOpsKIDURL });
Object.assign(newAgentOpts, { edvHMACKIDURL: data?.data?.edvHMACKIDURL });
}

// TODO to be removed after universal wallet migration
// TODO this property is not returned from the bootstrap data - remove if not needed
Object.assign(newAgentOpts, { authzKeyStoreURL: data?.data?.authzKeyStoreURL });
// TODO this property is not returned from the bootstrap data - remove if not needed
Object.assign(newAgentOpts, { opsKMSCapability: data?.data?.opsKMSCapability });

return { newAgentOpts, newProfileOpts: { bootstrap: data } };
})
.catch((e) => {
// http 403 denotes that user is not authenticated
if (e.response && e.response.status === 403) {
console.debug(e);
}
// http 400 denotes expired cookie at server - logout the user and prompt user to authenticate
// TODO: also logout user if access token has expired - initiate auth flow from the beginning
else if (e.response && e.response.status === 400) {
dispatch('agent/logout');
} else {
console.error('Error getting bootstrap data:', e);
}
});
return newOpts;
}
145 changes: 65 additions & 80 deletions cmd/wallet-web/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,57 +19,61 @@ router.beforeEach(async (to, from) => {
store.dispatch('agent/flushStore');
const locale = store.getters.getLocale;

if (to.path === '/gnap/redirect' && store.getters.getGnapReqAccessResp) {
await store.dispatch('initOpts');
const gnapResp = store.getters.getGnapReqAccessResp;
const params = new URL(document.location).searchParams;
const hash = params.get('hash');
const interactRef = params.get('interact_ref');
const data = gnapResp.clientNonceVal + '\n' + gnapResp.finish + '\n' + interactRef + '\n';
const shaHash = new SHA3(512);
shaHash.update(data);
let hashB64 = shaHash.digest({ format: 'base64' });
hashB64 = hashB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
if (hash === hashB64) {
const gnapAuthServerURL = store.getters.hubAuthURL;
const gnapKeyPair = await getGnapKeyPair();
const signer = { SignatureVal: gnapKeyPair };
const gnapContinueResp = await gnapContinue(
signer,
gnapAuthServerURL,
interactRef,
gnapResp.continue_access_token.value
);
const accessToken = gnapContinueResp.data.access_token[0].value;
const subjectId = gnapContinueResp.data.subject.sub_ids[0].id;
store.dispatch('updateSessionToken', accessToken);
store.dispatch('updateSubjectId', subjectId);
store.dispatch('updateUser', subjectId);

if (to.path === '/gnap/redirect') {
if (store.dispatch('loadUser') && store.getters.getGnapReqAccessResp) {
await store.dispatch('initOpts');
try {
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
console.error('error initializing agent in gnap flow:', e);
}
// continue access token should only be needed to complete initial auth flow
// thus, once successful call to gnapContinue is made, we can delete it
// later on, if we need to authenticate the same user again we just call requestAccess
// if needed, it will return us a new continue access token to complete user authentication
store.dispatch('updateGnapReqAccessResp', null);
const gnapResp = store.getters.getGnapReqAccessResp;
const params = new URL(document.location).searchParams;
const hash = params.get('hash');
const interactRef = params.get('interact_ref');
const data = gnapResp.clientNonceVal + '\n' + gnapResp.finish + '\n' + interactRef + '\n';
const shaHash = new SHA3(512);
shaHash.update(data);
let hashB64 = shaHash.digest({ format: 'base64' });
hashB64 = hashB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
if (hash === hashB64) {
const gnapAuthServerURL = store.getters.hubAuthURL;
const gnapKeyPair = await getGnapKeyPair();
const signer = { SignatureVal: gnapKeyPair };

const gnapContinueResp = await gnapContinue(
signer,
gnapAuthServerURL,
interactRef,
gnapResp.continue_access_token.value
);

const accessToken = gnapContinueResp.data.access_token[0].value;
const subjectId = gnapContinueResp.data.subject.sub_ids[0].id;
store.dispatch('updateAccessToken', accessToken);
store.dispatch('updateSubjectId', subjectId);
store.dispatch('updateUser', subjectId);

await store.dispatch('initOpts', { accessToken });
try {
await store.dispatch('agent/init', { accessToken, subjectId, newUser: true });
} catch (e) {
console.error('error initializing agent in gnap flow:', e);
}
// continue access token should only be needed to complete initial auth flow
// thus, once successful call to gnapContinue is made, we can delete it
// later on, if we need to authenticate the same user again we just call requestAccess
// if needed, it will return us a new continue access token to complete user authentication
store.dispatch('updateGnapReqAccessResp', null);

return { name: 'DashboardLayout' };
return { name: 'DashboardLayout' };
}
console.error('error authenticating user: invalid hash received');
return false;
}
console.error('error authenticating user: invalid hash received');
return false;
} else if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters.getCurrentUser) {
if (!store.getters['agent/isInitialized']) {
const accessToken = store.getters.getGnapSessionToken;
const accessToken = store.getters.getGnapAccessToken;
const subjectId = store.getters.getGnapSubjectId;

// initialize agent opts
await store.dispatch('initOpts');
await store.dispatch('initOpts', { accessToken });

try {
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
Expand All @@ -80,14 +84,12 @@ router.beforeEach(async (to, from) => {
return;
} else if (store.dispatch('loadUser') && store.getters.getCurrentUser) {
if (!store.getters['agent/isInitialized']) {
// try loading gnap access token and subject id from store
const accessToken = store.getters.getGnapSessionToken;
const accessToken = store.getters.getGnapAccessToken;
const subjectId = store.getters.getGnapSubjectId;

// initialize agent opts
await store.dispatch('initOpts');
await store.dispatch('initOpts', { accessToken });

try {
console.log('initializing agent for existing user');
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
console.error('error initializing agent for existing user:', e);
Expand All @@ -96,20 +98,14 @@ router.beforeEach(async (to, from) => {
}
return;
} else {
// user in not authenticated, initiate auth flow

// User is not authenticated, request access from auth server
await store.dispatch('initOpts');

// TODO add logic to redirect user to specific page in auth (sign-in/sign-up)
const { signin, disableCHAPI } = to.meta;

const gnapAccessTokens = await store.getters['getGnapAccessTokenConfig'];
const gnapAuthServerURL = store.getters.hubAuthURL;
const walletWebUrl = store.getters.walletWebUrl;
const gnapKeyPair = await getGnapKeyPair();

const signer = { SignatureVal: gnapKeyPair };

const clientNonceVal = (Math.random() + 1).toString(36).substring(7);

const resp = await gnapRequestAccess(
Expand All @@ -120,18 +116,16 @@ router.beforeEach(async (to, from) => {
clientNonceVal
);

// If user have already signed in then just redirect to requested page
// If user is not new, just make sure agent is initialized and proceed to the requested page
if (resp.data.access_token) {
const accessToken = resp.data.access_token[0].value;
const subjectId = resp.data.subject.sub_ids[0].id;
store.dispatch('updateSessionToken', accessToken);
store.dispatch('updateAccessToken', accessToken);
store.dispatch('updateSubjectId', subjectId);
store.dispatch('updateUser', subjectId);
if (!store.getters['agent/isInitialized']) {
// initialize agent opts
await store.dispatch('initOpts');
await store.dispatch('initOpts', { accessToken });
try {
console.log('initializing agent for existing user');
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
console.error('error initializing agent for new user:', e);
Expand All @@ -140,56 +134,47 @@ router.beforeEach(async (to, from) => {
}
return;
}

// If user is new, continue with the sign up flow and do not show the requested page at this step
const respMetaData = {
uri: resp.data.continue.uri,
continue_access_token: resp.data.continue.access_token,
finish: resp.data.interact.finish,
clientNonceVal: clientNonceVal,
};
store.dispatch('updateGnapReqAccessResp', respMetaData);
console.log('redirecting to interact url', resp.data.interact.redirect);

window.location.href = resp.data.interact.redirect;
return false;
}
} else if (to.matched.some((record) => record.meta.blockNoAuth)) {
if (store.getters.getCurrentUser) {
console.log(`store.getters['agent/isInitialized']`, store.getters['agent/isInitialized']);
if (!store.getters['agent/isInitialized']) {
console.log('agent not initialized');
// try loading gnap access token and subject id from store
const accessToken = store.getters.getGnapSessionToken;
const accessToken = store.getters.getGnapAccessToken;
const subjectId = store.getters.getGnapSubjectId;
console.log('accessToken', accessToken);
console.log('subjectId', subjectId);
// initialize agent opts
await store.dispatch('initOpts');

await store.dispatch('initOpts', { accessToken });

try {
console.log('initializing agent for existing user');
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
console.error('error initializing agent for existing user:', e);
}
console.log('initialized agent for user, redirecting to', to);
return;
}
return;
} else if (store.dispatch('loadUser') && store.getters.getCurrentUser) {
if (!store.getters['agent/isInitialized']) {
console.log('agent not initialized');
// try loading gnap access token and subject id from store
const accessToken = store.getters.getGnapSessionToken;
const accessToken = store.getters.getGnapAccessToken;
const subjectId = store.getters.getGnapSubjectId;
console.log('accessToken', accessToken);
console.log('subjectId', subjectId);
// initialize agent opts
await store.dispatch('initOpts');

await store.dispatch('initOpts', { accessToken });

try {
console.log('initializing agent for existing user');
await store.dispatch('agent/init', { accessToken, subjectId });
} catch (e) {
console.error('error initializing agent for existing user:', e);
}
console.log('initialized agent for user, redirecting to', to);
return;
}
return;
Expand Down
29 changes: 17 additions & 12 deletions cmd/wallet-web/src/store/modules/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,18 @@ let defaultAgentStartupOpts = {

export default {
actions: {
async initOpts({ commit, getters, dispatch }, location = window.location.origin) {
async initOpts(
{ commit, getters, dispatch },
{ location = window.location.origin, accessToken } = {}
) {
let agentOpts = {};

let profileOpts = getters.getProfileOpts;
const profileOpts = getters.getProfileOpts;
if (accessToken) {
Object.assign(profileOpts, {
userConfig: { accessToken },
});
}

let readCredentialManifests;

Expand All @@ -73,7 +81,6 @@ export default {
await axios
.get(agentOptsLocation(location))
.then((resp) => {
console.log('successfully fetched agent start up options: ', resp.data);
agentOpts = resp.data;
})
.catch((err) => {
Expand All @@ -88,15 +95,13 @@ export default {
? agentOpts['media-type-profiles'].split(',')
: ['didcomm/aip2;env=rfc587', 'didcomm/v2'];

console.log('agent opts processed', JSON.stringify(agentOpts, null, 2));

readCredentialManifests = readManifests(agentOpts['staticAssetsUrl']);

Object.assign(profileOpts, {
config: {
storageType: agentOpts.storageType,
kmsType: agentOpts.kmsType,
localKMSScret: agentOpts.localKMSPassphrase,
localKMSPassphrase: agentOpts.localKMSPassphrase,
},
});
} else {
Expand All @@ -108,7 +113,7 @@ export default {

dispatch('loadUser');
if (getters.getCurrentUser) {
let { profile } = getters.getCurrentUser;
const { profile } = getters.getCurrentUser;
user = profile ? profile.user : user;
}

Expand All @@ -117,17 +122,17 @@ export default {
agentOpts.hubAuthURL = 'https://localhost:8044';

Object.assign(profileOpts, {
config: {
storageType: defaultAgentStartupOpts.storageType,
kmsType: defaultAgentStartupOpts.kmsType,
localKMSScret: defaultAgentStartupOpts.localKMSPassphrase,
},
bootstrap: {
data: {
user,
tokenExpiry: '10',
},
},
config: {
storageType: defaultAgentStartupOpts.storageType,
kmsType: defaultAgentStartupOpts.kmsType,
localKMSPassphrase: defaultAgentStartupOpts.localKMSPassphrase,
},
});

readCredentialManifests = readManifests();
Expand Down
Loading

0 comments on commit ae1d4a0

Please sign in to comment.