From 72b13f526579be5cf25eecfbe5e68d19b8570bd6 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 15 Nov 2022 22:29:46 +0800 Subject: [PATCH] wip: oauth --- .gitignore | 2 ++ package.json | 3 +++ pnpm-lock.yaml | 10 +++++++++ scripts/registerApps.ts | 41 ++++++++++++++++++++++++++++++++++++ server/api/[server]/login.ts | 26 +++++++++++++++++++++++ server/api/[server]/oauth.ts | 16 ++++++++++---- server/shared.ts | 21 ++++++++++++++++++ 7 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 scripts/registerApps.ts create mode 100644 server/api/[server]/login.ts create mode 100644 server/shared.ts diff --git a/.gitignore b/.gitignore index 467d4664..3aa45eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ dist .output .nuxt .env + +registered-apps.json diff --git a/package.json b/package.json index 3768f683..948ffd98 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "private": true, + "type": "module", "packageManager": "pnpm@7.9.0", "scripts": { "build": "nuxi build", @@ -17,11 +18,13 @@ "@iconify-json/twemoji": "^1.1.5", "@nuxtjs/color-mode": "^3.1.8", "@pinia/nuxt": "^0.4.3", + "@types/fs-extra": "^9.0.13", "@types/sanitize-html": "^2.6.2", "@unocss/nuxt": "^0.46.5", "@vueuse/nuxt": "^9.5.0", "eslint": "^8.27.0", "form-data": "^4.0.0", + "fs-extra": "^10.1.0", "masto": "^4.6.1", "nuxt": "^3.0.0-rc.13", "pinia": "^2.0.23", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90c5cb45..15577bc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,11 +8,13 @@ specifiers: '@iconify-json/twemoji': ^1.1.5 '@nuxtjs/color-mode': ^3.1.8 '@pinia/nuxt': ^0.4.3 + '@types/fs-extra': ^9.0.13 '@types/sanitize-html': ^2.6.2 '@unocss/nuxt': ^0.46.5 '@vueuse/nuxt': ^9.5.0 eslint: ^8.27.0 form-data: ^4.0.0 + fs-extra: ^10.1.0 masto: ^4.6.1 nuxt: ^3.0.0-rc.13 pinia: ^2.0.23 @@ -30,11 +32,13 @@ devDependencies: '@iconify-json/twemoji': 1.1.5 '@nuxtjs/color-mode': 3.1.8 '@pinia/nuxt': 0.4.3_typescript@4.8.4 + '@types/fs-extra': 9.0.13 '@types/sanitize-html': 2.6.2 '@unocss/nuxt': 0.46.5 '@vueuse/nuxt': 9.5.0_nuxt@3.0.0-rc.13 eslint: 8.27.0 form-data: 4.0.0 + fs-extra: 10.1.0 masto: 4.6.1 nuxt: 3.0.0-rc.13_rmayb2veg2btbq6mbmnyivgasy pinia: 2.0.23_typescript@4.8.4 @@ -1169,6 +1173,12 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true + /@types/fs-extra/9.0.13: + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + dependencies: + '@types/node': 18.7.23 + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true diff --git a/scripts/registerApps.ts b/scripts/registerApps.ts new file mode 100644 index 00000000..9aaaf065 --- /dev/null +++ b/scripts/registerApps.ts @@ -0,0 +1,41 @@ +import fs from 'fs-extra' +import type { Client } from 'masto' +import { $fetch } from 'ohmyfetch' +import { APP_NAME } from '~~/constants' + +const KNOWN_SERVERS = [ + 'mastodon.social', + 'mas.to', + 'fosstodon.org', +] + +const filename = 'public/registered-apps.json' + +let registeredApps: Record = {} + +if (fs.existsSync(filename)) + registeredApps = await fs.readJSON(filename) + +for (const server of KNOWN_SERVERS) { + if (registeredApps[server]) + continue + + const app = await $fetch(`https://${server}/api/v1/apps`, { + method: 'POST', + body: { + client_name: APP_NAME, + redirect_uris: [ + 'urn:ietf:wg:oauth:2.0:oob', + 'http://localhost:3000/*', + 'https://nuxtodon.netlify.app/*', + ].join('\n'), + scopes: 'read write follow push', + }, + }) + + registeredApps[server] = app + + console.log(`Registered app for ${server}`) +} + +await fs.writeJSON(filename, registeredApps, { spaces: 2, EOL: '\n' }) diff --git a/server/api/[server]/login.ts b/server/api/[server]/login.ts new file mode 100644 index 00000000..28fdd312 --- /dev/null +++ b/server/api/[server]/login.ts @@ -0,0 +1,26 @@ +import { stringifyQuery } from 'ufo' +import { getApp } from '~/server/shared' +import { HOST_DOMAIN } from '~/constants' + +export default defineEventHandler(async ({ context, res }) => { + const server = context.params.server + const app = await getApp(server) + + if (!app) { + res.statusCode = 400 + return `App not registered for server: ${server}` + } + + const query = stringifyQuery({ + client_id: app.client_id, + scope: 'read write follow push', + redirect_uri: `${HOST_DOMAIN}/api/${server}/oauth`, + response_type: 'code', + }) + const url = `https://${server}/oauth/authorize?${query}` + + res.writeHead(302, { + Location: url, + }) + res.end() +}) diff --git a/server/api/[server]/oauth.ts b/server/api/[server]/oauth.ts index 563e4dc9..be3b876f 100644 --- a/server/api/[server]/oauth.ts +++ b/server/api/[server]/oauth.ts @@ -1,21 +1,29 @@ import { getQuery } from 'ufo' +import { getApp } from '~/server/shared' export default defineEventHandler(async (event) => { + const server = event.context.params.server + const app = await getApp(server) + + if (!app) { + event.res.statusCode = 400 + return `App not registered for server: ${server}` + } + const query = getQuery(event.req.url!) const code = query.code - const server = event.context.params.server - console.log({ query, server }) const res = await $fetch(`https://${server}/oauth/token`, { method: 'POST', body: { - client_id: 'your_client_id_here', - client_secret: 'your_client_secret_here', + client_id: app.client_id, + client_secret: app.client_secret, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'authorization_code', code, scope: 'read write follow push', }, }) + console.log({ res }) }) diff --git a/server/shared.ts b/server/shared.ts new file mode 100644 index 00000000..78ce395a --- /dev/null +++ b/server/shared.ts @@ -0,0 +1,21 @@ +import { $fetch } from 'ohmyfetch' + +export interface AppInfo { + id: string + name: string + website: string | null + redirect_uri: string + client_id: string + client_secret: string + vapid_key: string +} + +export const registeredApps: Record = {} + +const promise = $fetch(process.env.APPS_JSON_URL || 'http://localhost:3000/registered-apps.json') + .then(r => Object.assign(registeredApps, r)) + +export async function getApp(server: string) { + await promise + return registeredApps[server] +}