├── 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 |
2 | #home(v-html="readme")
3 |
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 |
2 | #app
3 | top-bar
4 | #divide
5 | side-bar
6 | .router-view
7 | router-view
8 |
9 |
22 |
23 |
64 |
--------------------------------------------------------------------------------
/src/components/TopBar.vue:
--------------------------------------------------------------------------------
1 |
2 | #topBar
3 | button#toggle-menu(
4 | @click="$store.commit('SET_HIDE', !hide)"
5 | :class="{'is-active': !hide}"
6 | class="hamburger hamburger--arrow"
7 | type="button")
8 | span(class="hamburger-box")
9 | span(class="hamburger-inner")
10 | div
11 | button#auth(@click="authorize") {{ auth ? 'Logout' : 'Start'}}
12 |
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 |
2 | #sideBar(:class="{hide:hide}")
3 | div
4 | h1
5 | router-link(to='/') Welcome
6 | #room-list
7 | div(v-if="auth")
8 | span(@click="launch('lobby')")
9 | router-link(to='lobby') Lobby
10 | div(
11 | v-for="token in rooms" :key="token.address"
12 | :class="{'ERC20': token.isERC20,'ERC721': token.isERC721}")
13 | span(@click="launch(token.address)")
14 | router-link(:to="token.address" :title="token.name" ) {{token.symbol ? token.symbol.substr(0, 20) : token.address.substr(0,6)}}
15 | a(:href="'https://etherscan.io/token/' + token.address + '?a=' + account" target="_blank")
16 | img(src="/static/link.png")
17 | #nameInput(v-if="auth")
18 | input#chatName(v-model="displayName" placeholder="Name" maxlength="7" type="text")
19 | //- code {{token}}
20 |
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 |
2 | #room
3 | #previous-chats(v-chat-scroll="{always: false, smooth: true}")
4 | .chat(v-for="chat in chats")
5 | .icon
6 | a(:title="chat.user_id" target="_blank" :href="'https://etherscan.io/address/' + chat.user_id.split('-')[0]")
7 | img(:src="getsrc(chat.user_id)")
8 | span.username
9 | b(v-text="xss(chat.name.substr(0,7))" )
10 | span.message
11 | div(v-text="xss(chat.message)")
12 | #new-chats(v-show="auth")
13 | input#chatInput(
14 | @submit="sendChat"
15 | v-model="chatInput"
16 | @keyup.enter="sendChat"
17 | maxlength="255"
18 | placeholder="Message"
19 | type="text")
20 | input(
21 | type="submit"
22 | @click="sendChat"
23 | value="Send"
24 | )
25 |
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 | }
--------------------------------------------------------------------------------