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

Commit

Permalink
feat: GNAP integration
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Biriukov <anton.biriukov@avast.com>
Signed-off-by: talwinder50 <talwinder.kaur@avast.com>
Signed-off-by: Andrii Holovko <andriy.holovko@gmail.com>
Signed-off-by: Filip Burlacu filip.burlacu@securekey.com
Signed-off-by: Rolson Quadras <rolson.quadras@securekey.com>
  • Loading branch information
rolsonquadras committed Jul 12, 2022
1 parent 695c043 commit a4f84bd
Show file tree
Hide file tree
Showing 21 changed files with 512 additions and 820 deletions.
30 changes: 7 additions & 23 deletions cmd/wallet-web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,26 @@
-->

<script setup>
import { computed, onBeforeMount, onMounted, onUnmounted, ref } from 'vue';
import { onBeforeMount, onMounted, onUnmounted, ref } from 'vue';
import { useStore } from 'vuex';
import getStartingLocale from '@/mixins/i18n/getStartingLocale.js';
import { updateI18nLocale } from '@/plugins/i18n';
import SpinnerIcon from '@/components/icons/SpinnerIcon.vue';

const store = useStore();
// Local Variables
const startingLocale = getStartingLocale(); // Get starting locale, set it in i18n and in the store
const loaded = ref(false);
const isAgentInitialized = computed(() => store.getters['agent/isInitialized']);
const initAgent = () => store.dispatch('agent/init');
const initOpts = () => store.dispatch('initOpts');
const loadUser = () => store.dispatch('loadUser');

// Get starting locale, set it in i18n and in the store
const startingLocale = getStartingLocale();
// Hooks
const store = useStore();

store.dispatch('setLocale', startingLocale);

onBeforeMount(async () => {
await updateI18nLocale(startingLocale.id);
});

