Integrate ActivityPub into the Backend

This commit is contained in:
Armin 2019-02-25 23:57:44 +01:00
parent ec2b1050bc
commit f74a45379f
23 changed files with 2265 additions and 19 deletions

View File

@ -8,5 +8,8 @@
}
}
]
],
"plugins": [
"@babel/plugin-proposal-throw-expressions"
]
}

View File

@ -26,6 +26,7 @@ install:
script:
- docker-compose exec backend yarn run lint
- docker-compose exec backend yarn run test --ci
- docker-compose exec backend yarn run test:cucumber
- docker-compose exec backend yarn run test:coverage
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed

View File

@ -12,13 +12,15 @@
"dev": "nodemon --exec babel-node src/",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
"lint": "eslint src --config .eslintrc.js",
"test": "nyc --reporter=text-lcov yarn run test:jest",
"test": "nyc --reporter=text-lcov yarn test:jest",
"test:cypress": "run-p --race test:before:*",
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null",
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 PERMISSIONS=disabled babel-node src/ 2> /dev/null",
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
"test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/",
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
"test:jest": "run-p --race test:before:* 'test:jest:cmd {@}' --",
"test:cucumber": "ACTIVITYPUB_PORT=4123 run-p --race test:before:* 'test:cucumber:cmd {@}' --",
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
@ -34,15 +36,18 @@
]
},
"dependencies": {
"activitystrea.ms": "^2.1.3",
"apollo-cache-inmemory": "~1.4.3",
"apollo-client": "~2.4.13",
"apollo-link-http": "~1.5.11",
"apollo-server": "~2.4.2",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.2",
"cors": "^2.8.5",
"cross-env": "~5.2.0",
"date-fns": "2.0.0-alpha.26",
"dotenv": "~6.2.0",
"express": "^4.16.4",
"faker": "~4.1.0",
"graphql": "~14.1.1",
"graphql-custom-directives": "~0.2.14",
@ -61,6 +66,7 @@
"npm-run-all": "~4.1.5",
"passport": "~0.4.0",
"passport-jwt": "~4.0.0",
"request": "^2.88.0",
"sanitize-html": "~1.20.0",
"slug": "~1.0.0",
"trunc-html": "~1.1.2",
@ -70,6 +76,7 @@
"@babel/cli": "~7.2.3",
"@babel/core": "~7.3.3",
"@babel/node": "~7.2.2",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.3.1",
"@babel/register": "~7.0.0",
"apollo-server-testing": "~2.4.2",
@ -77,6 +84,8 @@
"babel-eslint": "~10.0.1",
"babel-jest": "~24.1.0",
"chai": "~4.2.0",
"cucumber": "^5.1.0",
"debug": "^4.1.1",
"eslint": "~5.13.0",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.16.0",

View File

@ -0,0 +1,137 @@
import { sendAcceptActivity, sendRejectActivity, extractNameFromId, extractDomainFromUrl } from './utils'
import request from 'request'
import as from 'activitystrea.ms'
import NitroDatasource from './NitroDatasource'
import router from './routes'
import dotenv from 'dotenv'
import { resolve } from 'path'
const debug = require('debug')('ea')
let activityPub = null
export { activityPub }
export default class ActivityPub {
constructor (domain, port) {
if (domain === 'localhost') { this.domain = `${domain}:${port}` } else { this.domain = domain }
this.port = port
this.dataSource = new NitroDatasource(this.domain)
}
static init (server) {
if (!activityPub) {
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
// const app = express()
activityPub = new ActivityPub(process.env.ACTIVITYPUB_DOMAIN || 'localhost', process.env.ACTIVITYPUB_PORT || 4100)
server.express.set('ap', activityPub)
server.express.use(router)
debug('ActivityPub service added to graphql endpoint')
} else {
debug('ActivityPub service already added to graphql endpoint')
}
}
getFollowersCollection (actorId) {
return this.dataSource.getFollowersCollection(actorId)
}
getFollowersCollectionPage (actorId) {
return this.dataSource.getFollowersCollectionPage(actorId)
}
getFollowingCollection (actorId) {
return this.dataSource.getFollowingCollection(actorId)
}
getFollowingCollectionPage (actorId) {
return this.dataSource.getFollowingCollectionPage(actorId)
}
getOutboxCollection (actorId) {
return this.dataSource.getOutboxCollection(actorId)
}
getOutboxCollectionPage (actorId) {
return this.dataSource.getOutboxCollectionPage(actorId)
}
handleFollowActivity (activity) {
debug(`inside FOLLOW ${activity.actor}`)
let toActorName = extractNameFromId(activity.object)
let fromDomain = extractDomainFromUrl(activity.actor)
const dataSource = this.dataSource
return new Promise((resolve, reject) => {
request({
url: activity.actor,
headers: {
'Accept': 'application/activity+json'
}
}, async (err, response, toActorObject) => {
if (err) return reject(err)
debug(`name = ${toActorName}@${this.domain}`)
let followersCollectionPage = await this.dataSource.getFollowersCollectionPage(activity.object)
const followActivity = as.follow()
.id(activity.id)
.actor(activity.actor)
.object(activity.object)
// add follower if not already in collection
if (followersCollectionPage.orderedItems.includes(activity.actor)) {
debug('follower already in collection!')
debug(`inbox = ${toActorObject.inbox}`)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} else {
followersCollectionPage.orderedItems.push(activity.actor)
}
debug(`toActorObject = ${toActorObject}`)
toActorObject = typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject
debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`)
debug(`inbox = ${toActorObject.inbox}`)
debug(`outbox = ${toActorObject.outbox}`)
debug(`followers = ${toActorObject.followers}`)
debug(`following = ${toActorObject.following}`)
// TODO save after accept activity for the corresponding follow is received
try {
await dataSource.saveFollowersCollectionPage(followersCollectionPage)
debug('follow activity saved')
resolve(sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} catch (e) {
debug('followers update error!', e)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
}
})
})
}
handleUndoActivity (activity) {
debug('inside UNDO')
switch (activity.object.type) {
case 'Follow':
const followActivity = activity.object
return this.dataSource.undoFollowActivity(followActivity.actor, followActivity.object)
default:
}
}
handleCreateActivity (activity) {
debug('inside create')
switch (activity.object.type) {
case 'Article':
case 'Note':
const articleObject = activity.object
if (articleObject.inReplyTo) {
return this.dataSource.createComment(articleObject)
} else {
return this.dataSource.createPost(articleObject)
}
default:
}
}
handleDeleteActivity (activity) {
debug('inside delete')
}
}

View File

@ -0,0 +1,377 @@
import {
throwErrorIfGraphQLErrorOccurred,
extractIdFromActivityId,
createOrderedCollection,
createOrderedCollectionPage,
extractNameFromId,
createArticleActivity,
constructIdFromName
} from './utils'
import crypto from 'crypto'
import gql from 'graphql-tag'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'node-fetch'
import { ApolloClient } from 'apollo-client'
import dotenv from 'dotenv'
const debug = require('debug')('ea:nitro-datasource')
dotenv.config()
export default class NitroDatasource {
constructor (domain) {
this.domain = domain
const defaultOptions = {
query: {
fetchPolicy: 'network-only',
errorPolicy: 'all'
}
}
const link = createHttpLink({ uri: process.env.GRAPHQL_URI, fetch: fetch }) // eslint-disable-line
const cache = new InMemoryCache()
this.client = new ApolloClient({
link: link,
cache: cache,
defaultOptions
})
}
async getFollowersCollection (actorId) {
const slug = extractNameFromId(actorId)
debug(`slug= ${slug}`)
const result = await this.client.query({
query: gql`
query {
User(slug: "${slug}") {
followedByCount
}
}
`
})
debug('successfully fetched followers')
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followersCount = actor.followedByCount
const followersCollection = createOrderedCollection(slug, 'followers')
followersCollection.totalItems = followersCount
return followersCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async getFollowersCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
debug(`getFollowersPage slug = ${slug}`)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
followedBy {
slug
}
followedByCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followers = actor.followedBy
const followersCount = actor.followedByCount
const followersCollection = createOrderedCollectionPage(slug, 'followers')
followersCollection.totalItems = followersCount
debug(`followers = ${JSON.stringify(followers, null, 2)}`)
await Promise.all(
followers.map(async (follower) => {
followersCollection.orderedItems.push(constructIdFromName(follower.slug))
})
)
return followersCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async getFollowingCollection (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
followingCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followingCount = actor.followingCount
const followingCollection = createOrderedCollection(slug, 'following')
followingCollection.totalItems = followingCount
return followingCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async getFollowingCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
following {
slug
}
followingCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const following = actor.following
const followingCount = actor.followingCount
const followingCollection = createOrderedCollectionPage(slug, 'following')
followingCollection.totalItems = followingCount
await Promise.all(
following.map(async (user) => {
followingCollection.orderedItems.push(await constructIdFromName(user.slug))
})
)
return followingCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async getOutboxCollection (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
contributions {
title
slug
content
contentExcerpt
createdAt
}
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const posts = actor.contributions
const outboxCollection = createOrderedCollection(slug, 'outbox')
outboxCollection.totalItems = posts.length
return outboxCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async getOutboxCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
contributions {
id
title
slug
content
contentExcerpt
createdAt
}
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const posts = actor.contributions
const outboxCollection = createOrderedCollectionPage(slug, 'outbox')
outboxCollection.totalItems = posts.length
await Promise.all(
posts.map((post) => {
outboxCollection.orderedItems.push(createArticleActivity(post.content, slug, post.id, post.createdAt))
})
)
debug('after createNote')
return outboxCollection
} else {
throwErrorIfGraphQLErrorOccurred(result)
}
}
async undoFollowActivity (fromActorId, toActorId) {
const fromUserId = await this.ensureUser(fromActorId)
const toUserId = await this.ensureUser(toActorId)
const result = await this.client.mutate({
mutation: gql`
mutation {
RemoveUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
from { name }
}
}
`
})
debug(`undoFollowActivity result = ${JSON.stringify(result, null, 2)}`)
throwErrorIfGraphQLErrorOccurred(result)
}
async saveFollowersCollectionPage (followersCollection, onlyNewestItem = true) {
debug('inside saveFollowers')
let orderedItems = followersCollection.orderedItems
const toUserName = extractNameFromId(followersCollection.id)
const toUserId = await this.ensureUser(constructIdFromName(toUserName))
orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems
return Promise.all(
await Promise.all(orderedItems.map(async (follower) => {
debug(`follower = ${follower}`)
const fromUserId = await this.ensureUser(follower)
debug(`fromUserId = ${fromUserId}`)
debug(`toUserId = ${toUserId}`)
const result = await this.client.mutate({
mutation: gql`
mutation {
AddUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
from { name }
}
}
`
})
debug(`addUserFollowedBy edge = ${JSON.stringify(result, null, 2)}`)
throwErrorIfGraphQLErrorOccurred(result)
debug('saveFollowers: added follow edge successfully')
}))
)
}
async createPost (postObject) {
// TODO how to handle the to field? Now the post is just created, doesn't matter who is the recipient
// createPost
const title = postObject.summary ? postObject.summary : postObject.content.split(' ').slice(0, 5).join(' ')
const id = extractIdFromActivityId(postObject.id)
let result = await this.client.mutate({
mutation: gql`
mutation {
CreatePost(content: "${postObject.content}", title: "${title}", id: "${id}") {
id
}
}
`
})
throwErrorIfGraphQLErrorOccurred(result)
// ensure user and add author to post
const userId = await this.ensureUser(postObject.attributedTo)
result = await this.client.mutate({
mutation: gql`
mutation {
AddPostAuthor(from: {id: "${userId}"}, to: {id: "${id}"})
}
`
})
throwErrorIfGraphQLErrorOccurred(result)
}
async createComment (postObject) {
let result = await this.client.mutate({
mutation: gql`
mutation {
CreateComment(content: "${postObject.content}") {
id
}
}
`
})
throwErrorIfGraphQLErrorOccurred(result)
const postId = extractIdFromActivityId(postObject.inReplyTo)
result = await this.client.mutate({
mutation: gql`
mutation {
AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) {
id
}
}
`
})
throwErrorIfGraphQLErrorOccurred(result)
}
/**
* This function will search for user existence and will create a disabled user with a random 16 bytes password when no user is found.
*
* @param actorId
* @returns {Promise<*>}
*/
async ensureUser (actorId) {
debug(`inside ensureUser = ${actorId}`)
const queryResult = await this.client.query({
query: gql`
query {
User(slug: "${extractNameFromId(actorId)}") {
id
}
}
`
})
if (queryResult.data && Array.isArray(queryResult.data.User) && queryResult.data.User.length > 0) {
debug('ensureUser: user exists.. return id')
// user already exists.. return the id
return queryResult.data.User[0].id
} else {
debug('ensureUser: user not exists.. createUser')
// user does not exist.. create it
const result = await this.client.mutate({
mutation: gql`
mutation {
CreateUser(password: "${crypto.randomBytes(16).toString('hex')}", slug:"${extractNameFromId(actorId)}", actorId: "${actorId}", name: "${extractNameFromId(actorId)}") {
id
}
}
`
})
throwErrorIfGraphQLErrorOccurred(result)
return result.data.CreateUser.id
}
}
}

View File

@ -0,0 +1,50 @@
import express from 'express'
import { verify } from '../security'
const debug = require('debug')('ea:inbox')
const router = express.Router()
// Shared Inbox endpoint (federated Server)
// For now its only able to handle Note Activities!!
router.post('/', async function (req, res, next) {
debug(`Content-Type = ${req.get('Content-Type')}`)
debug(`body = ${JSON.stringify(req.body, null, 2)}`)
debug(`Request headers = ${JSON.stringify(req.headers, null, 2)}`)
debug(`verify = ${await verify(`${req.protocol}://${req.hostname}:${req.port}${req.originalUrl}`, req.headers)}`)
switch (req.body.type) {
case 'Create':
await await req.app.get('activityPub').handleCreateActivity(req.body).catch(next)
break
case 'Undo':
await await req.app.get('activityPub').handleUndoActivity(req.body).catch(next)
break
case 'Follow':
debug('handleFollow')
await req.app.get('activityPub').handleFollowActivity(req.body)
debug('handledFollow')
break
case 'Delete':
await await req.app.get('activityPub').handleDeleteActivity(req.body).catch(next)
break
/* eslint-disable */
case 'Update':
case 'Accept':
case 'Reject':
case 'Add':
case 'Remove':
case 'Like':
case 'Announce':
debug('else!!')
debug(JSON.stringify(req.body, null, 2))
}
/* eslint-enable */
res.status(200).end()
})
export default router

View File

@ -0,0 +1,27 @@
import user from './user'
import inbox from './inbox'
import webfinger from './webfinger'
import express from 'express'
import cors from 'cors'
const router = express.Router()
router.use('/.well-known/webfinger',
cors(),
express.urlencoded({ extended: true }),
webfinger
)
router.use('/activitypub/users',
cors(),
express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }),
express.urlencoded({ extended: true }),
user
)
router.use('/activitypub/inbox',
cors(),
express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }),
express.urlencoded({ extended: true }),
inbox
)
export default router

View File

@ -0,0 +1,88 @@
import { sendCollection } from '../utils'
import express from 'express'
import { serveUser } from '../utils/serveUser'
import { verify } from '../security'
const router = express.Router()
const debug = require('debug')('ea:user')
router.get('/:name', async function (req, res, next) {
debug('inside user.js -> serveUser')
await serveUser(req, res, next)
})
router.get('/:name/following', (req, res) => {
debug('inside user.js -> serveFollowingCollection')
const name = req.params.name
if (!name) {
res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'followingPage' : 'following'
sendCollection(collectionName, req, res)
}
})
router.get('/:name/followers', (req, res) => {
debug('inside user.js -> serveFollowersCollection')
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'followersPage' : 'followers'
sendCollection(collectionName, req, res)
}
})
router.get('/:name/outbox', (req, res) => {
debug('inside user.js -> serveOutboxCollection')
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'outboxPage' : 'outbox'
sendCollection(collectionName, req, res)
}
})
router.post('/:name/inbox', async function (req, res, next) {
debug(`body = ${JSON.stringify(req.body, null, 2)}`)
debug(`actorId = ${req.body.actor}`)
debug(`verify = ${await verify(`${req.protocol}://${req.hostname}:${req.port}${req.originalUrl}`, req.headers)}`)
// const result = await saveActorId(req.body.actor)
switch (req.body.type) {
case 'Create':
await req.app.get('ap').handleCreateActivity(req.body).catch(next)
break
case 'Undo':
await req.app.get('ap').handleUndoActivity(req.body).catch(next)
break
case 'Follow':
debug('handleFollow')
await req.app.get('ap').handleFollowActivity(req.body).catch(next)
debug('handledFollow')
break
case 'Delete':
req.app.get('ap').handleDeleteActivity(req.body).catch(next)
break
/* eslint-disable */
case 'Update':
case 'Accept':
case 'Reject':
case 'Add':
case 'Remove':
case 'Like':
case 'Announce':
debug('else!!')
debug(JSON.stringify(req.body, null, 2))
}
/* eslint-enable */
res.status(200).end()
})
export default router

View File

@ -0,0 +1,34 @@
import express from 'express'
import { createWebFinger } from '../utils'
import gql from 'graphql-tag'
const router = express.Router()
router.get('/', async function (req, res) {
const resource = req.query.resource
if (!resource || !resource.includes('acct:')) {
return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
} else {
const nameAndDomain = resource.replace('acct:', '')
const name = nameAndDomain.split('@')[0]
const result = await req.app.get('ap').dataSource.client.query({
query: gql`
query {
User(slug: "${name}") {
slug
}
}
`
})
if (result.data && result.data.User.length > 0) {
const webFinger = createWebFinger(name)
return res.contentType('application/jrd+json').json(webFinger)
} else {
return res.status(404).json({ error: `No record found for ${nameAndDomain}.` })
}
}
})
export default router

View File

@ -0,0 +1,227 @@
import dotenv from 'dotenv'
import { resolve } from 'path'
import crypto from 'crypto'
import { activityPub } from '../ActivityPub'
import gql from 'graphql-tag'
import request from 'request'
const debug = require('debug')('ea:security')
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
export function generateRsaKeyPair () {
return crypto.generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: process.env.PRIVATE_KEY_PASSPHRASE
}
})
}
export function signAndSend (activity, fromName, targetDomain, url) {
// fix for development: replace with http
url = url.indexOf('localhost') > -1 ? url.replace('https', 'http') : url
debug(`passhprase = ${process.env.PRIVATE_KEY_PASSPHRASE}`)
return new Promise(async (resolve, reject) => {
debug('inside signAndSend')
// get the private key
const result = await activityPub.dataSource.client.query({
query: gql`
query {
User(slug: "${fromName}") {
privateKey
}
}
`
})
if (result.error) {
reject(result.error)
} else {
const parsedActivity = JSON.parse(activity)
if (Array.isArray(parsedActivity['@context'])) {
parsedActivity['@context'].push('https://w3id.org/security/v1')
} else {
const context = [parsedActivity['@context']]
context.push('https://w3id.org/security/v1')
parsedActivity['@context'] = context
}
// deduplicate context strings
parsedActivity['@context'] = [...new Set(parsedActivity['@context'])]
const privateKey = result.data.User[0].privateKey
const date = new Date().toUTCString()
debug(`url = ${url}`)
request({
url: url,
headers: {
'Host': targetDomain,
'Date': date,
'Signature': createSignature(privateKey, `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`, url,
{
'Host': targetDomain,
'Date': date,
'Content-Type': 'application/activity+json'
}),
'Content-Type': 'application/activity+json'
},
method: 'POST',
body: JSON.stringify(parsedActivity)
}, (error, response) => {
if (error) {
debug(`Error = ${JSON.stringify(error, null, 2)}`)
reject(error)
} else {
debug('Response Headers:', JSON.stringify(response.headers, null, 2))
debug('Response Body:', JSON.stringify(response.body, null, 2))
resolve()
}
})
}
})
}
export function verify (url, headers) {
return new Promise((resolve, reject) => {
const signatureHeader = headers['signature'] ? headers['signature'] : headers['Signature']
if (!signatureHeader) {
debug('No Signature header present!')
resolve(false)
}
debug(`Signature Header = ${signatureHeader}`)
const signature = extractKeyValueFromSignatureHeader(signatureHeader, 'signature')
const algorithm = extractKeyValueFromSignatureHeader(signatureHeader, 'algorithm')
const headersString = extractKeyValueFromSignatureHeader(signatureHeader, 'headers')
const keyId = extractKeyValueFromSignatureHeader(signatureHeader, 'keyId')
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) {
debug('Unsupported hash algorithm specified!')
resolve(false)
}
const usedHeaders = headersString.split(' ')
const verifyHeaders = {}
Object.keys(headers).forEach((key) => {
if (usedHeaders.includes(key.toLowerCase())) {
verifyHeaders[key.toLowerCase()] = headers[key]
}
})
const signingString = constructSigningString(url, verifyHeaders)
debug(`keyId= ${keyId}`)
request({
url: keyId,
headers: {
'Accept': 'application/json'
}
}, (err, response, body) => {
if (err) reject(err)
debug(`body = ${body}`)
const actor = JSON.parse(body)
const publicKeyPem = actor.publicKey.publicKeyPem
resolve(httpVerify(publicKeyPem, signature, signingString, algorithm))
})
})
}
// specify the public key owner object
/* const testPublicKeyOwner = {
"@context": jsig.SECURITY_CONTEXT_URL,
'@id': 'https://example.com/i/alice',
publicKey: [testPublicKey]
} */
/* const publicKey = {
'@context': jsig.SECURITY_CONTEXT_URL,
type: 'RsaVerificationKey2018',
id: `https://${ActivityPub.domain}/users/${fromName}#main-key`,
controller: `https://${ActivityPub.domain}/users/${fromName}`,
publicKeyPem
} */
// Obtained from invoking crypto.getHashes()
export const SUPPORTED_HASH_ALGORITHMS = [
'rsa-md4',
'rsa-md5',
'rsa-mdC2',
'rsa-ripemd160',
'rsa-sha1',
'rsa-sha1-2',
'rsa-sha224',
'rsa-sha256',
'rsa-sha384',
'rsa-sha512',
'blake2b512',
'blake2s256',
'md4',
'md4WithRSAEncryption',
'md5',
'md5-sha1',
'md5WithRSAEncryption',
'mdc2',
'mdc2WithRSA',
'ripemd',
'ripemd160',
'ripemd160WithRSA',
'rmd160',
'sha1',
'sha1WithRSAEncryption',
'sha224',
'sha224WithRSAEncryption',
'sha256',
'sha256WithRSAEncryption',
'sha384',
'sha384WithRSAEncryption',
'sha512',
'sha512WithRSAEncryption',
'ssl3-md5',
'ssl3-sha1',
'whirlpool']
// signing
function createSignature (privKey, keyId, url, headers = {}, algorithm = 'rsa-sha256') {
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { return throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
const signer = crypto.createSign(algorithm)
const signingString = constructSigningString(url, headers)
signer.update(signingString)
const signatureB64 = signer.sign({ key: privKey, passphrase: process.env.PRIVATE_KEY_PASSPHRASE }, 'base64')
const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '')
return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"`
}
// signing
function constructSigningString (url, headers) {
const urlObj = new URL(url)
let signingString = `(request-target): post ${urlObj.pathname}${urlObj.search !== '' ? urlObj.search : ''}`
return Object.keys(headers).reduce((result, key) => {
return result + `\n${key.toLowerCase()}: ${headers[key]}`
}, signingString)
}
// verifying
function httpVerify (pubKey, signature, signingString, algorithm) {
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
const verifier = crypto.createVerify(algorithm)
verifier.update(signingString)
return verifier.verify(pubKey, signature, 'base64')
}
// verifying
// This function can be used to extract the signature,headers,algorithm etc. out of the Signature Header.
// Just pass what you want as key
function extractKeyValueFromSignatureHeader (signatureHeader, key) {
const keyString = signatureHeader.split(',').filter((el) => {
return !!el.startsWith(key)
})[0]
let firstEqualIndex = keyString.search('=')
// When headers are requested add 17 to the index to remove "(request-target) " from the string
if (key === 'headers') { firstEqualIndex += 17 }
return keyString.substring(firstEqualIndex + 2, keyString.length - 1)
}

View File

@ -0,0 +1,215 @@
import crypto from 'crypto'
import { signAndSend } from '../security'
import as from 'activitystrea.ms'
import { activityPub } from '../ActivityPub'
const debug = require('debug')('ea:utils')
export function extractNameFromId (uri) {
const urlObject = new URL(uri)
const pathname = urlObject.pathname
const splitted = pathname.split('/')
return splitted[splitted.indexOf('users') + 1]
}
export function extractIdFromActivityId (uri) {
const urlObject = new URL(uri)
const pathname = urlObject.pathname
const splitted = pathname.split('/')
return splitted[splitted.indexOf('status') + 1]
}
export function constructIdFromName (name, fromDomain = activityPub.domain) {
return `https://${fromDomain}/activitypub/users/${name}`
}
export function extractDomainFromUrl (url) {
return new URL(url).hostname
}
export async function getActorIdByName (name) {
debug(`name = ${name}`)
return Promise.resolve()
}
export function sendCollection (collectionName, req, res) {
const name = req.params.name
const id = constructIdFromName(name)
switch (collectionName) {
case 'followers':
attachThenCatch(activityPub.getFollowersCollection(id), res)
break
case 'followersPage':
attachThenCatch(activityPub.getFollowersCollectionPage(id), res)
break
case 'following':
attachThenCatch(activityPub.getFollowingCollection(id), res)
break
case 'followingPage':
attachThenCatch(activityPub.getFollowingCollectionPage(id), res)
break
case 'outbox':
attachThenCatch(activityPub.getOutboxCollection(id), res)
break
case 'outboxPage':
attachThenCatch(activityPub.getOutboxCollectionPage(id), res)
break
default:
res.status(500).end()
}
}
function attachThenCatch (promise, res) {
return promise
.then((collection) => {
res.status(200).contentType('application/activity+json').send(collection)
})
.catch((err) => {
debug(`error getting a Collection: = ${err}`)
res.status(500).end()
})
}
export function createActor (name, pubkey) {
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'id': `https://${activityPub.domain}/activitypub/users/${name}`,
'type': 'Person',
'preferredUsername': `${name}`,
'name': `${name}`,
'following': `https://${activityPub.domain}/activitypub/users/${name}/following`,
'followers': `https://${activityPub.domain}/activitypub/users/${name}/followers`,
'inbox': `https://${activityPub.domain}/activitypub/users/${name}/inbox`,
'outbox': `https://${activityPub.domain}/activitypub/users/${name}/outbox`,
'url': `https://${activityPub.domain}/activitypub/@${name}`,
'endpoints': {
'sharedInbox': `https://${activityPub.domain}/activitypub/inbox`
},
'publicKey': {
'id': `https://${activityPub.domain}/activitypub/users/${name}#main-key`,
'owner': `https://${activityPub.domain}/activitypub/users/${name}`,
'publicKeyPem': pubkey
}
}
}
export function createWebFinger (name) {
return {
'subject': `acct:${name}@${activityPub.domain}`,
'links': [
{
'rel': 'self',
'type': 'application/activity+json',
'href': `https://${activityPub.domain}/users/${name}`
}
]
}
}
export function createOrderedCollection (name, collectionName) {
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}`,
'summary': `${name}s ${collectionName} collection`,
'type': 'OrderedCollection',
'first': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}?page=true`,
'totalItems': 0
}
}
export function createOrderedCollectionPage (name, collectionName) {
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}?page=true`,
'summary': `${name}s ${collectionName} collection`,
'type': 'OrderedCollectionPage',
'totalItems': 0,
'partOf': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}`,
'orderedItems': []
}
}
export function createNoteActivity (text, name, id, published) {
const createUuid = crypto.randomBytes(16).toString('hex')
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${createUuid}`,
'type': 'Create',
'actor': `https://${activityPub.domain}/activitypub/users/${name}`,
'object': {
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${id}`,
'type': 'Note',
'published': published,
'attributedTo': `https://${activityPub.domain}/activitypub/users/${name}`,
'content': text,
'to': 'https://www.w3.org/ns/activitystreams#Public'
}
}
}
export function createArticleActivity (text, name, id, published) {
const createUuid = crypto.randomBytes(16).toString('hex')
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${createUuid}`,
'type': 'Create',
'actor': `https://${activityPub.domain}/activitypub/users/${name}`,
'object': {
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${id}`,
'type': 'Article',
'published': published,
'attributedTo': `https://${activityPub.domain}/activitypub/users/${name}`,
'content': text,
'to': 'https://www.w3.org/ns/activitystreams#Public'
}
}
}
export function sendAcceptActivity (theBody, name, targetDomain, url) {
as.accept()
.id(`https://${activityPub.domain}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
.actor(`https://${activityPub.domain}/activitypub/users/${name}`)
.object(theBody)
.prettyWrite((err, doc) => {
if (!err) {
return signAndSend(doc, name, targetDomain, url)
} else {
debug(`error serializing Accept object: ${err}`)
throw new Error('error serializing Accept object')
}
})
}
export function sendRejectActivity (theBody, name, targetDomain, url) {
as.reject()
.id(`https://${activityPub.domain}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
.actor(`https://${activityPub.domain}/activitypub/users/${name}`)
.object(theBody)
.prettyWrite((err, doc) => {
if (!err) {
return signAndSend(doc, name, targetDomain, url)
} else {
debug(`error serializing Accept object: ${err}`)
throw new Error('error serializing Accept object')
}
})
}
export function throwErrorIfGraphQLErrorOccurred (result) {
if (result.error && (result.error.message || result.error.errors)) {
throw new Error(`${result.error.message ? result.error.message : result.error.errors[0].message}`)
}
}

View File

@ -0,0 +1,43 @@
import { createActor } from '../utils'
const gql = require('graphql-tag')
const debug = require('debug')('ea:serveUser')
export async function serveUser (req, res, next) {
let name = req.params.name
if (name.startsWith('@')) {
name = name.slice(1)
}
debug(`name = ${name}`)
const result = await req.app.get('ap').dataSource.client.query({
query: gql`
query {
User(slug: "${name}") {
publicKey
}
}
`
}).catch(reason => { debug(`serveUser User fetch error: ${reason}`) })
if (result.data && Array.isArray(result.data.User) && result.data.User.length > 0) {
const publicKey = result.data.User[0].publicKey
const actor = createActor(name, publicKey)
debug(`actor = ${JSON.stringify(actor, null, 2)}`)
debug(`accepts json = ${req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])}`)
if (req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])) {
return res.json(actor)
} else if (req.accepts('text/html')) {
// TODO show user's profile page instead of the actor object
/* const outbox = JSON.parse(result.outbox)
const posts = outbox.orderedItems.filter((el) => { return el.object.type === 'Note'})
const actor = result.actor
debug(posts) */
// res.render('user', { user: actor, posts: JSON.stringify(posts)})
return res.json(actor)
}
} else {
debug(`error getting publicKey for actor ${name}`)
next()
}
}

