[user] indieauth
This commit is contained in:
parent
559f7c1ea9
commit
14a16c5cab
|
@ -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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 |
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
10
yarn.lock
10
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"
|
||||
|
|
Reference in New Issue