onMounted(async () => {
try {
// load opts
await initOpts();

// load user if already logged in
loadUser();

// load agent if user already logged in and agent not initialized (scenario: page refresh)
if (store.getters.getCurrentUser && !isAgentInitialized.value) {
await initAgent();
}
} catch (e) {
console.log('Could not initialize Vue App:', e);
}
onMounted(() => {
loaded.value = true;
});

Expand Down
2 changes: 1 addition & 1 deletion cmd/wallet-web/src/components/Signout/SignoutComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async function signout() {

await Promise.all(actions);

router.push({ name: 'signin' });
router.push({ path: '/', query: { signedOut: true } });
}

const chapi = ref(
Expand Down
1 change: 1 addition & 0 deletions cmd/wallet-web/src/mixins/gnap/gnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SPDX-License-Identifier: Apache-2.0
*/

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

export async function gnapRequestAccess(
signer,
Expand Down
2 changes: 1 addition & 1 deletion cmd/wallet-web/src/pages/VaultsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ onMounted(async () => {
<template>
<div>
<WelcomeBannerComponent
v-if="!currentUser.preference.skipWelcomeMsg && !skippedLocally && !loading"
v-if="!currentUser?.preference?.skipWelcomeMsg && !skippedLocally && !loading"
id="welcome-banner-close-button"
class="md:mb-10"
@click="updateUserPreferences"
Expand Down
50 changes: 50 additions & 0 deletions cmd/wallet-web/src/router/TheRoot.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
<!--
* Copyright SecureKey Technologies Inc. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
-->

<script setup>
import { computed, inject, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
import { CHAPIHandler } from '@/mixins';
import useBreakpoints from '@/plugins/breakpoints.js';

// Local Variables
const disableCHAPI = ref(false);
const loaded = ref(false);

// Hooks
const route = useRoute();
const store = useStore();
const breakpoints = useBreakpoints();
const polyfill = inject('polyfill');
const webCredentialHandler = inject('webCredentialHandler');

// Store Getters
const currentUser = computed(() => store.getters['getCurrentUser']);
const agentOpts = computed(() => store.getters['getAgentOpts']);

// Store Actions
const activateCHAPI = () => store.dispatch('activateCHAPI');

onMounted(async () => {
// if intended target doesn't require CHAPI.
disableCHAPI.value = route.params.disableCHAPI || false;
try {
if (!breakpoints.xs && !breakpoints.sm && !disableCHAPI.value) {
const chapi = new CHAPIHandler(
polyfill,
webCredentialHandler,
agentOpts.value.credentialMediatorURL
);
await chapi.install(currentUser.value.username);
activateCHAPI();
}
} catch (e) {
console.log('Could not initialize Vue App:', e);
}
loaded.value = true;
});
</script>
<template>
<router-view />
</template>
212 changes: 144 additions & 68 deletions cmd/wallet-web/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,17 @@ const router = createRouter({
routes,
});

router.beforeEach(async (to, from, next) => {
router.beforeEach(async (to, from) => {
store.dispatch('agent/flushStore');
if (to.path === '/gnap') {
await store.dispatch('initOpts');
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(
signer,
gnapAccessTokens,
gnapAuthServerURL,
walletWebUrl,
clientNonceVal
);
// If user have already signed in then just redirect
if (resp.data.access_token || false) {
store.dispatch('updateSessionToken', resp.data.access_token);
window.opener.location.href = walletWebUrl;
window.top.close();
next();
return;
}
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);
window.location.href = resp.data.interact.redirect;
}
if (to.path === '/gnap/redirect') {
const locale = store.getters.getLocale;

if (to.path === '/gnap/redirect' && store.getters.getGnapReqAccessResp) {
await store.dispatch('initOpts');
const gnapResp = store.getters.getGnapReqAccessResp;
const walletWebUrl = store.getters.walletWebUrl;
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' });
Expand All @@ -74,56 +42,166 @@ router.beforeEach(async (to, from, next) => {
);
const accessToken = gnapContinueResp.data.access_token[0].value;
const subjectId = gnapContinueResp.data.subject.sub_ids[0].id;
store.dispatch('updateSessionToken', gnapContinueResp.data.access_token);
store.dispatch('updateSessionToken', accessToken);
store.dispatch('updateSubjectId', subjectId);
store.dispatch('updateUser', subjectId);

await store.dispatch('initOpts');
try {
store.dispatch('agent/init', { accessToken, subjectId });
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);

return { name: 'DashboardLayout' };
}
window.opener.location.href = walletWebUrl;
window.top.close();
next();
return;
}
const locale = store.getters.getLocale;
if (to.matched.some((record) => record.meta.requiresAuth)) {
console.error('error authenticating user: invalid hash received');
return false;
} else if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters.getCurrentUser) {
next();
if (!store.getters['agent/isInitialized']) {
const accessToken = store.getters.getGnapSessionToken;
const subjectId = store.getters.getGnapSubjectId;

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

// initialize agent opts
await store.dispatch('initOpts');
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);
}
return;
}
return;
} else {
// user in not authenticated, initiate auth flow

await store.dispatch('initOpts');

// TODO add logic to redirect user to specific page in auth (sign-in/sign-up)
const { signin, disableCHAPI } = to.meta;
router.replace({
name: signin ? 'signin' : 'signup',
params: {
...router.currentRoute._value.params,
locale: locale.base,
redirect: to.name,
disableCHAPI,
},
query: to.query,
});
next();
return;

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(
signer,
gnapAccessTokens,
gnapAuthServerURL,
walletWebUrl,
clientNonceVal
);

// If user have already signed in then just redirect to 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('updateSubjectId', subjectId);
store.dispatch('updateUser', subjectId);
if (!store.getters['agent/isInitialized']) {
// initialize agent opts
await store.dispatch('initOpts');
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);
}
return;
}
return;
}
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.dispatch('loadUser') && store.getters.getCurrentUser) {
next();
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 subjectId = store.getters.getGnapSubjectId;
console.log('accessToken', accessToken);
console.log('subjectId', subjectId);
// initialize agent opts
await store.dispatch('initOpts');
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 subjectId = store.getters.getGnapSubjectId;
console.log('accessToken', accessToken);
console.log('subjectId', subjectId);
// initialize agent opts
await store.dispatch('initOpts');
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 {
router.replace({
return {
name: 'block-no-auth',
params: {
...router.currentRoute._value.params,
locale: locale.base,
redirect: { name: 'signup' },
redirect: '/',
},
});
next();
return;
};
}
} else {
if (to.params.locale && to.params.locale !== locale.id) {
Expand All @@ -136,10 +214,8 @@ router.beforeEach(async (to, from, next) => {
},
query: to.query,
});
next();
return;
} else {
next();
return;
}
}
Expand Down
Loading

0 comments on commit a4f84bd

Please sign in to comment.