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', enable: 'Enable',
disable: 'Disable', disable: 'Disable',
me: 'You', me: 'You',
password_updated: 'Password updated!' password_updated: 'Password updated!',
username: 'Username',
comments: 'Comments'
}, },
login: { login: {
@ -123,7 +125,8 @@ export default {
recurrent_2m_ordinal: '|The {n} {days} a month each two|The {n} {days} a month each two', recurrent_2m_ordinal: '|The {n} {days} a month each two|The {n} {days} a month each two',
due: 'due', due: 'due',
from: 'From', 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: { admin: {

View file

@ -49,7 +49,8 @@ export default {
disable: 'Disabilita', disable: 'Disabilita',
me: 'Sei te', me: 'Sei te',
password_updated: 'Password modificata!', password_updated: 'Password modificata!',
username: 'Nickname' username: 'Nickname',
comments: 'Commenti'
}, },
login: { login: {
@ -128,7 +129,8 @@ export default {
each_month: 'Ogni mese', each_month: 'Ogni mese',
due: 'alle', due: 'alle',
from: 'Dalle', 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: { admin: {

View file

@ -60,15 +60,14 @@
el-form-item(v-show='allow_recurrent_event' :label="$t('admin.recurrent_event_visible')") el-form-item(v-show='allow_recurrent_event' :label="$t('admin.recurrent_event_visible')")
el-switch(v-model='recurrent_event_visible') el-switch(v-model='recurrent_event_visible')
el-divider {{$t('admin.federation')}} el-divider {{$t('admin.federation')}}
el-form(inline @submit.native.prevent='associate_mastondon_instance' label-width='240px') el-form(inline label-width='400px')
p {{$t('admin.mastodon_description')}} el-form-item(:label="$t('admin.enable_federation')")
el-form-item(:label='$t("admin.mastodon_instance")') el-switch(v-model='enable_federation')
el-input(v-model="mastodon_instance")
el-form-item //- el-form-item(:label="$t('admin.allow_boost_like')")
el-button(native-type='submit' type='success' :disabled='!mastodon_instance') {{$t('common.associate')}} //- el-switch(v-model='allow_comments')
el-form-item(:label="$t('admin.allow_comments')")
el-switch(v-model='allow_comments')
</template> </template>
<script> <script>
@ -130,10 +129,10 @@ export default {
get () { return this.settings.recurrent_event_visible }, get () { return this.settings.recurrent_event_visible },
set (value) { this.setSetting({ key: 'recurrent_event_visible', value })} set (value) { this.setSetting({ key: 'recurrent_event_visible', value })}
}, },
allow_comments: { enable_federation: {
get () { return this.settings.allow_comments }, get () { return this.settings.enable_federation },
set (value) { this.setSetting({ key: 'allow_comments', value })} set (value) { this.setSetting({ key: 'enable_federation', value })}
}, },
paginatedEvents () { paginatedEvents () {
return this.events.slice((this.eventPage-1) * this.perPage, return this.events.slice((this.eventPage-1) * this.perPage,
this.eventPage * this.perPage) this.eventPage * this.perPage)
@ -151,12 +150,6 @@ export default {
preview (id) { preview (id) {
this.$router.push(`/event/${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) { async confirm (id) {
try { try {
this.loading = true 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='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')}} el-button(plain type='primary' size='mini' @click='$router.replace(`/add/${event.id}`)') {{$t('common.edit')}}
//- comments small {{event.likes.length}} - {{event.boost.length}}
#comments.card-body(v-if='event.comments.length') //- comments from fediverse
strong {{$t('common.related')}} - #comments.card-body(v-if='settings.enable_federation')
//a(:href='') {{$t('common.add')}} 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') .card-header(v-for='comment in event.comments' :key='comment.id')
a.float-right(:href='comment.data.url') a.float-right(:href='comment.data.url')

View file

@ -7,8 +7,8 @@ export default ({ app, store }) => {
// replace links with anchors // replace links with anchors
// TODO: remove fb tracking id // TODO: remove fb tracking id
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')) Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>'))
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('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('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')) // 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 moment = require('moment')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const lodash = require('lodash') 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 Sequelize = require('sequelize')
const notifier = require('../../notifier') const notifier = require('../../notifier')
@ -94,6 +94,7 @@ const eventController = {
}, },
include: [ include: [
{ model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } }, { model: Tag, attributes: ['tag', 'weigth'], through: { attributes: [] } },
{ model: User, attributes: ['username'] },
{ model: Place, attributes: ['name', 'address'] }, { model: Place, attributes: ['name', 'address'] },
Comment Comment
], ],

View file

@ -122,7 +122,9 @@ const userController = {
// send response to client // send response to client
res.json(event) res.json(event)
federation.sendEvent(event, req.user) if (req.user)
federation.sendEvent(event, req.user)
res.json(200) res.json(200)
// send notification (mastodon/email/confirmation) // 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.getAll)
// api.get('/event/:month/:year', eventController.getAfter) // api.get('/event/:month/:year', eventController.getAfter)
// mastodon oauth auth // Handle 404
//api.post('/settings/getauthurl', jwt, isAuth, isAdmin, settingsController.getAuthURL) api.use(function(req, res) {
//api.get('/settings/oauth', jwt, isAuth, isAdmin, settingsController.code) 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 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) { async follow (req, res, body, targetOrigin, domain) {
if (typeof body.object !== 'string') return if (typeof body.object !== 'string') return
const username = body.object.replace(`${config.baseurl}/federation/u/`, '') const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
console.error('someone wants to follow ' + username)
const user = await User.findOne({ where: { username }}) const user = await User.findOne({ where: { username }})
if (!user) { if (!user) {
console.error('No user found!') res.sendStatus(404)
return return
} }
console.error('FOLLOWERS ', user.followers) // check for duplicate
if (user.followers.indexOf(body.actor) === -1) { if (user.followers.indexOf(body.actor) === -1) {
console.error('ok this is a new follower: ', body.actor) console.error('ok this is a new follower: ', body.actor)
await user.update({ followers: [...user.followers, body.actor] }) await user.update({ followers: [...user.followers, body.actor] })
@ -27,8 +26,8 @@ module.exports = {
'actor': `${config.baseurl}/federation/u/${user.username}`, 'actor': `${config.baseurl}/federation/u/${user.username}`,
'object': body, 'object': body,
} }
return Helpers.signAndSend(message, user, body.actor) Helpers.signAndSend(message, user, body.actor)
res.sendStatus(200)
}, },
// unfollow request from fediverse // unfollow request from fediverse
unfollow () { unfollow () {

View file

@ -55,6 +55,26 @@ const Helpers = {
body['@context'] = 'https://www.w3.org/ns/activitystreams' body['@context'] = 'https://www.w3.org/ns/activitystreams'
Helpers.signAndSend(body, user, follower) 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 Users = require('./users')
const { event: Event, user: User } = require('../api/models') const { event: Event, user: User } = require('../api/models')
const Comments = require('./comments') const Comments = require('./comments')
const Helpers = require('./helpers')
const Ego = require('./ego')
/** /**
* Federation is calling! * Federation is calling!
@ -28,6 +30,11 @@ router.get('/m/:event_id', async (req, res) => {
// get any message coming from federation // get any message coming from federation
// Federation is calling! // Federation is calling!
router.post('/u/:name/inbox', async (req, res) => { 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 const b = req.body
console.error('> INBOX ', b.type, b) console.error('> INBOX ', b.type, b)
const targetOrigin = new URL(b.actor).origin const targetOrigin = new URL(b.actor).origin
@ -49,20 +56,18 @@ router.post('/u/:name/inbox', async (req, res) => {
break break
case 'Announce': case 'Announce':
console.error('This is a boost ?') console.error('This is a boost ?')
Ego.boost(req, res)
break break
case 'Note': case 'Note':
console.error('This is a note ! I probably should not receive this') console.error('This is a note ! I probably should not receive this')
break break
case 'Like': case 'Like':
console.error('This is a like!') console.error('This is a like!')
Ego.like(req, res)
break break
case 'Delete': case 'Delete':
console.error('Delete a comment ?!?!') console.error('Delete a comment ?!?!')
break break
case 'Announce':
console.error('Boost!')
break
case 'Create': case 'Create':
// this is a reply // this is a reply
if (b.object.type === 'Note' && b.object.inReplyTo) { if (b.object.type === 'Note' && b.object.inReplyTo) {

View file

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

View file

@ -1,10 +1,14 @@
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const { user: User } = require('../api/models') const { user: User } = require('../api/models')
const cors = require('cors')
const settingsController = require('../api/controller/settings')
const config = require('config') const config = require('config')
const version = require('../../package.json').version
router.get('/', async (req, res) => { router.use(cors())
console.error('ma sono dentro webfinger ?!?!')
router.get('/webfinger', async (req, res) => {
const resource = req.query.resource const resource = req.query.resource
if (!resource || !resource.includes('acct:')) { 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.') 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.set('Content-Type', 'application/jrd+json; charset=utf-8')
res.json(ret) 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 module.exports = router

View file

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

View file

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