mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-26 06:23:35 -04:00
Merge branch 'log-in-with-mastodon' into 'main'
logging in with mastodon See merge request Avris/Zaimki!226
This commit is contained in:
commit
de4b89db10
@ -12,9 +12,19 @@
|
||||
<div class="row flex-row-reverse">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="btn-group-vertical w-100 mb-3">
|
||||
<a :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`" v-for="(providerOptions, provider) in socialProviders" class="btn btn-outline-primary">
|
||||
<a :href="!providerOptions.instanceRequired ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : null"
|
||||
:class="providerOptions.instanceRequired ? 'btn border-primary text-primary' : 'btn btn-outline-primary'"
|
||||
v-for="(providerOptions, provider) in socialProviders">
|
||||
<Icon :v="providerOptions.icon || provider" set="b"/>
|
||||
{{ providerOptions.name }}
|
||||
<form :action="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`"
|
||||
v-if="providerOptions.instanceRequired" class="input-group">
|
||||
<input type="text" name="instance" class="form-control"
|
||||
:placeholder="$t('user.login.instancePlaceholder')">
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
<Icon v="arrow-right"/>
|
||||
</button>
|
||||
</form>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,15 @@
|
||||
{{ providerOptions.name }}
|
||||
</span>
|
||||
<span v-if="connection === undefined">
|
||||
<a :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`" class="badge bg-light text-dark border">
|
||||
<form :action="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`"
|
||||
v-if="providerOptions.instanceRequired" class="input-group input-group-sm">
|
||||
<input type="text" name="instance" class="form-control"
|
||||
:placeholder="$t('user.login.instancePlaceholder')">
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<Icon v="link"/>
|
||||
</button>
|
||||
</form>
|
||||
<a v-else :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`" class="badge bg-light text-dark border">
|
||||
<Icon v="link"/>
|
||||
<T>user.socialConnection.connect</T>
|
||||
</a>
|
||||
@ -18,7 +26,8 @@
|
||||
{{connection.name}}
|
||||
</span>
|
||||
<br class="d-md-none"/>
|
||||
<a :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`" class="badge bg-light text-dark border">
|
||||
<a :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` + (providerOptions.instanceRequired ? '?instance=' + connection.name.split('@')[1] : '')"
|
||||
class="badge bg-light text-dark border">
|
||||
<Icon v="sync"/>
|
||||
<T>user.socialConnection.refresh</T>
|
||||
</a>
|
||||
|
@ -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.'
|
||||
|
@ -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.'
|
||||
|
@ -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.'
|
||||
|
@ -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:
|
||||
|
@ -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.'
|
||||
|
@ -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.'
|
||||
|
@ -400,6 +400,7 @@ user:
|
||||
why: >
|
||||
ご登録いただいた方は、カードの設定を行うことができます。({/@example=こんなに}).
|
||||
passwordless: 'このウェブサイトはパスワードを保存しません。 {https://avris.it/blog/passwords-are-passé=詳細はこちら。}'
|
||||
instancePlaceholder: 'Instance' # TODO
|
||||
code:
|
||||
action: '確認'
|
||||
invalid: '無効なコード'
|
||||
|
@ -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:
|
||||
|
@ -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.'
|
||||
|
@ -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.'
|
||||
|
@ -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.'
|
||||
|
@ -425,6 +425,7 @@ user:
|
||||
why: >
|
||||
Регистрация позволяет вам управлять своими аккаунтами/карточками ({/@excemple=как, например, этой}).
|
||||
passwordless: 'Сайт не хранит пароли. {https://avris.it/blog/passwords-are-passé=Больше информации}'
|
||||
instancePlaceholder: 'Instance' # TODO
|
||||
code:
|
||||
action: 'Подтвердить'
|
||||
invalid: 'Неверный код.'
|
||||
|
@ -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.'
|
||||
|
@ -361,6 +361,7 @@ user:
|
||||
why: >
|
||||
註冊可以讓你管理你的卡({/@example=像這個})。
|
||||
passwordless: '該網站不存儲任何密碼。 {https://avris.it/blog/passwords-are-passé=更多信息。}'
|
||||
instancePlaceholder: 'Instance' # TODO
|
||||
code:
|
||||
action: '證實'
|
||||
invalid: '不對代碼'
|
||||
|
12
migrations/043-external-oauth.sql
Normal file
12
migrations/043-external-oauth.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- Up
|
||||
|
||||
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 oauth_keys;
|
@ -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);
|
||||
|
105
server/routes/grantOverrides.js
Normal file
105
server/routes/grantOverrides.js
Normal file
@ -0,0 +1,105 @@
|
||||
// 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) => {
|
||||
const existingKeys = await db.get(SQL`
|
||||
SELECT client_id, client_secret
|
||||
FROM oauth_keys
|
||||
WHERE instance = ${instance}
|
||||
AND provider = 'mastodon'
|
||||
`);
|
||||
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 oauth_keys (instance, provider, client_id, client_secret)
|
||||
VALUES (${instance}, 'mastodon', ${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;
|
@ -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
|
||||
|
@ -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,15 @@ export const handlers = {
|
||||
access_secret: r.access_secret,
|
||||
}
|
||||
},
|
||||
mastodon(r) {
|
||||
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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user