From 580280559722775820b087c817c9f636c418eeec Mon Sep 17 00:00:00 2001 From: Sam Gbafa Date: Wed, 3 Aug 2022 10:56:11 -0400 Subject: [PATCH] Add Sign-In with Ethereum (#14438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gregório Granado Magalhães Co-authored-by: George Marshall Co-authored-by: georgewrmarshall Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com> Co-authored-by: brad-decker --- .metamaskrc.dist | 3 + app/_locales/en/messages.json | 57 ++ app/scripts/lib/personal-message-manager.js | 6 + development/build/scripts.js | 2 + lavamoat/browserify/beta/policy.json | 18 + lavamoat/browserify/flask/policy.json | 18 + lavamoat/browserify/main/policy.json | 18 + lavamoat/build-system/policy.json | 775 ++++++++++++++++++ package.json | 1 + shared/modules/siwe.js | 150 ++++ ui/components/app/app-components.scss | 1 + .../permissions-connect-header.component.js | 8 +- .../app/signature-request-siwe/README.mdx | 15 + .../app/signature-request-siwe/index.js | 1 + .../app/signature-request-siwe/index.scss | 74 ++ .../signature-request-siwe-header/index.js | 1 + .../signature-request-siwe-header/index.scss | 26 + .../signature-request-siwe-header.js | 69 ++ .../signature-request-siwe-header.stories.js | 36 + .../signature-request-siwe-message/index.js | 1 + .../signature-request-siwe-message/index.scss | 13 + .../signature-request-siwe-message.js | 53 ++ .../signature-request-siwe-message.stories.js | 51 ++ .../signature-request-siwe.js | 174 ++++ .../signature-request-siwe.stories.js | 165 ++++ ui/components/ui/site-origin/site-origin.js | 6 + .../ui/site-origin/site-origin.stories.js | 10 + ui/pages/confirm-transaction/conf-tx.js | 21 +- yarn.lock | 12 + 29 files changed, 1777 insertions(+), 8 deletions(-) create mode 100644 shared/modules/siwe.js create mode 100644 ui/components/app/signature-request-siwe/README.mdx create mode 100644 ui/components/app/signature-request-siwe/index.js create mode 100644 ui/components/app/signature-request-siwe/index.scss create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-header/index.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-header/index.scss create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.stories.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-message/index.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-message/index.scss create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.stories.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe.js create mode 100644 ui/components/app/signature-request-siwe/signature-request-siwe.stories.js diff --git a/.metamaskrc.dist b/.metamaskrc.dist index d3bff3b46..3bddebfa2 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -7,5 +7,8 @@ SWAPS_USE_DEV_APIS= COLLECTIBLES_V1= TOKEN_DETECTION_V2= +; Set this to '1' to enable support for Sign-In with Ethereum [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) +SIWE_V1= + ; Set this to test changes to the phishing warning page. PHISHING_WARNING_PAGE_URL= diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f26eb8c03..92355a980 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -44,6 +44,60 @@ "QRHardwareWalletSteps2Description": { "message": "Ngrave (Coming Soon)" }, + "SIWEAddressInvalid": { + "message": "The address in the sign-in request does not match the address of the account you are using to sign in." + }, + "SIWEDomainInvalid": { + "message": "The website you are attempting to sign in to ($1) does not match the domain in the sign-in request. Proceed with caution.", + "description": "$1 represents the website domain" + }, + "SIWEDomainWarningBody": { + "message": "The website ($1) is asking you to sign in to the wrong domain. This may be a phishing attack.", + "description": "$1 represents the website domain" + }, + "SIWELabelChainID": { + "message": "Chain ID:" + }, + "SIWELabelExpirationTime": { + "message": "Expires At:" + }, + "SIWELabelIssuedAt": { + "message": "Issued At:" + }, + "SIWELabelMessage": { + "message": "Message:" + }, + "SIWELabelNonce": { + "message": "Nonce:" + }, + "SIWELabelNotBefore": { + "message": "Not Before:" + }, + "SIWELabelRequestID": { + "message": "Request ID:" + }, + "SIWELabelResources": { + "message": "Resources: $1", + "description": "$1 represents the number of resources" + }, + "SIWELabelURI": { + "message": "URI:" + }, + "SIWELabelVersion": { + "message": "Version:" + }, + "SIWESiteRequestSubtitle": { + "message": "This site is requesting to sign in with" + }, + "SIWESiteRequestTitle": { + "message": "Sign-in request" + }, + "SIWEWarningSubtitle": { + "message": "To confirm you understand, check:" + }, + "SIWEWarningTitle": { + "message": "Are you sure?" + }, "about": { "message": "About" }, @@ -3012,6 +3066,9 @@ "signed": { "message": "Signed" }, + "signin": { + "message": "Sign-In" + }, "simulationErrorMessageV2": { "message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail." }, diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 67924bef3..ffc45575b 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -7,6 +7,7 @@ import { MESSAGE_TYPE } from '../../../shared/constants/app'; import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import createId from '../../../shared/modules/random-id'; import { EVENT } from '../../../shared/constants/metametrics'; +import { detectSIWE } from '../../../shared/modules/siwe'; import { addHexPrefix } from './util'; const hexRe = /^[0-9A-Fa-f]+$/gu; @@ -135,6 +136,11 @@ export default class PersonalMessageManager extends EventEmitter { msgParams.origin = req.origin; } msgParams.data = this.normalizeMsgData(msgParams.data); + + // check for SIWE message + const siwe = detectSIWE(msgParams); + msgParams.siwe = siwe; + // create txData obj with parameters and meta data const time = new Date().getTime(); const msgId = createId(); diff --git a/development/build/scripts.js b/development/build/scripts.js index 566d9c8ec..7349cb819 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -44,6 +44,7 @@ const metamaskrc = require('rc')('metamask', { SENTRY_DSN_DEV: process.env.SENTRY_DSN_DEV || 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496', + SIWE_V1: process.env.SIWE_V1, }); const { streamFlatMap } = require('../stream-flat-map'); @@ -1131,6 +1132,7 @@ function getEnvironmentVariables({ buildType, devMode, testing, version }) { INFURA_PROJECT_ID: getInfuraProjectId({ buildType, environment, testing }), SEGMENT_HOST: metamaskrc.SEGMENT_HOST, SEGMENT_WRITE_KEY: getSegmentWriteKey({ buildType, environment }), + SIWE_V1: metamaskrc.SIWE_V1 === '1', SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS === '1', ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1', COLLECTIBLES_V1: metamaskrc.COLLECTIBLES_V1 === '1', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index e71a53c1a..3858d0886 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -3735,6 +3735,24 @@ "localforage": true } }, + "@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@spruceid/siwe-parser>apg-js": true + } + }, + "@spruceid/siwe-parser>apg-js": { + "globals": { + "mode": true + }, + "packages": { + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, "@storybook/api>regenerator-runtime": { "globals": { "regeneratorRuntime": "write" diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index e71a53c1a..3858d0886 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -3735,6 +3735,24 @@ "localforage": true } }, + "@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@spruceid/siwe-parser>apg-js": true + } + }, + "@spruceid/siwe-parser>apg-js": { + "globals": { + "mode": true + }, + "packages": { + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, "@storybook/api>regenerator-runtime": { "globals": { "regeneratorRuntime": "write" diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index e71a53c1a..3858d0886 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -3735,6 +3735,24 @@ "localforage": true } }, + "@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@spruceid/siwe-parser>apg-js": true + } + }, + "@spruceid/siwe-parser>apg-js": { + "globals": { + "mode": true + }, + "packages": { + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, "@storybook/api>regenerator-runtime": { "globals": { "regeneratorRuntime": "write" diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 6ee5838f7..364772a8d 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -4101,6 +4101,7 @@ "gulp-watch>chokidar>anymatch": true, "gulp-watch>chokidar>async-each": true, "gulp-watch>chokidar>braces": true, + "gulp-watch>chokidar>fsevents": true, "gulp-watch>chokidar>is-binary-path": true, "gulp-watch>chokidar>normalize-path": true, "gulp-watch>chokidar>readdirp": true, @@ -4249,6 +4250,389 @@ "webpack>micromatch>braces>fill-range>repeat-string": true } }, + "gulp-watch>chokidar>fsevents": { + "builtin": { + "events.EventEmitter": true, + "fs.stat": true, + "path.join": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "process.nextTick": true, + "process.platform": true, + "setImmediate": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp": { + "builtin": { + "events.EventEmitter": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "fs.renameSync": true, + "path.dirname": true, + "path.existsSync": true, + "path.join": true, + "path.resolve": true, + "url.parse": true, + "url.resolve": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "console.log": true, + "process.arch": true, + "process.cwd": true, + "process.env": true, + "process.platform": true, + "process.version.substr": true, + "process.versions": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>detect-libc": { + "builtin": { + "child_process.spawnSync": true, + "fs.readdirSync": true, + "os.platform": true + }, + "globals": { + "process.env": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt": { + "builtin": { + "path": true, + "stream.Stream": true, + "url": true + }, + "globals": { + "console": true, + "process.argv": true, + "process.env.DEBUG_NOPT": true, + "process.env.NOPT_DEBUG": true, + "process.platform": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>abbrev": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv": { + "builtin": { + "child_process.exec": true, + "path": true + }, + "globals": { + "process.env.COMPUTERNAME": true, + "process.env.ComSpec": true, + "process.env.EDITOR": true, + "process.env.HOSTNAME": true, + "process.env.PATH": true, + "process.env.PROMPT": true, + "process.env.PS1": true, + "process.env.Path": true, + "process.env.SHELL": true, + "process.env.USER": true, + "process.env.USERDOMAIN": true, + "process.env.USERNAME": true, + "process.env.VISUAL": true, + "process.env.path": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": { + "builtin": { + "os.homedir": true + }, + "globals": { + "process.env": true, + "process.getuid": true, + "process.platform": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { + "globals": { + "process.env.SystemRoot": true, + "process.env.TEMP": true, + "process.env.TMP": true, + "process.env.TMPDIR": true, + "process.env.windir": true, + "process.platform": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog": { + "builtin": { + "events.EventEmitter": true, + "util": true + }, + "globals": { + "process.nextTick": true, + "process.stderr": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>console-control-strings": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>set-blocking": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": { + "builtin": { + "events.EventEmitter": true, + "util.inherits": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>delegates": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>core-util-is": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>isarray": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>process-nextick-args": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>string_decoder": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>util-deprecate": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>core-util-is": { + "globals": { + "Buffer.isBuffer": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>process-nextick-args": { + "globals": { + "process": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>string_decoder": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>util-deprecate": { + "builtin": { + "util.deprecate": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge": { + "builtin": { + "util.format": true + }, + "globals": { + "clearInterval": true, + "process": true, + "setImmediate": true, + "setInterval": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>console-control-strings": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>aproba": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>has-unicode": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>object-assign": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>signal-exit": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>wide-align": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>signal-exit": { + "builtin": { + "assert.equal": true, + "events": true + }, + "globals": { + "process": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>code-point-at": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point>number-is-nan": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi>ansi-regex": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>wide-align": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>npmlog>set-blocking": { + "globals": { + "process.stderr": true, + "process.stdout": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf": { + "builtin": { + "assert": true, + "fs": true, + "path.join": true + }, + "globals": { + "process.platform": true, + "setTimeout": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": { + "builtin": { + "assert": true, + "events.EventEmitter": true, + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readdir": true, + "fs.readdirSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.join": true, + "path.resolve": true, + "util": true + }, + "globals": { + "console.error": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>fs.realpath": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>inflight": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>path-is-absolute": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>fs.realpath": { + "builtin": { + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readlink": true, + "fs.readlinkSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.normalize": true, + "path.resolve": true + }, + "globals": { + "console.error": true, + "console.trace": true, + "process.env.NODE_DEBUG": true, + "process.nextTick": true, + "process.noDeprecation": true, + "process.platform": true, + "process.throwDeprecation": true, + "process.traceDeprecation": true, + "process.version": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>inflight": { + "globals": { + "process.nextTick": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>once>wrappy": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": { + "builtin": { + "util.inherits": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch": { + "builtin": { + "path": true + }, + "globals": { + "console.error": true + }, + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion>balanced-match": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion>concat-map": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": { + "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>once>wrappy": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob>path-is-absolute": { + "globals": { + "process.platform": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": { + "globals": { + "console": true, + "process": true + } + }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": { + "builtin": { + "buffer": true + } + }, "gulp-watch>chokidar>is-binary-path": { "builtin": { "path.extname": true @@ -4744,6 +5128,7 @@ "gulp-watch>path-is-absolute": true, "gulp>glob-watcher>anymatch": true, "gulp>glob-watcher>chokidar>braces": true, + "gulp>glob-watcher>chokidar>fsevents": true, "gulp>glob-watcher>chokidar>is-binary-path": true, "gulp>glob-watcher>chokidar>normalize-path": true, "gulp>glob-watcher>chokidar>readdirp": true, @@ -4792,6 +5177,389 @@ "webpack>micromatch>braces>fill-range>repeat-string": true } }, + "gulp>glob-watcher>chokidar>fsevents": { + "builtin": { + "events.EventEmitter": true, + "fs.stat": true, + "path.join": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "process.nextTick": true, + "process.platform": true, + "setImmediate": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp": { + "builtin": { + "events.EventEmitter": true, + "fs.existsSync": true, + "fs.readFileSync": true, + "fs.renameSync": true, + "path.dirname": true, + "path.existsSync": true, + "path.join": true, + "path.resolve": true, + "url.parse": true, + "url.resolve": true, + "util.inherits": true + }, + "globals": { + "__dirname": true, + "console.log": true, + "process.arch": true, + "process.cwd": true, + "process.env": true, + "process.platform": true, + "process.version.substr": true, + "process.versions": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>detect-libc": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>semver": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>detect-libc": { + "builtin": { + "child_process.spawnSync": true, + "fs.readdirSync": true, + "os.platform": true + }, + "globals": { + "process.env": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt": { + "builtin": { + "path": true, + "stream.Stream": true, + "url": true + }, + "globals": { + "console": true, + "process.argv": true, + "process.env.DEBUG_NOPT": true, + "process.env.NOPT_DEBUG": true, + "process.platform": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>abbrev": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv": { + "builtin": { + "child_process.exec": true, + "path": true + }, + "globals": { + "process.env.COMPUTERNAME": true, + "process.env.ComSpec": true, + "process.env.EDITOR": true, + "process.env.HOSTNAME": true, + "process.env.PATH": true, + "process.env.PROMPT": true, + "process.env.PS1": true, + "process.env.Path": true, + "process.env.SHELL": true, + "process.env.USER": true, + "process.env.USERDOMAIN": true, + "process.env.USERNAME": true, + "process.env.VISUAL": true, + "process.env.path": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": { + "builtin": { + "os.homedir": true + }, + "globals": { + "process.env": true, + "process.getuid": true, + "process.platform": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { + "globals": { + "process.env.SystemRoot": true, + "process.env.TEMP": true, + "process.env.TMP": true, + "process.env.TMPDIR": true, + "process.env.windir": true, + "process.platform": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog": { + "builtin": { + "events.EventEmitter": true, + "util": true + }, + "globals": { + "process.nextTick": true, + "process.stderr": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>console-control-strings": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>set-blocking": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet": { + "builtin": { + "events.EventEmitter": true, + "util.inherits": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>delegates": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream": { + "builtin": { + "events.EventEmitter": true, + "stream": true, + "util": true + }, + "globals": { + "process.browser": true, + "process.env.READABLE_STREAM": true, + "process.stderr": true, + "process.stdout": true, + "process.version.slice": true, + "setImmediate": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>core-util-is": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>isarray": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>process-nextick-args": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>string_decoder": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>util-deprecate": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>core-util-is": { + "globals": { + "Buffer.isBuffer": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>process-nextick-args": { + "globals": { + "process": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>string_decoder": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>are-we-there-yet>readable-stream>util-deprecate": { + "builtin": { + "util.deprecate": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge": { + "builtin": { + "util.format": true + }, + "globals": { + "clearInterval": true, + "process": true, + "setImmediate": true, + "setInterval": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>console-control-strings": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>aproba": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>has-unicode": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>object-assign": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>signal-exit": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>wide-align": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>signal-exit": { + "builtin": { + "assert.equal": true, + "events": true + }, + "globals": { + "process": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>code-point-at": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width>is-fullwidth-code-point>number-is-nan": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>strip-ansi>ansi-regex": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>wide-align": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>gauge>string-width": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>npmlog>set-blocking": { + "globals": { + "process.stderr": true, + "process.stdout": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf": { + "builtin": { + "assert": true, + "fs": true, + "path.join": true + }, + "globals": { + "process.platform": true, + "setTimeout": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob": { + "builtin": { + "assert": true, + "events.EventEmitter": true, + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readdir": true, + "fs.readdirSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.join": true, + "path.resolve": true, + "util": true + }, + "globals": { + "console.error": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>fs.realpath": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>inflight": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>path-is-absolute": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>fs.realpath": { + "builtin": { + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readlink": true, + "fs.readlinkSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.normalize": true, + "path.resolve": true + }, + "globals": { + "console.error": true, + "console.trace": true, + "process.env.NODE_DEBUG": true, + "process.nextTick": true, + "process.noDeprecation": true, + "process.platform": true, + "process.throwDeprecation": true, + "process.traceDeprecation": true, + "process.version": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>inflight": { + "globals": { + "process.nextTick": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>once>wrappy": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>inherits": { + "builtin": { + "util.inherits": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch": { + "builtin": { + "path": true + }, + "globals": { + "console.error": true + }, + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion>balanced-match": true, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>minimatch>brace-expansion>concat-map": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>once": { + "packages": { + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>once>wrappy": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>rimraf>glob>path-is-absolute": { + "globals": { + "process.platform": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>semver": { + "globals": { + "console": true, + "process": true + } + }, + "gulp>glob-watcher>chokidar>fsevents>node-pre-gyp>tar>safe-buffer": { + "builtin": { + "buffer": true + } + }, "gulp>glob-watcher>chokidar>is-binary-path": { "builtin": { "path.extname": true @@ -5993,6 +6761,7 @@ "depcheck>readdirp": true, "eslint>is-glob": true, "sass>chokidar>braces": true, + "sass>chokidar>fsevents": true, "sass>chokidar>glob-parent": true, "sass>chokidar>is-binary-path": true, "watchify>anymatch": true @@ -6016,6 +6785,12 @@ "sass>chokidar>braces>fill-range>to-regex-range>is-number": true } }, + "sass>chokidar>fsevents": { + "globals": { + "process.platform": true + }, + "native": true + }, "sass>chokidar>glob-parent": { "builtin": { "os.platform": true, diff --git a/package.json b/package.json index df33cb3b4..7f6b7c231 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "@reduxjs/toolkit": "^1.6.2", "@sentry/browser": "^6.0.0", "@sentry/integrations": "^6.0.0", + "@spruceid/siwe-parser": "^1.1.3", "@truffle/codec": "^0.11.18", "@truffle/decoder": "^5.1.0", "@zxing/browser": "^0.0.10", diff --git a/shared/modules/siwe.js b/shared/modules/siwe.js new file mode 100644 index 000000000..4fa99457a --- /dev/null +++ b/shared/modules/siwe.js @@ -0,0 +1,150 @@ +import { stripHexPrefix } from 'ethereumjs-util'; +import { ParsedMessage } from '@spruceid/siwe-parser'; +import log from 'loglevel'; + +const msgHexToText = (hex) => { + try { + const stripped = stripHexPrefix(hex); + const buff = Buffer.from(stripped, 'hex'); + return buff.length === 32 ? hex : buff.toString('utf8'); + } catch (e) { + log.error(e); + return hex; + } +}; + +/** + * A locally defined object used to provide data to identify a Sign-In With Ethereum (SIWE)(EIP-4361) message and provide the parsed message + * + * @typedef localSIWEObject + * @param {boolean} isSIWEMessage - Does the intercepted message conform to the SIWE specification? + * @param {ParsedMessage} parsedMessage - The data parsed out of the message + */ + +/** + * This function intercepts a sign message, detects if it's a + * Sign-In With Ethereum (SIWE)(EIP-4361) message, and returns an object with + * relevant SIWE data. + * + * {@see {@link https://eips.ethereum.org/EIPS/eip-4361}} + * + * @param {object} msgParams - The params of the message to sign + * @returns {localSIWEObject} + */ +export const detectSIWE = (msgParams) => { + try { + const { data } = msgParams; + const message = msgHexToText(data); + const parsedMessage = new ParsedMessage(message); + + return { + isSIWEMessage: true, + parsedMessage, + }; + } catch (error) { + // ignore error, it's not a valid SIWE message + return { + isSIWEMessage: false, + parsedMessage: null, + }; + } +}; + +/** + * Takes in a parsed Sign-In with Ethereum Message (EIP-4361) + * and generates an array of label-value pairs + * + * @param {object} parsedMessage - A parsed SIWE message with message contents + * @param {Function} t - i18n function + * @returns {Array} An array of label-value pairs with the type of the value as the label + */ +export const formatMessageParams = (parsedMessage, t) => { + const output = []; + + const { + statement, + uri, + version, + chainId, + nonce, + issuedAt, + expirationTime, + notBefore, + requestId, + resources, + } = parsedMessage; + + if (statement) { + output.push({ + label: t('SIWELabelMessage'), + value: statement, + }); + } + + if (uri) { + output.push({ + label: t('SIWELabelURI'), + value: uri, + }); + } + + if (version) { + output.push({ + label: t('SIWELabelVersion'), + value: version, + }); + } + + if (chainId) { + output.push({ + label: t('SIWELabelChainID'), + value: chainId, + }); + } + + if (nonce) { + output.push({ + label: t('SIWELabelNonce'), + value: nonce, + }); + } + + if (issuedAt) { + output.push({ + label: t('SIWELabelIssuedAt'), + value: issuedAt, + }); + } + + if (expirationTime) { + output.push({ + label: t('SIWELabelExpirationTime'), + value: expirationTime, + }); + } + + if (notBefore) { + output.push({ + label: t('SIWELabelNotBefore'), + value: notBefore, + }); + } + + if (requestId) { + output.push({ + label: t('SIWELabelRequestID'), + value: requestId, + }); + } + + if (resources && resources.length > 0) { + output.push({ + label: t('SIWELabelResources', [resources.length]), + value: resources + .reduce((previous, resource) => `${previous}${resource}\n`, '') + .trim(), + }); + } + + return output; +}; diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 6fd148adc..d7cf310ed 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -58,6 +58,7 @@ @import 'step-progress-bar/index.scss'; @import 'selected-account/index'; @import 'signature-request/index'; +@import 'signature-request-siwe/index'; @import 'signature-request-original/index'; @import 'srp-input/srp-input'; @import 'tab-bar/index'; diff --git a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js index 706037899..ae6b37a6d 100644 --- a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js +++ b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import classnames from 'classnames'; import SiteOrigin from '../../ui/site-origin'; import Box from '../../ui/box'; import { @@ -18,12 +19,14 @@ export default class PermissionsConnectHeader extends Component { ///: END:ONLY_INCLUDE_IN static propTypes = { + className: PropTypes.string, iconUrl: PropTypes.string, iconName: PropTypes.string.isRequired, siteOrigin: PropTypes.string.isRequired, headerTitle: PropTypes.node, boxProps: PropTypes.shape({ ...Box.propTypes }), headerText: PropTypes.string, + rightIcon: PropTypes.node, ///: BEGIN:ONLY_INCLUDE_IN(flask) snapVersion: PropTypes.string, isSnapInstall: PropTypes.bool, @@ -42,6 +45,7 @@ export default class PermissionsConnectHeader extends Component { iconUrl, iconName, siteOrigin, + rightIcon, ///: BEGIN:ONLY_INCLUDE_IN(flask) isSnapInstall, ///: END:ONLY_INCLUDE_IN @@ -60,6 +64,7 @@ export default class PermissionsConnectHeader extends Component { siteOrigin={siteOrigin} iconSrc={iconUrl} name={iconName} + rightIcon={rightIcon} /> ); @@ -68,6 +73,7 @@ export default class PermissionsConnectHeader extends Component { render() { const { boxProps, + className, headerTitle, headerText, ///: BEGIN:ONLY_INCLUDE_IN(flask) @@ -78,7 +84,7 @@ export default class PermissionsConnectHeader extends Component { } = this.props; return ( + + + +## Component API + + diff --git a/ui/components/app/signature-request-siwe/index.js b/ui/components/app/signature-request-siwe/index.js new file mode 100644 index 000000000..1cb414e63 --- /dev/null +++ b/ui/components/app/signature-request-siwe/index.js @@ -0,0 +1 @@ +export { default } from './signature-request-siwe'; diff --git a/ui/components/app/signature-request-siwe/index.scss b/ui/components/app/signature-request-siwe/index.scss new file mode 100644 index 000000000..134b75e9d --- /dev/null +++ b/ui/components/app/signature-request-siwe/index.scss @@ -0,0 +1,74 @@ +@import 'signature-request-siwe-header/index'; +@import 'signature-request-siwe-message/index'; + +.signature-request-siwe { + display: flex; + flex-direction: column; + height: 100%; + min-width: 0; + background-color: var(--color-background-default); + margin-right: auto; + margin-left: auto; + overflow-y: auto; + + @media screen and (min-width: $break-large) { + width: 408px; + max-height: 82vh; + min-height: 570px; + flex: 0 0 auto; + border-radius: 8px; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.08); + } + + .signature-request-siwe__actionable-message { + margin: 4px 16px; + + svg { + width: 13px; + height: 13px; + left: 10px; + top: 20px; + } + } +} + + +.signature-request-siwe__page-container-footer.page-container__footer { + border-top: none; +} + +.signature-request-siwe__warning-popover { + .page-container__footer { + border-top: none; + padding: 0; + display: block; + } + + .popover-footer { + padding: inherit; + display: block; + } + + &__checkbox-wrapper { + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 8px 16px 24px; + + &__label { + @include H7; + + color: var(--color-text-default); + margin-inline-start: 8px; + margin-top: 1px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + cursor: pointer; + } + + .check-box { + color: var(--color-error-default); + } + } +} diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.js b/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.js new file mode 100644 index 000000000..ee666a598 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.js @@ -0,0 +1 @@ +export { default } from './signature-request-siwe-header'; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.scss b/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.scss new file mode 100644 index 000000000..59760562f --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-header/index.scss @@ -0,0 +1,26 @@ +.signature-request-siwe-header { + display: flex; + padding: 16px; + justify-content: center; + align-items: center; + flex-direction: column; + + &__tooltip__container { + display: flex !important; + } + + .account-list-item { + margin: 8px 0 0; + + &__top-row { + align-items: center; + margin: 0; + } + + &__account-name { + @include H5; + + font-weight: 500; + } + } +} diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js new file mode 100644 index 000000000..c622a93d2 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js @@ -0,0 +1,69 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import AccountListItem from '../../account-list-item'; +import { I18nContext } from '../../../../contexts/i18n'; +import Tooltip from '../../../ui/tooltip'; +import InfoIcon from '../../../ui/icon/info-icon.component'; +import { SEVERITIES } from '../../../../helpers/constants/design-system'; + +import PermissionsConnectHeader from '../../permissions-connect-header'; + +export default function SignatureRequestSIWEHeader({ + fromAccount, + domain, + isSIWEDomainValid, + subjectMetadata, +}) { + const t = useContext(I18nContext); + + return ( +
+ {t('SIWEDomainWarningBody', [domain])}

} + wrapperClassName="signature-request-siwe-header__tooltip" + containerClassName="signature-request-siwe-header__tooltip__container" + > + + + ) + } + /> + {fromAccount && ( + + )} +
+ ); +} + +SignatureRequestSIWEHeader.propTypes = { + /** + * The account that is requesting permissions + */ + fromAccount: PropTypes.object, + /** + * The domain that the request is for + */ + domain: PropTypes.string, + /** + * Whether the domain is valid + */ + isSIWEDomainValid: PropTypes.bool, + /** + * The metadata for the subject. This is used to display the icon and name + * and is selected from the domain in the SIWE request. + */ + subjectMetadata: PropTypes.object, +}; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.stories.js b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.stories.js new file mode 100644 index 000000000..36ae054f7 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.stories.js @@ -0,0 +1,36 @@ +import React from 'react'; +import testData from '../../../../../.storybook/test-data'; +import SignatureRequestSIWEHeader from '.'; + +const primaryIdentity = Object.values(testData.metamask.identities)[0]; +const subjectMetadata = { + iconUrl: '/images/logo/metamask-fox.svg', + name: 'MetaMask', + origin: 'http://localhost:8080', +}; + +export default { + title: 'Components/App/SignatureRequestSIWE/SignatureRequestSIWEHeader', + id: __filename, + argTypes: { + fromAccount: { + table: { + address: { control: 'text' }, + balance: { control: 'text' }, + name: { control: 'text' }, + }, + }, + domain: { control: 'text' }, + subjectMetadata: { control: 'object' }, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + fromAccount: primaryIdentity, + domain: window.location.host, + subjectMetadata, +}; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.js b/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.js new file mode 100644 index 000000000..0009d1b60 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.js @@ -0,0 +1 @@ +export { default } from './signature-request-siwe-message'; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.scss b/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.scss new file mode 100644 index 000000000..d78461332 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-message/index.scss @@ -0,0 +1,13 @@ +.signature-request-siwe-message { + flex: 1 100%; + border-radius: 8px; + padding: 8px 16px; + margin: 16px; + border: 1px solid var(--color-border-muted); + + &__sub-text { + white-space: pre-line; + overflow: hidden; + word-wrap: break-word; + } +} diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.js b/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.js new file mode 100644 index 000000000..c3199c7ff --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Box from '../../../ui/box'; +import Typography from '../../../ui/typography'; + +import { + FLEX_DIRECTION, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; + +const SignatureRequestSIWEMessage = ({ data }) => { + return ( + + + {data.map(({ label, value }, i) => ( + + + {label} + + + {value} + + + ))} + + + ); +}; + +SignatureRequestSIWEMessage.propTypes = { + /** + * The data array that contains objects of data about the message + */ + data: PropTypes.arrayOf( + PropTypes.shape({ + /** + * The label or title of the value data + */ + label: PropTypes.string, + /** + * The value of the data + */ + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }), + ), +}; + +export default React.memo(SignatureRequestSIWEMessage); diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.stories.js b/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.stories.js new file mode 100644 index 000000000..18b4788d2 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe-message/signature-request-siwe-message.stories.js @@ -0,0 +1,51 @@ +import React from 'react'; +import SignatureRequestMessage from '.'; + +export default { + title: 'Components/App/SignatureRequestSIWE/SignatureRequestMessage', + id: __filename, + argTypes: { + data: { + controls: 'object', + }, + }, + args: { + data: [ + { label: 'Label:', value: 'value' }, + { + label: 'Message:', + value: + 'Click to sign in and accept the Terms of Service: https://community.metamask.io/tos', + }, + { + label: 'URI:', + value: 'http://localhost:8080', + }, + { + label: 'Version:', + value: '1', + }, + { + label: 'Chain ID:', + value: 1, + }, + { + label: 'Nonce:', + value: 'STMt6KQMwwdOXE306', + }, + { + label: 'Issued At:', + value: '2022-03-18T21:40:40.823Z', + }, + { + label: 'Resources: 2', + value: + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu\nhttps://example.com/my-web2-claim.json', + }, + ], + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js new file mode 100644 index 000000000..06352d5f6 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.js @@ -0,0 +1,174 @@ +import React, { useCallback, useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import log from 'loglevel'; +import ActionableMessage from '../../ui/actionable-message'; +import Popover from '../../ui/popover'; +import Checkbox from '../../ui/check-box'; +import { I18nContext } from '../../../contexts/i18n'; +import { PageContainerFooter } from '../../ui/page-container'; +import { + accountsWithSendEtherInfoSelector, + getSubjectMetadata, +} from '../../../selectors'; +import { getAccountByAddress } from '../../../helpers/utils/util'; +import { formatMessageParams } from '../../../../shared/modules/siwe'; +import Header from './signature-request-siwe-header'; +import Message from './signature-request-siwe-message'; + +export default function SignatureRequestSIWE({ + txData, + cancelPersonalMessage, + signPersonalMessage, +}) { + const allAccounts = useSelector(accountsWithSendEtherInfoSelector); + const subjectMetadata = useSelector(getSubjectMetadata); + + const { + msgParams: { + from, + origin, + siwe: { parsedMessage }, + }, + } = txData; + + const fromAccount = getAccountByAddress(allAccounts, from); + const targetSubjectMetadata = subjectMetadata[origin]; + + const t = useContext(I18nContext); + + const isMatchingAddress = + from.toLowerCase() === parsedMessage.address.toLowerCase(); + + const checkSIWEDomain = () => { + let isSIWEDomainValid = false; + + if (origin) { + const { host } = new URL(origin); + isSIWEDomainValid = parsedMessage.domain === host; + } + return isSIWEDomainValid; + }; + + const isSIWEDomainValid = checkSIWEDomain(); + + const [isShowingDomainWarning, setIsShowingDomainWarning] = useState(false); + const [agreeToDomainWarning, setAgreeToDomainWarning] = useState(false); + + const onSign = useCallback( + async (event) => { + try { + await signPersonalMessage(event); + } catch (e) { + log.error(e); + } + }, + [signPersonalMessage], + ); + + const onCancel = useCallback( + async (event) => { + try { + await cancelPersonalMessage(event); + } catch (e) { + log.error(e); + } + }, + [cancelPersonalMessage], + ); + + return ( +
+
+ + {!isMatchingAddress && ( + + )} + {!isSIWEDomainValid && ( + + )} + setIsShowingDomainWarning(true) + } + cancelText={t('cancel')} + submitText={t('signin')} + /> + {isShowingDomainWarning && ( + setIsShowingDomainWarning(false)} + title={t('SIWEWarningTitle')} + subtitle={t('SIWEWarningSubtitle')} + className="signature-request-siwe__warning-popover" + footerClassName="signature-request-siwe__warning-popover__footer" + footer={ + setIsShowingDomainWarning(false)} + cancelText={t('cancel')} + cancelButtonType="default" + onSubmit={onSign} + submitText={t('confirm')} + submitButtonType="danger-primary" + disabled={!agreeToDomainWarning} + /> + } + > +
+ setAgreeToDomainWarning((checked) => !checked)} + /> + +
+
+ )} +
+ ); +} + +SignatureRequestSIWE.propTypes = { + /** + * The display content of transaction data + */ + txData: PropTypes.object.isRequired, + /** + * Handler for cancel button + */ + cancelPersonalMessage: PropTypes.func.isRequired, + /** + * Handler for sign button + */ + signPersonalMessage: PropTypes.func.isRequired, +}; diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.stories.js b/ui/components/app/signature-request-siwe/signature-request-siwe.stories.js new file mode 100644 index 000000000..e8a790e41 --- /dev/null +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.stories.js @@ -0,0 +1,165 @@ +import React from 'react'; +import testData from '../../../../.storybook/test-data'; +import README from './README.mdx'; +import SignatureRequestSIWE from './signature-request-siwe'; + +const { identities, selectedAddress } = testData.metamask; +const otherIdentity = Object.values(identities)[0]; + +export default { + title: 'Components/App/SignatureRequestSIWE', + id: __filename, + component: SignatureRequestSIWE, + parameters: { + docs: { + page: README, + }, + }, + argTypes: { + txData: { control: 'object' }, + cancelPersonalMessage: { action: 'Cancel' }, + signPersonalMessage: { action: 'Sign' }, + }, +}; + +const msgParams = { + from: selectedAddress, + data: '0x6c6f63616c686f73743a383038302077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078466232433135303034333433393034653566343038323537386334653865313131303563463765330a0a436c69636b20746f207369676e20696e20616e642061636365707420746865205465726d73206f6620536572766963653a2068747470733a2f2f636f6d6d756e6974792e6d6574616d61736b2e696f2f746f730a0a5552493a20687474703a2f2f6c6f63616c686f73743a383038300a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2053544d74364b514d7777644f58453330360a4973737565642041743a20323032322d30332d31385432313a34303a34302e3832335a0a5265736f75726365733a0a2d20697066733a2f2f516d653773733341525667787636725871565069696b4d4a3875324e4c676d67737a673133705972444b456f69750a2d2068747470733a2f2f6578616d706c652e636f6d2f6d792d776562322d636c61696d2e6a736f6e', + origin: 'http://localhost:8080', + siwe: { + isSIWEMessage: true, + isSIWEDomainValid: true, + parsedMessage: { + domain: 'localhost:8080', + address: selectedAddress, + statement: + 'Click to sign in and accept the Terms of Service: https://community.metamask.io/tos', + uri: 'http://localhost:8080', + version: '1', + nonce: 'STMt6KQMwwdOXE306', + chainId: 1, + issuedAt: '2022-03-18T21:40:40.823Z', + resources: [ + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu', + 'https://example.com/my-web2-claim.json', + ], + }, + }, +}; + +const badDomainParams = { + from: selectedAddress, + data: '0x6c6f63616c686f73743a383038302077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078466232433135303034333433393034653566343038323537386334653865313131303563463765330a0a436c69636b20746f207369676e20696e20616e642061636365707420746865205465726d73206f6620536572766963653a2068747470733a2f2f636f6d6d756e6974792e6d6574616d61736b2e696f2f746f730a0a5552493a20687474703a2f2f6c6f63616c686f73743a383038300a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2053544d74364b514d7777644f58453330360a4973737565642041743a20323032322d30332d31385432313a34303a34302e3832335a0a5265736f75726365733a0a2d20697066733a2f2f516d653773733341525667787636725871565069696b4d4a3875324e4c676d67737a673133705972444b456f69750a2d2068747470733a2f2f6578616d706c652e636f6d2f6d792d776562322d636c61696d2e6a736f6e', + origin: 'http://localhost:8080', + siwe: { + isSIWEMessage: true, + isSIWEDomainValid: false, + parsedMessage: { + domain: 'baddomain.com', + address: selectedAddress, + statement: + 'Click to sign in and accept the Terms of Service: https://community.metamask.io/tos', + uri: 'http://localhost:8080', + version: '1', + nonce: 'STMt6KQMwwdOXE306', + chainId: 1, + issuedAt: '2022-03-18T21:40:40.823Z', + resources: [ + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu', + 'https://example.com/my-web2-claim.json', + ], + }, + }, +}; + +const badAddressParams = { + from: otherIdentity.address, + data: '0x6c6f63616c686f73743a383038302077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078466232433135303034333433393034653566343038323537386334653865313131303563463765330a0a436c69636b20746f207369676e20696e20616e642061636365707420746865205465726d73206f6620536572766963653a2068747470733a2f2f636f6d6d756e6974792e6d6574616d61736b2e696f2f746f730a0a5552493a20687474703a2f2f6c6f63616c686f73743a383038300a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2053544d74364b514d7777644f58453330360a4973737565642041743a20323032322d30332d31385432313a34303a34302e3832335a0a5265736f75726365733a0a2d20697066733a2f2f516d653773733341525667787636725871565069696b4d4a3875324e4c676d67737a673133705972444b456f69750a2d2068747470733a2f2f6578616d706c652e636f6d2f6d792d776562322d636c61696d2e6a736f6e', + origin: 'http://localhost:8080', + siwe: { + isSIWEMessage: true, + isSIWEDomainValid: true, + parsedMessage: { + domain: 'localhost:8080', + address: selectedAddress, + statement: + 'Click to sign in and accept the Terms of Service: https://community.metamask.io/tos', + uri: 'http://localhost:8080', + version: '1', + nonce: 'STMt6KQMwwdOXE306', + chainId: 1, + issuedAt: '2022-03-18T21:40:40.823Z', + resources: [ + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu', + 'https://example.com/my-web2-claim.json', + ], + }, + }, +}; + +const badDomainAndAddressParams = { + from: otherIdentity.address, + data: '0x6c6f63616c686f73743a383038302077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078466232433135303034333433393034653566343038323537386334653865313131303563463765330a0a436c69636b20746f207369676e20696e20616e642061636365707420746865205465726d73206f6620536572766963653a2068747470733a2f2f636f6d6d756e6974792e6d6574616d61736b2e696f2f746f730a0a5552493a20687474703a2f2f6c6f63616c686f73743a383038300a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2053544d74364b514d7777644f58453330360a4973737565642041743a20323032322d30332d31385432313a34303a34302e3832335a0a5265736f75726365733a0a2d20697066733a2f2f516d653773733341525667787636725871565069696b4d4a3875324e4c676d67737a673133705972444b456f69750a2d2068747470733a2f2f6578616d706c652e636f6d2f6d792d776562322d636c61696d2e6a736f6e', + origin: 'http://localhost:8080', + siwe: { + isSIWEMessage: true, + isSIWEDomainValid: false, + parsedMessage: { + domain: 'baddomain.com', + address: selectedAddress, + statement: + 'Click to sign in and accept the Terms of Service: https://community.metamask.io/tos', + uri: 'http://localhost:8080', + version: '1', + nonce: 'STMt6KQMwwdOXE306', + chainId: 1, + issuedAt: '2022-03-18T21:40:40.823Z', + resources: [ + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu', + 'https://example.com/my-web2-claim.json', + ], + }, + }, +}; + +export const DefaultStory = (args) => { + return ; +}; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + txData: { + msgParams, + }, +}; + +export const BadDomainStory = (args) => { + return ; +}; + +BadDomainStory.args = { + txData: { + msgParams: badDomainParams, + }, +}; + +export const BadAddressStory = (args) => { + return ; +}; + +BadAddressStory.args = { + txData: { + msgParams: badAddressParams, + }, +}; + +export const BadDomainAndAddressStory = (args) => { + return ; +}; + +BadDomainAndAddressStory.args = { + txData: { + msgParams: badDomainAndAddressParams, + }, +}; diff --git a/ui/components/ui/site-origin/site-origin.js b/ui/components/ui/site-origin/site-origin.js index 78fee0e6b..59f84a4de 100644 --- a/ui/components/ui/site-origin/site-origin.js +++ b/ui/components/ui/site-origin/site-origin.js @@ -11,6 +11,7 @@ export default function SiteOrigin({ chip, className, title, + rightIcon, }) { return (
@@ -21,6 +22,7 @@ export default function SiteOrigin({ leftIcon={ } + rightIcon={rightIcon} /> ) : ( {siteOrigin} @@ -57,4 +59,8 @@ SiteOrigin.propTypes = { * if false iconSrc and iconName props will not be used. */ chip: PropTypes.bool, + /** + * The icon to display on the right side of the chip. + */ + rightIcon: PropTypes.node, }; diff --git a/ui/components/ui/site-origin/site-origin.stories.js b/ui/components/ui/site-origin/site-origin.stories.js index 2604b5704..b888f5756 100644 --- a/ui/components/ui/site-origin/site-origin.stories.js +++ b/ui/components/ui/site-origin/site-origin.stories.js @@ -1,4 +1,5 @@ import React from 'react'; +import InfoIcon from '../icon/info-icon.component'; import SiteOrigin from '.'; @@ -36,3 +37,12 @@ DefaultStory.args = { iconSrc: './metamark.svg', chip: true, }; + +export const RightIcon = (args) => ; + +RightIcon.args = { + siteOrigin: 'https://metamask.io', + iconName: 'MetaMask', + iconSrc: './metamark.svg', + rightIcon: , +}; diff --git a/ui/pages/confirm-transaction/conf-tx.js b/ui/pages/confirm-transaction/conf-tx.js index f27f865e4..acadc401a 100644 --- a/ui/pages/confirm-transaction/conf-tx.js +++ b/ui/pages/confirm-transaction/conf-tx.js @@ -7,6 +7,7 @@ import log from 'loglevel'; import * as actions from '../../store/actions'; import txHelper from '../../helpers/utils/tx-helper'; import SignatureRequest from '../../components/app/signature-request'; +import SignatureRequestSIWE from '../../components/app/signature-request-siwe'; import SignatureRequestOriginal from '../../components/app/signature-request-original'; import Loading from '../../components/ui/loading-screen'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; @@ -114,7 +115,12 @@ class ConfirmTxScreen extends Component { : unconfTxList[index]; } - signatureSelect(type, version) { + signatureSelect(txData) { + const { + type, + msgParams: { version, siwe }, + } = txData; + // Temporarily direct only v3 and v4 requests to new code. if ( type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA && @@ -123,6 +129,10 @@ class ConfirmTxScreen extends Component { return SignatureRequest; } + if (process.env.SIWE_V1 && siwe?.isSIWEMessage) { + return SignatureRequestSIWE; + } + return SignatureRequestOriginal; } @@ -251,18 +261,15 @@ class ConfirmTxScreen extends Component { const { currentCurrency, blockGasLimit } = this.props; const txData = this.getTxData() || {}; - const { - msgParams, - type, - msgParams: { version }, - } = txData; + const { msgParams } = txData; + log.debug('msgParams detected, rendering pending msg'); if (!msgParams) { return ; } - const SigComponent = this.signatureSelect(type, version); + const SigComponent = this.signatureSelect(txData); return (