├── example ├── app.js └── index.html ├── .npmignore ├── .gitignore ├── assets └── 3id-connect_readme-image.png ├── Dockerfile.example ├── .babelrc ├── src ├── index.js ├── authProvider │ ├── ethereumAuthProvider.js │ └── abstractAuthProvider.js ├── utils.js ├── threeIdProviderProxy.js ├── threeIdConnect.js └── threeIdConnectService.js ├── .dependabot └── config.yml ├── LICENSE-APACHE ├── public └── index.html ├── LICENSE-MIT ├── webpack.config.js ├── iframe ├── html │ └── template.js ├── index.js ├── css │ ├── variables.scss │ └── style.scss └── assets │ ├── logo.svg │ └── assets.js ├── package.json ├── README.md └── .circleci └── config.yml /example/app.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | iframe 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/index.js 3 | lib 4 | 5 | .idea -------------------------------------------------------------------------------- /assets/3id-connect_readme-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3box/3id-connect/HEAD/assets/3id-connect_readme-image.png -------------------------------------------------------------------------------- /Dockerfile.example: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | WORKDIR /3id-connect 4 | 5 | COPY package.json package-lock.json ./ 6 | 7 | COPY src ./src 8 | COPY webpack*.config.js .babelrc ./ 9 | COPY public ./public 10 | 11 | EXPOSE 30001 12 | 13 | CMD npm run start 14 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "presets": ["@babel/preset-env", "@babel/preset-react"], 4 | "plugins": [ 5 | "@babel/plugin-transform-runtime", 6 | "@babel/plugin-proposal-object-rest-spread", 7 | "@babel/plugin-transform-modules-commonjs" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ThreeIdProviderProxy from './threeIdProviderProxy.js' 2 | import ThreeIdConnectService from './threeIdConnectService.js' 3 | import ThreeIdConnect from './threeIdConnect.js' 4 | 5 | export { 6 | ThreeIdConnect, 7 | ThreeIdConnectService, 8 | ThreeIdProviderProxy 9 | } 10 | -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "javascript" 4 | target_branch: "develop" 5 | directory: "/" 6 | update_schedule: "weekly" 7 | default_reviewers: 8 | - "zachferland" 9 | default_assignees: 10 | - "zachferland" 11 | allowed_updates: 12 | - match: 13 | dependency_type: "production" 14 | update_type: "all" 15 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 2 | 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | -------------------------------------------------------------------------------- /src/authProvider/ethereumAuthProvider.js: -------------------------------------------------------------------------------- 1 | import { createLink, authenticate } from '3id-blockchain-utils' 2 | import AbstractAuthProvider from './abstractAuthProvider' 3 | 4 | /** 5 | * AuthProvider which can be used for ethreum providers with standard interface 6 | */ 7 | class EthereumAuthProvider extends AbstractAuthProvider { 8 | constructor(ethProvider) { 9 | super() 10 | this.network = 'ethereum' 11 | this.provider = ethProvider 12 | } 13 | 14 | async authenticate(message, accountId) { 15 | return authenticate(message, accountId, this.provider) 16 | } 17 | 18 | async createLink(did, accountId) { 19 | return createLink(did, accountId, this.provider, { type: 'ethereum-eoa' }) 20 | } 21 | } 22 | 23 | export default EthereumAuthProvider 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 3Box Account 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 3Box Account 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // Partically redundant with 3boxjs utils, but added to remove circular dependency entirely for now 2 | 3 | const HTTPError = (status, message) => { 4 | const e = new Error(message) 5 | e.statusCode = status 6 | return e 7 | } 8 | 9 | const fetchJson = async (url, body) => { 10 | let opts 11 | if (body) { 12 | opts = { body: JSON.stringify(body), method: 'POST', headers: { 'Content-Type': 'application/json' } } 13 | } 14 | const r = await window.fetch(url, opts) 15 | 16 | if (r.ok) { 17 | let res = await r.json() 18 | return res 19 | } else { 20 | throw HTTPError(r.status, (await r.json()).message) 21 | } 22 | } 23 | 24 | const isLinked = async (address) => { 25 | try { 26 | const res = await fetchJson(`https://beta.3box.io/address-server/odbAddress/${address}`) 27 | return Boolean(res.data.rootStoreAddress) 28 | } catch (err) { 29 | return false 30 | } 31 | } 32 | 33 | export { 34 | fetchJson, 35 | isLinked 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/threeIdProviderProxy.js: -------------------------------------------------------------------------------- 1 | import { caller } from 'postmsg-rpc' 2 | 3 | const callbackOrThrow = (callback, errMsg) => { 4 | if (callback) { 5 | callback(errMsg) 6 | } else { 7 | throw errMsg instanceof Error ? errMsg : new Error(errMsg) 8 | } 9 | } 10 | 11 | /** 12 | * A 3ID provider proxy, 3ID provider interface that acts as rpc client, to 13 | * relay request to iframe (rpc server) 14 | */ 15 | class ThreeIdProviderProxy { 16 | constructor (postMessage) { 17 | this.postMessage = postMessage 18 | this.is3idProvider = true 19 | this.threeIdConnect = true 20 | this.migration = true 21 | this.sendRPC = caller('send', {postMessage: this.postMessage}) 22 | } 23 | 24 | async send (req, origin, callback) { 25 | if (typeof origin === 'function') { 26 | callback = origin 27 | origin = null 28 | } 29 | 30 | // Catches rpc errors, method errors are relayed in response for client to handle 31 | try { 32 | const res = JSON.parse(await this.sendRPC(req)) 33 | if (callback) callback(undefined, res) 34 | return res 35 | } catch (err) { 36 | callbackOrThrow(callback, err) 37 | return 38 | } 39 | } 40 | } 41 | 42 | export default ThreeIdProviderProxy 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './iframe/index.js', 5 | output: { 6 | filename: 'index.js', 7 | path: path.resolve(__dirname, 'public'), 8 | libraryTarget: 'umd', 9 | umdNamedDefine: true 10 | }, 11 | module: { 12 | rules: [{ 13 | test: /\.js$/, 14 | exclude: /(node_modules)/, 15 | use: { 16 | loader: 'babel-loader', 17 | options: { 18 | presets: ['@babel/preset-env'], 19 | plugins: [ 20 | ['@babel/plugin-transform-runtime', { 21 | 'regenerator': true 22 | }], 23 | ['@babel/plugin-proposal-object-rest-spread'] 24 | ] 25 | } 26 | } 27 | }, 28 | { 29 | test: /\.scss$/, 30 | use: [{ 31 | loader: "css-loader", 32 | options: { 33 | sourceMap: true, 34 | modules: true, 35 | localIdentName: "[local]" 36 | } 37 | }, 38 | { 39 | loader: "sass-loader" 40 | } 41 | ] 42 | }, 43 | { 44 | test: /\.(png|woff|woff2|eot|ttf)$/, 45 | loader: 'url-loader' 46 | }, 47 | { 48 | test: /\.svg$/, 49 | loader: 'svg-inline-loader' 50 | }, 51 | ] 52 | }, 53 | node: { 54 | console: false, 55 | fs: 'empty', 56 | net: 'empty', 57 | tls: 'empty', 58 | child_process: 'empty' 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /iframe/html/template.js: -------------------------------------------------------------------------------- 1 | const style = require('style-loader!../css/style.scss') 2 | const assets = require('./../assets/assets.js') 3 | 4 | const capitalizeFirst = string => string.charAt(0).toUpperCase() + string.slice(1) 5 | const spaceString = (spaces) => spaces.join(', ') 6 | 7 | const template = (data,isMobile) => ` 8 |
9 |
10 |
11 | 17 | ${assets.Logo} 18 | 19 | Connect your data 20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |

33 | This site wants to access your profile${data.request.spaces.length === 0 ? '. ' : ' and ' + data.request.spaces.length + ' data source'}${data.request.spaces.length > 1 ? 's. ' : '.'} 34 |

35 |
36 |
37 |
38 | 41 | ${data.error ? error(data) :''} 42 |
43 |
44 |
45 |

This site uses 3Box and 3ID to give you control of your data. 46 | 47 | What is this? 48 | 49 |

50 |
51 |
52 |
53 | ` 54 | export default template 55 | 56 | const error = (data) => ` 57 |

${data.error}

58 | ` 59 | -------------------------------------------------------------------------------- /iframe/index.js: -------------------------------------------------------------------------------- 1 | import template from './html/template.js' 2 | import ThreeIdConnectService from './../src/threeIdConnectService.js' 3 | const store = require('store') 4 | const assets = require('./assets/assets.js') 5 | 6 | /** 7 | * UI Window Functions 8 | */ 9 | 10 | const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i 11 | const checkIsMobile = () => mobileRegex.test(navigator.userAgent) 12 | 13 | const error = (error) => ` 14 |

${error}

15 | ` 16 | 17 | // Given a request will render UI module templates 18 | const render = async (request) => { 19 | document.getElementById('root').innerHTML = template({request}, checkIsMobile()) 20 | } 21 | 22 | /** 23 | * Identity Wallet Service configuration and start 24 | */ 25 | 26 | const idwService = new ThreeIdConnectService() 27 | 28 | // IDW getConsent function. Consume IDW request, renders request to user, and resolve selection 29 | const getConsent = async (req) => { 30 | await idwService.displayIframe() 31 | await render(req) 32 | const accept = document.getElementById('accept') 33 | 34 | const result = await new Promise((resolve, reject) => { 35 | accept.addEventListener('click', () => { 36 | accept.innerHTML = `Confirm in your wallet ${assets.Loading}`; 37 | accept.style.boxShadow = 'none'; 38 | resolve(true) 39 | }) 40 | }) 41 | 42 | return result 43 | } 44 | 45 | // Service calls on error, renders error to UI 46 | const errorCb = (err, msg, req) => { 47 | if (!msg) msg = err.toString() 48 | msg = 'Error: Unable to connect' 49 | console.log(err) 50 | document.getElementById('action').innerHTML = error(msg) 51 | } 52 | 53 | // Closure to pass cancel state to IDW iframe service 54 | let closecallback 55 | 56 | window.hideIframe = () => { 57 | idwService.hideIframe() 58 | const root = document.getElementById('root') 59 | if (root) root.innerHTML = `` 60 | if (closecallback) closecallback() 61 | } 62 | 63 | const closing = (cb) => { 64 | closecallback = cb 65 | } 66 | 67 | idwService.start(getConsent, errorCb, closing) 68 | 69 | // For testing, uncomment one line to see static view 70 | // render(JSON.parse(`{"type":"authenticate","origin":"localhost:30001","spaces":["metamask", "3Box", "thingspace"], "opts": { "address": "0x9acb0539f2ea0c258ac43620dd03ef01f676a69b"}}`)) 71 | -------------------------------------------------------------------------------- /iframe/css/variables.scss: -------------------------------------------------------------------------------- 1 | $navHeight: 70px; 2 | $sideBarWidth: 200px; 3 | 4 | $inputPadding: 12px 14px; 5 | 6 | // Brand Colors 7 | $threeBoxBlue: #1168df; 8 | $threeBoxBlueHover: #0950b0; 9 | $textColor: #727581; 10 | 11 | // ThreeId Brand Colors 12 | // $threeIdOrange: #F76837; 13 | // $textColor: #817972; 14 | 15 | $symbolGrey: #D8D8D8; 16 | 17 | $indexModalPreviewImageHeight: 100px; 18 | $hoverBackground: #efefef; 19 | 20 | $hoverBackgroundDark: #e6e6e6; 21 | 22 | $editorBG: #4a4a4a; 23 | 24 | $radius: 5px; 25 | 26 | $threeBoxBlack: #181F21; 27 | $threeBoxBlackHover: rgb(47, 56, 58); 28 | 29 | $messageBackground: #1167df29; 30 | $blueBorderColor: #1167dfc6; 31 | 32 | $transition: all ease-in-out .3s; 33 | 34 | $sectionMargin: 40px; 35 | // $threeBoxBlueDeep: #004AA8; 36 | // $threeBoxBlueDeeper: #00357D; 37 | // $threeBoxBlueLight: #CBDBEE; 38 | // $threeBoxBlueLightTransparency: rgba(187, 218, 255, 0.493); 39 | 40 | // $threeBoxAccentBlue: #021333; 41 | // $threeBoxAccentBlueHover: #254277; 42 | 43 | // // Brand Blue Hues 44 | // $threeBoxBlueShade: #C5D7F2; 45 | // $threeBoxBlueLightBG: rgb(242, 248, 255); 46 | // $threeBoxBlueHue: rgb(251, 254, 255); 47 | // $threeBoxBlueHueSecondary: #EDEEEF; 48 | 49 | // $threeBoxBlueLightShade: #EFF5FE; 50 | 51 | // $turquoise: #8CFFDE; 52 | // $pink: #FF8AFF; 53 | // $yellow: #FEFE69; 54 | 55 | // $backgroundGrey: rgb(248, 248, 248); 56 | // $backgroundGreyDark: #dfdfdfa6; 57 | 58 | $backgroundHue: #F5F6FA; 59 | $backgroundHueDark: #e0e2e8; 60 | $backgroundHueDarkBorderColor: #c8cad2; 61 | // $backgroundHue: rgb(245, 246, 248); 62 | 63 | $threeBoxRed: rgb(202, 50, 50); 64 | // $threeBoxRedHover: rgb(155, 36, 36); 65 | // $threeBoxGreen: rgb(103, 204, 125); 66 | 67 | 68 | // // Font Colors 69 | // $lightFont: #6b6b6b; 70 | // $lightFontHover: #525252; 71 | $lighterFont: #747677; 72 | $lightestFont: #b1b1b1; 73 | 74 | 75 | // $faintFont: #b8b8b8; 76 | // $lightBlueFont: rgb(79, 117, 153); 77 | // $medFont: #546983; 78 | 79 | $borderColor: rgb(225, 225, 225); 80 | // $borderColorDark:#dcdcdc; 81 | 82 | // $feedTileGrey: rgb(249, 249, 249); 83 | 84 | // $defaultProfPic:#006ECC; 85 | 86 | $dropShadow: 0px 4px 10px 4px #00000025, 87 | // 0 16px 24px 0 #0000002f; 88 | 89 | // $maxCommentWidth: 600px; 90 | // $minCommentWidth: 300px; 91 | 92 | // $minCommentWidth-mobile: 260px; 93 | 94 | // $commentPictureDimension: 40px; 95 | 96 | // $commentPictureDimension-mobile: 50px; 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3id-connect", 3 | "version": "0.1.0", 4 | "description": "Account management for 3Box", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "lint": "./node_modules/.bin/standard --verbose src/**", 11 | "test": "jest --detectOpenHandles --coverage", 12 | "build:es5": "rm -rf ./lib; ./node_modules/.bin/babel src --out-dir lib", 13 | "build:dist": "./node_modules/.bin/webpack --config webpack.config.js --mode=development", 14 | "build:dist:watch": "./node_modules/.bin/webpack --config webpack.config.js --mode=development --watch", 15 | "build:dist:prod": "./node_modules/.bin/webpack --config webpack.config.js --mode=production --output-filename index.js", 16 | "build": "npm run build:dist:prod; npm run build:es5;", 17 | "prepublishOnly": "npm run build:es5", 18 | "prepare": "npm run build:es5", 19 | "server:start": "http-server -c-1 -p 30001 public", 20 | "start": "npm run build:dist:watch & npm run server:start" 21 | }, 22 | "jest": { 23 | "testEnvironment": "node", 24 | "testPathIgnorePatterns": [ 25 | "node_modules", 26 | "lib" 27 | ] 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/3box/3id-connect.git" 32 | }, 33 | "author": "3box ", 34 | "license": "(Apache-2.0 OR MIT)", 35 | "bugs": { 36 | "url": "https://github.com/3box/3id-connect/issues" 37 | }, 38 | "homepage": "https://github.com/3box/3id-connect#readme", 39 | "dependencies": { 40 | "3id-blockchain-utils": "^0.4.0", 41 | "@babel/runtime": "^7.1.2", 42 | "identity-wallet": "^1.3.0", 43 | "postmsg-rpc": "^2.4.0", 44 | "store": "^2.0.12", 45 | "url-parse": "^1.4.7" 46 | }, 47 | "devDependencies": { 48 | "@babel/cli": "^7.1.2", 49 | "@babel/core": "^7.1.2", 50 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 51 | "@babel/plugin-transform-modules-commonjs": "^7.2.0", 52 | "@babel/plugin-transform-runtime": "^7.1.0", 53 | "@babel/preset-env": "^7.1.0", 54 | "@babel/preset-react": "^7.0.0", 55 | "babel-core": "7.0.0-bridge.0", 56 | "babel-loader": "^8.0.5", 57 | "css-loader": "^2.1.1", 58 | "css-to-string-loader": "^0.1.3", 59 | "http-server": "^0.11.1", 60 | "jest": "^26.3.0", 61 | "node-sass": "^4.13.1", 62 | "sass-loader": "^8.0.2", 63 | "standard": "^12.0.1", 64 | "style-loader": "^0.23.1", 65 | "svg-inline-loader": "^0.8.2", 66 | "url-loader": "^4.0.0", 67 | "webpack": "^4.20.2", 68 | "webpack-cli": "^3.1.2" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ ⚠️ Deprecated in favor of Ceramic ⚠️ ⚠️ 2 | > 3box.js and related tools built by 3Box Labs are deprecated and no loger supported. Developers are encurraged to build with https://ceramic.network which is a more secure and decentralized protocol for sovereign data. 3 | 4 | 5 | # 3ID-Connect 6 | > ⚠️ This package has been moved to https://github.com/ceramicstudio/3id-connect 7 | 8 | 9 | 10 | ![3ID Connect Image](./assets/3id-connect_readme-image.png) 11 | 12 | [Find 3ID-Connect on Ceramic here.](https://github.com/ceramicstudio/3id-connect) 13 | 14 | 3ID-Connect is a 3ID account management service run in an iframe. It allows you to authenicate, manage, and permission your 3ID keys to applications. Used by default in [3box-js](https://github.com/3box/3box-js). [identity-wallet-js](https://github.com/3box/identity-wallet-js) handles most operations and the parent window (application) communicates with iframe service over RPC layer as defined by [3ID JSON-RPC](https://github.com/3box/3box/blob/master/3IPs/3ip-10.md) 15 | 16 | Right now you authenticate and link ethereum accounts to mange your 3ID, in the future other keypairs, blockchain accounts, and authentication methods can be added. 17 | 18 | # 3ID-Connect Ceramic 19 | 20 | The next verion of 3ID-Connect is being developed on [Ceramic](https://ceramic.network) and [identity-wallet-js V2](https://github.com/3box/identity-wallet-js). It is being developed in parallel with the current version. You can find 3ID-Connect with Ceramic support in the [following repo](https://github.com/ceramicstudio/3id-connect). In the future this repo will be depracated. It is released at 3id-connect@next and available at 3idconnect.org. 21 | 22 | ## Structure 23 | 24 | * **/src** - Core logic and consumable interfaces for clients and iframe 25 | * **/threeIdConnect.js** - Application interface (RPC client) to load iframe and return 3ID provider. 26 | * **/threeIdConnectService.js** - Identity wallet instance and RPC 'server' to handle requests 27 | * **/threeIdProviderProxy.js** - 3ID provider interface that relays request through RPC layer 28 | * **/iframe** - all html, css, js, design assets for iframe and flow 29 | * **/public** - build assets deployed for iframe 30 | 31 | ## Development 32 | 33 | Clone and install dependencies 34 | 35 | #### Run Locally 36 | 37 | Will serve iframe locally on port 30001 38 | 39 | ``` 40 | $ npm run start 41 | ``` 42 | 43 | #### Build 44 | 45 | ``` 46 | $ npm run build 47 | ``` 48 | 49 | ## Maintainers 50 | [@zachferland](https://github.com/zachferland) 51 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | aws-s3: circleci/aws-s3@1.0.15 5 | 6 | jobs: 7 | test-and-build: 8 | working_directory: ~/3id-connect 9 | docker: 10 | - image: circleci/node:10 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - dependencies-cache-{{ checksum "package.json" }} 18 | 19 | - run: 20 | name: install dependencies 21 | command: | 22 | sudo npm i -g codecov node-gyp 23 | npm i 24 | 25 | # - run: 26 | # name: test 27 | # command: npm test && codecov 28 | # 29 | # - run: 30 | # name: lint 31 | # command: npm run lint 32 | 33 | - run: 34 | name: build 35 | command: npm run build 36 | 37 | # - run: 38 | # name: code-coverage 39 | # command: bash <(curl -s https://codecov.io/bash) 40 | 41 | - persist_to_workspace: 42 | root: . 43 | paths: 44 | - public 45 | 46 | - save_cache: 47 | key: dependency-cache-{{ checksum "package.json" }} 48 | paths: 49 | - ./node_modules 50 | deploy-dev: 51 | working_directory: ~/3id-connect 52 | docker: 53 | - image: 'circleci/python:3.8' 54 | steps: 55 | - attach_workspace: 56 | at: . 57 | - aws-s3/sync: 58 | arguments: '--acl public-read --cache 604800' 59 | from: public 60 | overwrite: true 61 | to: 's3://3id-connect-dev/v1' 62 | - run: 63 | name: "Invalidate CloudFront Cache" 64 | command: aws cloudfront create-invalidation --distribution-id E3SV2UNQUEOU2O --paths /* 65 | 66 | deploy-prod: 67 | working_directory: ~/3id-connect 68 | docker: 69 | - image: 'circleci/python:3.8' 70 | steps: 71 | - attach_workspace: 72 | at: . 73 | - aws-s3/sync: 74 | arguments: '--acl public-read --cache 604800' 75 | from: public 76 | overwrite: true 77 | to: 's3://3id-connect-prod/v1' 78 | - run: 79 | name: "Invalidate CloudFront Cache" 80 | command: aws cloudfront create-invalidation --distribution-id E1CFVBE8FYHOZ0 --paths /* 81 | 82 | workflows: 83 | build-deploy: 84 | jobs: 85 | - test-and-build 86 | - deploy-dev: 87 | requires: 88 | - test-and-build 89 | # filters: 90 | # branches: 91 | # only: develop 92 | - deploy-prod: 93 | requires: 94 | - test-and-build 95 | filters: 96 | branches: 97 | only: master 98 | -------------------------------------------------------------------------------- /src/authProvider/abstractAuthProvider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AbstractAuthProvider defines the interface your custom authProvider must 3 | * must implement. The properties network, id, name, image are all required. 4 | * The authenticate function is required. Implement your class to extend this. 5 | */ 6 | class AbstractAuthProvider { 7 | 8 | /** 9 | * Creates AuthProvider instance. Must be instantiated with properties network, 10 | * id, name, image described below. Can pass any args in constructor when adding 11 | * to auth provider list. 12 | * 13 | * @param {String} network represents network, ie ethereum, bitcoin, polkadot etc. 14 | */ 15 | constructor() { 16 | // TODO could network be any use still 17 | this.network = null 18 | this.isAuthProvider = true 19 | } 20 | 21 | /** 22 | * (Required) Authenticate function consumes both a message (human readable string) and 23 | * accountId (often a hex address). It is strictly required that for any 24 | * given set of {message, accountId} this function deterministically returns 25 | * a unique 32 - 64 byte hex string of entropy. This will allow this external account 26 | * to continue to access 3ID in the future and for it be added as an auth method. 27 | * 28 | * For most implementations this will be signing the message with an 29 | * account/wallet from your blockchain account provider and returning a fixed length 30 | * string of the signature by hashing it. This function does not need to consume 31 | * accountId/address if your provider knows that value at later point of the interaction. 32 | * But should still map to a unique output for any given message and accountId pair. 33 | * 34 | * For your given network/blockchain you should be able to find an authenticate 35 | * function in https://github.com/ceramicnetwork/js-3id-blockchain-utils, if you 36 | * are using the standard account signing interface in your network/blockchain. 37 | * If you are using a standard interface and it doesn't exist in js-3id-blockchain-utils, 38 | * please open an issue there, so we can add shared support for your network. 39 | * 40 | * @param {String} message A human readable string 41 | * @param {String} accountId Id of account used with provider, most often hex address 42 | * @return {String} A 32-64 bytes hex string 43 | */ 44 | async authenticate(message, accountId) { 45 | throw new Error('AuthProvider must implement authenticat method') 46 | } 47 | 48 | /** 49 | * (Required) createLink will publish a public verifiable link between account 50 | * used to authenticate and the users 3ID. To implement this you need to import 51 | * createLink from https://github.com/ceramicnetwork/js-3id-blockchain-utils and 52 | * pass the link type. You must have support for your blockchain in 53 | * js-3id-blockchain-utils to add here. As other libraries need to be able to 54 | * verify and consume these links. 55 | * 56 | * @param {String} did A human readable string 57 | * @param {String} accountId Id of account used with provider, most often hex address 58 | * @return Returns on success 59 | */ 60 | async createLink(did, accountId) { 61 | return null 62 | } 63 | } 64 | 65 | 66 | export default AbstractAuthProvider 67 | -------------------------------------------------------------------------------- /iframe/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iframe/assets/assets.js: -------------------------------------------------------------------------------- 1 | import Logo from "./logo.svg"; 2 | 3 | const Loading = ` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ` 60 | 61 | 62 | export { 63 | Logo, 64 | Loading 65 | } 66 | -------------------------------------------------------------------------------- /src/threeIdConnect.js: -------------------------------------------------------------------------------- 1 | import ThreeIdProviderProxy from './threeIdProviderProxy.js' 2 | import { expose } from 'postmsg-rpc' 3 | import EthereumAuthProvider from './authProvider/ethereumAuthProvider.js' 4 | import { fakeIpfs } from 'identity-wallet/lib/utils' 5 | 6 | const IDENTITY_WALLET_IFRAME_URL = 'https://connect.3box.io' 7 | 8 | const HIDE_IFRAME_STYLE = 'position: fixed; width:0; height:0; border:0; border:none !important' 9 | const DISPLAY_IFRAME_STYLE = 'border:none border:0; z-index: 500; position: fixed; max-width: 100%;' 10 | const IFRAME_TOP = `top: 10px; right: 10px` 11 | const IFRAME_BOTTOM = `bottom: 0px; left: 0px;` 12 | 13 | const hide = (iframe) => () => iframe.style = HIDE_IFRAME_STYLE 14 | const display = (iframe) => (mobile = false, height = '245px', width = '440px') => iframe.style = `${DISPLAY_IFRAME_STYLE} width: ${width}; height: ${height}; ${mobile ? IFRAME_BOTTOM: IFRAME_TOP}` 15 | // TODO maybe have some more ui options here, because these can change after iframe loads 16 | 17 | /** 18 | * ThreeIdConnect provides interface for loading and instantiating IDW iframe, 19 | * and provides a 3ID provider interface to send requests to iframe. Acts like 20 | * rpc client. 21 | */ 22 | class ThreeIdConnect { 23 | 24 | /** 25 | * Creates ThreeIdConnect. Create and loads iframe. Should be instantiated 26 | * on page load. 27 | * 28 | * @param {String} iframeUrl iframe url, defaults to 3id-connect iframe service 29 | */ 30 | constructor (iframeUrl) { 31 | if (typeof window === 'undefined' || typeof document === 'undefined') { 32 | throw new Error('ThreeIdConnect not supported in this enviroment') 33 | } 34 | 35 | this.iframe = document.createElement('iframe') 36 | this.iframe.src = iframeUrl || IDENTITY_WALLET_IFRAME_URL 37 | this.iframe.style = HIDE_IFRAME_STYLE 38 | this.iframe.allowTransparency = true 39 | this.iframe.frameBorder = 0 40 | 41 | this.iframeLoadedPromise = new Promise((resolve, reject) => { 42 | this.iframe.onload = () => { resolve() } 43 | }) 44 | 45 | document.body.appendChild(this.iframe) 46 | } 47 | 48 | // Just passing ref to threeId and ipfs during migration 49 | async connect (provider, ThreeId, ipfs) { 50 | // assumes eth provider during migration 51 | this.provider = provider 52 | this.ThreeId = ThreeId 53 | this.ipfs = ipfs 54 | // after migration, can detect different provdier to create authProvider 55 | this.authProvider = new EthereumAuthProvider(provider) 56 | } 57 | 58 | /** 59 | * Handlers to consumer message to hide or display iframe 60 | * 61 | * @private 62 | */ 63 | _registerDisplayHandlers () { 64 | expose('display', display(this.iframe), {postMessage: this.postMessage}) 65 | expose('hide', hide(this.iframe), {postMessage: this.postMessage}) 66 | } 67 | 68 | /** 69 | * Handlers to consume messages for authProvider 70 | * 71 | * @private 72 | */ 73 | _registerAuthHandlers () { 74 | expose('authenticate', this.authenticate.bind(this), {postMessage: this.postMessage}) 75 | expose('migration', this.migration.bind(this), {postMessage: this.postMessage}) 76 | expose('createLink', this.createLink.bind(this), {postMessage: this.postMessage}) 77 | } 78 | 79 | /** 80 | * Returns ThreeId instance, used for migration of legacy 3boxjs accounts 81 | * 82 | * @private 83 | * @param {String} address An ethereum address 84 | * @return {ThreeId} 85 | */ 86 | async _getThreeId (address) { 87 | if(!this._threeId) { 88 | this._threeId = await this.ThreeId.getIdFromEthAddress(address, this.provider, this.ipfs, undefined, {}) 89 | } 90 | return this._threeId 91 | } 92 | 93 | async authenticate(message, address) { 94 | return this.authProvider.authenticate(message, address) 95 | } 96 | 97 | async migration(spaces, address) { 98 | const threeId = await this._getThreeId(address) 99 | await threeId.authenticate(spaces) 100 | return threeId.serializeState() 101 | } 102 | 103 | async createLink(did, address) { 104 | return this.authProvider.createLink(did, address) 105 | } 106 | 107 | /** 108 | * Returns a 3ID provider, which can send and receive 3ID messages from iframe 109 | * 110 | * @return {ThreeIdProviderProxy} A 3ID provider 111 | */ 112 | async get3idProvider() { 113 | await this.iframeLoadedPromise 114 | this.postMessage = this.iframe.contentWindow.postMessage.bind(this.iframe.contentWindow) 115 | this._registerDisplayHandlers() 116 | this._registerAuthHandlers() 117 | return new ThreeIdProviderProxy(this.postMessage) 118 | } 119 | } 120 | 121 | export default ThreeIdConnect 122 | -------------------------------------------------------------------------------- /src/threeIdConnectService.js: -------------------------------------------------------------------------------- 1 | import { expose, caller } from 'postmsg-rpc' 2 | import { fakeIpfs } from 'identity-wallet/lib/utils' 3 | const IdentityWallet = require('identity-wallet') 4 | const Url = require('url-parse') 5 | const store = require('store') 6 | const { isLinked } = require('./utils') 7 | 8 | const consentKey = (address, domain, space) => `3id_consent_${address}_${domain}_${space}` 9 | const serializedKey = (address) => `serialized3id_${address}` 10 | 11 | const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i 12 | const checkIsMobile = () => mobileRegex.test(navigator.userAgent) 13 | 14 | /** 15 | * ThreeIdConnectService runs an identity wallet instance and rpc server with 16 | * bindings to receive and relay rpc messages to identity wallet 17 | */ 18 | class ThreeIdConnectService { 19 | 20 | /** 21 | * Create ThreeIdConnectService 22 | */ 23 | constructor () { 24 | this._registerDisplayListeners() 25 | this._registerExternalAuthListeners() 26 | } 27 | 28 | /** 29 | * Registers rpc call function for display and hiding iframe (Note: reverse of 30 | * idw rpc calls, this is rpc client, sending messages to parent window) 31 | * @private 32 | */ 33 | _registerDisplayListeners () { 34 | this.display = caller('display', {postMessage: window.parent.postMessage.bind(window.parent)}) 35 | this.hide = caller('hide', {postMessage: window.parent.postMessage.bind(window.parent)}) 36 | } 37 | 38 | 39 | /** 40 | * Registers rpc call functions for handling external auth calls needed for IDW to parent window 41 | * @private 42 | */ 43 | _registerExternalAuthListeners () { 44 | this.migration = caller('migration', {postMessage: window.parent.postMessage.bind(window.parent)}) 45 | this.authenticate = caller('authenticate', {postMessage: window.parent.postMessage.bind(window.parent)}) 46 | this.createLink = caller('createLink', {postMessage: window.parent.postMessage.bind(window.parent)}) 47 | } 48 | 49 | 50 | /** 51 | * External Authencation method for IDW 52 | * 53 | * @param {Object} params 54 | * @param {String} params.address An ethereum address 55 | * @param {Array} params.spaces Array of space strings 56 | * @param {String} params.type Type of external auth request 57 | * @return {Object} Response depends on type of request 58 | */ 59 | async externalAuth({ address, spaces, type, did }) { 60 | let threeId 61 | if (type === '3id_auth') { 62 | // TODO IMPLEMENT full migration 63 | const message = 'Add this account as a 3ID authentication method' 64 | return this.authenticate(message, address) 65 | } else if (type === '3id_migration') { 66 | let new3id 67 | 68 | const cached3id = this._get3idState(address) 69 | 70 | if (!cached3id) { 71 | this.linkPromise = isLinked(address) 72 | } 73 | 74 | const diffSpaces = this._diff3idState(cached3id, address, spaces) 75 | 76 | let migration3id 77 | if (diffSpaces) { 78 | migration3id = await this.migration(diffSpaces, address) 79 | new3id = this._merge3idState(cached3id, JSON.parse(migration3id)) 80 | } else { 81 | new3id = cached3id 82 | } 83 | const new3idSerialized = JSON.stringify(new3id) 84 | this._write3idState(new3idSerialized, address) 85 | return new3idSerialized 86 | } else if (type === '3id_createLink' ) { 87 | if (this.linkPromise) { 88 | const link = await this.linkPromise 89 | if (!link) { 90 | return this.createLink(did, address) 91 | } 92 | } 93 | } 94 | } 95 | 96 | _write3idState(state, address) { 97 | store.set(serializedKey(address), state) 98 | } 99 | 100 | _get3idState(address) { 101 | const cached3id = store.get(serializedKey(address)) 102 | return cached3id ? JSON.parse(cached3id) : null 103 | } 104 | 105 | _merge3idState (target, apply) { 106 | if (!target) return apply 107 | const res = Object.assign({}, target) 108 | res.spaceSeeds = Object.assign(target.spaceSeeds, apply.spaceSeeds || {}) 109 | return res 110 | } 111 | 112 | _diff3idState (cached3id, address, spaces) { 113 | if (!cached3id) return spaces 114 | const cacheSpaces = Object.keys(cached3id.spaceSeeds) 115 | const diff = spaces.filter(x => !cacheSpaces.includes(x)) 116 | return diff.length === 0 ? null : diff 117 | } 118 | 119 | /** 120 | * Tells parent window to display iframe 121 | */ 122 | async displayIframe() { 123 | return this.display(checkIsMobile()) 124 | } 125 | 126 | /** 127 | * Tells parent window to hide iframe 128 | */ 129 | async hideIframe() { 130 | const root = document.getElementById('root') 131 | if (root) root.innerHTML = `` 132 | return this.hide() 133 | } 134 | 135 | /** 136 | * Removes cache consents. For partial migration in instance consent function 137 | * returns, but external auth to support consents fails. Refactored in future. 138 | * 139 | * @private 140 | * @param {Object} message IDW rpc request message 141 | * @param {String} domain Origin of caller/request 142 | * @return {ThreeId} 143 | */ 144 | _removeConsents(message, domain) { 145 | const spaces = [...message.params.spaces] 146 | const rootKeys = store.get(serializedKey(message.params.address)) 147 | //TODO current root 'space', name 148 | if (!rootKeys) spaces.push('undefined') 149 | spaces.forEach(space => { 150 | const key = consentKey(message.params.address, domain, space) 151 | store.remove(key) 152 | }) 153 | } 154 | 155 | /** 156 | * Start identity wallet service. Once returns ready to receive rpc requests 157 | * 158 | * @param {Web3Modal} web3Modal configured instance of web3modal 159 | * @param {Function} getConsent get consent function, reference IDW 160 | * @param {Function} erroCB Function to handle errors, function consumes error string (err) => {...}, called on errors 161 | * @param {Function} cancel Function to cancel request, consumes callback, which is called when request is cancelled (cb) => {...} 162 | */ 163 | start(getConsent, errorCb, cancel) { 164 | this.cancel = cancel 165 | this.errorCb = errorCb 166 | this.idWallet = new IdentityWallet(getConsent, { externalAuth: this.externalAuth.bind(this) }) 167 | this.provider = this.idWallet.get3idProvider() 168 | expose('send', this.providerRelay.bind(this), {postMessage: window.parent.postMessage.bind(window.parent)}) 169 | } 170 | 171 | /** 172 | * Consumes IDW RPC request message and relays to IDW instance. Also handles 173 | * logic to retry requests and cancel requests. 174 | * 175 | * @param {Object} message IDW RPC request message 176 | * @return {String} response message string 177 | */ 178 | async providerRelay(message) { 179 | const domain = new Url(document.referrer).host 180 | 181 | const responsePromise = new Promise(async (resolve, reject) => { 182 | // Register request cancel calback 183 | // TODO could make all rpc errors match spec 184 | this.cancel(()=> { 185 | const res = { 186 | 'id': message.id, 187 | 'json-rpc': '2.0', 188 | error: "3id-connect: Request not authorized" 189 | } 190 | resolve(res) 191 | }) 192 | 193 | if (message.method === '3id_authenticate') { 194 | try { 195 | const res = await this.provider.send(message, domain) 196 | this.hideIframe() 197 | resolve(res) 198 | } catch (e) { 199 | this.errorCb(e, 'Error: Unable to connect') 200 | this._removeConsents(message, domain) 201 | } 202 | } else { 203 | const res = await this.provider.send(message, domain) 204 | resolve(res) 205 | } 206 | }) 207 | 208 | return JSON.stringify(await responsePromise) 209 | } 210 | } 211 | 212 | export default ThreeIdConnectService 213 | -------------------------------------------------------------------------------- /iframe/css/style.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | 3 | @font-face { 4 | font-family: 'JetBrains'; 5 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/web/eot/JetBrainsMono-Regular.eot') format('embedded-opentype'), 6 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/web/woff2/JetBrainsMono-Regular.woff2') format('woff2'), 7 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/web/woff/JetBrainsMono-Regular.woff') format('woff'), 8 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/ttf/JetBrainsMono-Regular.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | button, 20 | p, { 21 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 22 | } 23 | 24 | h1, 25 | h2, 26 | h3, 27 | h4, 28 | h5, 29 | h6, 30 | p { 31 | padding: 0; 32 | margin: 0; 33 | } 34 | 35 | // p, span { 36 | // font-family: 'JetBrains'; 37 | // } 38 | 39 | button { 40 | width: 100%; 41 | height: 40px; 42 | border-radius: $radius; 43 | font-weight: 700; 44 | display: flex; 45 | justify-content: center; 46 | align-items: center; 47 | border: none; 48 | box-shadow: 2px 3px 8px 0px #00000025; 49 | 50 | &:active:focus { 51 | outline: none !important; 52 | border: none; 53 | box-shadow: inset 1px 1px 4px #0000001a; 54 | } 55 | 56 | &:active { 57 | outline: none !important; 58 | border: none; 59 | box-shadow: 2px 3px 8px 0px #00000025; 60 | } 61 | 62 | &:focus { 63 | outline: none !important; 64 | border: none; 65 | box-shadow: 2px 3px 8px 0px #00000025; 66 | } 67 | } 68 | 69 | .primaryButton { 70 | color: $threeBoxBlue; 71 | background-color: white; 72 | border: none; 73 | } 74 | 75 | button { 76 | svg { 77 | width: 24px; 78 | height: 24px; 79 | margin-left: 6px; 80 | } 81 | } 82 | 83 | @keyframes slideInFromLeft { 84 | 0% { 85 | transform: translateX(100%); 86 | } 87 | 100% { 88 | transform: translateX(0); 89 | } 90 | 91 | } 92 | 93 | @keyframes slideInFromBottom { 94 | 0% { 95 | transform: translateY(100%); 96 | } 97 | 100% { 98 | transform: translateY(0); 99 | } 100 | 101 | } 102 | 103 | .card { 104 | width: 420px; 105 | border: 1px solid $borderColor; 106 | border-radius: $radius; 107 | display: flex; 108 | flex-direction: column; 109 | justify-content: space-between; 110 | background-color: white; 111 | box-shadow: 0px 4px 10px 1px #00000025; 112 | margin-top: 10px; 113 | margin-left: 10px; 114 | position: absolute; 115 | } 116 | 117 | @mixin slide { 118 | animation-duration: 0.7s; 119 | animation-timing-function: ease-out; 120 | animation-delay: 0s; 121 | animation-iteration-count: 1; 122 | } 123 | 124 | .slideLeft { 125 | @include slide; 126 | animation-name: slideInFromLeft; 127 | } 128 | 129 | .slideBottom { 130 | @include slide; 131 | animation-name: slideInFromBottom; 132 | } 133 | 134 | 135 | .cardMobile { 136 | width: 100vw !important; 137 | height: 100vh !important; 138 | justify-content: flex-start !important; 139 | margin-top: 20px !important; 140 | margin-left: 0px !important; 141 | } 142 | 143 | .content { 144 | width: 100%; 145 | padding-top: 5px; 146 | } 147 | 148 | .contentMobile { 149 | display: flex !important; 150 | flex-direction: column !important; 151 | justify-content: space-between !important; 152 | } 153 | 154 | .controls { 155 | width: 100%; 156 | height: 55px; 157 | // border-bottom: 1px solid $borderColor; 158 | display: flex; 159 | justify-content: space-between; 160 | align-items: center; 161 | padding: 0 14px; 162 | 163 | .return { 164 | padding: 10px; 165 | 166 | &:hover { 167 | cursor: pointer; 168 | } 169 | } 170 | 171 | .controls_logo { 172 | display: flex; 173 | justify-content: flex-start; 174 | align-items: center; 175 | 176 | svg { 177 | height: 30px; 178 | width: 30px; 179 | cursor: pointer; 180 | // transition: $transition; 181 | 182 | &:hover { 183 | opacity: 1; 184 | } 185 | } 186 | 187 | span { 188 | margin-left: 15px; 189 | font-weight: 600; 190 | color: $textColor; 191 | font-size: 14px; 192 | } 193 | } 194 | 195 | .close { 196 | padding: 0 10px; 197 | display: flex; 198 | justify-content: center; 199 | align-items: center; 200 | 201 | &:hover { 202 | cursor: pointer; 203 | 204 | .close_line { 205 | background-color: $threeBoxBlack; 206 | } 207 | } 208 | 209 | .close_line { 210 | width: 2px; 211 | height: 16px; 212 | background-color: $symbolGrey; 213 | transform: rotate(45deg); 214 | } 215 | 216 | .close_line.flip { 217 | position: absolute; 218 | transform: rotate(135deg) !important; 219 | } 220 | } 221 | } 222 | 223 | .header { 224 | width: 100%; 225 | display: flex; 226 | flex-direction: column; 227 | justify-content: center; 228 | align-items: center; 229 | } 230 | 231 | .headerLogo { 232 | width: 50px; 233 | height: 50px; 234 | border-radius: $radius; 235 | background-color: transparent; 236 | box-shadow: $dropShadow; 237 | margin-top: 25px; 238 | } 239 | 240 | .headerLogo_empty { 241 | width: 50px; 242 | height: 50px; 243 | border-radius: $radius; 244 | background-color: $threeBoxBlue; 245 | box-shadow: $dropShadow; 246 | } 247 | 248 | .walletSelect_error { 249 | color: $threeBoxRed; 250 | font-size: 13px; 251 | font-weight: 600; 252 | height: 40px; 253 | padding-top: 11px; 254 | } 255 | 256 | .headerText { 257 | padding: 30px 0px; 258 | width: 100%; 259 | 260 | .primary { 261 | color: #2a4afe; 262 | text-align: center; 263 | font-size: 20px; 264 | font-weight: 600; 265 | // font-family: 'Marcher'; 266 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 267 | } 268 | 269 | .sub { 270 | text-align: center; 271 | font-size: 14px; 272 | font-weight: 400; 273 | color: #585c60; 274 | } 275 | } 276 | 277 | .headerProfile { 278 | margin-top: 20px; 279 | width: 200px; 280 | min-height: 50px; 281 | background-color: white; 282 | border-radius: 25px; 283 | border: solid #dfdfdf 2px; 284 | margin-left: auto; 285 | margin-right: auto; 286 | 287 | .img { 288 | height: 41px; 289 | width: 41px; 290 | margin: 2px; 291 | background-color: black; 292 | border-radius: 23px; 293 | float: left; 294 | } 295 | 296 | .name { 297 | float: left; 298 | padding: 12px 15px; 299 | font-weight: 500; 300 | } 301 | } 302 | 303 | .walletSelect { 304 | border: 1px solid $borderColor; 305 | box-shadow: $dropShadow; 306 | height: 62px; 307 | display: flex; 308 | justify-content: space-between; 309 | align-items: center; 310 | width: 100%; 311 | margin-bottom: 36px; 312 | border-radius: $radius; 313 | cursor: pointer; 314 | padding: 6px; 315 | } 316 | 317 | .walletSelectMobile { 318 | margin-bottom: 86px; 319 | } 320 | 321 | .walletSelect_content { 322 | margin-left: 8px; 323 | width: 100%; 324 | display: flex; 325 | justify-content: flex-start; 326 | align-items: center; 327 | 328 | h5 { 329 | width: 100%; 330 | color: $threeBoxBlue; 331 | } 332 | 333 | svg { 334 | margin-right: 12px; 335 | margin-left: 6px; 336 | height: 28px; 337 | width: 30px; 338 | } 339 | } 340 | 341 | .actions { 342 | display: flex; 343 | flex-direction: column; 344 | align-items: center; 345 | justify-content: flex-start; 346 | width: 100%; 347 | padding: 0 30px 0px 30px; 348 | position: relative; 349 | } 350 | 351 | .promptBox { 352 | padding: 20px; 353 | width: 100%; 354 | min-height: 100px; 355 | display: flex; 356 | flex-direction: column; 357 | align-items: center; 358 | justify-content: center; 359 | } 360 | 361 | .promptText { 362 | .primaryText { 363 | color: #2a4afe 364 | } 365 | 366 | .subText { 367 | position: relative; 368 | 369 | span { 370 | color: $threeBoxBlue; 371 | } 372 | } 373 | } 374 | 375 | .promptHeader { 376 | width: 100%; 377 | float: left; 378 | } 379 | 380 | .promptText { 381 | padding: 15px 30px 30px 30px; 382 | 383 | .primaryText { 384 | color: #63686d; 385 | font-weight: 600; 386 | font-size: 15px; 387 | } 388 | 389 | .primaryHighlight { 390 | color: #2a4afe; 391 | } 392 | 393 | .subText { 394 | font-weight: 400; 395 | color: $textColor; 396 | font-size: 13px; 397 | } 398 | } 399 | 400 | .onClickOutside { 401 | display: none; 402 | width: 100vw; 403 | height: 100vh; 404 | position: absolute; 405 | left: 0; 406 | top: 0; 407 | } 408 | 409 | .divider { 410 | width: 100%; 411 | border-top: solid #dfdfdf 1px; 412 | } 413 | 414 | .providerBox { 415 | width: 100%; 416 | padding: 10px; 417 | display: none; 418 | flex-direction: column; 419 | position: absolute; 420 | background-color: white; 421 | border: 1px solid $borderColor; 422 | box-shadow: $dropShadow; 423 | top: -150px; 424 | border-radius: $radius; 425 | 426 | grid-template-columns: repeat(1, 100% [col-start]); 427 | grid-row-gap: 6px; 428 | z-index: 1; 429 | 430 | .providerRow { 431 | width: 100%; 432 | float: left; 433 | padding-top: 15px; 434 | } 435 | 436 | .provider { 437 | width: 100%; 438 | display: flex; 439 | justify-content: flex-start; 440 | align-items: center; 441 | border-radius: $radius; 442 | padding: 6px; 443 | 444 | &:hover { 445 | cursor: pointer; 446 | background-color: $hoverBackground; 447 | } 448 | 449 | .providerText { 450 | font-weight: 500; 451 | } 452 | } 453 | } 454 | 455 | .providerBoxMobile { 456 | top: -110px !important; 457 | } 458 | 459 | .providerImage { 460 | display: flex; 461 | justify-content: center; 462 | align-items: center; 463 | 464 | svg { 465 | background-color: transparent; 466 | width: 40px; 467 | height: 40px; 468 | border-radius: $radius; 469 | object-fit: contain; 470 | margin-right: 15px; 471 | } 472 | } 473 | 474 | .providerImageText { 475 | font-size: 18px; 476 | } 477 | 478 | .providerBox.open { 479 | display: flex; 480 | } 481 | 482 | .spaceLine { 483 | width: 100%; 484 | padding: 10px 10px; 485 | 486 | .spaceName {} 487 | 488 | .access { 489 | float: right; 490 | color: #2a4afe; 491 | font-weight: 500; 492 | } 493 | } 494 | 495 | .footerText { 496 | font-size: 9px; 497 | padding: 25px 30px 20px 30px; 498 | color: #817972a6; 499 | text-align: center; 500 | 501 | a { 502 | color: #817972a6; 503 | &:hover { 504 | text-decoration: underline; 505 | cursor: pointer; 506 | } 507 | } 508 | } 509 | 510 | .buttonFooter { 511 | width: 100%; 512 | float: left; 513 | margin-top: 17px; 514 | 515 | .btnAllow { 516 | background-color: #2a4afe; 517 | border-color: #2e6da4; 518 | color: #ffffff; 519 | float: right; 520 | padding: 6px 25px; 521 | } 522 | 523 | .btnDecline { 524 | color: #2a4afe; 525 | float: left; 526 | margin: 7px; 527 | 528 | &:hover { 529 | cursor: pointer; 530 | } 531 | } 532 | } 533 | 534 | .marginTop25 { 535 | margin-top: 25px; 536 | } 537 | 538 | .secondaryButton { 539 | background-color: white; 540 | border: none; 541 | } 542 | 543 | #requestBox {} 544 | 545 | #requestHeader {} 546 | 547 | // @media only screen and (max-width: 600px) { 548 | // // .card { 549 | // // width: 100vw; 550 | // // height: 100vh; 551 | // // border: none; 552 | // // justify-content: flex-start; 553 | // // } 554 | 555 | // // .providerBox { 556 | // // top: -110px; 557 | // // } 558 | 559 | // // .content { 560 | // // width: 100%; 561 | // // height: 80%; 562 | // // padding-top: 125px; 563 | // // display: flex; 564 | // // flex-direction: column; 565 | // // justify-content: space-between; 566 | // // } 567 | 568 | // // .walletSelect { 569 | // // margin-bottom: 86px; 570 | // // } 571 | // } 572 | --------------------------------------------------------------------------------