How to extend OpenDAX stack with your own application?
In this tutorial, we’re going to create a custom application and integrate it into OpenDAX Crypto exchange platform on a simple docker-compose solution.
Before you continue, familiarize with OpenDAX and run it locally.
Before we create the app we need to understand how the authorization and sharing session works. Barong uses JWT for managing authorization.
An authorization flow diagram is presented below:
On successful user login, a cookie will be set. After that, this cookie is attached to each request. Every given request is first routed to Barong, where cookie gets validated and transformed into JWT signed by Barong. This JWT is then routed to each respective component. The recipient component (e.g. Peatio or any app) needs to validate the JWT and check the signature.
After JWT is decoded, the app has access to this object.
{
iat: 1565687278,
exp: 1565693278,
sub: 'session',
iss: 'barong',
aud: [ 'peatio', 'barong' ],
jti: '1111111111',
uid: 'ID123123123',
email: 'admin@barong.io',
role: 'admin',
level: 3,
state: 'active',
referral_id: null
}
An already working example can be found here
For this tutorial, we’re going to extend Rubykube API with another component written in Node js. This is a simple component that will share the session with barong and return a user email from barong JWT on /api/users/me
- Create a new Express app:
➜ mkdir my_app
➜ cd my_app
➜ npm init --yes
➜ npm i express node-auth-barong
➜ touch app.js
2. Create an example Server:
Simple server file app.js
const express = require('express')
const barongJwt = require('@openware/node-auth-barong')
const app = express()
const port = 3000barongPublicKey = Buffer.from(process.env.BARONG_JWT_PUBLIC_KEY.trim(), 'base64').toString('utf-8')app.use(barongJwt({ barongJwtPublicKey: barongPublicKey }))app.get('/api/user/me', (req, res) => res.send(req.session.email))app.listen(port, () => console.log(`Example app listening on port ${port}!`))
3. Integrate it in opendax and test
- Create a Dockerfile for our app and build image
FROM node:11.14.0-stretch
ARG UID=1000
ARG GID=1000ENV APP_HOME=/home/app
# Create group "app" and user "app".RUN useradd --system --create-home --home ${APP_HOME} --shell /sbin/nologin --no-log-init appWORKDIR $APP_HOME
USER appCOPY --chown=app:app package.json package-lock.json $APP_HOME/# Install dependencies
RUN npm i# Copy the main application.
COPY --chown=app:app . $APP_HOMEEXPOSE 3000CMD ["node","app.js"]
docker build -t myapp:test .
- Create a compose entry in opendax
Let’s change our directory to opendax
cd ~/
git clone git@github.com:openware/opendax.git
cd opendax
Append the following block to templates/compose/app.yaml.erb
, by doing so we’ve created a new service called myapp. Make sure that indentation is correct.
myapp:
image: "<%= @config['images']['myapp'] %>"
env_file:
- ../config/myapp.env
ports:
- "3000:3000"
- Create an env file for our app
Let’s create a file templates/config/myapp.env.erb
, be aware of the name, because we reference it in compose file. This file should be created in the templates folder as it will get rendered by opendax.
BARONG_JWT_PUBLIC_KEY=<%= @jwt_public_key %>
This is the only line we need.
- Reference our app docker image in opendax/config/app.yml
app:
name: "OpenDAX"
domain: "app.local"
subdomain: "www"
ssl:
enabled: true
email: "support@example.com"
images:
peatio: rubykube/peatio:2.0.34
barong: rubykube/barong:2.0.51
frontend: rubykube/mikroapp:0.1.5
myapp: myapp:test # here we specify the tag we have build our image with
tower: rubykube/tower:0.1.8
postmaster: quay.io/openware/postmaster:0.0.3
arke: rubykube/arke:0.1.5
vendor:
frontend: https://github.com/rubykube/mikroapp.git
storage:
provider: "Google"
bucketName: "opendax-barong-docs-bucket"
accessKey: "**********"
secretKey: "**********"
terraform:
credentials: "~/safe/opendax.json"
project: "example-opendax"
region: "europe-west4"
zone: "europe-west4-a"
instance_name: "opendax-cloud"
machine_type: "n1-standard-4"
image: "debian-cloud/debian-9"
twilio:
phone_number: "+15005550000"
account_sid: "changeme"
auth_token: "changeme"
sendgrid_api_key: changeme
arke:
strategy:
type: 'copy'
pair: 'ETHUSD'
target:
driver: rubykube
host: 'http://www.app.local'
name: John
key: changeme
secret: changeme
sources:
- driver: bitfinex
name: Joe
key: changeme
secret: changeme
- Add our application to envoy configuration
Open templates/config/gateway/envoy.yml.erb
# Find routes block configuration and change it as follows
routes:
- match:
prefix: "/api/v2/barong"
route:
cluster: barong
prefix_rewrite: "/api/v2/"
- match:
prefix: "/api/v2/peatio"
route:
cluster: peatio
prefix_rewrite: "/api/v2/"
- match: # add another match for our service
prefix: "/api/myapp" # this path on front
route:
cluster: myapp # we will define this cluster below
prefix_rewrite: "/api" # will rewrite to this path on our service# And also find this block and add the configuration
- name: peatio
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: peatio
port_value: 8000
- name: myapp
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: myapp # here we specify docker-compose service name because they are on the same network
port_value: 3000 # the port we've defined in docker-compose
- Run the application
We need to bring up all the stack to test our app:
rake service:all
Now, let’s deploy our app:
docker-compose up -Vd myapp
- Test deployed application
For easier testing let’s install httpie
After installing httpie we’re going to create a session for a user and test our application
- Add an entry to
/etc/hosts
Add this line to the file, to be able to access our application on one hostname
0.0.0.0 www.app.local
- Create a session
http --session=test POST http://www.app.local/api/v2/barong/identity/sessions
email='admin@barong.io' password='0lDHd9ufs9t@'HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Length: 102
Content-Type: application/json
Date: Tue, 13 Aug 2019 10:52:54 GMT
Etag: W/"03851ef25f9cd7e9372e07e9fa955cc1"
Server: envoy
Set-Cookie:
_session_id=j7aR4UMJXiCzpUIjk7VO9uZLICX%2FbWl4dB8yZRrQma9TOyquNseMXpL%2FsRvo4fR
2Dth40%2BuIyku5%2F9C8DKdxVIr1glDUtoNn3MJcNOxamSS0X3Adksmfu7xJV%2BkI7hgXSb57reEQ
DJy1zuxQzm5%2FGoGs4OwBG6J2PNjUE1ZlPc2%2Fnm79MT2z0CDF3g6F3U0H3jT2YKjN9IpbkQXQl8z
hUVi7pwIG--kI6uEaugsqMl4f3F--bZksgeobMx7XEgdiLCkMow%3D%3D; path=/; expires=Tue,
13 Aug 2019 11:22:54 -0000; HttpOnly
Set-Cookie:
_barong_session=7ikfYh7o8gmGGqETgBAx7Nfkn0547d9bRKNkmiwz%2B4JiBh3yxT8AsDoAIZwHf
3vRd%2FOp5ZCEEfcLRUnw1ig13rmMYCGEn9bJwpwxwDRqtRcY8wShWTygJpgOQMPv7QMPZPk53qFMEe
uViolp%2B7rTPV0SMeKpcO3FW59h9aczvOv6rt7Cmg2Gwf%2FlBy3rhQgTgoJfCA4lXcT6Li1lXNnFN
Wd%2B514h--F%2BpJvFwLU8y3C3YN--6mqtBzwnsBpoEH0DW5gk%2Bg%3D%3D; path=/;
expires=Tue, 13 Aug 2019 11:22:54 -0000; HttpOnly
Vary: Origin
X-Envoy-Upstream-Service-Time: 326
X-Request-Id: e20b879f-3c28-4a2e-8cb2-061aa4e98acc
X-Runtime: 0.325521{
"email": "admin@barong.io",
"level": 3,
"otp": false,
"role": "admin",
"state": "active",
"uid": "ID88613C9575"
}
- Test our application
http --session=test GET http://www.app.local/api/myapp/user/meHTTP/1.1 200 OK
Content-Length: 15
Content-Type: text/html; charset=utf-8
Date: Tue, 13 Aug 2019 10:53:38 GMT
Etag: W/"f-LjSPa0aKBpanEuP1qo3W6qAFJeY"
Server: envoy
X-Envoy-Upstream-Service-Time: 12
X-Powered-By: Expressadmin@barong.io
CONGRATULATIONS! We’ve successfully developed our custom component for OpenDAX solution. Now the sky is the only limit.
Useful links:
https://github.com/openware/nodelogic
https://github.com/openware/opendax
https://github.com/openware/node-auth-barong
https://www.openware.com/
https://rubykube.io/
Originally published at https://github.com.