166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
import { headers } from 'next/headers'
|
|
|
|
const AUTH_COOKIE_BASE_URL =
|
|
process.env.APP_PUBLIC_URL ||
|
|
process.env.NEXTAUTH_URL ||
|
|
''
|
|
|
|
export const SECURE_AUTH_SESSION_COOKIE_NAME = '__Secure-next-auth.session-token'
|
|
export const PLAIN_AUTH_SESSION_COOKIE_NAME = 'next-auth.session-token'
|
|
|
|
type HeaderAccessor =
|
|
| Headers
|
|
| {
|
|
get?: (name: string) => string | null | undefined
|
|
[key: string]: string | string[] | undefined | ((name: string) => string | null | undefined)
|
|
}
|
|
|
|
type RequestLike = {
|
|
headers?: HeaderAccessor
|
|
nextUrl?: {
|
|
protocol?: string
|
|
}
|
|
url?: string
|
|
}
|
|
|
|
function normalizeProtocol(value?: string | null) {
|
|
if (!value) {
|
|
return null
|
|
}
|
|
|
|
const normalized = value.split(',')[0].trim().replace(/:$/, '').toLowerCase()
|
|
|
|
if (normalized === 'https' || normalized === 'http') {
|
|
return normalized
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function getHeaderValue(headersLike: HeaderAccessor | undefined, name: string) {
|
|
if (!headersLike) {
|
|
return null
|
|
}
|
|
|
|
if (typeof headersLike.get === 'function') {
|
|
return headersLike.get(name) ?? null
|
|
}
|
|
|
|
const recordHeaders = headersLike as Record<string, string | string[] | undefined>
|
|
const directValue = recordHeaders[name]
|
|
if (Array.isArray(directValue)) {
|
|
return directValue[0] ?? null
|
|
}
|
|
|
|
if (typeof directValue === 'string') {
|
|
return directValue
|
|
}
|
|
|
|
const lowerCaseValue = recordHeaders[name.toLowerCase()]
|
|
if (Array.isArray(lowerCaseValue)) {
|
|
return lowerCaseValue[0] ?? null
|
|
}
|
|
|
|
return typeof lowerCaseValue === 'string' ? lowerCaseValue : null
|
|
}
|
|
|
|
function resolveProtocolFromHeaders(headersLike?: HeaderAccessor) {
|
|
const forwardedProto = normalizeProtocol(getHeaderValue(headersLike, 'x-forwarded-proto'))
|
|
if (forwardedProto) {
|
|
return forwardedProto
|
|
}
|
|
|
|
const forwardedSsl = getHeaderValue(headersLike, 'x-forwarded-ssl')
|
|
if (typeof forwardedSsl === 'string' && forwardedSsl.toLowerCase() === 'on') {
|
|
return 'https'
|
|
}
|
|
|
|
const forwardedPort = getHeaderValue(headersLike, 'x-forwarded-port')
|
|
if (forwardedPort === '443') {
|
|
return 'https'
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function resolveProtocolFromUrl(url?: string) {
|
|
if (!url) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
return normalizeProtocol(new URL(url).protocol)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function resolveSecureCookieFromPublicBase(headersLike?: HeaderAccessor, url?: string) {
|
|
if (!AUTH_COOKIE_BASE_URL.startsWith('https://')) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const publicUrl = new URL(AUTH_COOKIE_BASE_URL)
|
|
const forwardedHost = getHeaderValue(headersLike, 'x-forwarded-host')
|
|
const host = forwardedHost || getHeaderValue(headersLike, 'host') || (url ? new URL(url).host : '')
|
|
if (!host) {
|
|
return null
|
|
}
|
|
|
|
const normalizedHost = host.split(',')[0].trim().toLowerCase()
|
|
return normalizedHost === publicUrl.host.toLowerCase() ? true : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function resolveSecureAuthCookieFromRequest(request?: RequestLike) {
|
|
const secureFromPublicBase = resolveSecureCookieFromPublicBase(request?.headers, request?.url)
|
|
if (secureFromPublicBase === true) {
|
|
return true
|
|
}
|
|
|
|
const protocolFromHeaders = resolveProtocolFromHeaders(request?.headers)
|
|
if (protocolFromHeaders) {
|
|
return protocolFromHeaders === 'https'
|
|
}
|
|
|
|
const protocolFromNextUrl = normalizeProtocol(request?.nextUrl?.protocol)
|
|
if (protocolFromNextUrl) {
|
|
return protocolFromNextUrl === 'https'
|
|
}
|
|
|
|
const protocolFromUrl = resolveProtocolFromUrl(request?.url)
|
|
if (protocolFromUrl) {
|
|
return protocolFromUrl === 'https'
|
|
}
|
|
|
|
return AUTH_COOKIE_BASE_URL.startsWith('https://')
|
|
}
|
|
|
|
export function resolveSecureAuthCookieForCurrentRequest() {
|
|
try {
|
|
return resolveSecureAuthCookieFromRequest({
|
|
headers: headers(),
|
|
})
|
|
} catch {
|
|
return AUTH_COOKIE_BASE_URL.startsWith('https://')
|
|
}
|
|
}
|
|
|
|
export function getAuthSessionCookieName(useSecureCookie: boolean) {
|
|
return useSecureCookie
|
|
? SECURE_AUTH_SESSION_COOKIE_NAME
|
|
: PLAIN_AUTH_SESSION_COOKIE_NAME
|
|
}
|
|
|
|
export function getAuthSessionCookieCandidates(preferredSecureCookie: boolean) {
|
|
const preferredName = getAuthSessionCookieName(preferredSecureCookie)
|
|
const fallbackName = getAuthSessionCookieName(!preferredSecureCookie)
|
|
|
|
return preferredName === fallbackName
|
|
? [preferredName]
|
|
: [preferredName, fallbackName]
|
|
}
|