From db7f1bf13ab13d39fc60e0f616a21eb85dd6c543 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 6 Mar 2023 10:08:15 +0000 Subject: [PATCH 1/2] add missing auth loader to settings page --- frontend/src/routes/(admin)/settings/layout.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/routes/(admin)/settings/layout.tsx b/frontend/src/routes/(admin)/settings/layout.tsx index db131f0..7b3b1ce 100644 --- a/frontend/src/routes/(admin)/settings/layout.tsx +++ b/frontend/src/routes/(admin)/settings/layout.tsx @@ -1,5 +1,19 @@ import { component$, Slot } from '@builder.io/qwik' +import { loader$ } from '@builder.io/qwik-city' +import { getDatabase } from 'wildebeest/backend/src/database' import { WildebeestLogo } from '~/components/MastodonLogo' +import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml' +import { isUserAdmin } from '~/utils/isUserAdmin' + +export const authLoader = loader$(async ({ cookie, platform, html }) => { + const database = await getDatabase(platform) + const jwt = cookie.get('CF_Authorization')?.value ?? '' + const isAdmin = await isUserAdmin(jwt, database) + + if (!isAdmin) { + return html(401, getErrorHtml("You're unauthorized to view this page")) + } +}) export default component$(() => { return ( From d505ed278e2cb80a7980132ab4e1ff3196476c47 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 6 Mar 2023 11:14:10 +0000 Subject: [PATCH 2/2] add access check to isUserAdmin function --- .../routes/(admin)/settings/aliases/index.tsx | 5 +++-- frontend/src/routes/(admin)/settings/layout.tsx | 8 +++++--- .../routes/(admin)/settings/migration/index.tsx | 13 ------------- frontend/src/routes/layout.tsx | 5 +++-- frontend/src/utils/checkAuth.ts | 15 ++++----------- frontend/src/utils/isUserAdmin.ts | 17 +++++++++++++++-- functions/api/wb/settings/server/rules.ts | 14 +++++++------- functions/api/wb/settings/server/server.ts | 8 ++++---- functions/oauth/authorize.ts | 13 +++++-------- 9 files changed, 46 insertions(+), 52 deletions(-) diff --git a/frontend/src/routes/(admin)/settings/aliases/index.tsx b/frontend/src/routes/(admin)/settings/aliases/index.tsx index cb0863c..dd5cc46 100644 --- a/frontend/src/routes/(admin)/settings/aliases/index.tsx +++ b/frontend/src/routes/(admin)/settings/aliases/index.tsx @@ -2,8 +2,9 @@ import { component$, useStore, useSignal, $ } from '@builder.io/qwik' import { loader$ } from '@builder.io/qwik-city' import { checkAuth } from '~/utils/checkAuth' -export const loader = loader$(async ({ request, platform, redirect }) => { - const isAuthorized = await checkAuth(request, platform) +export const loader = loader$(async ({ cookie, request, platform, redirect }) => { + const jwt = cookie.get('CF_Authorization')?.value ?? '' + const isAuthorized = await checkAuth(request, jwt, platform.ACCESS_AUTH_DOMAIN, platform.ACCESS_AUD) if (!isAuthorized) { redirect(303, '/explore') diff --git a/frontend/src/routes/(admin)/settings/layout.tsx b/frontend/src/routes/(admin)/settings/layout.tsx index 7b3b1ce..a56cdd1 100644 --- a/frontend/src/routes/(admin)/settings/layout.tsx +++ b/frontend/src/routes/(admin)/settings/layout.tsx @@ -1,14 +1,16 @@ import { component$, Slot } from '@builder.io/qwik' import { loader$ } from '@builder.io/qwik-city' +import { parse } from 'cookie' import { getDatabase } from 'wildebeest/backend/src/database' import { WildebeestLogo } from '~/components/MastodonLogo' import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml' import { isUserAdmin } from '~/utils/isUserAdmin' -export const authLoader = loader$(async ({ cookie, platform, html }) => { +export const authLoader = loader$(async ({ request, platform, html }) => { const database = await getDatabase(platform) - const jwt = cookie.get('CF_Authorization')?.value ?? '' - const isAdmin = await isUserAdmin(jwt, database) + const cookie = parse(request.headers.get('Cookie') || '') + const jwtCookie = cookie.CF_Authorization ?? '' + const isAdmin = await isUserAdmin(request, jwtCookie, platform.ACCESS_AUTH_DOMAIN, platform.ACCESS_AUD, database) if (!isAdmin) { return html(401, getErrorHtml("You're unauthorized to view this page")) diff --git a/frontend/src/routes/(admin)/settings/migration/index.tsx b/frontend/src/routes/(admin)/settings/migration/index.tsx index 6180b41..e1c0e6b 100644 --- a/frontend/src/routes/(admin)/settings/migration/index.tsx +++ b/frontend/src/routes/(admin)/settings/migration/index.tsx @@ -1,17 +1,4 @@ import { component$ } from '@builder.io/qwik' -import { loader$ } from '@builder.io/qwik-city' -// import { checkAuth } from '~/utils/checkAuth' - -export const loader = loader$(async ({ redirect }) => { - // Hiding this page for now - redirect(303, '/explore') - - // const isAuthorized = await checkAuth(request, platform) - - // if (!isAuthorized) { - // redirect(303, '/explore') - // } -}) export default component$(() => { return ( diff --git a/frontend/src/routes/layout.tsx b/frontend/src/routes/layout.tsx index e4027ac..3dba437 100644 --- a/frontend/src/routes/layout.tsx +++ b/frontend/src/routes/layout.tsx @@ -8,8 +8,9 @@ type AccessLoaderData = { isAuthorized: boolean } -export const accessLoader = loader$>(async ({ platform, request }) => { - const isAuthorized = await checkAuth(request, platform) +export const accessLoader = loader$>(async ({ platform, request, cookie }) => { + const jwt = cookie.get('CF_Authorization')?.value ?? '' + const isAuthorized = await checkAuth(request, jwt, platform.ACCESS_AUTH_DOMAIN, platform.ACCESS_AUD) return { isAuthorized, diff --git a/frontend/src/utils/checkAuth.ts b/frontend/src/utils/checkAuth.ts index 23fbe14..3e447b4 100644 --- a/frontend/src/utils/checkAuth.ts +++ b/frontend/src/utils/checkAuth.ts @@ -1,27 +1,20 @@ -import { RequestContext } from '@builder.io/qwik-city/middleware/request-handler' import * as access from 'wildebeest/backend/src/access' -type Env = { - ACCESS_AUTH_DOMAIN: string - ACCESS_AUD: string -} - -export const checkAuth = async (request: RequestContext, platform: Env) => { - const jwt = request.headers.get('Cf-Access-Jwt-Assertion') || '' +export const checkAuth = async (request: Request, jwt: string, accessAuthDomain: string, accessAud: string) => { if (!jwt) return false try { const validate = access.generateValidator({ jwt, - domain: platform.ACCESS_AUTH_DOMAIN, - aud: platform.ACCESS_AUD, + domain: accessAuthDomain, + aud: accessAud, }) await validate(new Request(request.url)) } catch { return false } - const identity = await access.getIdentity({ jwt, domain: platform.ACCESS_AUTH_DOMAIN }) + const identity = await access.getIdentity({ jwt, domain: accessAuthDomain }) if (identity) { return true } diff --git a/frontend/src/utils/isUserAdmin.ts b/frontend/src/utils/isUserAdmin.ts index a247d96..a14a1f2 100644 --- a/frontend/src/utils/isUserAdmin.ts +++ b/frontend/src/utils/isUserAdmin.ts @@ -1,12 +1,25 @@ import { emailSymbol } from 'wildebeest/backend/src/activitypub/actors' import { Database } from 'wildebeest/backend/src/database' import { getAdmins } from 'wildebeest/functions/api/wb/settings/server/admins' +import { checkAuth } from './checkAuth' import { getJwtEmail } from './getJwtEmail' -export async function isUserAdmin(jwtCookie: string, database: Database): Promise { +export async function isUserAdmin( + request: Request, + jwt: string, + accessAuthDomain: string, + accessAud: string, + database: Database +): Promise { let email: string + try { - email = getJwtEmail(jwtCookie) + const authenticated = await checkAuth(request, jwt, accessAuthDomain, accessAud) + if (!authenticated) { + return false + } + + email = getJwtEmail(jwt) } catch { return false } diff --git a/functions/api/wb/settings/server/rules.ts b/functions/api/wb/settings/server/rules.ts index 1a66b30..98e2bd3 100644 --- a/functions/api/wb/settings/server/rules.ts +++ b/functions/api/wb/settings/server/rules.ts @@ -2,11 +2,11 @@ import type { Env } from 'wildebeest/backend/src/types/env' import type { ContextData } from 'wildebeest/backend/src/types/context' import * as errors from 'wildebeest/backend/src/errors' import { type Database, getDatabase } from 'wildebeest/backend/src/database' -import { parse } from 'cookie' import { isUserAdmin } from 'wildebeest/frontend/src/utils/isUserAdmin' +import { parse } from 'cookie' export const onRequestGet: PagesFunction = async ({ env, request }) => { - return handleRequestPost(await getDatabase(env), request) + return handleRequestPost(await getDatabase(env), request, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } export async function handleRequestGet(db: Database) { @@ -21,13 +21,13 @@ export async function handleRequestGet(db: Database) { } export const onRequestPost: PagesFunction = async ({ env, request }) => { - return handleRequestPost(await getDatabase(env), request) + return handleRequestPost(await getDatabase(env), request, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } -export async function handleRequestPost(db: Database, request: Request) { +export async function handleRequestPost(db: Database, request: Request, accessAuthDomain: string, accessAud: string) { const cookie = parse(request.headers.get('Cookie') || '') const jwt = cookie['CF_Authorization'] - const isAdmin = await isUserAdmin(jwt, db) + const isAdmin = await isUserAdmin(request, jwt, accessAuthDomain, accessAud, db) if (!isAdmin) { return errors.notAuthorized('Lacking authorization rights to edit server rules') @@ -56,10 +56,10 @@ export async function upsertRule(db: Database, rule: { id?: number; text: string .run() } -export async function handleRequestDelete(db: Database, request: Request) { +export async function handleRequestDelete(db: Database, request: Request, accessAuthDomain: string, accessAud: string) { const cookie = parse(request.headers.get('Cookie') || '') const jwt = cookie['CF_Authorization'] - const isAdmin = await isUserAdmin(jwt, db) + const isAdmin = await isUserAdmin(request, jwt, accessAuthDomain, accessAud, db) if (!isAdmin) { return errors.notAuthorized('Lacking authorization rights to edit server rules') diff --git a/functions/api/wb/settings/server/server.ts b/functions/api/wb/settings/server/server.ts index f286d49..2e4b2f3 100644 --- a/functions/api/wb/settings/server/server.ts +++ b/functions/api/wb/settings/server/server.ts @@ -7,7 +7,7 @@ import { isUserAdmin } from 'wildebeest/frontend/src/utils/isUserAdmin' import { ServerSettingsData } from 'wildebeest/frontend/src/routes/(admin)/settings/server-settings/layout' export const onRequestGet: PagesFunction = async ({ env, request }) => { - return handleRequestPost(await getDatabase(env), request) + return handleRequestPost(await getDatabase(env), request, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } export async function handleRequestGet(db: Database) { @@ -30,13 +30,13 @@ export async function handleRequestGet(db: Database) { } export const onRequestPost: PagesFunction = async ({ env, request }) => { - return handleRequestPost(await getDatabase(env), request) + return handleRequestPost(await getDatabase(env), request, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } -export async function handleRequestPost(db: Database, request: Request) { +export async function handleRequestPost(db: Database, request: Request, accessAuthDomain: string, accessAud: string) { const cookie = parse(request.headers.get('Cookie') || '') const jwt = cookie['CF_Authorization'] - const isAdmin = await isUserAdmin(jwt, db) + const isAdmin = await isUserAdmin(request, jwt, accessAuthDomain, accessAud, db) if (!isAdmin) { return errors.notAuthorized('Lacking authorization rights to edit server settings') diff --git a/functions/oauth/authorize.ts b/functions/oauth/authorize.ts index f0ed5bd..f3ba56f 100644 --- a/functions/oauth/authorize.ts +++ b/functions/oauth/authorize.ts @@ -8,6 +8,7 @@ import { getClientById } from 'wildebeest/backend/src/mastodon/client' import * as access from 'wildebeest/backend/src/access' import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors' import { type Database, getDatabase } from 'wildebeest/backend/src/database' +import { checkAuth } from 'wildebeest/frontend/src/utils/checkAuth' // Extract the JWT token sent by Access (running before us). const extractJWTFromRequest = (request: Request) => request.headers.get('Cf-Access-Jwt-Assertion') || '' @@ -79,18 +80,14 @@ export async function handleRequestPost( } const jwt = extractJWTFromRequest(request) - if (!jwt) { + const isAuthenticated = await checkAuth(request, jwt, accessDomain, accessAud) + + if (!isAuthenticated) { return new Response('', { status: 401 }) } - const validate = access.generateValidator({ jwt, domain: accessDomain, aud: accessAud }) - await validate(request) const identity = await access.getIdentity({ jwt, domain: accessDomain }) - if (!identity) { - return new Response('', { status: 401 }) - } - - const isFirstLogin = (await getPersonByEmail(db, identity.email)) === null + const isFirstLogin = (await getPersonByEmail(db, identity!.email)) === null return buildRedirect(db, request, isFirstLogin, jwt) }