style: cleanup oidc spaghetti
This commit is contained in:
parent
84855d9d51
commit
694b22f205
@ -27,11 +27,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// Only set if OIDC is properly enabled anyways
|
||||
if (context.oidc?.disableKeyLogin) {
|
||||
return startOidc(
|
||||
context.oidc.issuer,
|
||||
context.oidc.client,
|
||||
request,
|
||||
)
|
||||
return startOidc(context.oidc, request)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -46,16 +42,13 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
if (oidcStart) {
|
||||
const context = await loadContext()
|
||||
const issuer = context.oidc?.issuer
|
||||
const id = context.oidc?.client
|
||||
|
||||
if (!issuer || !id) {
|
||||
if (!context.oidc) {
|
||||
throw new Error('An invalid OIDC configuration was provided')
|
||||
}
|
||||
|
||||
// We know it exists here because this action only happens on OIDC
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return startOidc(issuer, id, request)
|
||||
return startOidc(context.oidc, request)
|
||||
}
|
||||
|
||||
const apiKey = String(formData.get('api-key'))
|
||||
|
||||
@ -9,10 +9,5 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
throw new Error('An invalid OIDC configuration was provided')
|
||||
}
|
||||
|
||||
return finishOidc(
|
||||
context.oidc.issuer,
|
||||
context.oidc.client,
|
||||
context.oidc.secret,
|
||||
request,
|
||||
)
|
||||
return finishOidc(context.oidc, request)
|
||||
}
|
||||
|
||||
@ -1,36 +1,42 @@
|
||||
import { redirect } from '@remix-run/node'
|
||||
import {
|
||||
authorizationCodeGrantRequest,
|
||||
calculatePKCECodeChallenge, type Client,
|
||||
calculatePKCECodeChallenge,
|
||||
type Client,
|
||||
discoveryRequest,
|
||||
generateRandomCodeVerifier,
|
||||
generateRandomNonce,
|
||||
generateRandomState,
|
||||
getValidatedIdTokenClaims, isOAuth2Error,
|
||||
getValidatedIdTokenClaims,
|
||||
isOAuth2Error,
|
||||
parseWwwAuthenticateChallenges,
|
||||
processAuthorizationCodeOpenIDResponse,
|
||||
processDiscoveryResponse,
|
||||
validateAuthResponse } from 'oauth4webapi'
|
||||
validateAuthResponse,
|
||||
} from 'oauth4webapi'
|
||||
|
||||
import { post } from '~/utils/headscale'
|
||||
import { commitSession, getSession } from '~/utils/sessions'
|
||||
|
||||
export async function startOidc(issuer: string, client: string, request: Request) {
|
||||
const session = await getSession(request.headers.get('Cookie'))
|
||||
import { HeadplaneContext } from './config/headplane'
|
||||
|
||||
type OidcConfig = NonNullable<HeadplaneContext['oidc']>
|
||||
|
||||
export async function startOidc(oidc: OidcConfig, req: Request) {
|
||||
const session = await getSession(req.headers.get('Cookie'))
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/', {
|
||||
status: 302,
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Set-Cookie': await commitSession(session)
|
||||
}
|
||||
'Set-Cookie': await commitSession(session),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const issuerUrl = new URL(issuer)
|
||||
const issuerUrl = new URL(oidc.issuer)
|
||||
const oidcClient = {
|
||||
client_id: client,
|
||||
token_endpoint_auth_method: 'client_secret_basic'
|
||||
client_id: oidc.client,
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
} satisfies Client
|
||||
|
||||
const response = await discoveryRequest(issuerUrl)
|
||||
@ -44,9 +50,9 @@ export async function startOidc(issuer: string, client: string, request: Request
|
||||
const verifier = generateRandomCodeVerifier()
|
||||
const challenge = await calculatePKCECodeChallenge(verifier)
|
||||
|
||||
const callback = new URL('/admin/oidc/callback', request.url)
|
||||
callback.protocol = request.url.includes('localhost') ? 'http:' : 'https:'
|
||||
callback.hostname = request.headers.get('Host') ?? ''
|
||||
const callback = new URL('/admin/oidc/callback', req.url)
|
||||
callback.protocol = req.url.includes('localhost') ? 'http:' : 'https:'
|
||||
callback.hostname = req.headers.get('Host') ?? ''
|
||||
const authUrl = new URL(processed.authorization_endpoint)
|
||||
|
||||
authUrl.searchParams.set('client_id', oidcClient.client_id)
|
||||
@ -65,29 +71,27 @@ export async function startOidc(issuer: string, client: string, request: Request
|
||||
return redirect(authUrl.href, {
|
||||
status: 302,
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Set-Cookie': await commitSession(session)
|
||||
}
|
||||
'Set-Cookie': await commitSession(session),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function finishOidc(issuer: string, client: string, secret: string, request: Request) {
|
||||
const session = await getSession(request.headers.get('Cookie'))
|
||||
export async function finishOidc(oidc: OidcConfig, req: Request) {
|
||||
const session = await getSession(req.headers.get('Cookie'))
|
||||
if (session.has('hsApiKey')) {
|
||||
return redirect('/', {
|
||||
status: 302,
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Set-Cookie': await commitSession(session)
|
||||
}
|
||||
'Set-Cookie': await commitSession(session),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const issuerUrl = new URL(issuer)
|
||||
const issuerUrl = new URL(oidc.issuer)
|
||||
const oidcClient = {
|
||||
client_id: client,
|
||||
client_secret: secret,
|
||||
token_endpoint_auth_method: 'client_secret_basic'
|
||||
client_id: oidc.client,
|
||||
client_secret: oidc.secret,
|
||||
token_endpoint_auth_method: 'client_secret_basic',
|
||||
} satisfies Client
|
||||
|
||||
const response = await discoveryRequest(issuerUrl)
|
||||
@ -103,22 +107,41 @@ export async function finishOidc(issuer: string, client: string, secret: string,
|
||||
throw new Error('No OIDC state found in the session')
|
||||
}
|
||||
|
||||
const parameters = validateAuthResponse(processed, oidcClient, new URL(request.url), state)
|
||||
const parameters = validateAuthResponse(
|
||||
processed,
|
||||
oidcClient,
|
||||
new URL(req.url),
|
||||
state,
|
||||
)
|
||||
|
||||
if (isOAuth2Error(parameters)) {
|
||||
throw new Error('Invalid response from the OIDC provider')
|
||||
}
|
||||
|
||||
const callback = new URL('/admin/oidc/callback', request.url)
|
||||
callback.protocol = request.url.includes('localhost') ? 'http:' : 'https:'
|
||||
callback.hostname = request.headers.get('Host') ?? ''
|
||||
const callback = new URL('/admin/oidc/callback', req.url)
|
||||
callback.protocol = req.url.includes('localhost') ? 'http:' : 'https:'
|
||||
callback.hostname = req.headers.get('Host') ?? ''
|
||||
|
||||
const tokenResponse = await authorizationCodeGrantRequest(
|
||||
processed,
|
||||
oidcClient,
|
||||
parameters,
|
||||
callback.href,
|
||||
verifier,
|
||||
)
|
||||
|
||||
const tokenResponse = await authorizationCodeGrantRequest(processed, oidcClient, parameters, callback.href, verifier)
|
||||
const challenges = parseWwwAuthenticateChallenges(tokenResponse)
|
||||
if (challenges) {
|
||||
throw new Error('Recieved a challenge from the OIDC provider')
|
||||
}
|
||||
|
||||
const result = await processAuthorizationCodeOpenIDResponse(processed, oidcClient, tokenResponse, nonce)
|
||||
const result = await processAuthorizationCodeOpenIDResponse(
|
||||
processed,
|
||||
oidcClient,
|
||||
tokenResponse,
|
||||
nonce,
|
||||
)
|
||||
|
||||
if (isOAuth2Error(result)) {
|
||||
throw new Error('Invalid response from the OIDC provider')
|
||||
}
|
||||
@ -126,21 +149,24 @@ export async function finishOidc(issuer: string, client: string, secret: string,
|
||||
const claims = getValidatedIdTokenClaims(result)
|
||||
const expDate = new Date(claims.exp * 1000).toISOString()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const keyResponse = await post<{ apiKey: string }>('v1/apikey', process.env.API_KEY!, {
|
||||
expiration: expDate
|
||||
})
|
||||
const keyResponse = await post<{ apiKey: string }>(
|
||||
'v1/apikey',
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
process.env.API_KEY!,
|
||||
{
|
||||
expiration: expDate,
|
||||
},
|
||||
)
|
||||
|
||||
session.set('hsApiKey', keyResponse.apiKey)
|
||||
session.set('user', {
|
||||
name: claims.name ? String(claims.name) : 'Anonymous',
|
||||
email: claims.email ? String(claims.email) : undefined
|
||||
email: claims.email ? String(claims.email) : undefined,
|
||||
})
|
||||
|
||||
return redirect('/machines', {
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Set-Cookie': await commitSession(session)
|
||||
}
|
||||
'Set-Cookie': await commitSession(session),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user