Commit 6695392b authored by Hidde-Jan Jongsma's avatar Hidde-Jan Jongsma

Implement irma verify request

parent 8fa11fd6
<template>
<b-card
:title="wallet.title"
class="wallet-card text-center"
:title="connector.title"
class="connector-card text-center"
:class="{
'wallet-enabled': wallet.enabled,
'wallet-disabled': !wallet.enabled,
'text-muted': !wallet.enabled,
'connector-enabled': connector.enabled,
'connector-disabled': !connector.enabled,
'text-muted': !connector.enabled,
}"
body-class="d-flex flex-column"
@click="wallet.enabled && $emit('choice')"
@click="connector.enabled && $emit('choice')"
>
<div class="flex-grow-1 d-flex align-items-center">
<div class="flex-grow-1 p-5">
<b-img class="wallet-image my-1" :src="wallet.imageUrl" fluid-grow />
<div class="enabled-notice text-muted" :aria-hidden="wallet.enabled">
<b-img
class="connector-image my-1"
:src="connector.imageUrl"
fluid-grow
/>
<div class="enabled-notice text-muted" :aria-hidden="connector.enabled">
Unavailable
</div>
</div>
......@@ -21,7 +25,7 @@
<template #footer>
<div class="d-inline-flex mx-1">
<a :href="wallet.appleDownloadUrl" target="_blank" @click.stop>
<a :href="connector.appleDownloadUrl" target="_blank" @click.stop>
<b-img
class="store-badge"
src="../assets/app-store-badge.svg"
......@@ -30,7 +34,7 @@
</a>
</div>
<div class="d-inline-flex mx-1">
<a :href="wallet.googleDownloadUrl" target="_blank" @click.stop>
<a :href="connector.googleDownloadUrl" target="_blank" @click.stop>
<b-img
class="store-badge"
alt="Get it on Google Play"
......@@ -45,9 +49,9 @@
<script>
export default {
name: "WalletCard",
name: "ConnectorCard",
props: {
wallet: {
connector: {
type: Object,
required: true,
},
......@@ -60,21 +64,21 @@ export default {
height: 40px;
}
.wallet-enabled:hover {
.connector-enabled:hover {
/* background: rgba(0, 0, 0, 0.05); */
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.125) !important;
cursor: pointer;
}
.wallet-enabled .enabled-notice {
.connector-enabled .enabled-notice {
opacity: 0;
}
.wallet-disabled {
.connector-disabled {
background: rgba(0, 0, 0, 0.05);
}
.wallet-disabled img {
.connector-disabled img {
opacity: 0.6;
}
</style>
<template>
<b-img
class="d-block mx-auto mb-4"
:src="connector.imageUrl"
:alt="connector.title"
width="150"
/>
</template>
<script>
export default {
name: "ConnectorImage",
props: {
connector: {
type: Object,
required: true,
},
},
};
</script>
<template>
<b-card-group class="connector-select">
<connector-card
v-for="connector in connectorConfigs"
:key="connector.name"
:connector="connector"
@choice="$emit('choice', connector.name)"
/>
</b-card-group>
</template>
<script>
import ConnectorCard from "./ConnectorCard.vue";
import { CONNECTORS } from "../constants";
export default {
name: "ConnectorSelect",
components: {
ConnectorCard,
},
props: {
enabledConnectors: {
type: Array,
required: false,
default: () => CONNECTORS.map((w) => w.name),
},
},
computed: {
connectorConfigs() {
return CONNECTORS.map((config) => {
return {
...config,
enabled: this.enabledConnectors.includes(config.name),
};
});
},
},
};
</script>
<template>
<div class="credential-request">
<div class="create-credential-request">
<slot></slot>
<b-overlay :show="status === 'loading'">
<wallet-select
:enabled-wallets="availableWallets"
@choice="(wallet) => (selectedWallet = wallet)"
<connector-select
:enabled-connectors="availableConnectors"
@choice="emitChoice"
/>
</b-overlay>
<p v-if="status === 'error'" class="text-danger">Something went wrong :(</p>
</div>
</template>
<script>
import axios from "axios";
import WalletSelect from "./WalletSelect.vue";
import IrmaVerify from "./IrmaVerify.vue";
import JolocomVerify from "./JolocomVerify.vue";
import JolocomIssue from "./JolocomIssue.vue";
import ConnectorSelect from "./ConnectorSelect.vue";
const WAIT_TIME = 1000;
const componentMap = {
jolocom: {
verify: JolocomVerify,
issue: JolocomIssue,
},
irma: {
verify: IrmaVerify,
},
};
export default {
name: "CredentialRequest",
components: {
WalletSelect,
ConnectorSelect,
},
props: {
token: {
......@@ -53,17 +38,10 @@ export default {
return {
status: "loading",
request: null,
availableWallets: [],
selectedWallet: null,
availableConnectors: [],
};
},
computed: {
requestComponent() {
if (this.request && this.selectedWallet) {
return componentMap[this.selectedWallet][this.type];
}
return null;
},
requestId() {
// TODO: get from request
if (!this.request) {
......@@ -72,22 +50,6 @@ export default {
return `credential-${this.type}-request:${this.request.uuid}`;
},
},
sockets: {
// connect() {
// console.log("Connecting to backend");
// this.$socket.emit("started", this.requestId);
// },
// update(status) {
// console.log("Received new status from backend");
// this.status = status;
// },
redirect({ status, redirectUrl }) {
// TODO: Maybe we don't need to show received data?
console.log("Received redirect", status);
this.status = status;
setTimeout(() => (window.location = redirectUrl), WAIT_TIME);
},
},
created() {
setTimeout(this.performRequest.bind(this), WAIT_TIME);
},
......@@ -101,7 +63,7 @@ export default {
});
console.log(result.data);
this.request = result.data[`${this.type}Request`];
this.availableWallets = result.data.availableConnectors;
this.availableConnectors = result.data.availableConnectors;
this.status = "ready";
this.$emit("request", this.request);
} catch (e) {
......@@ -110,6 +72,23 @@ export default {
console.error(e);
}
},
emitChoice(connector) {
if (!connector) {
this.$emit("error", "No connector selected");
return;
}
if (!this.request) {
this.$emit("error", "Got no request");
return;
}
this.$emit("choice", {
connector,
request: this.request,
requestId: this.requestId,
});
},
},
};
</script>
<template>
<div class="irma-session-stub"></div>
</template>
<script>
import irma from "@privacybydesign/irmajs";
import * as irma from "@privacybydesign/irmajs";
export default {
name: "IrmaSession",
......
......@@ -12,6 +12,7 @@
</template>
<script>
import axios from "axios";
import IrmaSession from "./IrmaSession.vue";
export default {
......@@ -20,14 +21,6 @@ export default {
IrmaSession,
},
props: {
// status: {
// type: String,
// required: true,
// },
// requestId: {
// type: String,
// required: true,
// },
jwt: {
type: String,
required: true,
......@@ -36,22 +29,30 @@ export default {
type: String,
required: true,
},
requestId: {
type: String,
required: true,
},
},
methods: {
handleResult(resultJwt) {
this.$emit("update", "handling");
console.log(resultJwt);
// axios
// .post(`/connectors/irma/verify/${this.requestId}`, resultJwt, {
// headers: {
// "Content-Type": "text/plain",
// },
// })
// .then((response) => {
// console.log("Backend handled jwt", response);
// })
// .catch(() => this.$emit("error"));
axios
.post(
`/api/verify/irma/disclose`,
{ jwt: resultJwt },
{
params: {
verifyRequestId: this.requestId,
},
}
)
.then((response) => {
console.log("Backend handled jwt", response);
})
.catch(() => this.$emit("error"));
},
},
};
......
<template>
<div class="jolocom-issue">
<div v-if="status === 'started'" class="text-center">
<b-spinner variant="secondary" />
</div>
<b-modal
:visible="showModal"
title="Please scan the QR code with your Jolocom Wallet"
......@@ -12,8 +8,8 @@
no-close-on-esc
no-close-on-backdrop
hide-header-close
@cancel="$emit('cancel')"
@ok="$emit('done')"
@cancel="(status = 'cancelled') && $emit('cancel')"
@ok="(status = 'success') && $emit('success')"
>
<p class="text-center">
<img :src="qr" alt="Could not render QR code..." />
......@@ -28,10 +24,6 @@ const WAIT_TIME = 1000;
export default {
name: "JolocomIssue",
props: {
status: {
type: String,
required: true,
},
qr: {
type: String,
required: true,
......@@ -39,6 +31,7 @@ export default {
},
data() {
return {
status: "started",
modal: false,
};
},
......
<template>
<div class="jolocom-verify">
<div v-if="status === 'started'" class="text-center">
<b-spinner variant="secondary" />
</div>
<b-modal
:visible="showModal"
title="Please scan the QR code with your Jolocom Wallet"
......
<template>
<div class="perform-credential-request py-5 text-center">
<connector-image v-if="connectorConfig" :connector="connectorConfig" />
<h2>{{ title }} {{ connectorConfig.title }} credentials</h2>
<p v-if="notice" v-text="notice"></p>
<slot></slot>
<div v-if="status === statuses.loading" class="py-5 text-center">
<b-spinner variant="secondary" />
</div>
<component
:is="component"
v-if="status !== statuses.loading && component"
v-bind="connectorData"
:request-id="requestId"
v-on="{ cancel, success, error, update }"
></component>
</div>
</template>
<script>
import axios from "axios";
import { CONNECTORS } from "../constants";
import ConnectorImage from "./ConnectorImage.vue";
import JolocomIssue from "./JolocomIssue.vue";
import JolocomVerify from "./JolocomVerify.vue";
import IrmaVerify from "./IrmaVerify.vue";
const componentMap = {
jolocom: {
issue: JolocomIssue,
verify: JolocomVerify,
},
irma: {
verify: IrmaVerify,
},
};
const statuses = {
loading: "loading",
ready: "ready",
handling: "handling",
error: "error",
cancelled: "cancelled",
success: "success",
};
const noticeMap = {
loading: "Starting session...",
handling: "Handling data...",
error: "Something went wrong, you will be redirected shortly.",
cancelled: "You cancelled the request, you will be redirected shortly.",
success: "Success! We'll redirect you shortly.",
};
const WAIT_TIME = 1000;
export default {
name: "PerformCredentialRequest",
components: {
ConnectorImage,
},
props: {
requestId: {
type: String,
required: true,
},
type: {
type: String,
required: true,
validator: (value) => ["issue", "verify"].includes(value),
},
connector: {
type: String,
required: true,
validator: (value) => Object.keys(componentMap).includes(value),
},
},
data() {
return {
socketConnected: false,
status: statuses.loading,
connectorData: null,
};
},
computed: {
component() {
const c = componentMap[this.connector][this.type];
if (!c) {
throw new Error("Component is unavailable");
}
return c;
},
connectorConfig() {
return CONNECTORS.find((c) => c.name == this.connector);
},
title() {
let initial = `${this.type[0]}`.toLocaleUpperCase();
return `${initial}${this.type.slice(1)}`;
},
statuses() {
return statuses;
},
notice() {
return noticeMap[this.status];
},
},
sockets: {
connect() {
this.setupSocketConnection();
},
update(status) {
console.log("Received new status from backend");
this.status = status;
},
redirect({ status, redirectUrl }) {
console.log("Received redirect", status, redirectUrl);
this.status = status;
setTimeout(() => (window.location = redirectUrl), WAIT_TIME);
},
},
created() {
setTimeout(this.performRequest.bind(this), WAIT_TIME);
},
mounted() {
if (!this.socketConnected) {
console.log("Socket not set up, trying again.");
this.setupSocketConnection();
}
},
methods: {
setupSocketConnection() {
console.log("Connecting to socket", this.requestId);
this.$socket.client.emit("request-started", this.requestId);
},
async performRequest() {
try {
const result = await axios.get(`/api/${this.type}/${this.connector}`, {
params: {
[`${this.type}RequestId`]: this.requestId,
},
});
console.log(result.data);
this.connectorData = result.data;
const that = this;
setTimeout(() => (that.status = "ready"), WAIT_TIME);
} catch (e) {
console.error(e);
this.error();
}
},
update(status) {
console.log("Received status update from child");
this.status = status;
},
cancel() {
this.$socket.client.emit("request-cancelled", this.requestId);
this.status = "cancelled";
},
error() {
this.$socket.client.emit("request-error", this.requestId);
this.status = "error";
},
success() {
this.$socket.client.emit("request-success", this.requestId);
this.status = "success";
},
},
};
</script>
......@@ -10,7 +10,7 @@ const notificationMap = {
handling: "Handling data...",
error: "Something went wrong, you will be redirected shortly.",
cancelled: "You cancelled the request, you will be redirected shortly.",
done: "Success! We'll redirect you shortly.",
success: "Success! We'll redirect you shortly.",
};
export default {
......
......@@ -78,7 +78,9 @@ export default {
return;
}
const name = this.isIssueRequest ? "Issue" : "Verify";
const name = this.isIssueRequest
? "CreateIssueRequest"
: "CreateVerifyRequest";
return {
name,
......@@ -113,7 +115,7 @@ export default {
const data = {
type: this.credentialType,
callbackUrl: this.callbackUrl,
aud: this.requestType,
sub: this.requestType,
};
if (this.isIssueRequest) {
......
<template>
<b-card-group class="wallet-select">
<wallet-card
v-for="wallet in walletConfigs"
:key="wallet.name"
:wallet="wallet"
@choice="$emit('choice', wallet.name)"
/>
</b-card-group>
</template>
<script>
import WalletCard from "./WalletCard.vue";
const WALLETS = [
export const CONNECTORS = [
{
name: "jolocom",
title: "Jolocom",
imageUrl: require("../assets/wallet-jolocom-logo.svg"),
imageUrl: require("./assets/connector-jolocom-logo.svg"),
appleDownloadUrl:
"https://apps.apple.com/us/app/jolocom-smartwallet/id1223869062",
googleDownloadUrl:
......@@ -25,7 +11,7 @@ const WALLETS = [
{
name: "irma",
title: "IRMA",
imageUrl: require("../assets/wallet-irma-logo.svg"),
imageUrl: require("./assets/connector-irma-logo.svg"),
appleDownloadUrl:
"https://apps.apple.com/nl/app/irma-authenticatie/id1294092994",
googleDownloadUrl:
......@@ -40,28 +26,3 @@ const WALLETS = [
"https://play.google.com/store/apps/details?id=me.connect",
},
];
export default {