diff --git a/components/SocialConnection.vue b/components/SocialConnection.vue index 5b9737ce2..e1ca5d614 100644 --- a/components/SocialConnection.vue +++ b/components/SocialConnection.vue @@ -1,16 +1,21 @@ diff --git a/components/SocialLogin.vue b/components/SocialLogin.vue index d11bcf2c9..bc47743ce 100644 --- a/components/SocialLogin.vue +++ b/components/SocialLogin.vue @@ -3,10 +3,10 @@ {{ options.name }} -
+ :placeholder="$t(options.domain ? 'user.login.domainPlaceholder' : 'user.login.instancePlaceholder')"/> @@ -15,11 +15,20 @@ - + + + {{ options.name }} + + {{ options.name }} @@ -37,11 +46,20 @@ export default { formShown: false, }; }, + computed: { + link() { + return this.options.redirectViaHome ? `${this.homeUrl}/api/user/social-redirect/${this.provider}/${this.config.locale}` : `/api/connect/${this.provider}` + } + }, methods: { showForm() { this.formShown = true; this.$nextTick(() => this.$refs.instance.focus()); - } + }, + async depreciationNotice(link) { + await this.$confirm(this.$t('user.login.depreciationNotice', {link}), 'warning'); + window.location.href = this.link; + }, } }; diff --git a/locale/_base/translations.suml b/locale/_base/translations.suml index 66c7894f9..9f1303653 100644 --- a/locale/_base/translations.suml +++ b/locale/_base/translations.suml @@ -497,6 +497,12 @@ user: 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' + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/de/translations.suml b/locale/de/translations.suml index e1e2b97a7..9205d1ca5 100644 --- a/locale/de/translations.suml +++ b/locale/de/translations.suml @@ -421,6 +421,13 @@ user: 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: 'Instanz' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validieren' invalid: 'Ungültiger Code.' diff --git a/locale/en/blog/facebook-login-deprecated.md b/locale/en/blog/facebook-login-deprecated.md new file mode 100644 index 000000000..aa513b9ab --- /dev/null +++ b/locale/en/blog/facebook-login-deprecated.md @@ -0,0 +1,23 @@ +# We don't recommend using Facebook login + +2022-04-23 | [@andrea](/@andrea) + +We all know that Meta/Facebook is a company that couldn't give a shit about morality or users' privacy. +But did you know they're also unfriendly towards developers and try to impose their prudish, conservative views on independent websites? + +This morning they've disabled two apps of mine whose whole purpose was simply to let people log in to a website using their Facebook account. +Nothing more, no actual dependency on Facebook, just the “log in with” button. + +Why? One of them, [NakedAdventure](https://naked-adventure.eu/), is a map of nude beaches and contains small amounts of non-sexual nudity. +The other, [OurSong](https://oursong.eurovote.eu/), is a harmless game where people vote for their favourite songs +– but it's only accessible for members, so Facebook didn't like the fact that they cannot sniff around. +They have blocked the apps without notice, making independent websites inaccessible for some users on a whim. + +If they think it's okay to suddenly cut people off other websites just because there was some non-sexual nudity present +or because they weren't able to snoop around, who knows if one day they won't block Pronouns.page because of the queer content? +After all, in countries like Russia distributing information about LGBTQ+ issues is already considered adult content. + +Users of Pronouns.page seem to dislike Facebook anyway – only 2.76% of you has login with Facebook set up +(compared for example to 38,95% of people using login with Twitter). +But to those who use it as their primary login option – +**we strongly recommend setting up a different login method and ditching Facebook**. diff --git a/locale/en/translations.suml b/locale/en/translations.suml index 5e33a0f70..70c103e98 100644 --- a/locale/en/translations.suml +++ b/locale/en/translations.suml @@ -521,6 +521,12 @@ user: 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' + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/eo/translations.suml b/locale/eo/translations.suml index 072ec40ab..c8c0236fc 100644 --- a/locale/eo/translations.suml +++ b/locale/eo/translations.suml @@ -482,6 +482,13 @@ user: 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 + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validate' invalid: 'Nevalida kodo.' diff --git a/locale/es/translations.suml b/locale/es/translations.suml index 2f8760cd2..44f89af0f 100644 --- a/locale/es/translations.suml +++ b/locale/es/translations.suml @@ -506,6 +506,13 @@ user: 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é=Más información.}' instancePlaceholder: 'Instancia' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validar' invalid: 'Código inválido.' diff --git a/locale/fr/translations.suml b/locale/fr/translations.suml index 4391c329c..d50c33aa0 100644 --- a/locale/fr/translations.suml +++ b/locale/fr/translations.suml @@ -424,6 +424,13 @@ user: 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 + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Valider' invalid: 'Code invalide.' diff --git a/locale/gl/translations.suml b/locale/gl/translations.suml index e7a537111..8140bae3f 100644 --- a/locale/gl/translations.suml +++ b/locale/gl/translations.suml @@ -405,6 +405,13 @@ user: 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 + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validar' invalid: 'Código inválido.' diff --git a/locale/it/translations.suml b/locale/it/translations.suml index 22f071757..5d538411d 100644 --- a/locale/it/translations.suml +++ b/locale/it/translations.suml @@ -498,6 +498,13 @@ user: 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 + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/ja/translations.suml b/locale/ja/translations.suml index d3391b5e7..6f1366489 100644 --- a/locale/ja/translations.suml +++ b/locale/ja/translations.suml @@ -426,6 +426,13 @@ user: ご登録いただいた方は、カードの設定を行うことができます。({/@example=カードの例}). passwordless: 'このウェブサイトはパスワードを保存しません。 {https://avris.it/blog/passwords-are-passé=詳細はこちら。}' instancePlaceholder: 'サーバー' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: '確認' invalid: '無効なコード' diff --git a/locale/ko/translations.suml b/locale/ko/translations.suml index e5e2100d8..e25f72fa2 100644 --- a/locale/ko/translations.suml +++ b/locale/ko/translations.suml @@ -495,6 +495,13 @@ user: 등록하면 카드를 ({/@example=이렇게}) 관리할 수 있습니다. passwordless: '웹사이트는 비밀번호를 저장하지 않습니다. {https://avris.it/blog/passwords-are-passé=추가 정보.}' instancePlaceholder: '사례' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: '확인' invalid: '유효하지 않은 코드' diff --git a/locale/lad/translations.suml b/locale/lad/translations.suml index 3fca1c472..e58fb2301 100644 --- a/locale/lad/translations.suml +++ b/locale/lad/translations.suml @@ -402,6 +402,13 @@ user: Rejistrarte te permete kontentarte tus kartas ({/@example=komo esta}). passwordless: 'Este sitio gueb no guadra las kontrasenyas. {https://avris.it/blog/passwords-are-passé=Mas informasion.}' instancePlaceholder: 'Instansia' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Balidar' invalid: 'Kodiche enfirmo.' diff --git a/locale/nl/translations.suml b/locale/nl/translations.suml index 561dec557..e4dd88c66 100644 --- a/locale/nl/translations.suml +++ b/locale/nl/translations.suml @@ -424,6 +424,13 @@ user: 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: 'Instantie' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Valideer' invalid: 'Ongeldige code.' diff --git a/locale/no/translations.suml b/locale/no/translations.suml index f79a7e707..7e3e9ff15 100644 --- a/locale/no/translations.suml +++ b/locale/no/translations.suml @@ -401,6 +401,13 @@ user: Å 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: 'Forekomst' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Gyldig' invalid: 'Ugyldig kode.' diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index 5fa9e2df5..bfb42c013 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -1196,6 +1196,12 @@ user: 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' + domainPlaceholder: 'Domena' + deprecated: 'Odradzane' + depreciationNotice: > + Ten dostawca uwierzytelniania nie jest godny zaufania. + Więcej szczegółów pod tym linkiem. + Mocno polecamy włączenie innej metody uwierzytelniania. code: action: 'Sprawdź' invalid: 'Kod nieprawidłowy.' diff --git a/locale/pt/translations.suml b/locale/pt/translations.suml index fe3382d75..deb505c49 100644 --- a/locale/pt/translations.suml +++ b/locale/pt/translations.suml @@ -427,6 +427,13 @@ user: 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: 'Instância' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validar' invalid: 'Código inválido.' diff --git a/locale/ru/translations.suml b/locale/ru/translations.suml index 9642c176a..a22abc972 100644 --- a/locale/ru/translations.suml +++ b/locale/ru/translations.suml @@ -518,6 +518,13 @@ user: Регистрация позволяет вам управлять своими аккаунтами/карточками ({/@exemple=как, например, этой}). passwordless: 'Сайт не хранит пароли. {https://avris.it/blog/passwords-are-passé=Больше информации}' instancePlaceholder: 'инстанция' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Подтвердить' invalid: 'Неверный код.' diff --git a/locale/sv/translations.suml b/locale/sv/translations.suml index a7ff0ff87..1fe66762f 100644 --- a/locale/sv/translations.suml +++ b/locale/sv/translations.suml @@ -476,6 +476,13 @@ user: Registering låter dig hantera dina kort ({/@andrea=som det här}). passwordless: 'Webbplatsen lagrar inga lösenord. {https://avris.it/blog/passwords-are-passé=Mer information.}' instancePlaceholder: 'Instans' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Bekräfta' invalid: 'Ogiltig kod.' diff --git a/locale/ua/translations.suml b/locale/ua/translations.suml index 643fd8dc0..11ddd9de4 100644 --- a/locale/ua/translations.suml +++ b/locale/ua/translations.suml @@ -541,6 +541,13 @@ user: Реєстрація дозволяє вам управляти своїми акаунтами/картками ({/@exemple=як, наприклад, цією}). passwordless: 'Сайт не зберігає паролі. {https://avris.it/blog/passwords-are-passé=Більше інформації}' instancePlaceholder: 'інстанція' + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Підтвердити' invalid: 'Невірний код.' diff --git a/locale/yi/translations.suml b/locale/yi/translations.suml index 371a3b0bf..d52dd6fb6 100644 --- a/locale/yi/translations.suml +++ b/locale/yi/translations.suml @@ -395,6 +395,13 @@ user: 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 + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: 'Validate' invalid: 'Invalid code.' diff --git a/locale/zh/translations.suml b/locale/zh/translations.suml index addc05810..90066f29e 100644 --- a/locale/zh/translations.suml +++ b/locale/zh/translations.suml @@ -380,6 +380,13 @@ user: 註冊可以讓你管理你的卡({/@example=像這個})。 passwordless: '該網站不存儲任何密碼。 {https://avris.it/blog/passwords-are-passé=更多信息。}' instancePlaceholder: 'Instance' # TODO + # TODO + domainPlaceholder: 'Domain' + deprecated: 'Deprecated' + depreciationNotice: > + This is not a reliable authentication provider. + Check out this link for more details. + We highly recommend making sure that you have a different login method available. code: action: '證實' invalid: '不對代碼' diff --git a/package.json b/package.json index 5377a63e2..2363d3f32 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "nuxt": "^2.15.2", "pageres": "^6.2.3", "qrcode": "^1.5.0", + "query-string": "^7.1.1", "rtlcss": "^3.1.2", "sha1": "^1.1.1", "speakeasy": "^2.0.0", diff --git a/server/routes/grantOverrides.js b/server/routes/grantOverrides.js index 268fe51c5..741bd0b9d 100644 --- a/server/routes/grantOverrides.js +++ b/server/routes/grantOverrides.js @@ -6,17 +6,25 @@ import SQL from 'sql-template-strings'; import fetch from 'node-fetch'; import assert from 'assert'; import { handleErrorAsync } from "../../src/helpers"; +import queryString from 'query-string'; const normalizeDomainName = (domain) => { - const url = new URL('https://' + domain); + const url = new URL('https://' + domain.replace(/^https?:\/\//, '')); assert(url.port === ''); return url.hostname; } +const baseUrl = process.env.BASE_URL || process.env.HOME_URL || 'https://pronouns.page'; + const config = { mastodon: { scopes: ['read:accounts'], - redirect_uri: `${process.env.HOME_URL || 'https://pronouns.page'}/api/user/social/mastodon`, + redirect_uri: `${baseUrl}/api/user/social/mastodon`, + }, + indieauth: { + server_uri: 'https://indieauth.com/auth', + client_id: baseUrl, + redirect_uri: `${baseUrl}/api/user/social/indieauth`, }, }; @@ -52,6 +60,7 @@ const mastodonGetOAuthKeys = async (db, instance) => { `); return keys; }; + router.get('/connect/mastodon', handleErrorAsync(async (req, res) => { assert(req.query.instance); const instance = normalizeDomainName(req.query.instance); @@ -64,6 +73,7 @@ router.get('/connect/mastodon', handleErrorAsync(async (req, res) => { 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(); @@ -99,7 +109,47 @@ router.get('/user/social/mastodon', handleErrorAsync(async (req, res, next) => { response.instance = instance; req.session.grant.response = response; next(); - return; +})); + +router.get('/connect/indieauth', handleErrorAsync(async (req, res) => { + assert(req.query.instance); + const instance = normalizeDomainName(req.query.instance); + req.session.grant = { instance }; + res.redirect(`${config.indieauth.server_uri}?` + new URLSearchParams({ + me: `https://${instance}`, + client_id: config.indieauth.client_id, + redirect_uri: config.indieauth.redirect_uri, + })); +})); + +router.get('/user/social/indieauth', handleErrorAsync(async (req, res, next) => { + if (!req.session.grant || !req.session.grant.instance || !req.query.code) { next(); return; } + + const response = await fetch(config.indieauth.server_uri, { + method: 'POST', + body: new URLSearchParams({ + code: req.query.code, + client_id: config.indieauth.client_id, + redirect_uri: config.indieauth.redirect_uri, + }).toString(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'pronouns.page', + }, + }).then(res => res.text()); + + const profile = queryString.parse(response); + + if (!profile || !profile.me) { next(); return; } + + profile.domain = normalizeDomainName(profile.me); + + req.session.grant.response = { + profile, + instance: req.session.grant.instance, + access_token: true, + }; + next(); })); export default router; diff --git a/server/social.js b/server/social.js index 4d0a89635..9f3713e95 100644 --- a/server/social.js +++ b/server/social.js @@ -32,6 +32,7 @@ module.exports.config = { }, // non-grant, but things break if it's not there mastodon: {}, + indieauth: {}, } module.exports.handlers = { @@ -84,6 +85,15 @@ module.exports.handlers = { name: acct, avatar: r.profile.avatar, access_token: r.access_token, + instance: r.instance, }; }, + indieauth(r) { + return { + id: r.profile.me, + email: 'indieauth@' + r.profile.domain, + name: r.profile.domain, + instance: r.instance, + } + }, }; diff --git a/src/socialProviders.js b/src/socialProviders.js index 8a6343d23..9c8a1e620 100644 --- a/src/socialProviders.js +++ b/src/socialProviders.js @@ -3,18 +3,36 @@ export const socialProviders = { name: 'Mastodon', instanceRequired: true, linkRegex: (p) => `^https?://(?:www.)?${p.name.split('@')[1]}/(?:(?:web/)?@|users/)?${p.name.split('@')[0]}/?$`, + avatars: true, + }, + indieauth: { + name: 'IndieAuth', + instanceRequired: true, + domain: true, + icon: 'indieauth.png', + iconMargin: true, + avatars: false, }, twitter: { name: 'Twitter', linkRegex: (p) => `^https?://(?:www.)?twitter.com/${p.name}/?$`, + redirectViaHome: true, + avatars: true, }, discord: { name: 'Discord', - }, - facebook: { - name: 'Facebook', + redirectViaHome: true, + avatars: true, }, google: { name: 'Google', + redirectViaHome: true, + avatars: true, + }, + facebook: { + name: 'Facebook', + redirectViaHome: true, + avatars: false, + deprecated: 'https://en.pronouns.page/blog/facebook-login-deprecated', }, } diff --git a/static/img/indieauth-inverse.png b/static/img/indieauth-inverse.png new file mode 100644 index 000000000..5a48219bd Binary files /dev/null and b/static/img/indieauth-inverse.png differ diff --git a/static/img/indieauth.png b/static/img/indieauth.png new file mode 100644 index 000000000..9a5b7cc2b Binary files /dev/null and b/static/img/indieauth.png differ diff --git a/yarn.lock b/yarn.lock index 743a8346f..73714abe5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8467,6 +8467,16 @@ query-string@^6.13.8: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +query-string@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" + integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"