Skip to content

Commit

Permalink
simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsorban44 committed Sep 26, 2024
1 parent 646fe8f commit 02a6e53
Showing 1 changed file with 40 additions and 69 deletions.
109 changes: 40 additions & 69 deletions packages/core/src/lib/actions/callback/oauth/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface CookiePayload {
async function sealCookie(
type: keyof CookiesOptions,
payload: string,
maxAge: number,
options: InternalOptions<"oauth" | "oidc" | WebAuthnProviderType>
options: InternalOptions<"oauth" | "oidc" | WebAuthnProviderType>,
maxAge: number = 60 * 15
): Promise<Cookie> {
const { cookies, logger } = options
const expires = new Date()
Expand Down Expand Up @@ -61,7 +61,7 @@ async function parseCookie(
salt: options.cookies[name].name,
})
if (parsed?.value) return parsed.value
throw new InvalidCheck(`${name} value could not be parsed`)
throw new Error("Invalid cookie")
} catch (error) {
throw new InvalidCheck(`${name} value could not be parsed`, {
cause: error,
Expand All @@ -81,43 +81,42 @@ function clearCookie(
})
}

const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
function useCookie(
check: "state" | "pkce" | "nonce",
name: keyof CookiesOptions
) {
return async function (
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oidc">
) {
const { provider } = options
if (!provider?.checks?.includes(check)) return
const cookieValue = cookies?.[options.cookies[name].name]
const parsed = await parseCookie(name, cookieValue, options)
clearCookie(name, options, resCookies)
return parsed
}
}

/**
* @see https://www.rfc-editor.org/rfc/rfc7636
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
*/
export const pkce = {
/** Creates a PKCE code challenge and verifier pair. The verifier in stored in the cookie. */
async create(options: InternalOptions<"oauth">) {
const code_verifier = o.generateRandomCodeVerifier()
const cookie = await sealCookie(
"pkceCodeVerifier",
code_verifier,
PKCE_MAX_AGE,
options
)
const value = await o.calculatePKCECodeChallenge(code_verifier)
const cookie = await sealCookie("pkceCodeVerifier", code_verifier, options)
return { cookie, value }
},
/**
* Returns code_verifier if the provider is configured to use PKCE,
* and clears the container cookie afterwards.
* An error is thrown if the code_verifier is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc7636
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<string | undefined> {
const { provider } = options

if (!provider?.checks?.includes("pkce")) return

const cookieValue = cookies?.[options.cookies.pkceCodeVerifier.name]
const parsed = await parseCookie("pkceCodeVerifier", cookieValue, options)

clearCookie("pkceCodeVerifier", options, resCookies)

return parsed
},
use: useCookie("pkce", "pkceCodeVerifier"),
}

interface EncodedState {
Expand All @@ -127,7 +126,13 @@ interface EncodedState {

const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
const encodedStateSalt = "encodedState"

/**
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
*/
export const state = {
/** Creates a state cookie with an optionally encoded body. */
async create(options: InternalOptions<"oauth">, origin?: string) {
const { provider } = options
if (!provider.checks.includes("state")) {
Expand All @@ -144,62 +149,44 @@ export const state = {
origin,
random: o.generateRandomState(),
} satisfies EncodedState

const value = await encryptedJWT.encode({
secret: options.jwt.secret,
token: payload,
salt: encodedStateSalt,
maxAge: STATE_MAX_AGE,
})
const cookie = await sealCookie("state", value, options)

const cookie = await sealCookie("state", value, STATE_MAX_AGE, options)
return { cookie, value }
},
/**
* Returns state if the provider is configured to use state,
* and clears the container cookie afterwards.
* An error is thrown if the state is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<string | undefined> {
const { provider } = options
if (!provider.checks.includes("state")) return

const cookieValue = cookies?.[options.cookies.state.name]
const parsed = await parseCookie("state", cookieValue, options)

clearCookie("state", options, resCookies)

return parsed
},
use: useCookie("state", "state"),
/** Decodes the state. If it could not be decoded, it returns `null`. */
async decode(state: string, options: InternalOptions) {
try {
options.logger.debug("DECODE_STATE", { state })
return await encryptedJWT.decode<EncodedState>({
const payload = await encryptedJWT.decode<EncodedState>({
secret: options.jwt.secret,
token: state,
salt: encodedStateSalt,
})
if (payload) return payload
throw new Error("Invalid state")
} catch (error) {
throw new InvalidCheck("State could not be decoded", { cause: error })
}
},
}

const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds

export const nonce = {
async create(options: InternalOptions<"oidc">) {
if (!options.provider.checks.includes("nonce")) return
const maxAge = NONCE_MAX_AGE
const value = o.generateRandomNonce()
const cookie = await sealCookie("nonce", value, maxAge, options)
const cookie = await sealCookie("nonce", value, options)
return { cookie, value }
},
/**
Expand All @@ -209,22 +196,7 @@ export const nonce = {
* @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oidc">
): Promise<string | undefined> {
const { provider } = options

if (!provider?.checks?.includes("nonce")) return

const cookieValue = cookies?.[options.cookies.nonce.name]
const parsed = await parseCookie("nonce", cookieValue, options)

clearCookie("nonce", options, resCookies)

return parsed
},
use: useCookie("nonce", "nonce"),
}

const WEBAUTHN_CHALLENGE_MAX_AGE = 60 * 15 // 15 minutes in seconds
Expand All @@ -250,7 +222,6 @@ export const webauthnChallenge = {
salt: webauthnChallengeSalt,
maxAge: WEBAUTHN_CHALLENGE_MAX_AGE,
}),
WEBAUTHN_CHALLENGE_MAX_AGE,
options
),
}
Expand Down

0 comments on commit 02a6e53

Please sign in to comment.