Skip to content

Commit

Permalink
Merge pull request #4 from mcstoer/login-consent-request-updating
Browse files Browse the repository at this point in the history
Update handling of the deeplinks for login consent requests.
  • Loading branch information
michaeltout authored Nov 5, 2024
2 parents e1533db + 71d2253 commit 6cdf0e8
Show file tree
Hide file tree
Showing 23 changed files with 578 additions and 142 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
"@testing-library/jest-dom": "5.11.4",
"@testing-library/react": "11.1.0",
"@testing-library/user-event": "12.1.10",
"base64url": "https://github.com/VerusCoin/base64url.git",
"bignumber.js": "9.1.2",
"blake2b": "https://github.com/VerusCoin/blake2b.git",
"crypto-js": "4.2.0",
"file-loader": "6.2.0",
"prop-types": "15.7.2",
"react": "17.0.1",
"react-dom": "17.0.1",
Expand All @@ -22,7 +25,8 @@
},
"scripts": {
"start": "npx webpack-dashboard -t 'verus-desktop-authenticator' -- npx webpack-dev-server --colors --no-info --mode development",
"build": "npx webpack --mode production"
"build": "npx webpack --mode production",
"devbuild": "npx webpack --mode development"
},
"eslintConfig": {
"extends": [
Expand Down
6 changes: 5 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import './App.css';
import LoginConsent from './components/LoginConsent/LoginConsent';
import { MOCK_IPC } from './env';
import { handleIpc } from './ipc/handlers';
import { SIMULATED_IPC_COIN_REQUEST, SIMULATED_IPC_INIT } from "./__tests__/mocks";
import { SIMULATED_IPC_COIN_REQUEST, SIMULATED_IPC_INIT, SIMULATED_IPC_LOGIN_CONSENT_REQUEST } from "./__tests__/mocks";

if (MOCK_IPC) {
setTimeout(() => {
console.log("Sending ipc login consent request")
handleIpc(SIMULATED_IPC_LOGIN_CONSENT_REQUEST)
}, 5000)
setTimeout(() => {
console.log("Sending ipc init")
handleIpc(SIMULATED_IPC_COIN_REQUEST)
Expand Down
28 changes: 27 additions & 1 deletion src/__tests__/mocks.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { LoginConsentRequest } from "verus-typescript-primitives";
import {
IPC_LOGIN_CONSENT_REQUEST_METHOD,
IPC_INIT_MESSAGE,
IPC_ORIGIN_DEV,
IPC_PUSH_MESSAGE,
} from "../utils/constants";
import base64url from "base64url";

export const SIMULATED_IPC_INIT = {
origin: IPC_ORIGIN_DEV,
Expand Down Expand Up @@ -35,4 +37,28 @@ export const SIMULATED_IPC_COIN_REQUEST = {
})
}

export const RPC_PASSWORD = "a35792af94edd32da7e8f35b1c38c337555482c0542e88d83b00d348289e33a1"

const req = new LoginConsentRequest();
// VRSC deeplink.
const value = "Aa4uvSgsLAWiinJ7Go12_G6zOcUB_TwBGvW4AVxk05q0TGDq2DF_n1qbbEywlZtATU9tgPUm2l0a7Sybm5W4LN3vHSRMeg9ysAUJ8BfPPdpjudQGAUkCBdNxMQABQR9V21X8oiI9U-4fYzas6n203JnoB-woHrjt6I18v_q-9VnalJ8i50jzA8VFBJCqqI4EYZIKWhSEYPQHBKGzKTpHGSnAPo-o1JwfjbV2JpOK-1fENZwBnxScadzcsjPtACKau-L3aiqQgZSISJe2-WYAAAAAAAAUy9qxMOboVB1n5pq7TC7U5vm_gkgBuonE5ahwRyi4Yzpj4r2yofpHq64BAAAAAAAAAf1cwLohmEeSbIZXeiNWWAKnYOcZASFodHRwczovL3ZlcnVzLmlvL2FwaS9hdXRoL3dlYmhvb2tWe5qHsXEx9u6y3QvdGR7OSl1gOwEBAA";
// Testnet deeplink.
// const value = "Aa4uvSgsLAWiinJ7Go12_G6zOcUB_SUBpu-eojVjXjKBJP80KdufnpG2Ti3u1m-zsh-isY5kO9G2fWKtgWgVod3vHSRMeg9ysAUJ8BfPPdpjudQGAUkCBQ20AwABQR-nXPGHPnf-1GJ3o58bWSk6EFJUEiN5MK1t8Cp06CzzVTibtozB2Ce0HTYjv2VOHOKTn8bJt0SYVq5114lSCxeZGSnAPo-o1JwfjbV2JpOK-1fENZwBiBTUY-p2HA3X1kP09WJjGNjJTvBXAQNiAGcAAAAAAAAAAbqJxOWocEcouGM6Y-K9sqH6R6uuAQAAAAAAAAFuVZUYA3ymyUd68h7Mlr3cYcLSpgEeaHR0cDovLzEyNy4wLjAuMS9jb25maXJtLWxvZ2luVnuah7FxMfbust0L3RkezkpdYDsBAQA"
req.fromBuffer(base64url.toBuffer(value));

export const SIMULATED_IPC_LOGIN_CONSENT_REQUEST = {
origin: IPC_ORIGIN_DEV,
data: JSON.stringify({
type: IPC_PUSH_MESSAGE,
method: IPC_LOGIN_CONSENT_REQUEST_METHOD,
data: {
request: req,
origin_app_info: {
id: "VERUS_DESKTOP_MAIN",
search_builtin: true
},
}
})
}

export const RPC_PASSWORD = "a35792af94edd32da7e8f35b1c38c337555482c0542e88d83b00d348289e33a1"
export const RPC_PORT = "17776"
63 changes: 18 additions & 45 deletions src/components/LoginConsent/Consent/Consent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,25 @@ import { setExternalAction, setNavigationPath } from '../../../redux/reducers/na
import {
ConsentRender
} from './Consent.render';
import { EXTERNAL_ACTION, EXTERNAL_CHAIN_START, REDIRECT, SCOPES, SELECT_LOGIN_ID } from '../../../utils/constants'
import { EXTERNAL_ACTION, EXTERNAL_CHAIN_START, SCOPES, SELECT_LOGIN_ID } from '../../../utils/constants';
import { checkAndUpdateIdentities } from '../../../redux/reducers/identity/identity.actions';
import { signResponse } from '../../../rpc/calls/signResponse';
import { setError } from '../../../redux/reducers/error/error.actions';
import { LoginConsentDecision, LoginConsentResponse } from 'verus-typescript-primitives';

class Consent extends React.Component {
constructor(props) {
super(props);
const scope = props.loginConsentRequest.request.challenge.requested_scope
const requestedPermissions = props.loginConsentRequest.request.challenge.requested_access;
const chainName = props.loginConsentRequest.request.chainName;
const signedBy = props.loginConsentRequest.request.signedBy;
const friendlyName = signedBy.friendlyname;
this.displayName = friendlyName.substring(0, friendlyName.lastIndexOf("." + chainName));

let leftText = []
let rightText = []
let permissionsDescriptions = [];

if (scope != null) {
for (const header in SCOPES) {
for (const vdxfid in SCOPES[header]) {
if (scope.includes(vdxfid)) {
const value = SCOPES[header][vdxfid]

if (rightText.length > 0) {
leftText.push('\n')
} else {
leftText.push(header)
}

rightText.push(value.description)
}
if (requestedPermissions != null) {
// Match permissions to the possible ids in the scopes.
for (const permission of requestedPermissions) {
if (SCOPES[permission.vdxfkey]) {
permissionsDescriptions.push(SCOPES[permission.vdxfkey].description);
}
}
}
Expand All @@ -42,37 +33,17 @@ class Consent extends React.Component {

this.tryLogin = this.tryLogin.bind(this);
this.cancel = this.cancel.bind(this);
this.leftText = leftText;
this.rightText = rightText;
this.permissionsText = permissionsDescriptions.join(", ");
}

tryLogin() {
this.setState({ loading: true }, async () => {
const { request } = this.props.loginConsentRequest
const userActions = await checkAndUpdateIdentities(request.chain_id)
const userActions = await checkAndUpdateIdentities(request.chainTicker)
userActions.map(action => this.props.dispatch(action))

if (this.props.canLoginOrGiveConsent()) {
try {
const response = new LoginConsentResponse({
chain_id: request.chain_id,
signing_id: this.props.activeIdentity.identity.identityaddress,
decision: new LoginConsentDecision({
subject: this.props.activeIdentity.identity.identityaddress,
remember: false,
remember_for: 0,
request: request
}),
});

const sigRes = await signResponse(response);

this.props.setRequestResult(sigRes, () => {
this.props.dispatch(setNavigationPath(REDIRECT))
})
} catch(e) {
this.props.dispatch(setError(e))
}
this.props.dispatch(setNavigationPath(SELECT_LOGIN_ID));
} else {
this.props.dispatch(setExternalAction(EXTERNAL_CHAIN_START))
this.props.dispatch(setNavigationPath(EXTERNAL_ACTION));
Expand All @@ -81,7 +52,9 @@ class Consent extends React.Component {
}

cancel() {
this.props.dispatch(setNavigationPath(SELECT_LOGIN_ID));
this.setState({ loading: true }, async () => {
await this.props.completeLoginConsent()
})
}

render() {
Expand Down
54 changes: 24 additions & 30 deletions src/components/LoginConsent/Consent/Consent.render.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from "react";
import Button from '@mui/material/Button';
import DividedText from "../../../containers/DividedText";
import { RequestCard } from "../../../containers/RequestCard/RequestCard";
import { VerusIdLogo } from "../../../images";

export const ConsentRender = function () {
const { loading } = this.state
const { loginConsentRequest } = this.props
const { request } = loginConsentRequest
const { signing_id, challenge } = request
const { clien } = challenge
const { sigBlockInfo, signedBy, signingRevocationIdentity, signingRecoveryIdentity } = request
const { time } = sigBlockInfo

return (
<div
Expand All @@ -24,49 +25,42 @@ export const ConsentRender = function () {
display: "flex",
padding: 32,
flexDirection: "column",
alignItems: "center",
}}
>
<img src={VerusIdLogo} width={'55%'} height={'10%'}/>
<div
style={{
width: "100%",
display: "flex",
justifyContent: "flex-start",
}}
>
<div
style={{
width: "100%",
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
flexDirection: "row",
}}
>
<a href="#">{signing_id}</a>
&nbsp;{` is requesting permission for the following`}
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
padding: 8,
justifyContent: "center",
flex: 1,
}}
>
<DividedText
left={<div style={{ fontWeight: "bold" }}>{this.leftText.join("\n")}</div>}
right={<div>{this.rightText.join("\n")}</div>}
/>
{this.displayName + "@"}{` is requesting login with VerusID`}
</div>

<RequestCard
chainName={request.chainName}
systemId={request.system_id}
signedBy={signedBy}
revocationIdentity={signingRevocationIdentity}
recoveryIdentity={signingRecoveryIdentity}
displayName={this.displayName}
time={time}
permissions={this.permissionsText}
height={"54vh"}
>
</RequestCard>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
alignItems: "flex-end",
justifyContent: "flex-end",
marginTop: "auto",
}}
>
<div
Expand All @@ -88,12 +82,12 @@ export const ConsentRender = function () {
padding: 8,
}}
>
{"Back"}
{"Cancel"}
</Button>
<Button
variant="contained"
color="primary"
disabled={loading || this.props.activeIdentity == null}
disabled={loading}
onClick={() => this.tryLogin()}
style={{
width: 120,
Expand Down
21 changes: 12 additions & 9 deletions src/components/LoginConsent/ExternalAction/ExternalAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { setNavigationPath } from '../../../redux/reducers/navigation/navigation
import {
ExternalActionRender
} from './ExternalAction.render';
import { EXTERNAL_ACTION, EXTERNAL_CHAIN_START, SELECT_LOGIN_ID } from '../../../utils/constants'
import { CONSENT_TO_SCOPE, EXTERNAL_ACTION, EXTERNAL_CHAIN_START, SELECT_LOGIN_ID } from '../../../utils/constants'
import { checkAndUpdateAll } from '../../../redux/reducers/identity/identity.actions';
import { focusVerusDesktop } from '../../../rpc/calls/focus';
import { SET_API_ERROR } from '../../../redux/reducers/error/error.types';
Expand All @@ -22,9 +22,9 @@ class ExternalAction extends React.Component {

this.actionTypes = {
[EXTERNAL_CHAIN_START]: () => ({
desc: `You need to launch ${this.props.loginConsentRequest.request.chain_id} in native mode and be fully synced to the blockchain in order to login with VerusID. When you are, press 'continue'.`,
desc: `You need to launch ${this.props.loginConsentRequest.request.chainTicker} in native mode and be fully synced to the blockchain in order to login with VerusID. When you are, press 'continue'.`,
check: async () => {
const userActions = await checkAndUpdateAll(this.props.loginConsentRequest.request.chain_id);
const userActions = await checkAndUpdateAll(this.props.loginConsentRequest.request.chainTicker);
userActions.map((action) => props.dispatch(action));

return userActions.some((x) => x.type === SET_API_ERROR)
Expand All @@ -33,12 +33,16 @@ class ExternalAction extends React.Component {
},
}),
[EXTERNAL_CHAIN_START]: () => ({
desc: `Launch ${this.props.loginConsentRequest.request.chain_id} in native mode, and ensure that you have at least one identity that you're able to sign with to login with VerusID. Then press 'continue'.`,
desc: `Launch ${this.props.loginConsentRequest.request.chainTicker} in native mode, and ensure that you have at least one identity that you're able to sign with to login with VerusID. Then press 'continue'.`,
check: async () => {
const userActions = await checkAndUpdateAll(this.props.loginConsentRequest.request.chain_id);
userActions.map((action) => props.dispatch(action));
// Process the request again if any of the required daemons were not running when first trying.
await this.props.handleRequest();

if (this.props.identities.length > 0) {
return CONSENT_TO_SCOPE
}

return this.props.identities.length > 0 ? SELECT_LOGIN_ID : EXTERNAL_ACTION;
return EXTERNAL_ACTION;
},
}),
};
Expand All @@ -57,9 +61,8 @@ class ExternalAction extends React.Component {
tryContinue() {
this.setState({ loading: true }, async () => {
if (this.actionTypes[this.props.externalAction]) {
this.props.dispatch(setNavigationPath(await ((this.actionTypes[this.props.externalAction])()).check()))

this.setState({ loading: false })
this.props.dispatch(setNavigationPath(await ((this.actionTypes[this.props.externalAction])()).check()))
}
})
}
Expand Down
37 changes: 31 additions & 6 deletions src/components/LoginConsent/Login/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { setExternalAction, setNavigationPath } from '../../../redux/reducers/na
import {
LoginRender
} from './Login.render';
import { CONSENT_TO_SCOPE, EXTERNAL_ACTION, EXTERNAL_CHAIN_START } from '../../../utils/constants'
import { CONSENT_TO_SCOPE, EXTERNAL_ACTION, EXTERNAL_CHAIN_START, REDIRECT } from '../../../utils/constants'
import { checkAndUpdateIdentities, setActiveVerusId } from '../../../redux/reducers/identity/identity.actions';
import { signResponse } from '../../../rpc/calls/signResponse';
import { setError } from '../../../redux/reducers/error/error.actions';
import { LoginConsentDecision, LoginConsentResponse } from 'verus-typescript-primitives';
import BigNumber from 'bignumber.js';

class Login extends React.Component {
constructor(props) {
Expand All @@ -23,11 +27,34 @@ class Login extends React.Component {
tryLogin() {
this.setState({ loading: true }, async () => {
const { request } = this.props.loginConsentRequest
const userActions = await checkAndUpdateIdentities(request.chain_id)
const userActions = await checkAndUpdateIdentities(request.chainTicker)
userActions.map(action => this.props.dispatch(action))

if (this.props.canLoginOrGiveConsent()) {
this.props.dispatch(setNavigationPath(CONSENT_TO_SCOPE));
try {
let response = new LoginConsentResponse({
system_id: request.system_id,
signing_id: this.props.activeIdentity.identity.identityaddress,
decision: new LoginConsentDecision({
decision_id: request.challenge.challenge_id,
request: request,
created_at: BigNumber(Date.now())
.dividedBy(1000)
.decimalPlaces(0)
.toNumber(),
})
});

response.chainTicker = request.chainTicker

const sigRes = await signResponse(response);

this.props.setRequestResult(sigRes, () => {
this.props.dispatch(setNavigationPath(REDIRECT))
})
} catch(e) {
this.props.dispatch(setError(e))
}
} else {
this.props.dispatch(setExternalAction(EXTERNAL_CHAIN_START))
this.props.dispatch(setNavigationPath(EXTERNAL_ACTION));
Expand All @@ -36,9 +63,7 @@ class Login extends React.Component {
}

cancel() {
this.setState({ loading: true }, async () => {
await this.props.completeLoginConsent()
})
this.props.dispatch(setNavigationPath(CONSENT_TO_SCOPE));
}

selectId(address) {
Expand Down
Loading

0 comments on commit 6cdf0e8

Please sign in to comment.