Skip to content

Commit

Permalink
feat: Publishing serviceEndpoint to DID Doc
Browse files Browse the repository at this point in the history
  • Loading branch information
simonas-notcat committed Feb 21, 2020
1 parent a6a1e94 commit a9fb385
Show file tree
Hide file tree
Showing 15 changed files with 450 additions and 231 deletions.
1 change: 1 addition & 0 deletions examples/expressjs-ethr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
secrets
20 changes: 19 additions & 1 deletion examples/expressjs-ethr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,26 @@ yarn
yarn build
```

Install [ngrok](https://dashboard.ngrok.com/get-started)

## Start

Run ngrok

```
ngrok http 8099
```

Copy https url (ex `https://d99de57b.ngrok.io`)

```
yarn start
HOST=https://d99de57b.ngrok.io PORT=8099 DEBUG=daf:* yarn start
```

This should fail with this message:

```
Error: Service endpoint not published. You probably need to send some ETH to did:ethr:rinkeby:0xc.....
```

To make it work, you need to send some ETH to that address
2 changes: 1 addition & 1 deletion examples/expressjs-ethr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"watch": "tsc -b --watch",
"build": "tsc",
"start": "node build/web-server",
"start": "node build/index",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as DafFs from 'daf-fs'

import * as W3c from 'daf-w3c'
import * as SD from 'daf-selective-disclosure'
import * as TG from 'daf-trust-graph'
// import * as TG from 'daf-trust-graph'
import * as DBG from 'daf-debug'
import * as DIDComm from 'daf-did-comm'
import * as URL from 'daf-url'
Expand All @@ -18,13 +18,13 @@ import { NodeSqlite3 } from 'daf-node-sqlite3'
import { DataStore } from 'daf-data-store'
import ws from 'ws'

const defaultPath = __dirname + '/.daf'
const defaultPath = __dirname + '/../secrets'

const dataStoreFilename = process.env.DAF_DATA_STORE ?? defaultPath + '/data-store.sqlite3'
const infuraProjectId = process.env.DAF_INFURA_ID ?? '5ffc47f65c4042ce847ef66a3fa70d4c'
if (process.env.DAF_TG_URI) TG.ServiceController.defaultUri = process.env.DAF_TG_URI
if (process.env.DAF_TG_WSURI) TG.ServiceController.defaultWsUri = process.env.DAF_TG_WSURI
TG.ServiceController.webSocketImpl = ws
// if (process.env.DAF_TG_URI) TG.ServiceController.defaultUri = process.env.DAF_TG_URI
// if (process.env.DAF_TG_WSURI) TG.ServiceController.defaultWsUri = process.env.DAF_TG_WSURI
// TG.ServiceController.webSocketImpl = ws

