From 97616e2754bb051950b92dadba7fe3dabbd83247 Mon Sep 17 00:00:00 2001 From: Lauren Liberda Date: Sun, 12 Dec 2021 23:34:39 +0000 Subject: [PATCH 1/6] logging in with mastodon --- components/Login.vue | 12 +++- locale/en/translations.suml | 1 + locale/pl/translations.suml | 1 + migrations/041-mastodon-oauth.sql | 11 ++++ server/index.js | 1 + server/routes/grantOverrides.js | 101 ++++++++++++++++++++++++++++++ server/routes/user.js | 4 +- server/social.js | 11 ++++ src/data.js | 1 + 9 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 migrations/041-mastodon-oauth.sql create mode 100644 server/routes/grantOverrides.js diff --git a/components/Login.vue b/components/Login.vue index c36c7133b..f56974924 100644 --- a/components/Login.vue +++ b/components/Login.vue @@ -12,9 +12,19 @@
diff --git a/locale/en/translations.suml b/locale/en/translations.suml index 7921250a0..4ab8f40e1 100644 --- a/locale/en/translations.suml +++ b/locale/en/translations.suml @@ -487,6 +487,7 @@ user: why: > Registering lets you manage your cards ({/@example=like this one}). passwordless: 'The website doesn''t store any passwords. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index c173297d0..aa9c9af93 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -1184,6 +1184,7 @@ user: why: > Założenie konta pozwala na zarządzanie swoimi wizytówkami ({/@example=takimi jak ta}). passwordless: 'Strona nie zapisuje żadnych haseł. {https://avris.it/blog/passwords-are-passé=Więcej info.}' + instancePlaceholder: 'Instancja' code: action: 'Sprawdź' invalid: 'Kod nieprawidłowy.' diff --git a/migrations/041-mastodon-oauth.sql b/migrations/041-mastodon-oauth.sql new file mode 100644 index 000000000..915889173 --- /dev/null +++ b/migrations/041-mastodon-oauth.sql @@ -0,0 +1,11 @@ +-- Up + +CREATE TABLE mastodon_oauth ( + instance TEXT NOT NULL PRIMARY KEY, + client_id TEXT NOT NULL, + client_secret TEXT NOT NULL +); + +-- Down + +DROP TABLE mastodon_oauth; diff --git a/server/index.js b/server/index.js index 270c3c8f3..23e8b2edc 100644 --- a/server/index.js +++ b/server/index.js @@ -82,6 +82,7 @@ app.use(async function (req, res, next) { } }); +app.use(require('./routes/grantOverrides').default); router.use(grant.express()(require('./social').config)); app.use(require('./routes/home').default); diff --git a/server/routes/grantOverrides.js b/server/routes/grantOverrides.js new file mode 100644 index 000000000..19cb94be7 --- /dev/null +++ b/server/routes/grantOverrides.js @@ -0,0 +1,101 @@ +// grant doesn't care about the specifics of some services, +// so for some services we don't care about grant :)))) + +import { Router } from 'express'; +import SQL from 'sql-template-strings'; +import fetch from 'node-fetch'; +import assert from 'assert'; +import { handleErrorAsync } from "../../src/helpers"; + +const normalizeDomainName = (domain) => { + const url = new URL('https://' + domain); + assert(url.port === ''); + return url.hostname; +} + +const config = { + mastodon: { + scopes: ['read:accounts'], + redirect_uri: `${process.env.HOME_URL || 'https://pronouns.page'}/api/user/social/mastodon`, + }, +}; + +const router = Router(); + +const mastodonGetOAuthKeys = async (db, instance, dbOnly = false) => { + const existingKeys = await db.get( + SQL`SELECT client_id, client_secret FROM mastodon_oauth WHERE instance = ${instance}`); + if (existingKeys) { + return existingKeys; + } + const keys = await fetch(`https://${instance}/api/v1/apps`, { + method: 'POST', + body: new URLSearchParams({ + client_name: 'pronouns.page', + redirect_uris: config.mastodon.redirect_uri, + scopes: config.mastodon.scopes.join(' '), + website: process.env.HOME_URL, + }).toString(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'pronouns.page', + }, + }).then(res => res.json()); + assert(keys.client_id && keys.client_secret && !keys.error); + db.get(SQL` + INSERT INTO mastodon_oauth (instance, client_id, client_secret) + VALUES (${instance}, ${keys.client_id}, ${keys.client_secret}) + `); + return keys; +}; +router.get('/connect/mastodon', handleErrorAsync(async (req, res) => { + assert(req.query.instance); + const instance = normalizeDomainName(req.query.instance); + const { client_id, client_secret } = await mastodonGetOAuthKeys(req.db, instance); + req.session.grant = { instance, client_id, client_secret }; + res.redirect(`https://${instance}/oauth/authorize?` + new URLSearchParams({ + client_id, + scope: config.mastodon.scopes.join(' '), + redirect_uri: config.mastodon.redirect_uri, + response_type: 'code', + })); +})); +router.get('/user/social/mastodon', handleErrorAsync(async (req, res, next) => { + if (!req.session.grant || !req.session.grant.instance || !req.query.code) { + next(); + return; + } + const { instance, client_id, client_secret } = req.session.grant; + const response = await fetch(`https://${instance}/oauth/token`, { + method: 'POST', + body: new URLSearchParams({ + grant_type: 'authorization_code', + client_id, + client_secret, + redirect_uri: config.mastodon.redirect_uri, + scope: config.mastodon.scopes.join(' '), + code: req.query.code, + }).toString(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'pronouns.page', + }, + }).then(res => res.json()); + if (!response.access_token || response.error) { + next(); + return; + } + const profile = await fetch(`https://${instance}/api/v1/accounts/verify_credentials`, { + headers: { + Authorization: `Bearer ${response.access_token}`, + 'User-Agent': 'pronouns.page', + }, + }).then(res => res.json()); + response.profile = profile; + response.instance = instance; + req.session.grant.response = response; + next(); + return; +})); + +export default router; diff --git a/server/routes/user.js b/server/routes/user.js index 861e987d0..b262629bf 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -388,7 +388,9 @@ router.post('/user/:id/set-roles', handleErrorAsync(async (req, res) => { // happens on home router.get('/user/social-redirect/:provider/:locale', handleErrorAsync(async (req, res) => { req.session.socialRedirect = req.params.locale; - return res.redirect(`/api/connect/${req.params.provider}`); + return res.redirect(`/api/connect/${req.params.provider}?${new URLSearchParams({ + instance: req.query.instance || undefined, + })}`); })); // happens on home diff --git a/server/social.js b/server/social.js index 3a22555d8..5b9fb5d44 100644 --- a/server/social.js +++ b/server/social.js @@ -30,6 +30,8 @@ export const config = { callback: '/api/user/social/discord', scope: ['identify', 'email'], }, + // non-grant, but things break if it's not there + mastodon: {}, } export const handlers = { @@ -73,4 +75,13 @@ export const handlers = { access_secret: r.access_secret, } }, + mastodon(r) { + const acct = `${r.profile.username}@${r.instance}`; + return { + id: acct, + name: acct, + avatar: r.profile.avatar, + access_token: r.access_token, + }; + }, }; diff --git a/src/data.js b/src/data.js index 769ce5966..8e86cd59b 100644 --- a/src/data.js +++ b/src/data.js @@ -7,6 +7,7 @@ export const socialProviders = { facebook: { name: 'Facebook' }, google: { name: 'Google' }, discord: { name: 'Discord' }, + mastodon: { name: 'Mastodon', instanceRequired: true }, } import pronounsRaw from '../data/pronouns/pronouns.tsv'; From d097208f2966ffa7cc0145f11e93ce787850c796 Mon Sep 17 00:00:00 2001 From: Lauren Liberda Date: Sun, 12 Dec 2021 23:59:26 +0000 Subject: [PATCH 2/6] more universal oauth key storage --- ...1-mastodon-oauth.sql => 041-external-oauth.sql} | 5 +++-- server/routes/grantOverrides.js | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) rename migrations/{041-mastodon-oauth.sql => 041-external-oauth.sql} (61%) diff --git a/migrations/041-mastodon-oauth.sql b/migrations/041-external-oauth.sql similarity index 61% rename from migrations/041-mastodon-oauth.sql rename to migrations/041-external-oauth.sql index 915889173..614497954 100644 --- a/migrations/041-mastodon-oauth.sql +++ b/migrations/041-external-oauth.sql @@ -1,11 +1,12 @@ -- Up -CREATE TABLE mastodon_oauth ( +CREATE TABLE oauth_keys ( instance TEXT NOT NULL PRIMARY KEY, + provider TEXT NOT NULL, client_id TEXT NOT NULL, client_secret TEXT NOT NULL ); -- Down -DROP TABLE mastodon_oauth; +DROP TABLE oauth_keys; diff --git a/server/routes/grantOverrides.js b/server/routes/grantOverrides.js index 19cb94be7..268fe51c5 100644 --- a/server/routes/grantOverrides.js +++ b/server/routes/grantOverrides.js @@ -22,9 +22,13 @@ const config = { const router = Router(); -const mastodonGetOAuthKeys = async (db, instance, dbOnly = false) => { - const existingKeys = await db.get( - SQL`SELECT client_id, client_secret FROM mastodon_oauth WHERE instance = ${instance}`); +const mastodonGetOAuthKeys = async (db, instance) => { + const existingKeys = await db.get(SQL` + SELECT client_id, client_secret + FROM oauth_keys + WHERE instance = ${instance} + AND provider = 'mastodon' + `); if (existingKeys) { return existingKeys; } @@ -43,8 +47,8 @@ const mastodonGetOAuthKeys = async (db, instance, dbOnly = false) => { }).then(res => res.json()); assert(keys.client_id && keys.client_secret && !keys.error); db.get(SQL` - INSERT INTO mastodon_oauth (instance, client_id, client_secret) - VALUES (${instance}, ${keys.client_id}, ${keys.client_secret}) + INSERT INTO oauth_keys (instance, provider, client_id, client_secret) + VALUES (${instance}, 'mastodon', ${keys.client_id}, ${keys.client_secret}) `); return keys; }; From 58259abd819e1354a8dfae864552eecff3b2ddd7 Mon Sep 17 00:00:00 2001 From: Lauren Liberda Date: Mon, 13 Dec 2021 00:17:26 +0000 Subject: [PATCH 3/6] store mastodon handle as an e-mail --- server/social.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/social.js b/server/social.js index 5b9fb5d44..9e46b4796 100644 --- a/server/social.js +++ b/server/social.js @@ -79,6 +79,8 @@ export const handlers = { const acct = `${r.profile.username}@${r.instance}`; return { id: acct, + // very possibly not really operated by the user + email: acct, name: acct, avatar: r.profile.avatar, access_token: r.access_token, From 6dc5b0945959ce058779b99534b7899aac700457 Mon Sep 17 00:00:00 2001 From: Lauren Liberda Date: Mon, 13 Dec 2021 00:23:55 +0000 Subject: [PATCH 4/6] instance input for connecting to mastodon on existing accounts --- components/SocialConnection.vue | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/SocialConnection.vue b/components/SocialConnection.vue index b16217a2b..195e085bc 100644 --- a/components/SocialConnection.vue +++ b/components/SocialConnection.vue @@ -5,7 +5,15 @@ {{ providerOptions.name }} - +
+ + +
+
user.socialConnection.connect @@ -18,7 +26,8 @@ {{connection.name}}

- + user.socialConnection.refresh From f7ef106240bf48c3a5fb775c66c2f11d7cf1d31f Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Mon, 13 Dec 2021 19:30:57 +0100 Subject: [PATCH 5/6] mastodon - add translation TODOs --- locale/_base/translations.suml | 1 + locale/de/translations.suml | 1 + locale/es/translations.suml | 15 ++++++++------- locale/fr/translations.suml | 1 + locale/gl/translations.suml | 1 + locale/ja/translations.suml | 1 + locale/nl/translations.suml | 17 +++++++++-------- locale/no/translations.suml | 1 + locale/pt/translations.suml | 1 + locale/ru/translations.suml | 1 + locale/yi/translations.suml | 1 + locale/zh/translations.suml | 1 + 12 files changed, 27 insertions(+), 15 deletions(-) diff --git a/locale/_base/translations.suml b/locale/_base/translations.suml index deea7a1bc..93d6876d0 100644 --- a/locale/_base/translations.suml +++ b/locale/_base/translations.suml @@ -486,6 +486,7 @@ user: why: > Registering lets you manage your cards ({/@example=like this one}). passwordless: 'The website doesn''t store any passwords. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/de/translations.suml b/locale/de/translations.suml index 61769efd4..80fbe23ef 100644 --- a/locale/de/translations.suml +++ b/locale/de/translations.suml @@ -384,6 +384,7 @@ user: why: > Mit der Registrierung kannst du deine Visitenkarten verwalten ({/@example=wie diese}). passwordless: 'Die Website speichert keine Passwörter. {https://avris.it/blog/passwords-are-passé=Weitere Infos.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Validieren' invalid: 'Ungültiger Code.' diff --git a/locale/es/translations.suml b/locale/es/translations.suml index 605f2e7ba..17b273b0d 100644 --- a/locale/es/translations.suml +++ b/locale/es/translations.suml @@ -397,6 +397,7 @@ user: why: > Registrarte te permite manejar tus tarjetas ({/@example=como esta}). passwordless: 'Este sitio web no guarda las contraseñas. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Validar' invalid: 'Código inválido.' @@ -630,21 +631,21 @@ terms: Hola! Queremos informarte que hemos actualizado nuestros Términos de Servicio. Una descripción general de los cambios: changes: - > - En enero comenzaremos a remover cuentas para prevenir + En enero comenzaremos a remover cuentas para prevenir la acumulación de nombres de usuarie (cuando no tengan tarjeta ni inicios de sesión durante un mes). - > - Nos gustaría proveer un espacio seguro para todas las personas queer - independientemente de su edad, pero también debemos respetar la ley - y proteger la privacidad de personas que no pueden consentir legalmente al procesamiento de sus datos. + Nos gustaría proveer un espacio seguro para todas las personas queer + independientemente de su edad, pero también debemos respetar la ley + y proteger la privacidad de personas que no pueden consentir legalmente al procesamiento de sus datos. Ahora nuestros términos de servicio mencionan explícitamente lo que siempre ha sido el caso de acuerdo con RGPD: Debes tener por lo menos 13 años de edad para crear una cuenta y les usuaries de entre 13 y 16 años deben tener consentimiento parental para que procesemos sus datos. - > - Para mejorar la accesibilidad, comenzamos a traducir nuestros términos de servicio a todas las lenguas de la página. + Para mejorar la accesibilidad, comenzamos a traducir nuestros términos de servicio a todas las lenguas de la página. Esas traducciones son sólamente auxiliares, y la versión en inglés sigue siendo la única legalmente vinculante. - > - Aunque la queerfobia, la transfobia, el exclusionismo queer y la presencia de trolls - siempre han sido consideradas infracciones a los términos de servicio bajo el término genérico de + Aunque la queerfobia, la transfobia, el exclusionismo queer y la presencia de trolls + siempre han sido consideradas infracciones a los términos de servicio bajo el término genérico de "infringir normas sociales", ahora están enlistadas explícitamente como prohibidas. admin: diff --git a/locale/fr/translations.suml b/locale/fr/translations.suml index 564e84830..6f0973a59 100644 --- a/locale/fr/translations.suml +++ b/locale/fr/translations.suml @@ -393,6 +393,7 @@ user: why: > S’inscrire vous permet de gérer vos cartes ({/@example=comme celle-ci}). passwordless: 'Ce site ne stocke aucun mot de passe. {https://avris.it/blog/passwords-are-passé=Plus d’infos.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Valider' invalid: 'Code invalide.' diff --git a/locale/gl/translations.suml b/locale/gl/translations.suml index 4ee8ea5ec..4eae49caa 100644 --- a/locale/gl/translations.suml +++ b/locale/gl/translations.suml @@ -396,6 +396,7 @@ user: why: > Registrar-se te permite dirigir os cartões ({/@example=como esta}). passwordless: 'O site não grava qualquer senha. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Validar' invalid: 'Código inválido.' diff --git a/locale/ja/translations.suml b/locale/ja/translations.suml index 851e5c882..2ec6a3fd4 100644 --- a/locale/ja/translations.suml +++ b/locale/ja/translations.suml @@ -400,6 +400,7 @@ user: why: > ご登録いただいた方は、カードの設定を行うことができます。({/@example=こんなに}). passwordless: 'このウェブサイトはパスワードを保存しません。 {https://avris.it/blog/passwords-are-passé=詳細はこちら。}' + instancePlaceholder: 'Instance' # TODO code: action: '確認' invalid: '無効なコード' diff --git a/locale/nl/translations.suml b/locale/nl/translations.suml index 75685d1cc..682c89062 100644 --- a/locale/nl/translations.suml +++ b/locale/nl/translations.suml @@ -223,7 +223,7 @@ faq: en het suggereert dat je een ongezonde nieuwsgierigheid hebt wat betreft de genitaliën van die persoon. In plaats daarvan zou je gewoon kunnen vragen: "Wat zijn je voornaamwoorden?" of "hoe wil je aangesproken worden?") - > - Het is ook belangrijk om situaties waaarin je jezelf voorstelt met jouw voornaamwoorden te normaliseren. + Het is ook belangrijk om situaties waaarin je jezelf voorstelt met jouw voornaamwoorden te normaliseren. ''Hoi, ik ben Michiel,{/he=he/him}”. Het is niet moeilijk – maar voor transgender en non-binaire personen is het heel belangrijk! Het is zelfs nog makkelijker online: plaats simpelweg jouw voornaamwoorden (of een link naar voorbeelden op onze website) in jouw bio. - > @@ -373,6 +373,7 @@ user: why: > Door te registreren kun je een kaart ({/@example=zoals deze}) maken. passwordless: 'De website slaat geen wachtwoorden op. {https://avris.it/blog/passwords-are-passé=Meer info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Valideer' invalid: 'Ongeldige code.' @@ -602,25 +603,25 @@ terms: changes: > We reserve the right to modify these Terms of Service at any time. If a change is material we will provide at least 30 days notice prior to any new Terms of Service taking effect. - + update: header: 'We hebben onze gebruikersvoorwaarden geüpdate.' intro: > Hey! We willen je laten weten dat we onze gebruikersvoorwaarden hebben geüpdate. Hier is een overzicht van de veranderingen: changes: - > - Vanaf januari zullen we ongebruikte accounts gaan verwijderen om het hamsteren van gebruikersnamen te voorkomen. + Vanaf januari zullen we ongebruikte accounts gaan verwijderen om het hamsteren van gebruikersnamen te voorkomen. Dit gebeurt in het geval dat er geen kaart aawezig is, en er tenminste één maand niet is ingelogd. - > - >Ook al willen we een veilige plek bieden voor alle queer personen ongeacht leeftijd, + >Ook al willen we een veilige plek bieden voor alle queer personen ongeacht leeftijd, moeten we de wet respecteren en de privacy van mensen beschermen die juridisch - niet toestemming kunnen geven om hun data te verwerken. + niet toestemming kunnen geven om hun data te verwerken. Daarom bevatten de gebruikersvoorwaarden nu wat altijd al zo is geweest volgens de AVG: - je moet minstens 13 jaar oud zijn om een account aan te maken, en in het geval van gebruikers tussen de 13 en de 16 + je moet minstens 13 jaar oud zijn om een account aan te maken, en in het geval van gebruikers tussen de 13 en de 16 jaar moeten ze daarnaast ook ouderlijke toestemming hebben om hun data te kunnen verwerken. - > Om de toegankelijkheid te vergroten zullen we onze gebruikersvoorwaarden vertalen in alle aangeboden talen. - Deze vertalingen zijn slechts ondersteunend, de enige juridische bindende versie hiervan is Engels. + Deze vertalingen zijn slechts ondersteunend, de enige juridische bindende versie hiervan is Engels. - > Hoewel queerfobie, transfobie, queer uitsluiting en trollen altijd al begrepen waren als overtredingen van de gebruikersvoorwaarden onder de algemene noemer van ''schending van sociale omgangsvormen'', @@ -898,7 +899,7 @@ calendar: freedressing_day: 'Freedressing Awareness Day' banner: 'Vandaag op de kalender' celebrating_custom: 'wordt gevierd op:' - celebrating_day: 'wordt gevierd op:' + celebrating_day: 'wordt gevierd op:' celebrating_week: 'wordt gevierd tijdens:' celebrating_month: 'wordt gevierd in:' image: diff --git a/locale/no/translations.suml b/locale/no/translations.suml index 7b442af1a..ce19366bb 100644 --- a/locale/no/translations.suml +++ b/locale/no/translations.suml @@ -388,6 +388,7 @@ user: why: > Å registrere seg lar deg redigere kortene dine ({/@example=sånn som denne}). passwordless: 'Denne nettsiden lagrer ingen passord. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Gyldig' invalid: 'Ugyldig kode.' diff --git a/locale/pt/translations.suml b/locale/pt/translations.suml index e47b5f8ca..2d86c3f7a 100644 --- a/locale/pt/translations.suml +++ b/locale/pt/translations.suml @@ -392,6 +392,7 @@ user: why: > Registrar-se te permite dirigir os cartões ({/@example=como esta}). passwordless: 'O site não grava qualquer senha. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Validar' invalid: 'Código inválido.' diff --git a/locale/ru/translations.suml b/locale/ru/translations.suml index 21e2ed66a..78f26fec7 100644 --- a/locale/ru/translations.suml +++ b/locale/ru/translations.suml @@ -425,6 +425,7 @@ user: why: > Регистрация позволяет вам управлять своими аккаунтами/карточками ({/@excemple=как, например, этой}). passwordless: 'Сайт не хранит пароли. {https://avris.it/blog/passwords-are-passé=Больше информации}' + instancePlaceholder: 'Instance' # TODO code: action: 'Подтвердить' invalid: 'Неверный код.' diff --git a/locale/yi/translations.suml b/locale/yi/translations.suml index 2cd3a3f6f..a4d5113e6 100644 --- a/locale/yi/translations.suml +++ b/locale/yi/translations.suml @@ -392,6 +392,7 @@ user: why: > Registering lets you manage your cards ({/@example=like this one}). passwordless: 'The website doesn''t store any passwords. {https://avris.it/blog/passwords-are-passé=More info.}' + instancePlaceholder: 'Instance' # TODO code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/zh/translations.suml b/locale/zh/translations.suml index 480c5db91..495e988e3 100644 --- a/locale/zh/translations.suml +++ b/locale/zh/translations.suml @@ -361,6 +361,7 @@ user: why: > 註冊可以讓你管理你的卡({/@example=像這個})。 passwordless: '該網站不存儲任何密碼。 {https://avris.it/blog/passwords-are-passé=更多信息。}' + instancePlaceholder: 'Instance' # TODO code: action: '證實' invalid: '不對代碼' From 8912b3d3a222134cfbcac337beb6ee47f8d32f0e Mon Sep 17 00:00:00 2001 From: Andrea Vos Date: Mon, 13 Dec 2021 19:31:20 +0100 Subject: [PATCH 6/6] mastodon - migration number --- migrations/{041-external-oauth.sql => 043-external-oauth.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename migrations/{041-external-oauth.sql => 043-external-oauth.sql} (100%) diff --git a/migrations/041-external-oauth.sql b/migrations/043-external-oauth.sql similarity index 100% rename from migrations/041-external-oauth.sql rename to migrations/043-external-oauth.sql