mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #254 from Human-Connection/fix_performance_issues_during_tests
Bundle all activityPub in middleware
This commit is contained in:
commit
7d26b03f88
@ -1,69 +1,84 @@
|
|||||||
import { createSignature, verifySignature } from '.'
|
import { generateRsaKeyPair, createSignature, verifySignature } from '.'
|
||||||
import Factory from '../../seed/factories'
|
|
||||||
import { host, login } from '../../jest/helpers'
|
|
||||||
import { GraphQLClient } from 'graphql-request'
|
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { expect } from 'chai'
|
import request from 'request'
|
||||||
const factory = Factory()
|
jest.mock('request')
|
||||||
|
|
||||||
describe('Signature creation and verification', () => {
|
let privateKey
|
||||||
let user = null
|
let publicKey
|
||||||
let client = null
|
let headers
|
||||||
const headers = {
|
const passphrase = 'a7dsf78sadg87ad87sfagsadg78'
|
||||||
'Date': '2019-03-08T14:35:45.759Z',
|
|
||||||
'Host': 'democracy-app.de',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
describe('activityPub/security', () => {
|
||||||
await factory.create('User', {
|
beforeEach(() => {
|
||||||
'slug': 'test-user',
|
const pair = generateRsaKeyPair({ passphrase })
|
||||||
'name': 'Test User',
|
privateKey = pair.privateKey
|
||||||
'email': 'user@example.org',
|
publicKey = pair.publicKey
|
||||||
'password': 'swordfish'
|
headers = {
|
||||||
|
'Date': '2019-03-08T14:35:45.759Z',
|
||||||
|
'Host': 'democracy-app.de',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createSignature', () => {
|
||||||
|
describe('returned http signature', () => {
|
||||||
|
let signatureB64
|
||||||
|
let httpSignature
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const signer = crypto.createSign('rsa-sha256')
|
||||||
|
signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json')
|
||||||
|
signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64')
|
||||||
|
httpSignature = createSignature({ privateKey, keyId: 'https://human-connection.org/activitypub/users/lea#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('contains keyId', () => {
|
||||||
|
expect(httpSignature).toContain('keyId="https://human-connection.org/activitypub/users/lea#main-key"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('contains default algorithm "rsa-sha256"', () => {
|
||||||
|
expect(httpSignature).toContain('algorithm="rsa-sha256"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('contains headers', () => {
|
||||||
|
expect(httpSignature).toContain('headers="(request-target) date host content-type"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('contains signature', () => {
|
||||||
|
expect(httpSignature).toContain('signature="' + signatureB64 + '"')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
const headers = await login({ email: 'user@example.org', password: 'swordfish' })
|
})
|
||||||
client = new GraphQLClient(host, { headers })
|
|
||||||
const result = await client.request(`query {
|
describe('verifySignature', () => {
|
||||||
User(slug: "test-user") {
|
let httpSignature
|
||||||
privateKey
|
|
||||||
publicKey
|
beforeEach(() => {
|
||||||
|
httpSignature = createSignature({ privateKey, keyId: 'http://localhost:4001/activitypub/users/test-user#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase })
|
||||||
|
const body = {
|
||||||
|
'publicKey': {
|
||||||
|
'id': 'https://localhost:4001/activitypub/users/test-user#main-key',
|
||||||
|
'owner': 'https://localhost:4001/activitypub/users/test-user',
|
||||||
|
'publicKeyPem': publicKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`)
|
|
||||||
user = result.User[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
const mockedRequest = jest.fn((_, callback) => callback(null, null, JSON.stringify(body)))
|
||||||
await factory.cleanDatabase()
|
request.mockImplementation(mockedRequest)
|
||||||
})
|
|
||||||
|
|
||||||
describe('Signature creation', () => {
|
|
||||||
let signatureB64 = ''
|
|
||||||
beforeEach(() => {
|
|
||||||
const signer = crypto.createSign('rsa-sha256')
|
|
||||||
signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json')
|
|
||||||
signatureB64 = signer.sign({ key: user.privateKey, passphrase: 'a7dsf78sadg87ad87sfagsadg78' }, 'base64')
|
|
||||||
})
|
|
||||||
it('creates a Signature with given privateKey, keyId, url and headers (default algorithm: "rsa-sha256")', () => {
|
|
||||||
const httpSignature = createSignature(user.privateKey, 'https://human-connection.org/activitypub/users/lea#main-key', 'https://democracy-app.de/activitypub/users/max/inbox', headers)
|
|
||||||
|
|
||||||
expect(httpSignature).to.contain('keyId="https://human-connection.org/activitypub/users/lea#main-key"')
|
|
||||||
expect(httpSignature).to.contain('algorithm="rsa-sha256"')
|
|
||||||
expect(httpSignature).to.contain('headers="(request-target) date host content-type"')
|
|
||||||
expect(httpSignature).to.contain('signature="' + signatureB64 + '"')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Signature verification', () => {
|
|
||||||
let httpSignature = ''
|
|
||||||
beforeEach(() => {
|
|
||||||
httpSignature = createSignature(user.privateKey, 'http://localhost:4001/activitypub/users/test-user#main-key', 'https://democracy-app.de/activitypub/users/max/inbox', headers)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('verifies a Signature correct', async () => {
|
it('resolves false', async () => {
|
||||||
headers['Signature'] = httpSignature
|
await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(false)
|
||||||
const isVerified = await verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)
|
})
|
||||||
expect(isVerified).to.equal(true)
|
|
||||||
|
describe('valid signature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
headers.Signature = httpSignature
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves true', async () => {
|
||||||
|
await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,7 +6,8 @@ const debug = require('debug')('ea:security')
|
|||||||
|
|
||||||
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
|
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
|
||||||
|
|
||||||
export function generateRsaKeyPair () {
|
export function generateRsaKeyPair (options = {}) {
|
||||||
|
const { passphrase = process.env.PRIVATE_KEY_PASSPHRASE } = options
|
||||||
return crypto.generateKeyPairSync('rsa', {
|
return crypto.generateKeyPairSync('rsa', {
|
||||||
modulusLength: 4096,
|
modulusLength: 4096,
|
||||||
publicKeyEncoding: {
|
publicKeyEncoding: {
|
||||||
@ -17,18 +18,24 @@ export function generateRsaKeyPair () {
|
|||||||
type: 'pkcs8',
|
type: 'pkcs8',
|
||||||
format: 'pem',
|
format: 'pem',
|
||||||
cipher: 'aes-256-cbc',
|
cipher: 'aes-256-cbc',
|
||||||
passphrase: process.env.PRIVATE_KEY_PASSPHRASE
|
passphrase
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// signing
|
// signing
|
||||||
export function createSignature (privKey, keyId, url, headers = {}, algorithm = 'rsa-sha256') {
|
export function createSignature (options) {
|
||||||
|
const {
|
||||||
|
privateKey, keyId, url,
|
||||||
|
headers = {},
|
||||||
|
algorithm = 'rsa-sha256',
|
||||||
|
passphrase = process.env.PRIVATE_KEY_PASSPHRASE
|
||||||
|
} = options
|
||||||
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
|
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
|
||||||
const signer = crypto.createSign(algorithm)
|
const signer = crypto.createSign(algorithm)
|
||||||
const signingString = constructSigningString(url, headers)
|
const signingString = constructSigningString(url, headers)
|
||||||
signer.update(signingString)
|
signer.update(signingString)
|
||||||
const signatureB64 = signer.sign({ key: privKey, passphrase: process.env.PRIVATE_KEY_PASSPHRASE }, 'base64')
|
const signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64')
|
||||||
const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '')
|
const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '')
|
||||||
return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"`
|
return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,12 +75,15 @@ export function signAndSend (activity, fromName, targetDomain, url) {
|
|||||||
headers: {
|
headers: {
|
||||||
'Host': targetDomain,
|
'Host': targetDomain,
|
||||||
'Date': date,
|
'Date': date,
|
||||||
'Signature': createSignature(privateKey, `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`, url,
|
'Signature': createSignature({ privateKey,
|
||||||
{
|
keyId: `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`,
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
'Host': targetDomain,
|
'Host': targetDomain,
|
||||||
'Date': date,
|
'Date': date,
|
||||||
'Content-Type': 'application/activity+json'
|
'Content-Type': 'application/activity+json'
|
||||||
}),
|
}
|
||||||
|
}),
|
||||||
'Content-Type': 'application/activity+json'
|
'Content-Type': 'application/activity+json'
|
||||||
},
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export default async (driver, authorizationHeader) => {
|
|||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const query = `
|
const query = `
|
||||||
MATCH (user:User {id: {id} })
|
MATCH (user:User {id: {id} })
|
||||||
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled}
|
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`
|
`
|
||||||
const result = await session.run(query, { id })
|
const result = await session.run(query, { id })
|
||||||
|
|||||||
56
src/middleware/activityPubMiddleware.js
Normal file
56
src/middleware/activityPubMiddleware.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { generateRsaKeyPair } from '../activitypub/security'
|
||||||
|
import { activityPub } from '../activitypub/ActivityPub'
|
||||||
|
import as from 'activitystrea.ms'
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
const debug = require('debug')('backend:schema')
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Mutation: {
|
||||||
|
CreatePost: async (resolve, root, args, context, info) => {
|
||||||
|
args.activityId = activityPub.generateStatusId(context.user.slug)
|
||||||
|
args.objectId = activityPub.generateStatusId(context.user.slug)
|
||||||
|
|
||||||
|
const post = await resolve(root, args, context, info)
|
||||||
|
|
||||||
|
const { user: author } = context
|
||||||
|
const actorId = author.actorId
|
||||||
|
debug(`actorId = ${actorId}`)
|
||||||
|
const createActivity = await new Promise((resolve, reject) => {
|
||||||
|
as.create()
|
||||||
|
.id(`${actorId}/status/${args.activityId}`)
|
||||||
|
.actor(`${actorId}`)
|
||||||
|
.object(
|
||||||
|
as.article()
|
||||||
|
.id(`${actorId}/status/${post.id}`)
|
||||||
|
.content(post.content)
|
||||||
|
.to('https://www.w3.org/ns/activitystreams#Public')
|
||||||
|
.publishedNow()
|
||||||
|
.attributedTo(`${actorId}`)
|
||||||
|
).prettyWrite((err, doc) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
debug(doc)
|
||||||
|
const parsedDoc = JSON.parse(doc)
|
||||||
|
parsedDoc.send = true
|
||||||
|
resolve(JSON.stringify(parsedDoc))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await activityPub.sendActivity(createActivity)
|
||||||
|
} catch (e) {
|
||||||
|
debug(`error sending post activity\n${e}`)
|
||||||
|
}
|
||||||
|
return post
|
||||||
|
},
|
||||||
|
CreateUser: async (resolve, root, args, context, info) => {
|
||||||
|
const keys = generateRsaKeyPair()
|
||||||
|
Object.assign(args, keys)
|
||||||
|
args.actorId = `${process.env.GRAPHQL_URI}/activitypub/users/${args.slug}`
|
||||||
|
return resolve(root, args, context, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import activityPubMiddleware from './activityPubMiddleware'
|
||||||
import passwordMiddleware from './passwordMiddleware'
|
import passwordMiddleware from './passwordMiddleware'
|
||||||
import softDeleteMiddleware from './softDeleteMiddleware'
|
import softDeleteMiddleware from './softDeleteMiddleware'
|
||||||
import sluggifyMiddleware from './sluggifyMiddleware'
|
import sluggifyMiddleware from './sluggifyMiddleware'
|
||||||
@ -25,6 +26,7 @@ export default schema => {
|
|||||||
// add permisions middleware at the first position (unless we're seeding)
|
// add permisions middleware at the first position (unless we're seeding)
|
||||||
// NOTE: DO NOT SET THE PERMISSION FLAT YOUR SELF
|
// NOTE: DO NOT SET THE PERMISSION FLAT YOUR SELF
|
||||||
if (process.env.PERMISSIONS !== 'disabled' && process.env.NODE_ENV !== 'production') {
|
if (process.env.PERMISSIONS !== 'disabled' && process.env.NODE_ENV !== 'production') {
|
||||||
|
middleware.unshift(activityPubMiddleware)
|
||||||
middleware.unshift(permissionsMiddleware.generate(schema))
|
middleware.unshift(permissionsMiddleware.generate(schema))
|
||||||
}
|
}
|
||||||
return middleware
|
return middleware
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import createOrUpdateLocations from './nodes/locations'
|
import createOrUpdateLocations from './nodes/locations'
|
||||||
import { generateRsaKeyPair } from '../activitypub/security'
|
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -7,9 +6,6 @@ dotenv.config()
|
|||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateUser: async (resolve, root, args, context, info) => {
|
CreateUser: async (resolve, root, args, context, info) => {
|
||||||
const keys = generateRsaKeyPair()
|
|
||||||
Object.assign(args, keys)
|
|
||||||
args.actorId = `${process.env.GRAPHQL_URI}/activitypub/users/${args.slug}`
|
|
||||||
const result = await resolve(root, args, context, info)
|
const result = await resolve(root, args, context, info)
|
||||||
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -1,24 +1,12 @@
|
|||||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
import { activityPub } from '../activitypub/ActivityPub'
|
|
||||||
import as from 'activitystrea.ms'
|
|
||||||
import dotenv from 'dotenv'
|
|
||||||
/*
|
|
||||||
import as from 'activitystrea.ms'
|
|
||||||
import request from 'request'
|
|
||||||
*/
|
|
||||||
|
|
||||||
const debug = require('debug')('backend:schema')
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreatePost: async (object, params, context, resolveInfo) => {
|
CreatePost: async (object, params, context, resolveInfo) => {
|
||||||
params.activityId = activityPub.generateStatusId(context.user.slug)
|
|
||||||
params.objectId = activityPub.generateStatusId(context.user.slug)
|
|
||||||
const result = await neo4jgraphql(object, params, context, resolveInfo, false)
|
const result = await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const author = await session.run(
|
await session.run(
|
||||||
'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' +
|
'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' +
|
||||||
'MERGE (post)<-[:WROTE]-(author) ' +
|
'MERGE (post)<-[:WROTE]-(author) ' +
|
||||||
'RETURN author', {
|
'RETURN author', {
|
||||||
@ -28,38 +16,6 @@ export default {
|
|||||||
)
|
)
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
debug(`actorId = ${author.records[0]._fields[0].properties.actorId}`)
|
|
||||||
if (Array.isArray(author.records) && author.records.length > 0) {
|
|
||||||
const actorId = author.records[0]._fields[0].properties.actorId
|
|
||||||
const createActivity = await new Promise((resolve, reject) => {
|
|
||||||
as.create()
|
|
||||||
.id(`${actorId}/status/${params.activityId}`)
|
|
||||||
.actor(`${actorId}`)
|
|
||||||
.object(
|
|
||||||
as.article()
|
|
||||||
.id(`${actorId}/status/${result.id}`)
|
|
||||||
.content(result.content)
|
|
||||||
.to('https://www.w3.org/ns/activitystreams#Public')
|
|
||||||
.publishedNow()
|
|
||||||
.attributedTo(`${actorId}`)
|
|
||||||
).prettyWrite((err, doc) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
} else {
|
|
||||||
debug(doc)
|
|
||||||
const parsedDoc = JSON.parse(doc)
|
|
||||||
parsedDoc.send = true
|
|
||||||
resolve(JSON.stringify(parsedDoc))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
await activityPub.sendActivity(createActivity)
|
|
||||||
} catch (e) {
|
|
||||||
debug(`error sending post activity\n${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user