From a7c4592a35a4b984df18da32f61d0b3cf7a636b5 Mon Sep 17 00:00:00 2001 From: lesion Date: Tue, 30 Jul 2019 18:32:26 +0200 Subject: [PATCH] cleaning federation --- locales/en.js | 106 ++++++++++++------------- locales/it.js | 9 +-- package.json | 1 + pages/add/_edit.vue | 8 +- server/federation/follows.js | 28 +++++++ server/federation/helpers.js | 54 +++++++++++++ server/federation/index.js | 145 ++++++++--------------------------- server/federation/users.js | 53 +++++++++++++ server/index.js | 3 +- yarn.lock | 26 ++++++- 10 files changed, 253 insertions(+), 180 deletions(-) create mode 100644 server/federation/follows.js create mode 100644 server/federation/helpers.js create mode 100644 server/federation/users.js diff --git a/locales/en.js b/locales/en.js index 935bce61..751ed567 100644 --- a/locales/en.js +++ b/locales/en.js @@ -87,12 +87,8 @@ export default { description: `I movimenti hanno bisogno di organizzarsi e autofinanziarsi.
Questo è un dono per voi, usatelo solo per eventi non commerciali e ovviamente antifascisti, antisessisti, antirazzisti.
Prima di poter pubblicare dobbiamo approvare l'account, considera che dietro questo sito ci sono delle persone di carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`, - error: 'Errore: ', - admin_complete: 'Sei il primo utente e quindi sei amministratore!', - complete: 'Confermeremo la registrazione quanto prima.', - request: 'Richiesta di registrazione', - registration_email: `Ciao, - ci è arrivata una richiesta di registrazione su gancio, la confermeremo quanto prima.` + error: 'Error: ', + complete: 'Registration has to be confirmed.' }, event: { @@ -103,79 +99,79 @@ export default { Puoi invece fare il login o registrarti, altrimenti vai avanti e riceverai una risposta il prima possibile. `, same_day: 'Same day', - what_description: 'Event\' name', + what_description: 'Event\'s name', description_description: 'Description', - tag_description: 'Tag...', - media_description: 'Puoi aggiungere un volantino', - added: 'Evento aggiunto', - added_anon: 'Evento aggiunto, verrà confermato quanto prima.', - where_description: `Dov'è il gancio? Se il posto non è presente, scrivilo e premi invio. `, - confirmed: 'Evento confermato', - not_found: 'Evento non trovato', - remove_confirmation: `Sicura di voler eliminare questo evento?`, - recurrent: `Ricorrente`, - recurrent_description: 'Scegli la frequenza e seleziona i giorni', - multidate_description: 'Un festival o una tre giorni? Scegli quando comincia e quando finisce.', - multidate: 'Più giorni', - normal: 'Normale', - normal_description: 'Scegli il giorno.', - recurrent_1w_days: 'Ogni {days}', - recurrent_2w_days: 'Un {days} ogni due', - recurrent_1m_days: '|Il giorno {days} di ogni mese|I giorni {days} di ogni mese', - recurrent_2m_days: '|Il giorno {days} ogni due mesi|I giorni {days} ogni due mesi', - recurrent_1m_ordinal: 'Il {n} {days} di ogni mese', - recurrent_2m_ordinal: '|Il {n} {days} un mese sì e uno no|Il {n} {days} un mese sì e uno no', - due: 'alle', - from: 'Dalle', - image_too_big: 'Immagine troppo grande! Massimo 4M' + tag_description: 'Tag', + media_description: 'You could add an event\'s flyer (optional)', + added: 'Event added', + added_anon: 'Event added but has to be confirmed.', + where_description: `Where's the event? If not present, write it and press enter. `, + confirmed: 'Event confirmed', + not_found: 'Event not found', + remove_confirmation: `Are you sure to remove this event?`, + recurrent: `Recurrent`, + recurrent_description: 'Choose the frequency and select the days', + multidate_description: 'It\'s a festival? Choose when it starts and when it ends?', + multidate: 'More days', + normal: 'Normal', + normal_description: 'Choose the day.', + recurrent_1w_days: 'Each {days}', + recurrent_2w_days: 'A {days} each two', + recurrent_1m_days: '|The {days} of each month|{days} of each month', + recurrent_2m_days: '|The {days} a month each two|The {days} a month each two', + recurrent_1m_ordinal: 'The {n} {days} of each month', + 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' }, admin: { - mastodon_instance: 'Istanza', - mastodon_description: 'Puoi associare un account mastodon a questa istanza di gancio, ogni evento verrà pubblicato lì.', place_description: `Nel caso in cui un luogo sia errato o cambi indirizzo, puoi modificarlo.
Considera che tutti gli eventi associati a questo luogo cambieranno indirizzo (anche quelli passati!)`, event_confirm_description: 'Puoi confermare qui gli eventi inseriti da utenti anonimi', - delete_user: 'Elimina', - remove_admin: 'Rimuovi admin', - delete_user_confirm: 'Sicura di rimuovere questo utente?', - user_remove_ok: 'Utente eliminato', - user_create_ok: 'Utente creato', - allow_registration_description : 'Vuoi abilitare la registrazione?', - allow_anon_event: 'Si possono inserire eventi anonimi (previa conferma)?', - allow_comments: 'Abilita commenti', - allow_recurrent_event: 'Abilita eventi fissi', - recurrent_event_visible: 'Appuntamenti fissi visibili di default', - federation: 'Federazione / ActivityPub' + delete_user: 'Remove', + remove_admin: 'Remove admin', + delete_user_confirm: 'Are you sure to remove this user?', + user_remove_ok: 'User removed', + user_create_ok: 'User created', + allow_registration_description : 'Allow open registrations?', + allow_anon_event: 'Allow anon events (has to be confirmed)?', + allow_recurrent_event: 'Enable recurrent events', + recurrent_event_visible: 'Show recurrent events by default', + federation: 'Federation / ActivityPub', + enable_federation: 'Enable federation' }, auth: { - not_confirmed: 'Non abbiamo ancora confermato questa mail...', - fail: 'Autenticazione fallita. Sicura la password è giusta? E la mail?' + not_confirmed: 'Not confirmed yet', + fail: 'Auth failed!. Are you sure password is correct?' }, settings: { - change_password: 'Cambia password', - password_updated: 'Password modificata', - danger_section: 'Sezione pericolosa', + change_password: 'Modify your password', + password_updated: 'Password updated', + danger_section: 'Dangerous section', remove_account: 'Premendo il seguente tasto il tuo utente verrà eliminato. Gli eventi da te pubblicati invece no.', remove_account_confirm: 'Stai per eliminare definitivamente il tuo account', }, err: { - register_error: 'Errore nella registrazione' + register_error: 'Error during registration' }, ordinal: { - 1: 'primo', - 2: 'secondo', - 3: 'terzo', - 4: 'quarto', - 5: 'quinto', - [-1]: 'ultimo', + 1: 'first', + 2: 'second', + 3: 'third', + 4: 'fourth', + 5: 'fifth', + [-1]: 'last', }, about: `

+ Gancio is a shared agenda for local communities. + Gancio e' un progetto dell'underscore hacklab e uno dei servizi di cisti.org.

diff --git a/locales/it.js b/locales/it.js index a63fd5eb..e21e56c2 100644 --- a/locales/it.js +++ b/locales/it.js @@ -90,11 +90,7 @@ export default {
Prima di poter pubblicare dobbiamo approvare l'account, considera che dietro questo sito ci sono delle persone di carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`, error: 'Errore: ', - admin_complete: 'Sei il primo utente e quindi sei amministratore!', - complete: 'Confermeremo la registrazione quanto prima.', - request: 'Richiesta di registrazione', - registration_email: `Ciao, - ci è arrivata una richiesta di registrazione su gancio, la confermeremo quanto prima.` + complete: 'Confermeremo la registrazione quanto prima.' }, event: { @@ -150,7 +146,8 @@ export default { allow_comments: 'Abilita commenti', allow_recurrent_event: 'Abilita eventi fissi', recurrent_event_visible: 'Appuntamenti fissi visibili di default', - federation: 'Federazione / ActivityPub' + federation: 'Federazione / ActivityPub', + enable_federation: 'Abilita la federazione!' }, auth: { diff --git a/package.json b/package.json index 2c527174..12b8a6af 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "email-templates": "^6.0.0", "express": "^4.17.1", "express-jwt": "^5.3.1", + "fetch": "^1.1.0", "ics": "^2.15.1", "inquirer": "^6.5.0", "jsonwebtoken": "^8.5.1", diff --git a/pages/add/_edit.vue b/pages/add/_edit.vue index 508091fd..d0fcd9e4 100644 --- a/pages/add/_edit.vue +++ b/pages/add/_edit.vue @@ -127,6 +127,11 @@ export default { validate ({store}) { return (store.state.auth.loggedIn || store.state.settings.allow_anon_event) }, + head () { + return { + title: `${this.settings.title} - ${this.$t('common.add_event')}` + } + }, data() { const month = moment().month()+1 const year = moment().year() @@ -197,7 +202,7 @@ export default { data.event.type = 'normal' data.date = new Date(event.start_datetime) } - + data.time.start = moment(event.start_datetime).format('HH:mm') data.time.end = moment(event.end_datetime).format('HH:mm') data.event.title = event.title @@ -217,6 +222,7 @@ export default { places: state => state.places, user: state => state.user, events: state => state.events, + settings: state => state.settings }), whenPatterns () { const dates = this.date diff --git a/server/federation/follows.js b/server/federation/follows.js new file mode 100644 index 00000000..027b7c70 --- /dev/null +++ b/server/federation/follows.js @@ -0,0 +1,28 @@ +const config = require('config') +const Helpers = require('./helpers') +const { user: User } = require('../api/models') + +module.exports = { + // follow request from fediverse + async follow (req, res, body, targetOrigin, domain) { + if (typeof b.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!') + return + } + Helpers.sendAcceptMessage(body, user, domain, req, res, targetOrigin) + console.error('FOLLOWERS ', user.followers) + 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] }) + } + + }, + // unfollow request from fediverse + unfollow () { + console.error('inside unfollow') + } +} \ No newline at end of file diff --git a/server/federation/helpers.js b/server/federation/helpers.js new file mode 100644 index 00000000..9222bdea --- /dev/null +++ b/server/federation/helpers.js @@ -0,0 +1,54 @@ +const fetch = require('fetch') +const request = require('request') +const crypto = require('crypto') + +const Helpers = { + async signAndSend(message, user, domain, req, res, targetOrigin) { + // get the URI of the actor object and append 'inbox' to it + let inbox = message.object.actor+'/inbox' + let inboxFragment = inbox.replace(targetOrigin,'') + const targetDomain = new URL(targetOrigin).host + // get the private key + const privkey = user.rsa.privateKey + const signer = crypto.createSign('sha256') + let d = new Date() + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}` + signer.update(stringToSign) + signer.end() + const signature = signer.sign(privkey) + const signature_b64 = signature.toString('base64') + let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"` + request({ + url: inbox, + headers: { + 'Host': targetDomain, + 'Date': d.toUTCString(), + 'Signature': header + }, + method: 'POST', + json: true, + body: message + }, function (error, response){ + if (error) { + console.log('Error:', error, response.body) + } + else { + console.log('Response:', response.body) + } + }) + return res.status(200) + }, + async sendAcceptMessage (body, user, domain, req, res, targetOrigin) { + const guid = crypto.randomBytes(16).toString('hex') + let message = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `${config.baseurl}/federation/${guid}`, + 'type': 'Accept', + 'actor': `${config.baseurl}/federation/u/${user.username}`, + 'object': body, + } + Helpers.signAndSend(message, user, domain, req, res, targetOrigin) + } +} + +module.exports = Helpers \ No newline at end of file diff --git a/server/federation/index.js b/server/federation/index.js index 41096a67..784c9c18 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -1,130 +1,47 @@ const express = require('express') const router = express.Router() -const { user: User } = require('../api/models') const config = require('config') -const get = require('lodash/get') -const crypto = require('crypto') -const request = require('request') +const bodyParser = require('body-parser') +const cors = require('cors') +const Follows = require('./follows') +const Users = require('./users') -function signAndSend(message, user, domain, req, res, targetOrigin) { - // get the URI of the actor object and append 'inbox' to it - let inbox = message.object.actor+'/inbox' - let inboxFragment = inbox.replace(targetOrigin,'') - const targetDomain = new URL(targetOrigin).host - // get the private key - const privkey = user.rsa.privateKey - const signer = crypto.createSign('sha256') - let d = new Date() - let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}` - signer.update(stringToSign) - signer.end() - const signature = signer.sign(privkey) - const signature_b64 = signature.toString('base64') - let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"` - request({ - url: inbox, - headers: { - 'Host': targetDomain, - 'Date': d.toUTCString(), - 'Signature': header - }, - method: 'POST', - json: true, - body: message - }, function (error, response){ - if (error) { - console.log('Error:', error, response.body) - } - else { - console.log('Response:', response.body) - } - }) - return res.status(200); -} +/** + * Federation is calling! + * ref: https://www.w3.org/TR/activitypub/#Overview + */ +router.use(cors()) +router.use(bodyParser.json({type: 'application/activity+json'})) -function sendAcceptMessage (body, user, domain, req, res, targetOrigin) { - const guid = crypto.randomBytes(16).toString('hex') - let message = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${config.baseurl}/federation/${guid}`, - 'type': 'Accept', - 'actor': `${config.baseurl}/federation/u/${user.username}`, - 'object': body, - } - signAndSend(message, user, domain, req, res, targetOrigin) -} - -router.post('/inbox', async (req, res) => { +// get any message coming from federation +// Federation is calling! +router.post('/u/:name/inbox', async (req, res) => { const b = req.body - console.error('> INBOX ', b) + console.error('> INBOX ', b.type, b) const targetOrigin = new URL(b.actor).origin const domain = new URL(config.baseurl).host + switch(b.type) { case 'Follow': - if (typeof b.object !== 'string') return - const username = b.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!') - return - } - sendAcceptMessage(b, user, domain, req, res, targetOrigin) - console.error('FOLLOWERS ', user.followers) - if (user.followers.indexOf(b.actor) === -1) { - console.error('ok this is a new follower: ', b.actor) - await user.update({ followers: [...user.followers, b.actor] }) - } - + Follows.follow(req, res, b, targetOrigin, domain) + break + case 'Undo': + Follows.unfollow(req, res, b, targetOrigin, domain) + break + case 'Announce': + console.error('This is a boost ?') + break + case 'Note': + console.error('this is a note ! I should not receive this') + break + case 'Create': + console.error('Create what? This is probably a reply', b.object.type) break } }) -router.get('/u/:name', async (req, res) => { - const name = req.params.name - if (!name) return res.status(400).send('Bad request.') - const user = await User.findOne({where: { username: name }}) - if (!user) return res.status(404).send(`No record found for ${name}`) - const ret = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - id: `${config.baseurl}/federation/u/${name}`, - type: 'Person', - preferredUsername: name, - nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`, - inbox: `${config.baseurl}/federation/inbox`, - followers: `${config.baseurl}/federation/u/${name}/followers`, - publicKey: { - id: `${config.baseurl}/federation/u/${name}#main-key`, - owner: `${config.baseurl}/federation/u/${name}`, - publicKeyPem: get(user, 'rsa.publicKey', '') - } - } - res.json(ret) -}) - -router.get('/u/:name/followers', async (req, res) => { - const name = req.params.name - if (!name) return res.status(400).send('Bad request.') - const user = await User.findOne({where: { username: name }}) - if (!user) return res.status(404).send(`No record found for ${name}`) - const ret = { - '@context': [ 'https://www.w3.org/ns/activitystreams' ], - id: `${config.baseurl}/federation/u/${name}/followers`, - type: 'OrderedCollection', - totalItems: user.followers.length, - first: { - id: `${config.baseurl}/federation/u/${name}/followers?page=1`, - type: 'OrderedCollectionPage', - totalItems: user.followers.length, - partOf: `${config.baseurl}/federation/u/${name}/followers`, - orderedItems: user.followers, - } - } - res.json(ret) -}) - +router.get('/u/:name/outbox', Users.outbox) +router.get('/u/:name/followers', Users.followers) +router.get('/u/:name', Users.get) module.exports = router diff --git a/server/federation/users.js b/server/federation/users.js new file mode 100644 index 00000000..227cf926 --- /dev/null +++ b/server/federation/users.js @@ -0,0 +1,53 @@ +const { user: User } = require('../api/models') +const config = require('config') + +module.exports = { + async get (req, res) { + const name = req.params.name + if (!name) return res.status(400).send('Bad request.') + const user = await User.findOne({where: { username: name }}) + if (!user) return res.status(404).send(`No record found for ${name}`) + const ret = { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + id: `${config.baseurl}/federation/u/${name}`, + type: 'Person', + preferredUsername: name, + nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`, + inbox: `${config.baseurl}/federation/u/${name}/inbox`, + outbox: `${config.baseurl}/federation/u/${name}/outbox`, + followers: `${config.baseurl}/federation/u/${name}/followers`, + publicKey: { + id: `${config.baseurl}/federation/u/${name}#main-key`, + owner: `${config.baseurl}/federation/u/${name}`, + publicKeyPem: get(user, 'rsa.publicKey', '') + } + } + res.json(ret) + }, + async followers (req, res) { + const name = req.params.name + if (!name) return res.status(400).send('Bad request.') + const user = await User.findOne({where: { username: name }}) + if (!user) return res.status(404).send(`No record found for ${name}`) + const ret = { + '@context': [ 'https://www.w3.org/ns/activitystreams' ], + id: `${config.baseurl}/federation/u/${name}/followers`, + type: 'OrderedCollection', + totalItems: user.followers.length, + first: { + id: `${config.baseurl}/federation/u/${name}/followers?page=1`, + type: 'OrderedCollectionPage', + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/followers`, + orderedItems: user.followers, + } + } + res.json(ret) + }, + outbox (req, res) { + console.error('Inside outbox, should return all events from this user') + } +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index d8e1790d..b34e0e67 100644 --- a/server/index.js +++ b/server/index.js @@ -3,7 +3,6 @@ const path = require('path') const express = require('express') const consola = require('consola') const morgan = require('morgan') -const bodyParser = require('body-parser') const cors = require('cors') const { Nuxt, Builder } = require('nuxt') @@ -37,7 +36,7 @@ async function start() { // 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', cors(), bodyParser.join({type: 'applicatoin/activity+json'}), require('./federation')) + app.use('/federation', require('./federation')) // Give nuxt middleware to express app.use(nuxt.render) diff --git a/yarn.lock b/yarn.lock index 749c6d24..efbb9686 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1953,6 +1953,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +biskviit@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/biskviit/-/biskviit-1.0.1.tgz#037a0cd4b71b9e331fd90a1122de17dc49e420a7" + integrity sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc= + dependencies: + psl "^1.1.7" + bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -3681,6 +3688,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + dependencies: + iconv-lite "~0.4.13" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -4422,6 +4436,14 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fetch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fetch/-/fetch-1.1.0.tgz#0a8279f06be37f9f0ebb567560a30a480da59a2e" + integrity sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4= + dependencies: + biskviit "1.0.1" + encoding "0.1.12" + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -5315,7 +5337,7 @@ i18n@^0.8.3: mustache "*" sprintf-js ">=1.0.3" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -8620,7 +8642,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: +psl@^1.1.24, psl@^1.1.7: version "1.2.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==