Skip to content

Commit

Permalink
feat: Web DID path (multi user) support (#282)
Browse files Browse the repository at this point in the history
* feat: Add Daf express routers
* feat: Add ApiSchemaRouter, DidDocRouter
* fix: Rename to WebDidDocRouter
* feat: Remove experimental public profile
  • Loading branch information
simonas-notcat authored Nov 26, 2020
1 parent b52b529 commit 08996bd
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 232 deletions.
1 change: 0 additions & 1 deletion __tests__/restAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ const setup = async (options?: IAgentOptions): Promise<boolean> => {
const agentRouter = AgentRouter({
getAgentForRequest: async (req) => serverAgent,
exposedMethods: serverAgent.availableMethods(),
basePath,
})

return new Promise((resolve) => {
Expand Down
275 changes: 139 additions & 136 deletions packages/daf-cli/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import express from 'express'
import program from 'commander'
import ngrok from 'ngrok'
import parse from 'url-parse'
import { AgentRouter } from 'daf-express'
import { getOpenApiSchema } from 'daf-rest'
import { AgentRouter, ApiSchemaRouter, WebDidDocRouter, didDocEndpoint } from 'daf-express'
import swaggerUi from 'swagger-ui-express'
import { getAgent, getConfig } from './setup'
import { createObjects } from './lib/objectCreator'
import passport from 'passport'
import Bearer from 'passport-http-bearer'
import { IIdentity } from 'daf-core'
const exphbs = require('express-handlebars')
const hbs = exphbs.create({
helpers: {
Expand All @@ -27,150 +27,153 @@ program
const agent = getAgent(program.config)
const { server: options } = createObjects(getConfig(program.config), { server: '/server' })

passport.use(
new Bearer.Strategy((token, done) => {
if (!options.apiKey || options.apiKey === token) {
done(null, {}, { scope: 'all' })
} else {
done(null, false)
}
/**
* Ngrok configuration
*/
let baseUrl = options.baseUrl
if (options.ngrok?.connect) {
baseUrl = await ngrok.connect({
addr: cmd.port || options.port,
subdomain: options.ngrok.subdomain,
region: options.ngrok.region,
authtoken: options.ngrok.authtoken,
})
app.set('trust proxy', 'loopback')
}
const hostname = parse(baseUrl).hostname

/**
* Authentication setup
*/
let authMiddleware = (req: any, res: any, next: any) => {
next()
}
let securityScheme
if (options.apiKey) {
console.log('🔐 Secured by API Key')
passport.use(
new Bearer.Strategy((token, done) => {
if (!options.apiKey || options.apiKey === token) {
done(null, {}, { scope: 'all' })
} else {
done(null, false)
}
}),
)

authMiddleware = passport.authenticate('bearer', { session: false })
securityScheme = 'bearer'
}

/**
* Exposing agent methods
*/
const exposedMethods = options.exposedMethods ? options.exposedMethods : agent.availableMethods()
const getAgentForRequest = async (req: express.Request) => agent

console.log('🗺 API base path', baseUrl + options.apiBasePath)
app.use(
options.apiBasePath,
authMiddleware,
AgentRouter({
getAgentForRequest,
exposedMethods,
}),
)

const exposedMethods = options.exposedMethods ? options.exposedMethods : agent.availableMethods()
/**
* Exposing OpenAPI schema
*/
console.log('🗺 OpenAPI schema', baseUrl + options.schemaPath)
app.use(
options.schemaPath,
ApiSchemaRouter({
basePath: options.apiBasePath,
getAgentForRequest,
exposedMethods,
securityScheme,
}),
)

const apiBasePath = options.apiBasePath
console.log('📖 API Documentation', baseUrl + options.apiDocsPath)
app.use(
options.apiDocsPath,
swaggerUi.serve,
swaggerUi.setup(undefined, {
swaggerOptions: {
url: baseUrl + options.schemaPath,
},
}),
)

const agentRouter = AgentRouter({
basePath: apiBasePath,
getAgentForRequest: async (req) => agent,
exposedMethods,
serveSchema: false,
})
console.log('🧩 Available methods', agent.availableMethods().length)
console.log('🛠 Exposed methods', exposedMethods.length)

app.use(apiBasePath, passport.authenticate('bearer', { session: false }), agentRouter)

app.listen(cmd.port || options.port, async () => {
console.log(`🚀 Agent server ready at http://localhost:${cmd.port || options.port}`)
console.log('🧩 Available methods', agent.availableMethods().length)
console.log('🛠 Exposed methods', exposedMethods.length)

let baseUrl = options.baseUrl

if (options.ngrok?.connect) {
baseUrl = await ngrok.connect({
addr: cmd.port || options.port,
subdomain: options.ngrok.subdomain,
region: options.ngrok.region,
authtoken: options.ngrok.authtoken,
})
}
const hostname = parse(baseUrl).hostname

const openApiSchema = getOpenApiSchema(agent, apiBasePath, exposedMethods)
openApiSchema.servers = [{ url: baseUrl }]

if (options.apiKey && openApiSchema.components) {
openApiSchema.components.securitySchemes = {
bearerAuth: { type: 'http', scheme: 'bearer' },
}
openApiSchema.security = [{ bearerAuth: [] }]
}

app.use(options.apiDocsPath, swaggerUi.serve, swaggerUi.setup(openApiSchema))
console.log('📖 API Documentation', baseUrl + options.apiDocsPath)

app.get(options.schemaPath, (req, res) => {
res.json(openApiSchema)
/**
* Creating server identity and configuring messaging service endpoint
*/
let serverIdentity: IIdentity
if (options.defaultIdentity.create) {
serverIdentity = await agent.identityManagerGetOrCreateIdentity({
provider: 'did:web',
alias: hostname,
})
console.log('🆔', serverIdentity.did)

const messagingServiceEndpoint = baseUrl + options.defaultIdentity.messagingServiceEndpoint

console.log('📨 Messaging endpoint', messagingServiceEndpoint)
await agent.identityManagerAddService({
did: serverIdentity.did,
service: {
id: serverIdentity.did + '#msg',
type: 'Messaging',
description: 'Handles incoming POST messages',
serviceEndpoint: messagingServiceEndpoint,
},
})

console.log('🗺 OpenAPI schema', baseUrl + options.schemaPath)

if (options.defaultIdentity.create) {
let serverIdentity = await agent.identityManagerGetOrCreateIdentity({
provider: 'did:web',
alias: hostname,
})
console.log('🆔', serverIdentity.did)

const messagingServiceEndpoint = baseUrl + options.defaultIdentity.messagingServiceEndpoint

await agent.identityManagerAddService({
did: serverIdentity.did,
service: {
id: serverIdentity.did + '#msg',
type: 'Messaging',
description: 'Handles incoming POST messages',
serviceEndpoint: messagingServiceEndpoint,
},
})
console.log('📨 Messaging endpoint', messagingServiceEndpoint)

app.post(
options.defaultIdentity.messagingServiceEndpoint,
express.text({ type: '*/*' }),
async (req, res) => {
try {
const message = await agent.handleMessage({ raw: req.body, save: true })
console.log('Received message', message.type, message.id)
res.json({ id: message.id })
} catch (e) {
console.log(e)
res.send(e.message)
}
},
)

const didDocEndpoint = '/.well-known/did.json'
app.get(didDocEndpoint, async (req, res) => {
serverIdentity = await agent.identityManagerGetOrCreateIdentity({
provider: 'did:web',
alias: hostname,
})

const didDoc = {
'@context': 'https://w3id.org/did/v1',
id: serverIdentity.did,
publicKey: serverIdentity.keys.map((key) => ({
id: serverIdentity.did + '#' + key.kid,
type: key.type === 'Secp256k1' ? 'Secp256k1VerificationKey2018' : 'Ed25519VerificationKey2018',
controller: serverIdentity.did,
publicKeyHex: key.publicKeyHex,
})),
authentication: serverIdentity.keys.map((key) => ({
type:
key.type === 'Secp256k1'
? 'Secp256k1SignatureAuthentication2018'
: 'Ed25519SignatureAuthentication2018',
publicKey: serverIdentity.did + '#' + key.kid,
})),
service: serverIdentity.services,
app.post(
options.defaultIdentity.messagingServiceEndpoint,
express.text({ type: '*/*' }),
async (req, res) => {
try {
const message = await agent.handleMessage({ raw: req.body, save: true })
console.log('Received message', message.type, message.id)
res.json({ id: message.id })
} catch (e) {
console.log(e)
res.send(e.message)
}
},
)
}

/**
* Handling 'did:web' requests ('/.well-known/did.json' and '/^\/(.+)\/did.json$/')
* warning: 'did:web' method requires HTTPS (that is one of the reasons to use ngrok for development)
*/
console.log('📋 DID Document ' + baseUrl + didDocEndpoint)
console.log('📋 DID Documents ' + baseUrl + '/(.+)/did.json')
app.use(WebDidDocRouter({ getAgentForRequest }))

/**
* Serving homepage
*/
app.get('/', async (req, res) => {
const links = [
{ label: 'API Docs', url: options.apiDocsPath },
{ label: 'API Schema', url: options.schemaPath },
{ label: 'DID Document', url: didDocEndpoint },
]

const template = options.homePageTemplate || __dirname + '/../views/home.html'
const rendered = await hbs.render(template, { links })
res.send(rendered)
})

res.json(didDoc)
})
console.log('📋 DID Document ' + baseUrl + didDocEndpoint)

app.get('/', async (req, res) => {
const links = [
{ label: 'API Docs', url: options.apiDocsPath },
{ label: 'API Schema', url: options.schemaPath },
{ label: 'DID Document', url: '/.well-known/did.json' },
]

const presentations = await agent.dataStoreORMGetVerifiablePresentations({
where: [
{ column: 'holder', value: [serverIdentity.did] },
{ column: 'verifier', value: [baseUrl] },
],
})

const verifiablePresentation =
presentations.length > 0 ? presentations[presentations.length - 1].verifiablePresentation : null
const template = options.homePageTemplate || __dirname + '/../views/home.html'
const rendered = await hbs.render(template, { verifiablePresentation, links })
res.send(rendered)
})
}
app.listen(cmd.port || options.port, async () => {
console.log(`🚀 Cloud Agent ready at ${baseUrl}`)
})
})
4 changes: 1 addition & 3 deletions packages/daf-cli/views/home.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{{#with verifiablePresentation}} {{#each verifiableCredential}} {{toJSON credentialSubject}}
<hr />
{{/each}} {{/with}} {{#each links}}
{{#each links}}
<a href="{{url}}">{{label}}</a><br />
{{/each}}
24 changes: 23 additions & 1 deletion packages/daf-express/api/daf-express.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,40 @@
```ts

import { IAgent } from 'daf-core';
import { IIdentityManager } from 'daf-core';
import { Request as Request_2 } from 'express';
import { Router } from 'express';
import { TAgent } from 'daf-core';

// @public
export const AgentRouter: (options: AgentRouterOptions) => Router;

// @public (undocumented)
export interface AgentRouterOptions {
exposedMethods: Array<string>;
getAgentForRequest: (req: Request_2) => Promise<IAgent>;
}

// @public
export const ApiSchemaRouter: (options: ApiSchemaRouterOptions) => Router;

// @public (undocumented)
export interface ApiSchemaRouterOptions {
basePath: string;
exposedMethods: Array<string>;
getAgentForRequest: (req: Request_2) => Promise<IAgent>;
serveSchema?: boolean;
securityScheme?: string;
}

// @public (undocumented)
export const didDocEndpoint = "/.well-known/did.json";

// @public
export const WebDidDocRouter: (options: WebDidDocRouterOptions) => Router;

// @public (undocumented)
export interface WebDidDocRouterOptions {
getAgentForRequest: (req: Request_2) => Promise<TAgent<IIdentityManager>>;
}


Expand Down
Loading

0 comments on commit 08996bd

Please sign in to comment.