if (!process.env.DAF_IDENTITY_STORE || process.env.DAF_DATA_STORE || process.env.DAF_ENCRYPTION_STORE) {
const fs = require('fs')
Expand All @@ -46,14 +46,16 @@ if (process.env.DAF_UNIVERSAL_RESOLVER_URL) {

const identityProviders = [
new EthrDid.IdentityProvider({
identityStore: new DafFs.IdentityStore(defaultPath + '/identity-store.json'),
kms: new DafLibSodium.KeyManagementSystem(new DafFs.KeyStore(defaultPath + '/key-store.json')),
identityStore: new DafFs.IdentityStore(defaultPath + '/rinkeby-identity-store.json'),
kms: new DafLibSodium.KeyManagementSystem(new DafFs.KeyStore(defaultPath + '/rinkeby-kms.json')),
network: 'rinkeby',
rpcUrl: 'https://rinkeby.infura.io/v3/' + infuraProjectId,
resolver: didResolver,
}),
]
const serviceControllers = [TG.ServiceController]
const serviceControllers: any[] = [
// TG.ServiceController
]

const messageValidator = new DBG.MessageValidator()
messageValidator
Expand All @@ -66,7 +68,7 @@ messageValidator
const actionHandler = new DBG.ActionHandler()
actionHandler
.setNext(new DIDComm.ActionHandler())
.setNext(new TG.ActionHandler())
// .setNext(new TG.ActionHandler())
.setNext(new W3c.ActionHandler())
.setNext(new SD.ActionHandler())

Expand Down
39 changes: 39 additions & 0 deletions examples/expressjs-ethr/src/identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as Daf from 'daf-core'
import { core } from './framework'

export const getIdentity = async () => {
// Get of create new identity
let identity: Daf.AbstractIdentity
const identities = await core.identityManager.getIdentities()
if (identities.length > 0) {
identity = identities[0]
} else {
const identityProviders = await core.identityManager.getIdentityProviderTypes()
identity = await core.identityManager.createIdentity(identityProviders[0].type)
}

return identity
}

export const setServiceEndpoint = async (identity: Daf.AbstractIdentity, serviceEndpoint: string) => {
// Check if DID Document contains current serviceEndpoint
const didDoc = await core.didResolver.resolve(identity.did)
const exists = didDoc?.service?.find(item => item.serviceEndpoint === serviceEndpoint)
if (!exists) {
try {
const txHash = await identity.identityController.addService({
id: 'any',
type: 'Messaging',
serviceEndpoint,
})
if (txHash) {
console.log('New service endpoint published. Tx Hash:', txHash)
return true
}
} catch (e) {
console.log(e)
}
return false
}
return true
}
224 changes: 224 additions & 0 deletions examples/expressjs-ethr/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import express from 'express'
import * as Daf from 'daf-core'
import * as SD from 'daf-selective-disclosure'
import * as W3C from 'daf-w3c'
import { app, server, io, sessionStore } from './server'
import { core, dataStore } from './framework'
import { getIdentity, setServiceEndpoint } from './identity'

import Debug from 'debug'
const debug = Debug('main')

if (!process.env.HOST) throw Error('Environment variable HOST not set')
if (!process.env.PORT) throw Error('Environment variable PORT not set')

async function main() {
await dataStore.initialize()
const identity = await getIdentity()

const messagingEndpoint = '/handle-message'
const serviceEndpoint = process.env.HOST + messagingEndpoint

const result = await setServiceEndpoint(identity, serviceEndpoint)
if (!result) {
throw Error('Service endpoint not published. You probably need to send some ETH to ' + identity.did)
}

app.post(messagingEndpoint, express.text({ type: '*/*' }), async (req, res) => {
try {
// This will trigger Daf.EventTypes.validatedMessage
const result = await core.validateMessage(
new Daf.Message({ raw: req.body, meta: { type: 'serviceEndpoint', id: serviceEndpoint } }),
)
res.json({ id: result.id })
} catch (e) {
res.send(e.message)
}
})

core.on(Daf.EventTypes.validatedMessage, async (message: Daf.Message) => {
await dataStore.saveMessage(message)

if (message.type === W3C.MessageTypes.vp && message.threadId) {
// TODO check for required vcs

const sessionId = message.threadId
io.in(sessionId).emit('loggedin', { did: message.sender })

sessionStore.get(sessionId, (error, session) => {
if (error) throw Error(error)

if (session) {
session.did = message.sender
sessionStore.set(sessionId, session)
}
})
}
})

const requireLogin = (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (!req.session) throw Error('Session not configured')
if (!req.session.did) {
res.redirect('/login')
} else {
next()
}
}

app.get('/', requireLogin, async (req, res) => {
res.redirect('/home')
})

app.get('/home', requireLogin, async (req, res) => {
if (!req.session) throw Error('Session not configured')

// Counting views to show that session is working
req.session.views = req.session.views ? req.session.views + 1 : 1

const { did, views } = req.session
const name = await dataStore.shortId(did)
res.render('home', { did, views, name })
})

app.get('/history', requireLogin, async (req, res) => {
if (!req.session) throw Error('Session not configured')

// Counting views to show that session is working
req.session.views = req.session.views ? req.session.views + 1 : 1

const { did, views } = req.session
const name = await dataStore.shortId(did)
const messages = await dataStore.findMessages({ sender: did })
console.log(messages)
res.render('history', { did, views, name, messages })
})

app.get('/login', async (req, res) => {
if (!req.session) throw Error('Session not configured')

// Counting views to show that session is working
req.session.views = req.session.views ? req.session.views + 1 : 1

// Sign Selective Disclosure Request
const jwt = await core.handleAction({
type: SD.ActionTypes.signSdr,
did: identity.did,
data: {
tag: req.sessionID,
claims: [
{
reason: 'We need this information',
essential: true,
claimType: 'name',
},
],
},
} as SD.ActionSignSdr)

const url = encodeURI(process.env.HOST + '/?c_i=') + jwt

res.render('login', { views: req.session.views, url })
})

app.get('/credential', requireLogin, async (req, res) => {
if (!req.session) throw Error('Session not configured')

// Counting views to show that session is working
req.session.views = req.session.views ? req.session.views + 1 : 1

const { did, views } = req.session
const name = await dataStore.shortId(did)

// Sign verifiable credential
const nameJwt = await core.handleAction({
type: W3C.ActionTypes.signVc,
did: identity.did,
data: {
sub: did,
vc: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential'],
credentialSubject: {
name,
},
},
},
} as W3C.ActionSignW3cVc)

const kwcJwt = await core.handleAction({
type: W3C.ActionTypes.signVc,
did: identity.did,
data: {
sub: did,
vc: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential'],
credentialSubject: {
kycId: '123XZY',
},
},
},
} as W3C.ActionSignW3cVc)

const vpJwt = await core.handleAction({
type: W3C.ActionTypes.signVp,
did: identity.did,
data: {
sub: did,
vp: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential'],
verifiableCredential: [nameJwt, kwcJwt],
},
},
} as W3C.ActionSignW3cVp)

const url = encodeURI(process.env.HOST + '/?c_i=') + vpJwt

res.render('credential', { views, url })
})

