├── static ├── .gitkeep └── link.png ├── src ├── style │ └── hamburgers.scss ├── store │ ├── getters.js │ ├── mutations.js │ ├── index.js │ └── actions.js ├── assets │ ├── logo.png │ └── auth.js ├── components │ ├── Home.vue │ ├── TopBar.vue │ ├── SideBar.vue │ └── Room.vue ├── router │ └── index.js ├── api │ ├── web3.js │ └── firebase.js ├── main.js ├── App.vue └── lambda │ └── ethauth.js ├── test └── unit │ ├── setup.js │ ├── .eslintrc │ ├── specs │ └── HelloWorld.spec.js │ └── jest.conf.js ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── netlify.toml ├── _redirects ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── README.md ├── .eslintrc.js ├── LICENSE └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/style/hamburgers.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } -------------------------------------------------------------------------------- /static/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okwme/eac-chat/HEAD/static/link.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okwme/eac-chat/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | Command = "yarn build" 3 | Functions = "lambda" 4 | Publish = "dist" 5 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | /ethauth https://dupgv1xzc7.execute-api.us-east-2.amazonaws.com/default/ether-auth 200 2 | /* /index.html 200 -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/assets/auth.js: -------------------------------------------------------------------------------- 1 | export const signingParams = [ 2 | { 3 | type: 'string', 4 | name: 'Message', 5 | value: 'Hello World. By signing this message you are proving that you are in control of this account.' 6 | } 7 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | .env 17 | serviceAccountKey.json 18 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ethereum authentication 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Room from '@/components/Room' 4 | import Home from '@/components/Home' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'Home', 14 | component: Home 15 | }, 16 | { 17 | path: '/:room', 18 | name: 'Room', 19 | component: Room, 20 | props: true 21 | } 22 | ] 23 | }) 24 | -------------------------------------------------------------------------------- /src/api/web3.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | import Web3 from 'web3' 3 | import { 4 | PortisProvider 5 | } from 'portis' 6 | 7 | if (window.ethereum) { 8 | window.web3 = new Web3(window.ethereum) 9 | } else if (window.web3) { 10 | window.web3 = new Web3(window.web3.currentProvider) 11 | } else { 12 | window.web3 = new Web3( 13 | new PortisProvider({ 14 | apiKey: '4f10a8de9f9fd31b1d6caa907759c00f', 15 | network: 'mainnet' 16 | }) 17 | ) 18 | } 19 | 20 | export const web3 = window.web3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Authenticated Chat 2 | 3 | * Authenticate with an Ethereum wallet 4 | * Access via balances in ERC-20 or ERC-721 tokens 5 | * Blockchain data via amberdata.io 6 | * Chat via google firebase 7 | * Hosting via netlify 8 | * Authentication via AWS Lambda 9 | * Check out the github for more info -------------------------------------------------------------------------------- /src/api/firebase.js: -------------------------------------------------------------------------------- 1 | 2 | import firebase from 'firebase/app' 3 | import 'firebase/auth' 4 | import 'firebase/database' 5 | 6 | var config = { 7 | apiKey: 'AIzaSyAdK9qrNj1G6abHZiYkyDvdJ3pajUnBtS8', 8 | authDomain: 'eac-chat-ddecb.firebaseapp.com', 9 | databaseURL: 'https://eac-chat-ddecb.firebaseio.com', 10 | projectId: 'eac-chat-ddecb', 11 | storageBucket: 'eac-chat-ddecb.appspot.com', 12 | messagingSenderId: '453164258119' 13 | } 14 | 15 | firebase.initializeApp(config) 16 | 17 | export const db = firebase.database() 18 | // export const db = firebase.firestore() 19 | // Disable deprecated features 20 | // db.settings({ 21 | // timestampsInSnapshots: true 22 | // }) 23 | 24 | 25 | export const fb = firebase -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | 'plugin:vue/essential', 10 | ], 11 | "parserOptions": { 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-console": [ 16 | 0 17 | ], 18 | "indent": [ 19 | "error", 20 | 2 21 | ], 22 | "linebreak-style": [ 23 | "error", 24 | "unix" 25 | ], 26 | "quotes": [ 27 | "error", 28 | "single" 29 | ], 30 | "semi": [ 31 | "error", 32 | "never" 33 | ] 34 | } 35 | }; -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 18 | setupFiles: ['/test/unit/setup'], 19 | mapCoverage: true, 20 | coverageDirectory: '/test/unit/coverage', 21 | collectCoverageFrom: [ 22 | 'src/**/*.{js,vue}', 23 | '!src/main.js', 24 | '!src/router/index.js', 25 | '!**/node_modules/**' 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default { 4 | SET_CLAIMS(state, claims) { 5 | Vue.set(state, 'claims', claims) 6 | }, 7 | SET_TOKENS(state, tokens) { 8 | state.tokens = tokens 9 | }, 10 | SET_AUTH(state, bool) { 11 | state.auth = bool 12 | }, 13 | SET_SIGNATURE(state, sig) { 14 | state.signature = sig 15 | }, 16 | SET_ACCOUNT(state, acct) { 17 | state.account = acct 18 | }, 19 | SET_CHAT(state, data) { 20 | state.chats = data 21 | }, 22 | ADD_CHAT(state, data) { 23 | state.chats.push(data) 24 | }, 25 | SET_SUBSCRIPTION(state, subscription) { 26 | state.subscription = subscription 27 | }, 28 | SET_NAME(state, name) { 29 | state.chatName = name 30 | }, 31 | SET_BLOCKIE(state, {address, blockie}) { 32 | Vue.set(state.blockies, address, blockie) 33 | }, 34 | SET_HIDE(state, bool) { 35 | state.hide = bool 36 | } 37 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import VuexPersistence from 'vuex-persist' 4 | 5 | import actions from './actions' 6 | import getters from './getters' 7 | import mutations from './mutations' 8 | 9 | Vue.use(Vuex) 10 | 11 | const debug = process.env.NODE_ENV !== 'production' 12 | 13 | const vuexLocal = new VuexPersistence({ 14 | storage: window.localStorage, 15 | reducer: (state) => ({ 16 | chatName: state.chatName, 17 | account: state.account, 18 | blockies: state.blockies, 19 | signature: state.signature 20 | }), 21 | }) 22 | 23 | const state = { 24 | hide: true, 25 | chatName: null, 26 | auth: false, 27 | account: false, 28 | signature: false, 29 | tokens: [], 30 | claims: {}, 31 | chats: [], 32 | subscription: null, 33 | blockies: {} 34 | } 35 | 36 | export default new Vuex.Store({ 37 | state, 38 | getters, 39 | actions, 40 | mutations, 41 | strict: debug, 42 | plugins: [vuexLocal.plugin] 43 | }) -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import store from './store' 5 | import {fb} from '@/api/firebase' 6 | import App from './App' 7 | import router from './router' 8 | import VueChatScroll from 'vue-chat-scroll' 9 | 10 | Vue.use(VueChatScroll) 11 | Vue.config.productionTip = false 12 | 13 | fb.auth().onAuthStateChanged((user) => { 14 | if (user) { 15 | if (store.state.account && store.state.signature && !store.state.auth) { 16 | store.dispatch('verify') 17 | } 18 | // store.commit('SET_AUTH', true) 19 | // console.log('user is ', user) 20 | // User is signed in. 21 | } else if (store.state.auth) { 22 | store.dispatch('logout') 23 | } 24 | }) 25 | 26 | 27 | /* eslint-disable no-new */ 28 | new Vue({ 29 | el: '#app', 30 | router, 31 | store, 32 | components: { 33 | App 34 | }, 35 | template: '' 36 | }) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 billy rennekamp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 22 | 23 | 64 | -------------------------------------------------------------------------------- /src/components/TopBar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 59 | 60 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/ethauth': { 15 | target: 'http://localhost:9000/ethauth', 16 | // target: 'https://dupgv1xzc7.execute-api.us-east-2.amazonaws.com/default/ether-auth', 17 | changeOrigin: true 18 | } 19 | }, 20 | 21 | // Various Dev Server settings 22 | host: 'localhost', // can be overwritten by process.env.HOST 23 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 24 | autoOpenBrowser: false, 25 | errorOverlay: true, 26 | notifyOnErrors: true, 27 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 28 | 29 | 30 | /** 31 | * Source Maps 32 | */ 33 | 34 | // https://webpack.js.org/configuration/devtool/#development 35 | devtool: 'cheap-module-eval-source-map', 36 | 37 | // If you have problems debugging vue-files in devtools, 38 | // set this to false - it *may* help 39 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 40 | cacheBusting: true, 41 | 42 | cssSourceMap: true 43 | }, 44 | 45 | build: { 46 | // Template for index.html 47 | index: path.resolve(__dirname, '../dist/index.html'), 48 | 49 | // Paths 50 | assetsRoot: path.resolve(__dirname, '../dist'), 51 | assetsSubDirectory: 'static', 52 | assetsPublicPath: '/', 53 | 54 | /** 55 | * Source Maps 56 | */ 57 | 58 | productionSourceMap: true, 59 | // https://webpack.js.org/configuration/devtool/#production 60 | devtool: '#source-map', 61 | 62 | // Gzip off by default as many popular static hosts such as 63 | // Surge or Netlify already gzip all static assets for you. 64 | // Before setting to `true`, make sure to: 65 | // npm install --save-dev compression-webpack-plugin 66 | productionGzip: false, 67 | productionGzipExtensions: ['js', 'css'], 68 | 69 | // Run the build command with an extra argument to 70 | // View the bundle analyzer report after build finishes: 71 | // `npm run build --report` 72 | // Set to `true` or `false` to always turn it on or off 73 | bundleAnalyzerReport: process.env.npm_config_report 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 53 | 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eac-chat", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "node build/build.js && cp _redirects dist/_redirects", 7 | "test": "npm run unit", 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "unit": "jest --config test/unit/jest.conf.js --coverage" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "dotenv": "^6.1.0", 15 | "eth-sig-util": "1.4.2", 16 | "ethereum-blockies-png": "^0.1.3", 17 | "ethjs": "^0.4.0", 18 | "firebase": "^5.5.8", 19 | "firebase-admin": "^6.1.0", 20 | "hamburgers": "^1.1.3", 21 | "markdown-loader": "^4.0.0", 22 | "portis": "^1.2.18", 23 | "pug": "^2.0.3", 24 | "sass": "^1.15.1", 25 | "vue": "^2.5.2", 26 | "vue-chat-scroll": "^1.3.3", 27 | "vue-router": "^3.0.1", 28 | "vuex": "^3.0.1", 29 | "vuex-persist": "^2.0.0", 30 | "web3": "^1.0.0-beta.36", 31 | "xss": "^1.0.3" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "^7.1.2", 35 | "babel-core": "^6.22.1", 36 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 37 | "babel-jest": "^21.0.2", 38 | "babel-loader": "^7.1.1", 39 | "babel-plugin-dynamic-import-node": "^1.2.0", 40 | "babel-plugin-syntax-jsx": "^6.18.0", 41 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 42 | "babel-plugin-transform-runtime": "^6.22.0", 43 | "babel-plugin-transform-vue-jsx": "^3.5.0", 44 | "babel-preset-env": "^1.3.2", 45 | "babel-preset-stage-2": "^6.22.0", 46 | "chalk": "^2.0.1", 47 | "copy-webpack-plugin": "^4.0.1", 48 | "css-loader": "^0.28.0", 49 | "eslint": "^5.9.0", 50 | "eslint-plugin-vue": "^5.0.0-beta.4", 51 | "extract-text-webpack-plugin": "^3.0.0", 52 | "file-loader": "^1.1.4", 53 | "friendly-errors-webpack-plugin": "^1.6.1", 54 | "html-loader": "^0.5.5", 55 | "html-webpack-plugin": "^2.30.1", 56 | "jest": "^22.0.4", 57 | "jest-serializer-vue": "^0.3.0", 58 | "node-notifier": "^5.1.2", 59 | "node-sass": "^4.10.0", 60 | "optimize-css-assets-webpack-plugin": "^3.2.0", 61 | "ora": "^1.2.0", 62 | "portfinder": "^1.0.13", 63 | "postcss-import": "^11.0.0", 64 | "postcss-loader": "^2.0.8", 65 | "postcss-url": "^7.2.1", 66 | "rimraf": "^2.6.0", 67 | "sass-loader": "^7.1.0", 68 | "semver": "^5.3.0", 69 | "shelljs": "^0.7.6", 70 | "uglifyjs-webpack-plugin": "^1.1.1", 71 | "url-loader": "^0.5.8", 72 | "vue-cli-plugin-netlify-lambda": "^0.1.0", 73 | "vue-jest": "^1.0.2", 74 | "vue-loader": "^13.3.0", 75 | "vue-style-loader": "^3.0.1", 76 | "vue-template-compiler": "^2.5.2", 77 | "webpack": "^3.6.0", 78 | "webpack-bundle-analyzer": "^2.9.0", 79 | "webpack-dev-server": "^2.9.1", 80 | "webpack-merge": "^4.1.0" 81 | }, 82 | "browserslist": [ 83 | "> 1%", 84 | "last 2 versions", 85 | "not ie <= 8" 86 | ], 87 | "globals": { 88 | "require": true, 89 | "process": true, 90 | "global": true, 91 | "module": true, 92 | "web3": true, 93 | "ga": true, 94 | "fetch": true 95 | }, 96 | "description": "Ethereum Access Control Chat", 97 | "author": "Billy Rennekamp ", 98 | "engines": { 99 | "node": ">= 6.0.0", 100 | "npm": ">= 3.0.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/Room.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | 73 | -------------------------------------------------------------------------------- /src/lambda/ethauth.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | import sigUtil from 'eth-sig-util' 3 | import { signingParams } from '../assets/auth' 4 | import axios from 'axios' 5 | 6 | function atob(a) { 7 | return new Buffer(a, 'base64').toString('binary') 8 | } 9 | // function btoa(b) { 10 | // return new Buffer(b).toString('base64'); 11 | // }; 12 | // setup firebase w serviceAccountKey.json 13 | var admin = require('firebase-admin') 14 | // const serviceAccount = JSON.parse(process.env.FIREBASE_CERT) 15 | // const serviceAccount = require('../../serviceAccountKey.json') 16 | const serviceAccount = { 17 | } 18 | Object.keys(process.env).forEach((k) => { 19 | console.log(k) 20 | if (k.substr(0,3) === 'FB_') { 21 | console.log(k, k === 'FB_private_key') 22 | serviceAccount[k.replace('FB_', '')] = k === 'FB_private_key' ? atob(process.env[k]) : process.env[k] 23 | } 24 | }) 25 | console.log('serviceAccount!!!', serviceAccount) 26 | // console.log('btoa', btoa(serviceAccount.private_key)) 27 | admin.initializeApp({ 28 | credential: admin.credential.cert(serviceAccount), 29 | databaseURL: 'https://eac-chat-ddecb.firebaseio.com' 30 | }) 31 | 32 | const mainnet = 1 33 | const headers = { 34 | 'Access-Control-Allow-Origin': '*', 35 | 'Access-Control-Allow-Headers': 36 | 'Origin, X-Requested-With, Content-Type, Accept', 37 | } 38 | 39 | export const handler = function(event, context, callback) { 40 | console.log('begin') 41 | if (event.httpMethod !== 'POST') { 42 | return callback(null, { 43 | headers, 44 | statusCode: 200, 45 | body: JSON.stringify({msg: 'Nope'}) 46 | }) 47 | } 48 | console.log('is a post') 49 | 50 | // get POST values 51 | let data = JSON.parse(event.body) 52 | let network = data.network 53 | 54 | // recover address used to sign 55 | let recovered 56 | try { 57 | recovered = sigUtil.recoverTypedSignature({ 58 | data: signingParams, 59 | sig: data.signature 60 | }) 61 | } catch(error) { 62 | return callback(error) 63 | } 64 | console.log('3') 65 | 66 | // confirm address and signature match (unnecessary) 67 | if (recovered.toLowerCase() !== data.account.toLowerCase()) { 68 | return callback(null, { 69 | headers, 70 | statusCode: 500, 71 | body: 'signature doesnt match account' 72 | }) 73 | } 74 | console.log('confirm address and signature match (unnecessary)') 75 | 76 | // get token balances using the proper network id 77 | axios({ 78 | url: 'https://web3api.io/api/v1/addresses/' + recovered + '/tokens', 79 | method: 'GET', 80 | headers: { 81 | 'accept': 'application/json', 82 | 'x-amberdata-api-key': network === mainnet ? process.env.AMBER_DATA_MAINNET_KEY : process.env.AMBER_DATA_RINKEBY_KEY, 83 | 'x-amberdata-blockchain-id': network === mainnet ? process.env.AMBER_DATA_MAINNET : process.env.AMBER_DATA_RINKEBY 84 | } 85 | // authenticate the user with firebase and give them token specific permissions 86 | }).then((response) => { 87 | console.log('authenticate the user with firebase and give them token specific permissions') 88 | // network specific firebase user id 89 | var uid = recovered + '-' + (network === mainnet ? '1' : '4') 90 | 91 | // add access to lobby 92 | let additionalClaims = {lobby: true} 93 | 94 | // add access to tokens with balance > 0 95 | response.data.payload.records.forEach(r => { 96 | additionalClaims[r.address] = parseInt(r.balance) > 0 //&& r.symbol !== '' 97 | }) 98 | 99 | // create token with additional claims 100 | admin.auth().createCustomToken(uid, additionalClaims).then((customToken) => { 101 | console.log('create token with additional claims') 102 | let body = JSON.stringify({ 103 | customToken, 104 | data: response.data, 105 | additionalClaims 106 | }) 107 | 108 | // send back custom token, token balances and additional claims 109 | callback(null, { 110 | headers, 111 | statusCode: '200', 112 | body 113 | }) 114 | }).catch((error) => { 115 | console.log('Error creating custom token:', error) 116 | callback(error) 117 | }) 118 | }).catch(error => { 119 | console.log('Axios error') 120 | callback(error) 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | import {fb, db} from '@/api/firebase' 2 | import {web3} from '@/api/web3' 3 | 4 | const blockies = require('ethereum-blockies-png') 5 | import axios from 'axios' 6 | 7 | import {signingParams} from '@/assets/auth' 8 | const debug = true 9 | export default { 10 | makeBlockie({commit}, address) { 11 | let blockie = blockies.createDataURL({ 12 | size: 3, 13 | scale: 12, 14 | // bgcolor : '#ffffff', 15 | // color: '#000000', 16 | // spotcolor: '#808080', 17 | seed: address.split('-')[0] 18 | }) 19 | commit('SET_BLOCKIE', {address, blockie}) 20 | return blockie 21 | }, 22 | getAccount({commit}) { 23 | if (debug) console.log('getAccount') 24 | return new Promise((resolve ,reject) => { 25 | if (!window.web3) return reject('no web3') 26 | window.web3.eth.getAccounts((err, accounts) => { 27 | if (err) { 28 | return reject(err) 29 | } else if (accounts.length) { 30 | commit('SET_ACCOUNT', accounts[0]) 31 | } 32 | resolve() 33 | }) 34 | }) 35 | }, 36 | getSignature ({state, commit}) { 37 | if (debug) console.log('getSignature') 38 | return new Promise((resolve, reject) => { 39 | global.web3.currentProvider.sendAsync( 40 | { 41 | method: 'eth_signTypedData', 42 | params: [signingParams, state.account], 43 | from: state.account 44 | }, (err, { result }) => { 45 | if (err) { 46 | reject(err) 47 | } else { 48 | commit('SET_SIGNATURE', result) 49 | resolve() 50 | } 51 | } 52 | ) 53 | }) 54 | }, 55 | async approve() { 56 | if (debug) console.log('approve') 57 | if (window.ethereum) { 58 | try { 59 | await window.ethereum.enable() 60 | } catch (error) { 61 | console.error(error) 62 | } 63 | } 64 | }, 65 | async logout({commit}) { 66 | if(debug) console.log('logout') 67 | try { 68 | await fb.auth().signOut() 69 | commit('SET_ACCOUNT', false) 70 | commit('SET_SIGNATURE', false) 71 | commit('SET_AUTH', false) 72 | commit('SET_CLAIMS', {}) 73 | commit('SET_TOKENS', []) 74 | commit('SET_CHAT', []) 75 | commit('SET_NAME', null) 76 | } catch (error) { 77 | console.error(error) 78 | } 79 | }, 80 | async verify({state, commit}) { 81 | if (debug) console.log('verify') 82 | let network = await web3.eth.net.getId() 83 | // let resp = await axios.post('/ethauth', { 84 | let resp = await axios.post('https://dupgv1xzc7.execute-api.us-east-2.amazonaws.com/default/ether-auth', { 85 | network, 86 | account: state.account, 87 | signature: state.signature 88 | }) 89 | await fb.auth().signInWithCustomToken(resp.data.customToken) 90 | commit('SET_HIDE', false) 91 | commit('SET_AUTH', true) 92 | commit('SET_CLAIMS', resp.data.additionalClaims) 93 | commit('SET_TOKENS', resp.data.data.payload) 94 | }, 95 | addChat({state}, {id, chat}) { 96 | if(debug) console.log('addChat') 97 | let user = fb.auth().currentUser 98 | if (user) chat.user_id = user.uid 99 | if (!chat.name) chat.name = state.account.substr(0,7) 100 | db.ref('rooms/' + id).push(chat) 101 | }, 102 | async launch({state, dispatch}, room) { 103 | if (debug) console.log('launch ' + room) 104 | try { 105 | dispatch('stopChat', room) 106 | await dispatch('startChat', room) 107 | } catch (error) { 108 | console.log(error) 109 | setTimeout(() => { 110 | if (!state.auth || state.claims[room]) dispatch('launch', room) 111 | }, 2000) 112 | } 113 | }, 114 | stopChat(_, id) { 115 | if(debug) console.log('stopChat') 116 | db.ref('rooms/' + id).off() 117 | }, 118 | async startChat({commit}, id) { 119 | if(debug) console.log('startChat', 'rooms/' + id) 120 | commit('SET_CHAT', []) 121 | // let chats = db.collection('rooms').doc(id) 122 | // chats.get().then((doc) => { 123 | // if (!doc.exists) { 124 | // console.log('doc doesn\'t exist') 125 | // return 126 | // } 127 | // console.log(doc) 128 | // console.log(doc.data()) 129 | // // commit('SET_CHAT', data) 130 | // }) 131 | // chats.onSnapshot((doc) => { 132 | // console.log(doc.data()) 133 | // }) 134 | let newRoom = true 135 | let isFirst = true 136 | try { 137 | let data = await db.ref('rooms/' + id).limitToLast(100).once('value') 138 | if (data.val()) { 139 | newRoom = false 140 | commit('SET_CHAT', Object.values(data.val())) 141 | } 142 | db.ref('rooms/' + id).limitToLast(1).on('child_added', function(data) { 143 | if (newRoom || !isFirst) commit('ADD_CHAT', data.val()) 144 | if (isFirst) isFirst = false 145 | }) 146 | } catch (error) { 147 | return Promise.reject(error) 148 | } 149 | 150 | 151 | // chats.once('value').then((data) => { 152 | // console.log('value', data.val()) 153 | // // commit('SET_CHAT', data) 154 | // }) 155 | // chats.on('child_added', function(data) { 156 | // console.log('child_added', data.val()) 157 | // commit('ADD_CHAT', data.val()) 158 | // }) 159 | // chats.on('child_changed', function(data) { 160 | // console.log('child_changed', data.val()) 161 | // // commit('EDIT_CHAT', data) 162 | // }) 163 | // chats.on('child_removed', function(data) { 164 | // console.log('child_removed', data.val()) 165 | // // commit('REMOVE_CHAT', data) 166 | // }) 167 | } 168 | } --------------------------------------------------------------------------------