Skip to content

Commit

Permalink
fix: do a correct implementation of getServerBaseUrl
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Jan 30, 2025
1 parent c4d3826 commit 326781e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 29 deletions.
1 change: 0 additions & 1 deletion src/runtime/composables/authjs/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ async function getSession(getSessionOptions?: GetSessionOptions): Promise<Sessio
}

const headers = await getRequestHeaders(nuxt)
console.log('BEFORE doing fetch', headers)

return _fetch<SessionData>(nuxt, '/session', {
onResponse: ({ response }) => {
Expand Down
7 changes: 4 additions & 3 deletions src/runtime/server/plugins/assertOrigin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/
import type { NitroApp } from 'nitropack/types'
import { ERROR_MESSAGES } from '../services/errors'
import { isProduction } from '../../helpers'
import { getServerOrigin } from '../services/authjs/utils'
import { isProduction, useTypedBackendConfig } from '../../helpers'
import { getServerBaseUrl } from '../services/authjs/utils'
import { useRuntimeConfig } from '#imports'

// type stub
Expand All @@ -18,7 +18,8 @@ function defineNitroPlugin(def: NitroAppPlugin): NitroAppPlugin {
export default defineNitroPlugin(() => {
try {
const runtimeConfig = useRuntimeConfig()
getServerOrigin(runtimeConfig)
const trustHostUserPreference = useTypedBackendConfig(runtimeConfig, 'authjs').trustHost
getServerBaseUrl(runtimeConfig, false, trustHostUserPreference, isProduction)
}
catch (error) {
if (!isProduction) {
Expand Down
13 changes: 7 additions & 6 deletions src/runtime/server/services/authjs/nuxtAuthHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { defu } from 'defu'
import { joinURL } from 'ufo'
import { ERROR_MESSAGES } from '../errors'
import { isNonEmptyObject } from '../../../utils/checkSessionResult'
import { useTypedBackendConfig } from '../../../helpers'
import { isProduction, useTypedBackendConfig } from '../../../helpers'
import { resolveApiBaseURL } from '../../../utils/url'
import { getHostValueForAuthjs, getServerOrigin } from './utils'
import { getHostValueForAuthjs, getServerBaseUrl } from './utils'
import { useRuntimeConfig } from '#imports'

type RuntimeConfig = ReturnType<typeof useRuntimeConfig>
Expand Down Expand Up @@ -45,7 +45,7 @@ export function NuxtAuthHandler(nuxtAuthOptions?: AuthOptions) {
logger: undefined,
providers: [],

// SAFETY: We trust host here because `getRequestURLFromH3Event` is responsible for producing a trusted URL
// SAFETY: We trust host here because `getHostValueForAuthjs` is responsible for producing a trusted URL
trustHost: true,

// AuthJS uses `/auth` as default, but we rely on `/api/auth` (same as in previous `next-auth`)
Expand Down Expand Up @@ -133,7 +133,7 @@ export async function getServerSession(event: H3Event) {
cookies: parseCookies(event),
providerId: undefined,
error: undefined,
host: getHostValueForAuthjs(event, runtimeConfig, trustHostUserPreference),
host: getHostValueForAuthjs(event, runtimeConfig, trustHostUserPreference, isProduction),
query: {}
}

Expand Down Expand Up @@ -162,6 +162,7 @@ export async function getServerSession(event: H3Event) {
*/
export function getToken<R extends boolean = false>({ event, secureCookie, secret, ...rest }: Omit<GetTokenParams<R>, 'req'> & { event: H3Event }) {
const runtimeConfig = useRuntimeConfig()
const trustHostUserPreference = useTypedBackendConfig(runtimeConfig, 'authjs').trustHost

return authjsGetToken({
// @ts-expect-error As our request is not a real next-auth request, we pass down only what's required for the method, as per code from https://github.com/nextauthjs/next-auth/blob/8387c78e3fef13350d8a8c6102caeeb05c70a650/packages/next-auth/src/jwt/index.ts#L68
Expand All @@ -170,7 +171,7 @@ export function getToken<R extends boolean = false>({ event, secureCookie, secre
headers: getHeaders(event) as IncomingHttpHeaders
},
// see https://github.com/nextauthjs/next-auth/blob/8387c78e3fef13350d8a8c6102caeeb05c70a650/packages/next-auth/src/jwt/index.ts#L73
secureCookie: secureCookie ?? getServerOrigin(runtimeConfig, event).startsWith('https://'),
secureCookie: secureCookie ?? getServerBaseUrl(runtimeConfig, false, trustHostUserPreference, isProduction, event).startsWith('https://'),
secret: secret || usedSecret,
...rest
})
Expand All @@ -189,7 +190,7 @@ async function createRequestForAuthjs(
): Promise<RequestInternal> {
const nextRequest: Omit<RequestInternal, 'action'> = {
// `authjs` expects the baseURL here despite the param name
host: getHostValueForAuthjs(event, runtimeConfig, trustHostUserPreference),
host: getHostValueForAuthjs(event, runtimeConfig, trustHostUserPreference, isProduction),
body: undefined,
cookies: parseCookies(event),
query: undefined,
Expand Down
52 changes: 33 additions & 19 deletions src/runtime/server/services/authjs/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { H3Event } from 'h3'
import getURL from 'requrl'
import { parseURL } from 'ufo'
import { isProduction } from '../../../helpers'
import { getRequestURL } from 'h3'
import { parseURL, withLeadingSlash } from 'ufo'
import { type RuntimeConfig, resolveApiBaseURL } from '../../../utils/url'
import { ERROR_MESSAGES } from '../errors'

Expand All @@ -28,31 +27,33 @@ import { ERROR_MESSAGES } from '../errors'
*
* @param event The H3 Event containing the request
* @param runtimeConfig Nuxt RuntimeConfig
* @param trustHost Whether the host can be trusted. If `true`, base will be inferred from the request, otherwise the configured origin will be used. * @returns {string} Value formatted for usage with Authjs
* @param trustHostUserPreference Whether the host can be trusted. If `true`, base will be inferred from the request, otherwise the configured origin will be used. * @returns {string} Value formatted for usage with Authjs
* @throws {Error} When server origin was incorrectly configured or when URL building failed
*/
export function getHostValueForAuthjs(
event: H3Event,
runtimeConfig: RuntimeConfig,
trustHost: boolean,
trustHostUserPreference: boolean,
isProduction: boolean
): string {
if (trustHost) {
return getServerBaseUrl(runtimeConfig, true, event)
}

return resolveApiBaseURL(runtimeConfig, false)
return getServerBaseUrl(runtimeConfig, true, trustHostUserPreference, isProduction, event)
}

/**
* Get `origin` and fallback to `x-forwarded-host` or `host` headers if not in production.
* Get the full base URL including Origin and pathname
*
* @param runtimeConfig Nuxt Runtime Config
* @param includePath Whether function should output just Origin or the full URL
* @param trustHostUserPreference Whether the host can be trusted. If `true`, base will be inferred from the request, otherwise the configured origin will be used. * @returns {string} Value formatted for usage with Authjs
* @param isProduction Whether app is running in production mode. In non-production mode function will try to infer the result from the passed event.
* @param event The H3 Event for inferring the result (optional)
* @throws {Error} When the calculated result did not include a valid Origin, e.g. it will throw for the result of `/api/auth`, but will succeed for `https://example.com/api/auth`
*/
export function getServerOrigin(runtimeConfig: RuntimeConfig, event?: H3Event): string {
return getServerBaseUrl(runtimeConfig, false, event)
}

function getServerBaseUrl(
export function getServerBaseUrl(
runtimeConfig: RuntimeConfig,
includePath: boolean,
trustHostUserPreference: boolean,
isProduction: boolean,
event?: H3Event,
): string {
// Prio 1: Environment variable
Expand All @@ -69,9 +70,22 @@ function getServerBaseUrl(
: base
}

// Prio 3: Try to infer the origin if we're not in production
if (event && !isProduction) {
return getURL(event.node.req, includePath)
// Prio 3: Try to infer the origin if we're not in production or if user trusts host
if (event && (!isProduction || trustHostUserPreference)) {
const requestUrl = getRequestURL(event, {
xForwardedHost: trustHostUserPreference,
xForwardedProto: trustHostUserPreference || undefined
})

if (!includePath) {
return requestUrl.origin
}

// When path is needed, use the preconfigured base path instead of parsing request's pathname
const basePath = withLeadingSlash(parsed.pathname)
requestUrl.pathname = basePath

return requestUrl.href
}

throw new Error(ERROR_MESSAGES.NO_ORIGIN)
Expand Down

0 comments on commit 326781e

Please sign in to comment.