This repository has been archived on 2024-07-22. You can view files and clone it, but cannot push or open issues or pull requests.
Zaimki/server/routes/grantOverrides.js

156 lines
5.2 KiB
JavaScript
Raw Normal View History

2021-12-05 11:56:39 -08:00
// 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";
2022-04-22 16:48:40 -07:00
import queryString from 'query-string';
2021-12-05 11:56:39 -08:00
const normalizeDomainName = (domain) => {
2022-04-22 16:48:40 -07:00
const url = new URL('https://' + domain.replace(/^https?:\/\//, ''));
2021-12-05 11:56:39 -08:00
assert(url.port === '');
return url.hostname;
}
2022-04-22 16:48:40 -07:00
const baseUrl = process.env.HOME_URL || 'https://pronouns.page';
2021-12-05 11:56:39 -08:00
const config = {
mastodon: {
scopes: ['read:accounts'],
2022-04-22 16:48:40 -07:00
redirect_uri: `${baseUrl}/api/user/social/mastodon`,
},
indieauth: {
server_uri: 'https://indieauth.com/auth',
client_id: baseUrl,
redirect_uri: `${baseUrl}/api/user/social/indieauth`,
2021-12-05 11:56:39 -08:00
},
};
const router = Router();
2021-12-12 15:29:26 -08:00
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'
`);
2021-12-05 11:56:39 -08:00
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`
2021-12-12 15:29:26 -08:00
INSERT INTO oauth_keys (instance, provider, client_id, client_secret)
VALUES (${instance}, 'mastodon', ${keys.client_id}, ${keys.client_secret})
2021-12-05 11:56:39 -08:00
`);
return keys;
};
2022-04-22 16:48:40 -07:00
2021-12-05 11:56:39 -08:00
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',
}));
}));
2022-04-22 16:48:40 -07:00
2021-12-05 11:56:39 -08:00
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();
2022-04-22 16:48:40 -07:00
}));
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();
2021-12-05 11:56:39 -08:00
}));
export default router;