app.get('/about', async (req, res) => {
if (!req.session) throw Error('Session not configured')

// Counting views to show that session is working
req.session.views = req.session.views ? req.session.views + 1 : 1

const { views } = req.session

// Sign verifiable credential
const jwt = await core.handleAction({
type: W3C.ActionTypes.signVc,
did: identity.did,
data: {
sub: identity.did,
vc: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential'],
credentialSubject: {
name: 'DAF Demo',
description: 'Demo application',
profileImage: 'https://i.imgur.com/IMn3dIg.png',
},
},
},
} as W3C.ActionSignW3cVc)

const url = encodeURI(process.env.HOST + '/?c_i=') + jwt

res.render('about', { views, url })
})

app.get('/logout', (req, res) => req.session?.destroy(() => res.redirect('/')))

server.listen(process.env.PORT, async () => {
console.log(`Server running at http://localhost:${process.env.PORT}/`)
console.log(`Messaging service endpoint ${serviceEndpoint}`)

// await core.setupServices()
// await core.listen()
// await core.getMessagesSince(await dataStore.latestMessageTimestamps())
})
}

main().catch(e => console.log(e.message))
38 changes: 38 additions & 0 deletions examples/expressjs-ethr/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import express from 'express'
import session, { MemoryStore } from 'express-session'
import socketio from 'socket.io'
import http from 'http'
import handlebars from 'express-handlebars'
import sharedSession from 'express-socket.io-session'

const app = express()
app.engine('handlebars', handlebars())
app.set('view engine', 'handlebars')
app.use(express.text())

const sessionStore = new MemoryStore()
const sess = session({
secret: 'keyboard cat',
cookie: { maxAge: 10 * 60000 },
saveUninitialized: true,
resave: true,
store: sessionStore,
})
app.use(sess)

const server = http.createServer(app)
const io = socketio(server)

io.use(
sharedSession(sess, {
autoSave: true,
}),
)

io.on('connection', function(socket) {
if (socket.handshake?.session) {
socket.join(socket.handshake.session.id)
}
})

export { app, server, io, sessionStore }
Loading

0 comments on commit a9fb385

Please sign in to comment.