View File

@ -1,4 +1,5 @@
import createServer from './server'
import ActiviyPub from './activitypub/ActivityPub'
const serverConfig = {
port: process.env.GRAPHQL_PORT || 4000
@ -12,4 +13,5 @@ const server = createServer()
server.start(serverConfig, options => {
/* eslint-disable-next-line no-console */
console.log(`Server ready at ${process.env.GRAPHQL_URI} 🚀`)
ActiviyPub.init(server)
})

View File

@ -1,8 +1,16 @@
import createOrUpdateLocations from './nodes/locations'
import { generateRsaKeyPair } from '../activitypub/security'
import dotenv from 'dotenv'
import { resolve } from 'path'
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
export default {
Mutation: {
CreateUser: async (resolve, root, args, context, info) => {
const keys = generateRsaKeyPair()
Object.assign(args, keys)
args.actorId = `${process.env.ACTIVITYPUB_URI}/activitypub/users/${args.slug}`
const result = await resolve(root, args, context, info)
await createOrUpdateLocations(args.id, args.locationName, context.driver)
return result

View File

@ -77,6 +77,7 @@ type Location {
type User {
id: ID!
actorId: String
name: String
email: String
slug: String
@ -85,6 +86,8 @@ type User {
deleted: Boolean
disabled: Boolean
role: UserGroupEnum
publicKey: String
privateKey: String
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
locationName: String

View File

@ -0,0 +1,30 @@
Feature: Send and receive Articles
I want to send and receive article's via ActivityPub
Background:
Given our own server runs at "http://localhost:4100"
And we have the following users in our database:
| Slug |
| marvin |
| max |
Scenario: Send an article to a user inbox and make sure it's added to the inbox
When I send a POST request with the following activity to "/activitypub/users/max/inbox":
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://aronda.org/users/marvin/status/lka7dfzkjn2398hsfd",
"type": "Create",
"actor": "https://aronda.org/users/marvin",
"object": {
"id": "https://aronda.org/users/marvin/status/kljsdfg9843jknsdf",
"type": "Article",
"published": "2019-02-07T19:37:55.002Z",
"attributedTo": "https://aronda.org/users/marvin",
"content": "Hi John, how are you?",
"to": "https://localhost:4100/activitypub/users/max"
}
}
"""
Then I expect the status code to be 200
And the post with id "kljsdfg9843jknsdf" to be created

View File

@ -0,0 +1,28 @@
Feature: Delete an object
I want to delete objects
Background:
Given our own server runs at "http://localhost:4100"
And we have the following users in our database:
| Slug |
| bernd-das-brot|
Scenario: Deleting a post (Article Object)
When I send a POST request with the following activity to "/activitypub/users/bernd-das-brot/inbox":
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4100/users/karl-heinz/status/a4DJ2afdg323v32641vna42lkj685kasd2",
"type": "Delete",
"object": {
"id": "https://aronda.org/users/marvin/status/kljsdfg9843jknsdf",
"type": "Article",
"published": "2019-02-07T19:37:55.002Z",
"attributedTo": "https://aronda.org/users/marvin",
"content": "Hi John, how are you?",
"to": "https://localhost:4100/activitypub/users/max"
}
}
"""
Then I expect the status code to be 200
And the object is removed from the outbox collection of "karl-heinz"

View File

@ -0,0 +1,51 @@
Feature: Follow a user
I want to be able to follow a user on another instance.
Also if I do not want to follow a previous followed user anymore,
I want to undo the follow.
Background:
Given our own server runs at "http://localhost:4123"
And we have the following users in our database:
| Slug |
| karl-heinz |
| peter-lustiger |
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
When I send a POST request with the following activity to "/activitypub/users/peter-lustiger/inbox":
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/bob-der-baumeister/status/83J23549sda1k72fsa4567na42312455kad83",
"type": "Follow",
"actor": "http://localhost:4123/activitypub/users/bob-der-baumeister",
"object": "http://localhost:4123/activitypub/users/peter-lustiger"
}
"""
Then I expect the status code to be 200
And the follower is added to the followers collection of "peter-lustiger"
"""
https://localhost:4123/activitypub/users/bob-der-baumeister
"""
Scenario: Send an undo activity to revert the previous follow activity
When I send a POST request with the following activity to "/activitypub/users/bob-der-baumeister/inbox":
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/peter-lustiger/status/a4DJ2afdg323v32641vna42lkj685kasd2",
"type": "Undo",
"actor": "http://localhost:4123/activitypub/users/peter-lustiger",
"object": {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/bob-der-baumeister/status/83J23549sda1k72fsa4567na42312455kad83",
"type": "Follow",
"actor": "http://localhost:4123/activitypub/users/bob-der-baumeister",
"object": "http://localhost:4123/activitypub/users/peter-lustiger"
}
}
"""
Then I expect the status code to be 200
And the follower is removed from the followers collection of "peter-lustiger"
"""
https://localhost:4123/activitypub/users/bob-der-baumeister
"""

View File

@ -0,0 +1,101 @@
Feature: Receiving collections
As a member of the Fediverse I want to be able of fetching collections
Background:
Given our own server runs at "http://localhost:4123"
And we have the following users in our database:
| Slug |
| renate-oberdorfer |
Scenario: Send a request to the outbox URI of peter-lustig and expect a ordered collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/outbox"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
"summary": "renate-oberdorfers outbox collection",
"type": "OrderedCollection",
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
"totalItems": 0
}
"""
Scenario: Send a request to the following URI of peter-lustig and expect a ordered collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/following"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/following",
"summary": "renate-oberdorfers following collection",
"type": "OrderedCollection",
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
"totalItems": 0
}
"""
Scenario: Send a request to the followers URI of peter-lustig and expect a ordered collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/followers"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers",
"summary": "renate-oberdorfers followers collection",
"type": "OrderedCollection",
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
"totalItems": 0
}
"""
Scenario: Send a request to the outbox URI of peter-lustig and expect a paginated outbox collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/outbox?page=true"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
"summary": "renate-oberdorfers outbox collection",
"type": "OrderedCollectionPage",
"totalItems": 0,
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
"orderedItems": []
}
"""
Scenario: Send a request to the following URI of peter-lustig and expect a paginated following collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/following?page=true"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
"summary": "renate-oberdorfers following collection",
"type": "OrderedCollectionPage",
"totalItems": 0,
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/following",
"orderedItems": []
}
"""
Scenario: Send a request to the followers URI of peter-lustig and expect a paginated followers collection
When I send a GET request to "/activitypub/users/renate-oberdorfer/followers?page=true"
Then I expect the status code to be 200
And I receive the following json:
"""
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
"summary": "renate-oberdorfers followers collection",
"type": "OrderedCollectionPage",
"totalItems": 0,
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers",
"orderedItems": []
}
"""

View File

@ -0,0 +1,125 @@
// features/support/steps.js
import { Given, When, Then, AfterAll } from 'cucumber'
import { expect } from 'chai'
// import { client } from '../../../src/activitypub/apollo-client'
import { GraphQLClient } from 'graphql-request'
import Factory from '../../../src/seed/factories'
import { host } from '../../../src/jest/helpers'
const debug = require('debug')('ea:test:steps')
const factory = Factory()
const client = new GraphQLClient(host)
function createUser (slug) {
debug(`creating user ${slug}`)
return factory.create('User', {
name: slug,
email: 'example@test.org',
password: '1234'
})
// await login({ email: 'example@test.org', password: '1234' })
}
AfterAll('Clean up the test data', function () {
debug('All the tests are done! Deleting test data')
})
Given('our own server runs at {string}', function (string) {
// just documenation
})
Given('we have the following users in our database:', async function (dataTable) {
await Promise.all(dataTable.hashes().map((user) => {
return createUser(user.Slug)
}))
})
When('I send a GET request to {string}', async function (pathname) {
const response = await this.get(pathname)
this.lastContentType = response.lastContentType
this.lastResponses.push(response.lastResponse)
this.statusCode = response.statusCode
})
When('I send a POST request with the following activity to {string}:', async function (inboxUrl, activity) {
debug(`inboxUrl = ${inboxUrl}`)
debug(`activity = ${activity}`)
this.lastInboxUrl = inboxUrl
this.lastActivity = activity
const response = await this.post(inboxUrl, activity)
this.lastResponses.push(response.lastResponse)
this.lastResponse = response.lastResponse
this.statusCode = response.statusCode
})
Then('I receive the following json:', function (docString) {
const parsedDocString = JSON.parse(docString)
const parsedLastResponse = JSON.parse(this.lastResponses.shift())
if (Array.isArray(parsedDocString.orderedItems)) {
parsedDocString.orderedItems.forEach((el) => {
delete el.id
if (el.object) delete el.object.published
})
parsedLastResponse.orderedItems.forEach((el) => {
delete el.id
if (el.object) delete el.object.published
})
}
if (parsedDocString.publicKey && parsedDocString.publicKey.publicKeyPem) {
delete parsedDocString.publicKey.publicKeyPem
delete parsedLastResponse.publicKey.publicKeyPem
}
expect(parsedDocString).to.eql(parsedLastResponse)
})
Then('I expect the Content-Type to be {string}', function (contentType) {
expect(this.lastContentType).to.equal(contentType)
})
Then('I expect the status code to be {int}', function (statusCode) {
expect(this.statusCode).to.equal(statusCode)
})
Then('the activity is added to the {string} collection', async function (collectionName) {
const response = await this.get(this.lastInboxUrl.replace('inbox', collectionName) + '?page=true')
debug(`orderedItems = ${JSON.parse(response.lastResponse).orderedItems}`)
expect(JSON.parse(response.lastResponse).orderedItems).to.include(JSON.parse(this.lastActivity).object)
})
Then('the follower is added to the followers collection of {string}', async function (userName, follower) {
const response = await this.get(`/activitypub/users/${userName}/followers?page=true`)
const responseObject = JSON.parse(response.lastResponse)
expect(responseObject.orderedItems).to.include(follower)
})
Then('the follower is removed from the followers collection of {string}', async function (userName, follower) {
const response = await this.get(`/activitypub/users/${userName}/followers?page=true`)
const responseObject = JSON.parse(response.lastResponse)
expect(responseObject.orderedItems).to.not.include(follower)
})
Then('the activity is added to the users inbox collection', async function () {
})
Then('the post with id {string} to be created', async function (id) {
const result = await client.request(`
query {
Post(id: "${id}") {
name
}
}
`)
expect(result.data.Post).to.be.an('array').that.is.not.empty // eslint-disable-line
})
Then('the object is removed from the outbox collection of {string}', (name) => {
})
Then('I send a GET request to {string} and expect a ordered collection', () => {
})

View File

@ -0,0 +1,65 @@
Feature: Webfinger discovery
From an external server, e.g. Mastodon
I want to search for an actor alias
In order to follow the actor
Background:
Given our own server runs at "http://localhost:4100"
And we have the following users in our database:
| Slug |
| peter-lustiger |
Scenario: Search
When I send a GET request to "/.well-known/webfinger?resource=acct:peter-lustiger@localhost"
Then I receive the following json:
"""
{
"subject": "acct:peter-lustiger@localhost:4123",
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://localhost:4123/users/peter-lustiger"
}
]
}
"""
And I expect the Content-Type to be "application/jrd+json; charset=utf-8"
Scenario: User does not exist
When I send a GET request to "/.well-known/webfinger?resource=acct:nonexisting@localhost"
Then I receive the following json:
"""
{
"error": "No record found for nonexisting@localhost."
}
"""
Scenario: Receiving an actor object
When I send a GET request to "/activitypub/users/peter-lustiger"
Then I receive the following json:
"""
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://localhost:4123/activitypub/users/peter-lustiger",
"type": "Person",
"preferredUsername": "peter-lustiger",
"name": "peter-lustiger",
"following": "https://localhost:4123/activitypub/users/peter-lustiger/following",
"followers": "https://localhost:4123/activitypub/users/peter-lustiger/followers",
"inbox": "https://localhost:4123/activitypub/users/peter-lustiger/inbox",
"outbox": "https://localhost:4123/activitypub/users/peter-lustiger/outbox",
"url": "https://localhost:4123/activitypub/@peter-lustiger",
"endpoints": {
"sharedInbox": "https://localhost:4123/activitypub/inbox"
},
"publicKey": {
"id": "https://localhost:4123/activitypub/users/peter-lustiger#main-key",
"owner": "https://localhost:4123/activitypub/users/peter-lustiger",
"publicKeyPem": "adglkjlk89235kjn8obn2384f89z5bv9..."
}
}
"""

58
test/features/world.js Normal file
View File

@ -0,0 +1,58 @@
// features/support/world.js
import { setWorldConstructor } from 'cucumber'
import request from 'request'
const debug = require('debug')('ea:test:world')
class CustomWorld {
constructor () {
// webfinger.feature
this.lastResponses = []
this.lastContentType = null
this.lastInboxUrl = null
this.lastActivity = null
// activity-article.feature.disable
this.statusCode = null
}
get (pathname) {
return new Promise((resolve, reject) => {
request(`http://localhost:4123/${this.replaceSlashes(pathname)}`, {
headers: {
'Accept': 'application/activity+json'
}}, function (error, response, body) {
if (!error) {
debug(`get response = ${response.headers['content-type']}`)
debug(`body = ${body}`)
resolve({ lastResponse: body, lastContentType: response.headers['content-type'], statusCode: response.statusCode })
} else {
reject(error)
}
})
})
}
replaceSlashes (pathname) {
return pathname.replace(/^\/+/, '')
}
post (pathname, activity) {
return new Promise((resolve, reject) => {
request({
url: `http://localhost:4123/${this.replaceSlashes(pathname)}`,
method: 'POST',
headers: {
'Content-Type': 'application/activity+json'
},
body: activity
}, function (error, response, body) {
if (!error) {
debug(`post response = ${response.headers['content-type']}`)
resolve({ lastResponse: body, lastContentType: response.headers['content-type'], statusCode: response.statusCode })
} else {
reject(error)
}
})
})
}
}
setWorldConstructor(CustomWorld)

600
yarn.lock
View File

@ -291,6 +291,14 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-proposal-throw-expressions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz#2d9e452d370f139000e51db65d0a85dc60c64739"
integrity sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-throw-expressions" "^7.2.0"
"@babel/plugin-proposal-unicode-property-regex@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520"
@ -328,6 +336,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-throw-expressions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.2.0.tgz#79001ee2afe1b174b1733cdc2fc69c9a46a0f1f8"
integrity sha512-ngwynuqu1Rx0JUS9zxSDuPgW1K8TyVZCi2hHehrL4vyjqE7RGoNHWlZsS7KQT2vw9Yjk4YLa0+KldBXTRdPLRg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-arrow-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
@ -558,6 +573,14 @@
core-js "^2.5.7"
regenerator-runtime "^0.11.1"
"@babel/polyfill@^7.2.3":
version "7.2.5"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
integrity sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==
dependencies:
core-js "^2.5.7"
regenerator-runtime "^0.12.0"
"@babel/preset-env@~7.3.1":
version "7.3.1"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
@ -876,6 +899,33 @@ acorn@^6.0.1, acorn@^6.0.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754"
integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==
activitystrea.ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/activitystrea.ms/-/activitystrea.ms-2.1.3.tgz#553548733e367dc0b6a7badc25fa6f8996cd80c3"
integrity sha512-iiG5g5fYgfdaaqqFPaFIZC/KX8/4mOWkvniK+BNwJY6XDDKdIu56wmc9r0x1INHVnbFOTGuM8mZEntaM3I+YXw==
dependencies:
activitystreams-context "^3.0.0"
jsonld "^0.4.11"
jsonld-signatures "^1.1.5"
moment "^2.17.1"
readable-stream "^2.2.3"
reasoner "2.0.0"
rfc5646 "^2.0.0"
vocabs-as "^3.0.0"
vocabs-asx "^0.11.1"
vocabs-interval "^0.11.1"
vocabs-ldp "^0.1.0"
vocabs-owl "^0.11.1"
vocabs-rdf "^0.11.1"
vocabs-rdfs "^0.11.1"
vocabs-social "^0.11.1"
vocabs-xsd "^0.11.1"
activitystreams-context@>=3.0.0, activitystreams-context@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/activitystreams-context/-/activitystreams-context-3.1.0.tgz#28334e129f17cfb937e8c702c52c1bcb1d2830c7"
integrity sha512-KBQ+igwf1tezMXGVw5MvRSEm0gp97JI1hTZ45I6MEkWv25lEgNoA9L6wqfaOiCX8wnMRWw9pwRsPZKypdtxAtg==
ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1:
version "6.6.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
@ -920,6 +970,11 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@ -1295,6 +1350,15 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assertion-error-formatter@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-2.0.1.tgz#6bbdffaec8e2fa9e2b0eb158bfe353132d7c0a9b"
integrity sha512-cjC3jUCh9spkroKue5PDSKH5RFQ/KNuZJhk3GwHYmB/8qqETxLOmMdLH+ohi/VukNzxDlMvIe7zScvLoOdhb6Q==
dependencies:
diff "^3.0.0"
pad-right "^0.2.2"
repeat-string "^1.6.1"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
@ -1337,6 +1401,11 @@ async-retry@^1.2.1:
dependencies:
retry "0.12.0"
async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
async@^2.5.0, async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
@ -1485,11 +1554,49 @@ bcryptjs@~2.4.3:
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
becke-ch--regex--s0-0-v1--base--pl--lib@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz#429ceebbfa5f7e936e78d73fbdc7da7162b20e20"
integrity sha1-Qpzuu/pffpNueNc/vcfacWKyDiA=
binary-extensions@^1.0.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
bitcore-lib@^0.13.7:
version "0.13.19"
resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc"
integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w=
dependencies:
bn.js "=2.0.4"
bs58 "=2.0.0"
buffer-compare "=1.0.0"
elliptic "=3.0.3"
inherits "=2.0.1"
lodash "=3.10.1"
"bitcore-message@github:comakery/bitcore-message#dist":
version "1.0.2"
resolved "https://codeload.github.com/comakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf"
dependencies:
bitcore-lib "^0.13.7"
bluebird@^3.4.1:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
bn.js@=2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480"
integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA=
bn.js@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625"
integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU=
body-parser-graphql@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/body-parser-graphql/-/body-parser-graphql-1.1.0.tgz#80a80353c7cb623562fd375750dfe018d75f0f7c"
@ -1555,6 +1662,11 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
browser-process-hrtime@^0.1.2:
version "0.1.3"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4"
@ -1576,6 +1688,11 @@ browserslist@^4.3.4:
electron-to-chromium "^1.3.86"
node-releases "^1.0.5"
bs58@=2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5"
integrity sha1-crcTvtIjoKxRi72g484/SBfznrU=
bser@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
@ -1583,6 +1700,11 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
buffer-compare@=1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2"
integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI=
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@ -1794,7 +1916,7 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
cli-table3@^0.5.0:
cli-table3@^0.5.0, cli-table3@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
@ -1860,7 +1982,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.5.0, commander@^2.8.1:
commander@^2.5.0, commander@^2.8.1, commander@^2.9.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@ -1870,6 +1992,13 @@ commander@~2.17.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
commander@~2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=
dependencies:
graceful-readlink ">= 1.0.0"
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -1973,7 +2102,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cors@^2.8.4:
cors@^2.8.4, cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
@ -2064,6 +2193,60 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
cucumber-expressions@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-6.0.1.tgz#47c9c573781c2ff721d7ad5b2cd1c97f4399ab8e"
integrity sha1-R8nFc3gcL/ch161bLNHJf0OZq44=
dependencies:
becke-ch--regex--s0-0-v1--base--pl--lib "^1.2.0"
cucumber-tag-expressions@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-1.1.1.tgz#7f5c7b70009bc2b666591bfe64854578bedee85a"
integrity sha1-f1x7cACbwrZmWRv+ZIVFeL7e6Fo=
cucumber@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-5.1.0.tgz#7b166812c255bec7eac4b0df7007a40d089c895d"
integrity sha512-zrl2VYTBRgvxucwV2GKAvLqcfA1Naeax8plPvWgPEzl3SCJiuPPv3WxBHIRHtPYcEdbHDR6oqLpZP4bJ8UIdmA==
dependencies:
"@babel/polyfill" "^7.2.3"
assertion-error-formatter "^2.0.1"
bluebird "^3.4.1"
cli-table3 "^0.5.1"
colors "^1.1.2"
commander "^2.9.0"
cross-spawn "^6.0.5"
cucumber-expressions "^6.0.0"
cucumber-tag-expressions "^1.1.1"
duration "^0.2.1"
escape-string-regexp "^1.0.5"
figures "2.0.0"
gherkin "^5.0.0"
glob "^7.1.3"
indent-string "^3.1.0"
is-generator "^1.0.2"
is-stream "^1.1.0"
knuth-shuffle-seeded "^1.0.6"
lodash "^4.17.10"
mz "^2.4.0"
progress "^2.0.0"
resolve "^1.3.3"
serialize-error "^3.0.0"
stack-chain "^2.0.0"
stacktrace-js "^2.0.0"
string-argv "0.1.1"
title-case "^2.1.1"
util-arity "^1.0.2"
verror "^1.9.0"
d@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=
dependencies:
es5-ext "^0.10.9"
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@ -2217,6 +2400,11 @@ diff-sequences@^24.0.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.0.0.tgz#cdf8e27ed20d8b8d3caccb4e0c0d8fe31a173013"
integrity sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==
diff@^3.0.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@ -2302,6 +2490,14 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
duration@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/duration/-/duration-0.2.2.tgz#ddf149bc3bc6901150fe9017111d016b3357f529"
integrity sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==
dependencies:
d "1"
es5-ext "~0.10.46"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -2327,6 +2523,16 @@ electron-to-chromium@^1.3.86:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz#f36ab32634f49ef2b0fdc1e82e2d1cc17feb29e7"
integrity sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A==
elliptic@=3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595"
integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU=
dependencies:
bn.js "^2.0.0"
brorand "^1.0.1"
hash.js "^1.0.0"
inherits "^2.0.1"
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@ -2351,6 +2557,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
error-stack-parser@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d"
integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw==
dependencies:
stackframe "^1.0.4"
es-abstract@^1.4.3:
version "1.12.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
@ -2383,11 +2596,52 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.46:
version "0.10.48"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.48.tgz#9a0b31eeded39e64453bcedf6f9d50bbbfb43850"
integrity sha512-CdRvPlX/24Mj5L4NVxTs4804sxiS2CjVprgCmrgoDkdmjdY4D+ySHa7K3jJf8R40dFg0tIm3z/dk326LrnuSGw==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.1"
next-tick "1"
es6-error@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-promise@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.3.0.tgz#96edb9f2fdb01995822b263dd8aadab6748181bc"
integrity sha1-lu258v2wGZWCKyY92KratnSBgbw=
es6-promise@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-2.0.1.tgz#ccc4963e679f0ca9fb187c777b9e583d3c7573c2"
integrity sha1-zMSWPmefDKn7GHx3e55YPTx1c8I=
es6-promise@~4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.0.5.tgz#7882f30adde5b240ccfa7f7d78c548330951ae42"
integrity sha1-eILzCt3lskDM+n99eMVIMwlRrkI=
es6-symbol@^3.1.1, es6-symbol@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
dependencies:
d "1"
es5-ext "~0.10.14"
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@ -2670,7 +2924,7 @@ expect@^24.1.0:
jest-message-util "^24.0.0"
jest-regex-util "^24.0.0"
express@^4.0.0, express@^4.16.3:
express@^4.0.0, express@^4.16.3, express@^4.16.4:
version "4.16.4"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
@ -2786,7 +3040,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
figures@^2.0.0:
figures@2.0.0, figures@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
@ -3024,6 +3278,11 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
gherkin@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5"
integrity sha1-aEu7A63STq9731RPWAM+so+zxtU=
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@ -3078,6 +3337,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
graphql-custom-directives@~0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/graphql-custom-directives/-/graphql-custom-directives-0.2.14.tgz#88611b8cb074477020ad85af47bfe168c4c23992"
@ -3230,14 +3494,7 @@ graphql-yoga@~1.17.4:
graphql-tools "^4.0.0"
subscriptions-transport-ws "^0.9.8"
"graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0", graphql@^14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.0.2.tgz#7dded337a4c3fd2d075692323384034b357f5650"
integrity sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==
dependencies:
iterall "^1.2.2"
graphql@~14.1.1:
"graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0", graphql@^14.0.2, graphql@~14.1.1:
version "14.1.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.1.1.tgz#d5d77df4b19ef41538d7215d1e7a28834619fac0"
integrity sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q==
@ -3326,6 +3583,14 @@ has@^1.0.1, has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hash.js@^1.0.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hasha@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39"
@ -3491,6 +3756,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -3504,6 +3774,11 @@ inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, i
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@=2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
@ -3681,6 +3956,11 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e"
integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==
is-generator@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3"
integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@ -4348,6 +4628,38 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonld-signatures@^1.1.5:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jsonld-signatures/-/jsonld-signatures-1.2.1.tgz#493df5df9cd3a9f1b1cb296bbd3d081679f20ca8"
integrity sha1-ST3135zTqfGxyylrvT0IFnnyDKg=
dependencies:
async "^1.5.2"
bitcore-message "github:CoMakery/bitcore-message#dist"
commander "~2.9.0"
es6-promise "~4.0.5"
jsonld "0.4.3"
node-forge "~0.6.45"
jsonld@0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.4.3.tgz#0bbc929190064d6650a5af5876e1bfdf0ed288f3"
integrity sha1-C7ySkZAGTWZQpa9YduG/3w7SiPM=
dependencies:
es6-promise "~2.0.1"
pkginfo "~0.3.0"
request "^2.61.0"
xmldom "0.1.19"
jsonld@^0.4.11:
version "0.4.12"
resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.4.12.tgz#a02f205d5341414df1b6d8414f1b967a712073e8"
integrity sha1-oC8gXVNBQU3xtthBTxuWenEgc+g=
dependencies:
es6-promise "^2.0.0"
pkginfo "~0.4.0"
request "^2.61.0"
xmldom "0.1.19"
jsonwebtoken@^8.2.0, jsonwebtoken@~8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#ebd0ca2a69797816e1c5af65b6c759787252947e"
@ -4420,6 +4732,13 @@ kleur@^3.0.0:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.1.tgz#4f5b313f5fa315432a400f19a24db78d451ede62"
integrity sha512-P3kRv+B+Ra070ng2VKQqW4qW7gd/v3iD8sy/zOdcYRsfiD+QBokQNOps/AfP6Hr48cBhIIBFWckB9aO+IZhrWg==
knuth-shuffle-seeded@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz#01f1b65733aa7540ee08d8b0174164d22081e4e1"
integrity sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=
dependencies:
seed-random "~2.2.0"
latest-version@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
@ -4571,6 +4890,11 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@=3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.11:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
@ -4588,6 +4912,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lowercase-keys@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -4735,6 +5064,11 @@ mimic-fn@^1.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -4787,6 +5121,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1:
dependencies:
minimist "0.0.8"
moment@^2.17.1:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
moment@^2.22.2:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
@ -4807,6 +5146,20 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
mz@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
n3@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/n3/-/n3-0.9.1.tgz#430b547d58dc7381408c45784dd8058171903932"
integrity sha1-QwtUfVjcc4FAjEV4TdgFgXGQOTI=
nan@^2.9.2:
version "2.11.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
@ -4866,11 +5219,23 @@ neo4j-graphql-js@~2.3.1:
lodash "^4.17.11"
neo4j-driver "^1.7.2"
next-tick@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
dependencies:
lower-case "^1.1.1"
node-fetch@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
@ -4881,6 +5246,11 @@ node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@~2.3.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==
node-forge@~0.6.45:
version "0.6.49"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.49.tgz#f1ee95d5d74623938fe19d698aa5a26d54d2f60f"
integrity sha1-8e6V1ddGI5OP4Z1piqWibVTS9g8=
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -5299,6 +5669,13 @@ package-json@^4.0.0:
registry-url "^3.0.3"
semver "^5.1.0"
pad-right@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774"
integrity sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=
dependencies:
repeat-string "^1.5.2"
parent-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5"
@ -5469,6 +5846,16 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
pkginfo@~0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
pkginfo@~0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
pn@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
@ -5716,7 +6103,7 @@ readable-stream@1.1.x:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.5:
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.3, readable-stream@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@ -5754,6 +6141,18 @@ realpath-native@^1.0.0, realpath-native@^1.0.2:
dependencies:
util.promisify "^1.0.0"
reasoner@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/reasoner/-/reasoner-2.0.0.tgz#6ccf76cb9baf96b82c45ab0bd60211c2aa1b701b"
integrity sha1-bM92y5uvlrgsRasL1gIRwqobcBs=
dependencies:
n3 "^0.9.1"
rfc5646 "^2.0.0"
vocabs-asx "^0.11.1"
vocabs-rdf "^0.11.1"
vocabs-rdfs "^0.11.1"
vocabs-xsd "^0.11.1"
regenerate-unicode-properties@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
@ -5861,7 +6260,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
repeat-string@^1.6.1:
repeat-string@^1.5.2, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@ -5882,7 +6281,7 @@ request-promise-native@^1.0.5:
stealthy-require "^1.1.0"
tough-cookie ">=2.3.3"
request@^2.87.0, request@^2.88.0:
request@^2.61.0, request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@ -5945,7 +6344,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
@ -5970,6 +6369,11 @@ retry@0.12.0:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
rfc5646@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rfc5646/-/rfc5646-2.0.0.tgz#ac0c67b6cd04411ef7c80751ba159d9371ce116c"
integrity sha1-rAxnts0EQR73yAdRuhWdk3HOEWw=
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@ -6069,6 +6473,11 @@ scheduler@^0.11.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
seed-random@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"
integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
@ -6105,6 +6514,11 @@ send@0.16.2:
range-parser "~1.2.0"
statuses "~1.4.0"
serialize-error@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-3.0.0.tgz#80100282b09be33c611536f50033481cb9cc87cf"
integrity sha512-+y3nkkG/go1Vdw+2f/+XUXM1DXX1XcxTl99FfiD/OEPUNw4uo0i6FKABfTAN5ZcgGtjTRZcEbxcE/jtXbEY19A==
serve-static@1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
@ -6265,6 +6679,11 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@ -6348,11 +6767,45 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
stack-chain@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-2.0.0.tgz#d73d1172af89565f07438b5bcc086831b6689b2d"
integrity sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==
stack-generator@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.3.tgz#bb74385c67ffc4ccf3c4dee5831832d4e509c8a0"
integrity sha512-kdzGoqrnqsMxOEuXsXyQTmvWXZmG0f3Ql2GDx5NtmZs59sT2Bt9Vdyq0XdtxUi58q/+nxtbF9KOQ9HkV1QznGg==
dependencies:
stackframe "^1.0.4"
stack-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
stackframe@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b"
integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==
stacktrace-gps@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz#33f8baa4467323ab2bd1816efa279942ba431ccc"
integrity sha512-9o+nWhiz5wFnrB3hBHs2PTyYrS60M1vvpSzHxwxnIbtY2q9Nt51hZvhrG1+2AxD374ecwyS+IUwfkHRE/2zuGg==
dependencies:
source-map "0.5.6"
stackframe "^1.0.4"
stacktrace-js@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.0.tgz#776ca646a95bc6c6b2b90776536a7fc72c6ddb58"
integrity sha1-d2ymRqlbxsayuQd2U2p/xyxt21g=
dependencies:
error-stack-parser "^2.0.1"
stack-generator "^2.0.1"
stacktrace-gps "^3.0.1"
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -6381,6 +6834,11 @@ streamsearch@0.1.2:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
string-argv@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.1.1.tgz#66bd5ae3823708eaa1916fa5412703150d4ddfaf"
integrity sha512-El1Va5ehZ0XTj3Ekw4WFidXvTmt9SrC0+eigdojgtJMVtPkF0qbBe9fyNSl9eQf+kUHnTSQxdQYzuHfZy8V+DQ==
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@ -6584,6 +7042,20 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.0"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
dependencies:
any-promise "^1.0.0"
throat@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
@ -6599,6 +7071,14 @@ timed-out@^4.0.0:
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
title-case@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"
integrity sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o=
dependencies:
no-case "^2.2.0"
upper-case "^1.0.3"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -6843,6 +7323,11 @@ update-notifier@^2.5.0:
semver-diff "^2.0.0"
xdg-basedir "^3.0.0"
upper-case@^1.0.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
uri-js@^4.2.1, uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
@ -6875,6 +7360,11 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
util-arity@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/util-arity/-/util-arity-1.1.0.tgz#59d01af1fdb3fede0ac4e632b0ab5f6ce97c9330"
integrity sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -6923,7 +7413,7 @@ vary@^1, vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
verror@1.10.0:
verror@1.10.0, verror@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
@ -6932,6 +7422,75 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vocabs-as@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/vocabs-as/-/vocabs-as-3.0.0.tgz#0dd0549cecb331ba4e917d2c5a4e83b146865c23"
integrity sha512-Dfze+B0CYZzhSK12jWvbxaL8/vXPnlzhhqhQTrEVxkGht+qzU4MmSLXSomQrdiSNKokVVtt16tyKoJWBW9TdNQ==
dependencies:
activitystreams-context ">=3.0.0"
vocabs ">=0.11.2"
vocabs-asx@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-asx/-/vocabs-asx-0.11.1.tgz#6667e4e174dc4556722b6cb1b9619fb16491519a"
integrity sha1-Zmfk4XTcRVZyK2yxuWGfsWSRUZo=
dependencies:
vocabs ">=0.11.1"
vocabs-interval@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-interval/-/vocabs-interval-0.11.1.tgz#1c009421f3e88a307aafbb75bfa670ff0f4f6d3c"
integrity sha1-HACUIfPoijB6r7t1v6Zw/w9PbTw=
dependencies:
vocabs ">=0.11.1"
vocabs-ldp@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/vocabs-ldp/-/vocabs-ldp-0.1.0.tgz#da1728df560471750dfc7050e7e2df1bab901ce6"
integrity sha1-2hco31YEcXUN/HBQ5+LfG6uQHOY=
dependencies:
vocabs ">=0.11.1"
vocabs-owl@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-owl/-/vocabs-owl-0.11.1.tgz#2355bbd27bfc19c5992d98079bbab3d7d65459e9"
integrity sha1-I1W70nv8GcWZLZgHm7qz19ZUWek=
dependencies:
vocabs ">=0.11.1"
vocabs-rdf@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-rdf/-/vocabs-rdf-0.11.1.tgz#c7fa91d83b050ffb7b98ce2c72ab25c6fbcd1194"
integrity sha1-x/qR2DsFD/t7mM4scqslxvvNEZQ=
dependencies:
vocabs ">=0.11.1"
vocabs-rdfs@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-rdfs/-/vocabs-rdfs-0.11.1.tgz#2e2df56ae0de008585b21057570386018da455bf"
integrity sha1-Li31auDeAIWFshBXVwOGAY2kVb8=
dependencies:
vocabs ">=0.11.1"
vocabs-social@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-social/-/vocabs-social-0.11.1.tgz#d28545868cce325ba0c88e394f3de6e03fad85b1"
integrity sha1-0oVFhozOMlugyI45Tz3m4D+thbE=
dependencies:
vocabs ">=0.11.1"
vocabs-xsd@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/vocabs-xsd/-/vocabs-xsd-0.11.1.tgz#20e201d8fd0fd330d6650d9061fda60baae6cd6c"
integrity sha1-IOIB2P0P0zDWZQ2QYf2mC6rmzWw=
dependencies:
vocabs ">=0.11.1"
vocabs@>=0.11.1, vocabs@>=0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/vocabs/-/vocabs-0.11.2.tgz#8944b40f11d415f07db6e259804024a1dbfaa4d4"
integrity sha512-OIon2MWA21ZO42UBsTa5DuMsk5zv72DxMdQNvLsPN1M9GrjVTovn3LgWUZdPVnKBpdWhqWV7Mfbq/Sh0vkHIBw==
w3c-hr-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
@ -7125,6 +7684,11 @@ xmlbuilder@~9.0.1:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xmldom@0.1.19:
version "0.1.19"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
xtend@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"