From e00f05cdf2cf5d51109843e3395729680a5b2fa0 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Fri, 20 Jan 2023 15:03:52 +0000 Subject: [PATCH] MOW-100: protect first-login behind Access --- backend/test/mastodon/oauth.spec.ts | 29 +++++++++++++++++++--- functions/first-login.ts | 38 +++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/backend/test/mastodon/oauth.spec.ts b/backend/test/mastodon/oauth.spec.ts index 45f19ad..406129e 100644 --- a/backend/test/mastodon/oauth.spec.ts +++ b/backend/test/mastodon/oauth.spec.ts @@ -104,12 +104,11 @@ describe('Mastodon APIs', () => { assert.equal(count, 0) }) - test('first login creates the user and redirects', async () => { + test('first login is protected by Access', async () => { const db = await makeDB() const params = new URLSearchParams({ redirect_uri: 'https://redirect.com/a', - email: 'a@cloudflare.com', }) const formData = new FormData() @@ -120,7 +119,29 @@ describe('Mastodon APIs', () => { method: 'POST', body: formData, }) - const res = await first_login.handlePostRequest(req, db, userKEK) + const res = await first_login.handlePostRequest(req, db, userKEK, accessDomain, accessAud) + assert.equal(res.status, 401) + }) + + test('first login creates the user and redirects', async () => { + const db = await makeDB() + + const params = new URLSearchParams({ + redirect_uri: 'https://redirect.com/a', + }) + + const formData = new FormData() + formData.set('username', 'username') + formData.set('name', 'name') + + const req = new Request('https://example.com/first-login?' + params, { + method: 'POST', + body: formData, + headers: { + cookie: `CF_Authorization=${TEST_JWT}`, + }, + }) + const res = await first_login.handlePostRequest(req, db, userKEK, accessDomain, accessAud) assert.equal(res.status, 302) const location = res.headers.get('location') @@ -129,7 +150,7 @@ describe('Mastodon APIs', () => { const actor = await db.prepare('SELECT * FROM actors').first() const properties = JSON.parse(actor.properties) - assert.equal(actor.email, 'a@cloudflare.com') + assert.equal(actor.email, 'sven@cloudflare.com') assert.equal(properties.preferredUsername, 'username') assert.equal(properties.name, 'name') assert(isUrlValid(actor.id)) diff --git a/functions/first-login.ts b/functions/first-login.ts index f53e603..61156fd 100644 --- a/functions/first-login.ts +++ b/functions/first-login.ts @@ -3,17 +3,41 @@ import type { Env } from 'wildebeest/backend/src/types/env' import type { ContextData } from 'wildebeest/backend/src/types/context' import { createPerson } from 'wildebeest/backend/src/activitypub/actors' +import { parse } from 'cookie' +import * as errors from 'wildebeest/backend/src/errors' +import * as access from 'wildebeest/backend/src/access' export const onRequestPost: PagesFunction = async ({ request, env }) => { - return handlePostRequest(request, env.DATABASE, env.userKEK) + return handlePostRequest(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD) } -// FIXME: move this behind Cloudflare Access. We can find the JWT in the cookies -export async function handlePostRequest(request: Request, db: D1Database, userKEK: string): Promise { +export async function handlePostRequest( + request: Request, + db: D1Database, + userKEK: string, + accessDomain: string, + accessAud: string +): Promise { const url = new URL(request.url) - // TODO: email is in the JWT, should be parsed, verified and passed in the - // request context. - const email = url.searchParams.get('email') || '' + const cookie = parse(request.headers.get('Cookie') || '') + + const jwt = cookie['CF_Authorization'] + if (!jwt) { + return errors.notAuthorized('missing CF_Authorization') + } + + const payload = access.getPayload(jwt) + if (!payload.email) { + return errors.notAuthorized('missing email') + } + + const validatate = access.generateValidator({ + jwt, + domain: accessDomain, + aud: accessAud, + }) + await validatate(request) + const domain = url.hostname const formData = await request.formData() @@ -27,7 +51,7 @@ export async function handlePostRequest(request: Request, db: D1Database, userKE properties.name = formData.get('name') || '' } - await createPerson(domain, db, userKEK, email, properties) + await createPerson(domain, db, userKEK, payload.email, properties) if (!url.searchParams.has('redirect_uri')) { return new Response('', { status: 400 })