[user] indieauth

This commit is contained in:
Andrea 2022-04-23 01:48:40 +02:00
parent 559f7c1ea9
commit 14a16c5cab
9 changed files with 101 additions and 12 deletions

View File

@ -1,13 +1,14 @@
<template>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center">
<span class="my-2">
<Icon :v="providerOptions.icon || provider" set="b"/>
<Icon :v="providerOptions.icon || provider" set="b"
:class="[providerOptions.icon && providerOptions.icon.endsWith('.png') ? 'mx-1 invertible' : '']"/>
{{ providerOptions.name }}
</span>
<span v-if="connection === undefined">
<template v-if="providerOptions.instanceRequired">
<form v-if="formShown"
:action="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`"
:action="providerOptions.redirectViaHome ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : `/api/connect/${provider}`"
class="input-group input-group-sm">
<input type="text" name="instance" class="form-control" autofocus required
:placeholder="$t('user.login.instancePlaceholder')"/>
@ -20,20 +21,20 @@
<T>user.socialConnection.connect</T>
</button>
</template>
<a v-else :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`" class="badge bg-light text-dark border">
<a v-else :href="providerOptions.redirectViaHome ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : `/api/connect/${provider}`" class="badge bg-light text-dark border">
<Icon v="link"/>
<T>user.socialConnection.connect</T>
</a>
</span>
<span v-else class="text-center">
<span class="mr-3">
<a href="#" @click.prevent="$emit('setAvatar', provider)">
<span class="me-2">
<a v-if="providerOptions.avatars && connection.avatar" href="#" @click.prevent="$emit('setAvatar', provider)">
<Avatar :src="connection.avatar" :user="$user()" dsize="2rem"/>
</a>
{{connection.name}}
</span>
<br class="d-md-none"/>
<a :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` + (providerOptions.instanceRequired ? '?instance=' + connection.name.split('@')[1] : '')"
<a :href="(providerOptions.redirectViaHome ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : `/api/connect/${provider}`) + (providerOptions.instanceRequired ? '?instance=' + connection.name.split('@')[1] : '')"
class="badge bg-light text-dark border">
<Icon v="sync"/>
<T>user.socialConnection.refresh</T>

View File

@ -3,7 +3,7 @@
<Icon :v="options.icon || provider" set="b"/>
{{ options.name }}
<form :action="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`"
<form :action="options.redirectViaHome ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : `/api/connect/${provider}`"
v-if="options.instanceRequired" class="input-group my-2">
<input type="text" name="instance" class="form-control" autofocus required ref="instance"
:placeholder="$t('user.login.instancePlaceholder')">
@ -15,10 +15,11 @@
<button v-else-if="options.instanceRequired && !formShown"
class="btn btn-outline-primary"
@click="showForm">
<Icon :v="options.icon || provider" set="b"/>
<Icon :v="options.icon || provider" set="b"
:class="[options.icon && options.icon.endsWith('.png') ? 'mx-1 invertible' : '']"/>
{{ options.name }}
</button>
<a v-else :href="`${homeUrl}/api/user/social-redirect/${provider}/${config.locale}`"
<a v-else :href="options.redirectViaHome ? `${homeUrl}/api/user/social-redirect/${provider}/${config.locale}` : `/api/connect/${provider}`"
class="btn btn-outline-primary">
<Icon :v="options.icon || provider" set="b"/>
{{ options.name }}

View File

@ -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",

View File

@ -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.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;

View File

@ -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,
}
},
};

View File

@ -3,18 +3,34 @@ 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,
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',
redirectViaHome: true,
avatars: true,
},
facebook: {
name: 'Facebook',
redirectViaHome: true,
avatars: false,
},
google: {
name: 'Google',
redirectViaHome: true,
avatars: true,
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
static/img/indieauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -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"