Merge pull request #198 from cloudflare/sven/MOW-132

MOW-132: redirect to /first-login for unkown user
pull/200/head
Sven Sauleau 2023-02-06 14:49:12 +00:00 zatwierdzone przez GitHub
commit 4013786ecf
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 62 dodań i 39 usunięć

Wyświetl plik

@ -36,9 +36,17 @@ describe('Mastodon APIs', () => {
let req = new Request('https://example.com/oauth/authorize')
let res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud)
assert.equal(res.status, 401)
const headers = {
'Cf-Access-Jwt-Assertion': TEST_JWT,
}
req = new Request('https://example.com/oauth/authorize', { headers })
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')
req = new Request('https://example.com/oauth/authorize?scope=foobar', { headers })
res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud)
assert.equal(res.status, 400)
})
@ -46,13 +54,17 @@ describe('Mastodon APIs', () => {
test('authorize unsupported response_type', async () => {
const db = await makeDB()
const headers = {
'Cf-Access-Jwt-Assertion': TEST_JWT,
}
const params = new URLSearchParams({
redirect_uri: 'https://example.com',
response_type: 'hein',
client_id: 'client_id',
})
const req = new Request('https://example.com/oauth/authorize?' + params)
const req = new Request('https://example.com/oauth/authorize?' + params, { headers })
const res = await oauth_authorize.handleRequestPost(req, db, userKEK, accessDomain, accessAud)
assert.equal(res.status, 400)
})

Wyświetl plik

@ -5,8 +5,9 @@ import { getClientById } from 'wildebeest/backend/src/mastodon/client'
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
import { WildebeestLogo } from '~/components/MastodonLogo'
import { Avatar } from '~/components/avatar'
import { getPersonByEmail, Person } from 'wildebeest/backend/src/activitypub/actors'
import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors'
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
import { buildRedirect } from 'wildebeest/functions/oauth/authorize'
export const clientLoader = loader$<{ DATABASE: D1Database }, Promise<Client>>(async ({ platform, query, html }) => {
const client_id = query.get('client_id') || ''
@ -25,7 +26,7 @@ export const clientLoader = loader$<{ DATABASE: D1Database }, Promise<Client>>(a
export const userLoader = loader$<
{ DATABASE: D1Database; domain: string },
Promise<{ email: string; avatar: URL; name: string; url: URL }>
>(async ({ cookie, platform, html }) => {
>(async ({ cookie, platform, html, request, redirect, text }) => {
const jwt = cookie.get('CF_Authorization')
if (jwt === null) {
throw html(500, getErrorHtml('Missing Authorization'))
@ -44,15 +45,15 @@ export const userLoader = loader$<
throw html(500, getErrorHtml("The Access JWT doesn't contain an email"))
}
let person: Person | null = null
try {
person = await getPersonByEmail(platform.DATABASE, payload.email)
if (!person) {
throw new Error('Person not found')
const person = await getPersonByEmail(platform.DATABASE, payload.email)
if (person === null) {
const isFirstLogin = true
const res = await buildRedirect(platform.DATABASE, request as Request, isFirstLogin, jwt.value)
if (res.status === 302) {
throw redirect(302, res.headers.get('location') || '')
} else {
throw text(res.status, await res.body.text())
}
} catch {
throw html(500, getErrorHtml(`Failed to fetch a person for the provided email (${payload.email})`))
}
const name = person.name
@ -60,7 +61,7 @@ export const userLoader = loader$<
const url = person.url
if (!name || !avatar) {
throw html(500, getErrorHtml("Error: The person associated with the Access JWT doesn't include a name or avatar"))
throw html(500, getErrorHtml("The person associated with the Access JWT doesn't include a name or avatar"))
}
return { email: payload.email, avatar, name, url }

Wyświetl plik

@ -15,21 +15,12 @@ export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ requ
return handleRequestPost(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
}
export async function handleRequestPost(
request: Request,
export async function buildRedirect(
db: D1Database,
userKEK: string,
accessDomain: string,
accessAud: string
request: Request,
isFirstLogin: boolean,
jwt: string
): Promise<Response> {
if (request.method === 'OPTIONS') {
const headers = {
...cors(),
'content-type': 'application/json',
}
return new Response('', { headers })
}
const url = new URL(request.url)
if (
@ -47,6 +38,8 @@ export async function handleRequestPost(
return new Response('', { status: 400 })
}
const state = url.searchParams.get('state')
const clientId = url.searchParams.get('client_id') || ''
const client = await getClientById(db, clientId)
if (client === null) {
@ -58,9 +51,36 @@ export async function handleRequestPost(
return new Response('', { status: 403 })
}
const state = url.searchParams.get('state')
const code = `${client.id}.${jwt}`
const redirect = redirect_uri + `?code=${code}` + (state ? `&state=${state}` : '')
if (isFirstLogin) {
url.pathname = '/first-login'
url.searchParams.set('redirect_uri', encodeURIComponent(redirect))
return URLsafeRedirect(url.toString())
}
return URLsafeRedirect(redirect)
}
export async function handleRequestPost(
request: Request,
db: D1Database,
userKEK: string,
accessDomain: string,
accessAud: string
): Promise<Response> {
if (request.method === 'OPTIONS') {
const headers = {
...cors(),
'content-type': 'application/json',
}
return new Response('', { headers })
}
const jwt = extractJWTFromRequest(request)
if (!jwt) {
return new Response('', { status: 401 })
}
const validate = access.generateValidator({ jwt, domain: accessDomain, aud: accessAud })
await validate(request)
@ -69,19 +89,9 @@ export async function handleRequestPost(
return new Response('', { status: 401 })
}
const code = `${client.id}.${jwt}`
const isFirstLogin = (await getPersonByEmail(db, identity.email)) === null
const redirect = redirect_uri + `?code=${code}` + (state ? `&state=${state}` : '')
const person = await getPersonByEmail(db, identity.email)
if (person === null) {
url.pathname = '/first-login'
url.searchParams.set('email', identity.email)
url.searchParams.set('redirect_uri', encodeURIComponent(redirect))
return URLsafeRedirect(url.toString())
}
return URLsafeRedirect(redirect)
return buildRedirect(db, request, isFirstLogin, jwt)
}
// Workaround bug EW-7148, constructing an URL with unknown protocols