Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial version of miniwallet server #7

Closed
wants to merge 8 commits into from
36 changes: 36 additions & 0 deletions miniserver/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module.exports = {
root: true,
env: {
node: true,
es2020: true,
jest: true,
},
extends: [
'standard',
],
globals: {
artifacts: 'readonly',
contract: 'readonly',
assert: 'readonly',
ethers: 'readonly'
},
// 'parser': '@babel/eslint-parser',
rules: {
'no-await-in-loop': 0,
'no-underscore-dangle': 0,
'import/prefer-default-export': 0,
'import/no-extraneous-dependencies': 1,
'comma-dangle': 0,
'no-console': 0,
'no-mixed-operators': 0,
'new-cap': 0,
'max-len': 0,
},
parserOptions: {
requireConfigFile: false,
ecmaVersion: 2020
},
plugins: [
'@babel',
],
}
4 changes: 4 additions & 0 deletions miniserver/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/credentials
/certs/*
!/certs/README.md
!/certs/gen.sh
144 changes: 144 additions & 0 deletions miniserver/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
require('dotenv').config()
const createError = require('http-errors')
// const rateLimit = require('express-rate-limit')
const Fingerprint = require('express-fingerprint')
const express = require('express')
const path = require('path')
const cookieParser = require('cookie-parser')
const logger = require('morgan')
const config = require('./config')
const _index = require('./routes/index')
const bodyParser = require('body-parser')
const app = express()
const https = require('https')
const http = require('http')
const env = process.env.NODE_ENV || 'development'
const fs = require('fs')
const blockchain = require('./blockchain')

const swaggerUI = require('swagger-ui-express')
johnwhitton marked this conversation as resolved.
Show resolved Hide resolved
const swaggerJsDoc = require('swagger-jsdoc')
const url = config.url + ':' + config.httpsPort
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'MiniWallet Server',
version: '0.0.1',
description: 'API Layer to manage the miniwallet',
},
servers: [
{
url: url,
},
],
},
apis: ['./routes/*.js'],
}

if (config.apiDocs) {
const openapiSpecification = swaggerJsDoc(options)
app.use('/docs', swaggerUI.serve, swaggerUI.setup(openapiSpecification))
}

Error.stackTraceLimit = 100
app.locals.ENV = env
app.locals.ENV_DEVELOPMENT = env === 'development'

app.set('trust proxy', true)

try {
blockchain.init().catch(ex => {
console.error('Blockchain initialization failed')
console.error(ex)
process.exit(2)
})
} catch (ex) {
console.error(ex)
process.exit(1)
}

let httpServer, httpsServer

const httpsOptions = {
key: fs.readFileSync(config.https.key),
cert: fs.readFileSync(config.https.cert)
}

if (config.https.only) {
const httpApp = express()
const httpRouter = express.Router()
httpApp.use('*', httpRouter)
httpRouter.get('*', function (req, res) {
const hostPort = (req.get('host') || '').split(':')
const url = hostPort.length === 2 ? `https://${hostPort[0]}:${config.httpsPort}${req.originalUrl}` : `https://${hostPort[0]}${req.originalUrl}`
res.redirect(url)
})
httpServer = http.createServer(httpApp)
} else {
httpServer = http.createServer(app)
}

httpsServer = https.createServer(httpsOptions, app)

app.use(Fingerprint({
parameters: [
Fingerprint.useragent,
Fingerprint.acceptHeaders,
Fingerprint.geoip,
]
}))

app.use(bodyParser.json({
verify: function (req, _res, buf) {
req.rawBody = buf
}
}))
app.use(bodyParser.urlencoded({ extended: true }))
app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())

if (config.corsOrigins) {
app.use((req, res, next) => {
// res.header('Access-Control-Allow-Origin', config.corsOrigins)
if (config.corsOrigins === '*' || config.corsOrigins.indexOf(req.headers.origin) >= 0) {
res.header('Access-Control-Allow-Origin', req.headers.origin || config.corsOrigins)
} else {
res.header('Access-Control-Allow-Origin', config.corsOrigins)
}

res.header('Access-Control-Allow-Credentials', 'true')

res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS')

res.header('Access-Control-Allow-Headers', 'X-SECRET, X-NETWORK, X-MAJOR-VERSION, X-MINOR-VERSION, Accept, Accept-CH, Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Ext, Accept-Features, Accept-Language, Accept-Params, Accept-Ranges, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Request-Headers, Access-Control-Request-Method, Age, Allow, Alternates, Authentication-Info, Authorization, C-Ext, C-Man, C-Opt, C-PEP, C-PEP-Info, CONNECT, Cache-Control, Compliance, Connection, Content-Base, Content-Disposition, Content-Encoding, Content-ID, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Script-Type, Content-Security-Policy, Content-Style-Type, Content-Transfer-Encoding, Content-Type, Content-Version, Cookie, Cost, DAV, DELETE, DNT, DPR, Date, Default-Style, Delta-Base, Depth, Derived-From, Destination, Differential-ID, Digest, ETag, Expect, Expires, Ext, From, GET, GetProfile, HEAD, HTTP-date, Host, IM, If, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Keep-Alive, Label, Last-Event-ID, Last-Modified, Link, Location, Lock-Token, MIME-Version, Man, Max-Forwards, Media-Range, Message-ID, Meter, Negotiate, Non-Compliance, OPTION, OPTIONS, OWS, Opt, Optional, Ordering-Type, Origin, Overwrite, P3P, PEP, PICS-Label, POST, PUT, Pep-Info, Permanent, Position, Pragma, ProfileObject, Protocol, Protocol-Query, Protocol-Request, Proxy-Authenticate, Proxy-Authentication-Info, Proxy-Authorization, Proxy-Features, Proxy-Instruction, Public, RWS, Range, Referer, Refresh, Resolution-Hint, Resolver-Location, Retry-After, Safe, Sec-Websocket-Extensions, Sec-Websocket-Key, Sec-Websocket-Origin, Sec-Websocket-Protocol, Sec-Websocket-Version, Security-Scheme, Server, Set-Cookie, Set-Cookie2, SetProfile, SoapAction, Status, Status-URI, Strict-Transport-Security, SubOK, Subst, Surrogate-Capability, Surrogate-Control, TCN, TE, TRACE, Timeout, Title, Trailer, Transfer-Encoding, UA-Color, UA-Media, UA-Pixels, UA-Resolution, UA-Windowpixels, URI, Upgrade, User-Agent, Variant-Vary, Vary, Version, Via, Viewport-Width, WWW-Authenticate, Want-Digest, Warning, Width, X-Content-Duration, X-Content-Security-Policy, X-Content-Type-Options, X-CustomHeader, X-DNSPrefetch-Control, X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, X-Frame-Options, X-Modified, X-OTHER, X-PING, X-PINGOTHER, X-Powered-By, X-Requested-With')
next()
})
}

app.use(express.static(path.join(__dirname, 'public')))
app.options('*', async (_req, res) => res.end())
app.use('/', _index)

// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404))
})

// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message
res.locals.error = config.debug ? err : {}

// render the error page
res.status(err.status || 500)
res.json({ error: res.locals.error, message: err.message })
})

module.exports = {
httpServer,
httpsServer
}
16 changes: 16 additions & 0 deletions miniserver/bin/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node
const apps = require('../app')
const config = require('../config')
const httpsServer = apps.httpsServer
const httpServer = apps.httpServer
console.log('Starting web server...')

httpsServer.listen(config.httpsPort || 8443, () => {
const addr = httpsServer.address()
console.log(`HTTPS server listening on port ${addr.port} at ${addr.address}`)
})

httpServer.listen(config.port || 3000, () => {
const addr = httpServer.address()
console.log(`HTTP server listening on port ${addr.port} at ${addr.address}`)
})
31 changes: 31 additions & 0 deletions miniserver/blockchain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const config = require('./config')
const { ethers } = require('ethers')
const { Logger } = require('./logger')
const AssetManager = require('../miniwallet/build/contracts/AssetManager.sol/AssetManager.json')

const chainInfo = {}

const init = async () => {
Logger.log('Initializing blockchain for server')
try {
Logger.log(`config.defaultNetwork: ${config.defaultNetwork}`)
chainInfo.network = config.networks[config.defaultNetwork]
// Logger.log(`network: ${JSON.stringify(chainInfo.network)}`)
chainInfo.provider = ethers.getDefaultProvider(chainInfo.network.url)
chainInfo.signer = new ethers.Wallet(chainInfo.network.key, chainInfo.provider)
johnwhitton marked this conversation as resolved.
Show resolved Hide resolved
chainInfo.assetManager = new ethers.Contract(chainInfo.network.assetManagerAddress, AssetManager.abi, chainInfo.signer)
} catch (ex) {
console.error(ex)
console.trace(ex)
}
// Logger.log(`network: ${JSON.stringify(chainInfo.network)}`)
}

module.exports = {
init,
getchainInfo: () => chainInfo,
johnwhitton marked this conversation as resolved.
Show resolved Hide resolved
getNetwork: () => chainInfo.network,
getProvider: () => chainInfo.provider,
getSigner: () => chainInfo.signer,
getAssetManager: () => chainInfo.assetManager,
}
1 change: 1 addition & 0 deletions miniserver/certs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
For hosting temporary credentials. Run `./gen.sh` to generate a local dev certificate
4 changes: 4 additions & 0 deletions miniserver/certs/gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
!/bin/bash
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=sms-wallet.local" \
-keyout test.key -out test.cert
84 changes: 84 additions & 0 deletions miniserver/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require('dotenv').config()
const ethers = require('ethers')
const DEBUG = process.env.MINISERVER_DEBUG === 'true' || process.env.MINISERVER_DEBUG === '1'
const APIDOCS = process.env.APIDOCS === 'true' || process.env.APIDOCS === '1'
const config = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's clean this up to bare minimum

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated @polymorpher can you review again and see what else (if anything) should be removed 🙏

debug: DEBUG,
url: process.env.SERVER_URL || 'https://localhost',
port: process.env.PORT || 3000,
httpsPort: process.env.HTTPS_PORT || 8443,
apiDocs: APIDOCS,
relayerId: process.env.RELAYER_ID || 'unknown',
nullAddress: '0x0000000000000000000000000000000000000000',
verbose: process.env.VERBOSE === 'true' || process.env.VERBOSE === '1',
https: {
only: process.env.HTTPS_ONLY === 'true' || process.env.HTTPS_ONLY === '1',
key: DEBUG ? './certs/test.key' : './certs/privkey.pem',
cert: DEBUG ? './certs/test.cert' : './certs/fullchain.pem'
},
corsOrigins: process.env.CORS,
secret: process.env.SECRET,
safeNonce: process.env.SAFE_NONCE === '1' || process.env.SAFE_NONCE === 'true',
pollingInterval: parseInt(process.env.pollingInterval || 1000),
defaultNetwork: process.env.DEFAULT_NETWORK || 'eth-local',
networks: {
'harmony-testnet': {
key: process.env.HARMONY_TESTNET_KEY || '',
url: process.env.TESTNET_RPC || 'https://api.s0.b.hmny.io',
wss: process.env.TESTNET_WSS,
mnemonic: process.env.HARMONY_TESTNET_MNEMONIC,
skip: process.env.SKIP_TESTNE || true,
numAccounts: process.env.TESTNET_NUM_ACCOUNTS || 1,
blockTime: 2,
assetManagerAddress: process.env.TESTNET_ASSET_MANAGER,
},
'harmony-mainnet': {
key: process.env.HARMONY_MAINNET_KEY || '',
beacon: process.env.BEACON_MAINNET_RPC,
url: process.env.MAINNET_RPC || process.env.BEACON_MAINNET_RPC || 'https://api.s0.t.hmny.io',
wss: process.env.MAINNET_WSS || process.env.BEACON_MAINNET_WSS,
mnemonic: process.env.HARMONY_MAINNET_MNEMONIC,
skip: process.env.SKIP_MAINNET || true,
numAccounts: process.env.MAINNET_NUM_ACCOUNTS || 1,
blockTime: 2,
assetManagerAddress: process.env.MAINNET_ASSET_MANAGER,
},
'eth-local': {
url: process.env.ETH_LOCAL_RPC || 'http://127.0.0.1:8545',
wss: process.env.ETH_LOCAL_WSS,
key: process.env.ETH_LOCAL_KEY,
mnemonic: process.env.ETH_LOCAL_MNEMONIC,
skip: process.env.SKIP_ETH || true,
numAccounts: process.env.ETH_LOCAL_NUM_ACCOUNTS || 1,
assetManagerAddress: process.env.ETH_LOCAL_ASSET_MANAGER,
},
},
gasLimit: parseInt(process.env.GAS_LIMIT || '12345678'),
gasPrice: ethers.BigNumber.from(process.env.GAS_PRICE || '200'),
stats: {
// relevant to relayer root directory
path: process.env.STATS_PATH || '../data/stats.json'
},

datastore: {
gceProjectId: process.env.GCP_PROJECT,
cred: !process.env.GCP_CRED_PATH ? {} : require(process.env.GCP_CRED_PATH),
mock: !process.env.GCP_CRED_PATH,
mockPort: 9000,
namespace: process.env.GCP_NAMESPACE || 'sms-wallet-server'
},

twilio: {
sid: process.env.TWILIO_ACCOUNT_SID,
token: process.env.TWILIO_AUTH_TOKEN,
from: process.env.TWILIO_FROM,
},

otp: {
salt: process.env.OTP_SALT,
interval: parseInt(process.env.OTP_INTERVAL || 60000)
},
defaultSignatureValidDuration: 1000 * 60 * 15,
clientRoot: process.env.CLIENT_ROOT || 'https://smswallet.xyz',
}
module.exports = config
6 changes: 6 additions & 0 deletions miniserver/deploy/enable.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/node
sudo cp sms-miniwallet-server.service /etc/systemd/system/sms-miniwallet-server.service
sudo systemctl start sms-miniwallet-server
sudo systemctl enable sms-miniwallet-server
systemctl status sms-miniwallet-server
2 changes: 2 additions & 0 deletions miniserver/deploy/log.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
journalctl -u sms-miniwallet-server
8 changes: 8 additions & 0 deletions miniserver/deploy/port.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
!#/bin/sh
sudo iptables -A INPUT -i eth0 -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -i eth0 -p tcp --dport 3000 -j ACCEPT
sudo iptables -A INPUT -i eth0 -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -i eth0 -p tcp --dport 8443 -j ACCEPT
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443
sudo iptables --flush
15 changes: 15 additions & 0 deletions miniserver/deploy/sms-miniwallet-server.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=SMS MiniWallet Server
Documentation=https://github.com/polymorpher/sms-wallet/
After=network.target

[Service]
Environment=PORT=80 HTTPS_PORT=443
Type=simple
User=ec2-user
WorkingDirectory=/home/ec2-user/sms-wallet/miniserver
ExecStart=/usr/bin/node /home/ec2-user/sms-wallet/miniserver/bin/run.js
Restart=on-failure

[Install]
WantedBy=multi-user.target
8 changes: 8 additions & 0 deletions miniserver/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

const Logger = {
log: (...args) => {
console.log(...args)
}
}

module.exports = { Logger }
Loading