From 4d68c3b27dc8784a1c47cbefa56ae19c79fde640 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Mon, 30 Jan 2023 11:28:56 +0000 Subject: [PATCH] gh-86: implement authorization screen --- backend/test/mastodon/oauth.spec.ts | 12 +-- .../routes/(admin)/oauth/authorize/index.tsx | 75 +++++++++++++++++++ functions/oauth/authorize.ts | 6 +- 3 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 frontend/src/routes/(admin)/oauth/authorize/index.tsx diff --git a/backend/test/mastodon/oauth.spec.ts b/backend/test/mastodon/oauth.spec.ts index 219dd78..47a91a9 100644 --- a/backend/test/mastodon/oauth.spec.ts +++ b/backend/test/mastodon/oauth.spec.ts @@ -35,11 +35,11 @@ describe('Mastodon APIs', () => { const db = await makeDB() let req = new Request('https://example.com/oauth/authorize') - let res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + let res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 400) req = new Request('https://example.com/oauth/authorize?scope=foobar') - res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 400) }) @@ -53,7 +53,7 @@ describe('Mastodon APIs', () => { }) const req = new Request('https://example.com/oauth/authorize?' + params) - const res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + const res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 400) }) @@ -72,7 +72,7 @@ describe('Mastodon APIs', () => { const req = new Request('https://example.com/oauth/authorize?' + params, { headers, }) - const res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + const res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 403) }) @@ -91,7 +91,7 @@ describe('Mastodon APIs', () => { const req = new Request('https://example.com/oauth/authorize?' + params, { headers, }) - const res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + const res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 302) const location = new URL(res.headers.get('location') || '') @@ -223,7 +223,7 @@ describe('Mastodon APIs', () => { const req = new Request('https://example.com/oauth/authorize', { method: 'OPTIONS', }) - const res = await oauth_authorize.handleRequest(req, db, userKEK, accessDomain, accessAud) + const res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 200) assertCORS(res) }) diff --git a/frontend/src/routes/(admin)/oauth/authorize/index.tsx b/frontend/src/routes/(admin)/oauth/authorize/index.tsx new file mode 100644 index 0000000..e9f4052 --- /dev/null +++ b/frontend/src/routes/(admin)/oauth/authorize/index.tsx @@ -0,0 +1,75 @@ +import { component$ } from '@builder.io/qwik' +import * as access from 'wildebeest/backend/src/access' +import type { Client } from 'wildebeest/backend/src/mastodon/client' +import { getClientById } from 'wildebeest/backend/src/mastodon/client' +import { DocumentHead, loader$ } from '@builder.io/qwik-city' +import { WildebeestLogo } from '~/components/MastodonLogo' + +export const clientLoader = loader$<{ DATABASE: D1Database }, Promise>(async ({ platform, query }) => { + const client_id = query.get('client_id') || '' + const client = await getClientById(platform.DATABASE, client_id) + if (client === null) { + throw new Error('client not found') + } + return client +}) + +export const userLoader = loader$<{ DATABASE: D1Database; domain: string }, Promise<{ email: string }>>( + async ({ cookie }) => { + const jwt = cookie.get('CF_Authorization') + if (jwt === null) { + throw new Error('missing authorization') + } + try { + // TODO: eventually, verify the JWT with Access, however this + // is not critical. + const payload = access.getPayload(jwt.value) + return { email: payload.email } + } catch (err: unknown) { + console.warn(err.stack) + throw new Error('failed to validate Access JWT') + } + } +) + +export default component$(() => { + const client = clientLoader.use().value + const user = userLoader.use().value + return ( +
+

+ +

+
+

Signed in as: {user.email}.

+

+ Click here to change account. +

+
+

+ {client.name} would like permission to access your account. It is a third-party application. If you do + not trust it, then you should not authorize it. +

+
+ +
+
+ ) +}) + +export const head: DocumentHead = () => { + return { + title: 'Wildebeest Authorization required', + meta: [ + { + name: 'description', + content: 'Wildebeest Authorization required', + }, + ], + } +} diff --git a/functions/oauth/authorize.ts b/functions/oauth/authorize.ts index 06b0c0c..694d0b2 100644 --- a/functions/oauth/authorize.ts +++ b/functions/oauth/authorize.ts @@ -11,11 +11,11 @@ import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors' // Extract the JWT token sent by Access (running before us). const extractJWTFromRequest = (request: Request) => request.headers.get('Cf-Access-Jwt-Assertion') || '' -export const onRequest: PagesFunction = async ({ request, env }) => { - return handleRequest(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) +export const onRequestPost: PagesFunction = async ({ request, env }) => { + return handleRequestPost(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } -export async function handleRequest( +export async function handleRequestPost( request: Request, db: D1Database, userKEK: string,