This commit is contained in:
lesion 2019-08-02 13:43:28 +02:00
parent 5b013829fe
commit 40f7ffa99e
16 changed files with 183 additions and 51 deletions

View file

@ -48,7 +48,9 @@ export default {
enable: 'Enable',
disable: 'Disable',
me: 'You',
password_updated: 'Password updated!'
password_updated: 'Password updated!',
username: 'Username',
comments: 'Comments'
},
login: {
@ -123,7 +125,8 @@ export default {
recurrent_2m_ordinal: '|The {n} {days} a month each two|The {n} {days} a month each two',
due: 'due',
from: 'From',
image_too_big: 'Image too big! Max 4M'
image_too_big: 'Image too big! Max 4M',
interact_with_me_at: 'Interact with me on fediverse at'
},
admin: {

View file

@ -49,7 +49,8 @@ export default {
disable: 'Disabilita',
me: 'Sei te',
password_updated: 'Password modificata!',
username: 'Nickname'
username: 'Nickname',
comments: 'Commenti'
},
login: {
@ -128,7 +129,8 @@ export default {
each_month: 'Ogni mese',
due: 'alle',
from: 'Dalle',
image_too_big: 'Immagine troppo grande! Massimo 4M'
image_too_big: 'Immagine troppo grande! Massimo 4M',
interact_with_me_at: 'Seguimi nel fediverso su'
},
admin: {

View file

@ -60,15 +60,14 @@
el-form-item(v-show='allow_recurrent_event' :label="$t('admin.recurrent_event_visible')")
el-switch(v-model='recurrent_event_visible')
el-divider {{$t('admin.federation')}}
el-form(inline @submit.native.prevent='associate_mastondon_instance' label-width='240px')
p {{$t('admin.mastodon_description')}}
el-form-item(:label='$t("admin.mastodon_instance")')
el-input(v-model="mastodon_instance")
el-form-item
el-button(native-type='submit' type='success' :disabled='!mastodon_instance') {{$t('common.associate')}}
el-form-item(:label="$t('admin.allow_comments')")
el-switch(v-model='allow_comments')
el-form(inline label-width='400px')
el-form-item(:label="$t('admin.enable_federation')")
el-switch(v-model='enable_federation')
//- el-form-item(:label="$t('admin.allow_boost_like')")
//- el-switch(v-model='allow_comments')
</template>
<script>
@ -130,9 +129,9 @@ export default {
get () { return this.settings.recurrent_event_visible },
set (value) { this.setSetting({ key: 'recurrent_event_visible', value })}
},
allow_comments: {
get () { return this.settings.allow_comments },
set (value) { this.setSetting({ key: 'allow_comments', value })}
enable_federation: {
get () { return this.settings.enable_federation },
set (value) { this.setSetting({ key: 'enable_federation', value })}
},
paginatedEvents () {
return this.events.slice((this.eventPage-1) * this.perPage,
@ -151,12 +150,6 @@ export default {
preview (id) {
this.$router.push(`/event/${id}`)
},
async associate_mastondon_instance () {
if (!this.mastodon_instance) return false
const url = await this.$axios.$post('/settings/getauthurl', { instance: this.mastodon_instance })
setTimeout( () => window.location.href=url, 100);
},
async confirm (id) {
try {
this.loading = true

View file

@ -39,10 +39,11 @@
el-button(plain type='danger' size='mini' @click.prevent='remove') {{$t('common.remove')}}
el-button(plain type='primary' size='mini' @click='$router.replace(`/add/${event.id}`)') {{$t('common.edit')}}
//- comments
#comments.card-body(v-if='event.comments.length')
strong {{$t('common.related')}} -
//a(:href='') {{$t('common.add')}}
small {{event.likes.length}} - {{event.boost.length}}
//- comments from fediverse
#comments.card-body(v-if='settings.enable_federation')
strong {{$t('common.comments')}} -
<small>{{$t('event.interact_with_me_at')}} <u>{{event.user.username}}@{{settings.baseurl|url2host}}</u></small>
.card-header(v-for='comment in event.comments' :key='comment.id')
a.float-right(:href='comment.data.url')

View file

@ -7,8 +7,8 @@ export default ({ app, store }) => {
// replace links with anchors
// TODO: remove fb tracking id
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>'))
// Vue.filter('datetime', value => moment(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
Vue.filter('datetime', value => moment(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
// Vue.filter('short_datetime', value => moment(value).locale(store.state.locale).format('D/MM HH:mm'))
// Vue.filter('hour', value => moment(value).locale(store.state.locale).format('HH:mm'))

View file

@ -2,7 +2,7 @@ const crypto = require('crypto')
const moment = require('moment')
const { Op } = require('sequelize')
const lodash = require('lodash')
const { event: Event, comment: Comment, tag: Tag, place: Place, notification: Notification } = require('../models')
const { event: Event, comment: Comment, tag: Tag, place: Place, user: User, notification: Notification } = require('../models')
const Sequelize = require('sequelize')
const notifier = require('../../notifier')
@ -94,6 +94,7 @@ const eventController = {
},
include: [
{ model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } },
{ model: User, attributes: ['username'] },
{ model: Place, attributes: ['name', 'address'] },
Comment
],

View file

@ -122,7 +122,9 @@ const userController = {
// send response to client
res.json(event)
federation.sendEvent(event, req.user)
if (req.user)
federation.sendEvent(event, req.user)
res.json(200)
// send notification (mastodon/email/confirmation)

View file

@ -101,8 +101,15 @@ api.get('/export/:type', exportController.export)
api.get('/event/:month/:year', eventController.getAll)
// api.get('/event/:month/:year', eventController.getAfter)
// mastodon oauth auth
//api.post('/settings/getauthurl', jwt, isAuth, isAdmin, settingsController.getAuthURL)
//api.get('/settings/oauth', jwt, isAuth, isAdmin, settingsController.code)
// Handle 404
api.use(function(req, res) {
res.send('404: Page not Found', 404)
})
// Handle 500
api.use(function(error, req, res, next) {
res.send('500: Internal Server Error', 500)
})
module.exports = api

17
server/federation/ego.js Normal file
View file

@ -0,0 +1,17 @@
const { event: Event } = require('../api/models')
const config = require('config')
module.exports = {
async boost (req, res) {
const event_id = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)[1]
const event = await Event.findByPk(event_id)
await event.update({ boost: [...event.boost, req.body.actor]})
res.sendStatus(201)
},
async like (req, res) {
const event_id = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)[1]
const event = await Event.findByPk(event_id)
await event.update({ likes: [...event.likes, req.body.actor]})
res.sendStatus(201)
}
}

View file

@ -8,13 +8,12 @@ module.exports = {
async follow (req, res, body, targetOrigin, domain) {
if (typeof body.object !== 'string') return
const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
console.error('someone wants to follow ' + username)
const user = await User.findOne({ where: { username }})
if (!user) {
console.error('No user found!')
res.sendStatus(404)
return
}
console.error('FOLLOWERS ', user.followers)
// check for duplicate
if (user.followers.indexOf(body.actor) === -1) {
console.error('ok this is a new follower: ', body.actor)
await user.update({ followers: [...user.followers, body.actor] })
@ -27,8 +26,8 @@ module.exports = {
'actor': `${config.baseurl}/federation/u/${user.username}`,
'object': body,
}
return Helpers.signAndSend(message, user, body.actor)
Helpers.signAndSend(message, user, body.actor)
res.sendStatus(200)
},
// unfollow request from fediverse
unfollow () {

View file

@ -55,6 +55,26 @@ const Helpers = {
body['@context'] = 'https://www.w3.org/ns/activitystreams'
Helpers.signAndSend(body, user, follower)
}
},
// TODO: cache
// user: les@mastodon.cisti.org
async getFederatedUser(address) {
address = address.trim()
let [ user, host ] = address.split('@')
const url = `https://${host}/.well-known/webfinger?resource=acct:${user}@${host}`
console.error('get federated user at => ', address, url)
const user = await fetch(url, { headers: {'Accept': 'application/jrd+json, application/json'} })
return user
},
async verifySignature(req, res) {
console.error(req.headers['signature'])
// https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
const signature_header = req.headers['signature'].split(',')
.map(pair => pair.split('='))
console.error(signature_header)
return true
}
}

View file

@ -6,6 +6,8 @@ const Follows = require('./follows')
const Users = require('./users')
const { event: Event, user: User } = require('../api/models')
const Comments = require('./comments')
const Helpers = require('./helpers')
const Ego = require('./ego')
/**
* Federation is calling!
@ -28,6 +30,11 @@ router.get('/m/:event_id', async (req, res) => {
// get any message coming from federation
// Federation is calling!
router.post('/u/:name/inbox', async (req, res) => {
if (!Helpers.verifySignature(req, res)) {
res.send('Request signature could not be verified', 401)
}
const b = req.body
console.error('> INBOX ', b.type, b)
const targetOrigin = new URL(b.actor).origin
@ -49,20 +56,18 @@ router.post('/u/:name/inbox', async (req, res) => {
break
case 'Announce':
console.error('This is a boost ?')
Ego.boost(req, res)
break
case 'Note':
console.error('This is a note ! I probably should not receive this')
break
case 'Like':
console.error('This is a like!')
Ego.like(req, res)
break
case 'Delete':
console.error('Delete a comment ?!?!')
break
case 'Announce':
console.error('Boost!')
break
case 'Create':
// this is a reply
if (b.object.type === 'Note' && b.object.inReplyTo) {

View file

@ -1,10 +1,12 @@
const express = require('express')
const router = express.Router()
const { user: User } = require('../api/models')
const cors = require('cors')
const settingsController = require('../api/controller/settings')
const config = require('config')
const version = require('../../package.json').version
router.use(cors())
router.get('/', async (req, res) => {
const ret = {
version: '1.0',

View file

@ -1,10 +1,14 @@
const express = require('express')
const router = express.Router()
const { user: User } = require('../api/models')
const cors = require('cors')
const settingsController = require('../api/controller/settings')
const config = require('config')
const version = require('../../package.json').version
router.get('/', async (req, res) => {
console.error('ma sono dentro webfinger ?!?!')
router.use(cors())
router.get('/webfinger', async (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.')
@ -26,4 +30,78 @@ router.get('/', async (req, res) => {
res.set('Content-Type', 'application/jrd+json; charset=utf-8')
res.json(ret)
})
router.get('/nodeinfo/:nodeinfo_version', async (req, res) => {
const ret = {
metadata: {
nodeDescription: 'Gancio instance',
nodeName: config.title
},
openRegistrations : settingsController.settings.allow_registration,
protocols :['activitypub'],
services: { inbound: [], outbound :["atom1.0"]},
software: {
name: 'gancio',
version
},
usage: {
localComments: 0,
localPosts:0,
users: {
total:3
}
},
version: req.params.nodeinfo_version
}
if(req.params.nodeinfo_version === '2.1') {
ret.software.repository = 'https://git.lattuga.net/cisti/gancio'
}
res.json(ret)
})
router.get('/x-nodeinfo2', async (req, res) => {
const ret = {
version: '1.0',
server: {
baseUrl: config.baseurl,
name: config.title,
software: 'Gancio',
version
},
protocols: ['activitypub'],
openRegistrations: settingsController.settings.allow_registration,
usage:{
users: {
total: 10
}
},
localPost: 3,
localComments: 0
}
res.json(ret)
})
router.get('/nodeinfo', async (req, res) => {
const ret = {
links: [
{ href: `${config.baseurl}/.well-known/nodeinfo/2.0`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.0` },
{ href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` },
]
}
res.json(ret)
})
// Handle 404
router.use(function(req, res) {
res.send('404: Page not Found', 404)
})
// Handle 500
router.use(function(error, req, res, next) {
res.send('500: Internal Server Error', 500)
})
module.exports = router

View file

@ -3,9 +3,12 @@ const path = require('path')
const express = require('express')
const consola = require('consola')
const morgan = require('morgan')
const cors = require('cors')
const { Nuxt, Builder } = require('nuxt')
const api = require('./api')
const federation = require('./federation')
const webfinger = require('./federation/webfinger')
// Import and Set Nuxt.js options
const nuxt_config = require('../nuxt.config.js')
const config = require('config')
@ -31,12 +34,11 @@ async function start() {
app.use('/media/', express.static(config.upload_path))
// gancio standard api
app.use('/api', require('./api/index'))
app.use('/api', api)
// federation api / activitypub / webfinger / nodeinfo
app.use('/.well-known/webfinger', cors(), require('./federation/webfinger'))
app.use('/.well-known/x-nodeinfo2', cors(), require('./federation/nodeinfo'))
app.use('/federation', require('./federation'))
app.use('/.well-known', webfinger)
app.use('/federation', federation)
// Give nuxt middleware to express
app.use(nuxt.render)

View file

@ -12,7 +12,7 @@ export const state = () => ({
allow_anon_event: true,
allow_recurrent_event: true,
recurrent_event_visible: false,
allow_comments: false,
enable_federation: false,
},
filters: {
tags: [],