Skip to content

Commit

Permalink
fix: getBlitzContext() can only be used in React Server Components in…
Browse files Browse the repository at this point in the history
… Nextjs 13 or higher (#4299)

* fix: remove custom errors thrown by blitz

* Create curvy-cougars-lick.md

* use require and eval rather than the await which becomes a `yield import` possibly causing the issue

* pnpm lock fix

* Update .changeset/curvy-cougars-lick.md

* add comment

* use correct error type

* Apply suggestions from code review
  • Loading branch information
siddhsuresh authored Feb 16, 2024
1 parent e8f564e commit 6f54841
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 170 deletions.
6 changes: 6 additions & 0 deletions .changeset/curvy-cougars-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blitzjs/auth": patch
"blitz": patch
---

fix: getBlitzContext() can only be used in React Server Components in Nextjs 13 or higher
132 changes: 72 additions & 60 deletions packages/blitz-auth/src/server/auth-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,30 +182,36 @@ export async function getSession(
}

export async function getBlitzContext(): Promise<Ctx> {
const {headers, cookies} = await import("next/headers").catch(() => {
throw new Error(
"getBlitzContext() can only be used in React Server Components in Nextjs 13 or higher",
try {
//using eval to avoid bundling next/headers
const {headers, cookies} = eval("require('next/headers')")
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
cookies: {[key: string]: string}
}
req.headers = Object.fromEntries(headers())
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
if (csrfToken) {
req.headers[HEADER_CSRF] = csrfToken.value
}
req.cookies = Object.fromEntries(
cookies()
.getAll()
.map((c: {name: string; value: string}) => [c.name, c.value]),
)
})
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
cookies: {[key: string]: string}
}
req.headers = Object.fromEntries(headers())
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
if (csrfToken) {
req.headers[HEADER_CSRF] = csrfToken.value
}
req.cookies = Object.fromEntries(
cookies()
.getAll()
.map((c: {name: string; value: string}) => [c.name, c.value]),
)
const res = new ServerResponse(req)
const session = await getSession(req, res, true)
const ctx: Ctx = {
session,
const res = new ServerResponse(req)
const session = await getSession(req, res, true)
const ctx: Ctx = {
session,
}
return ctx
} catch (e) {
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
throw new Error(
"Usage of `useAuthenticatedBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.",
)
}
throw e
}
return ctx
}

interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
Expand All @@ -227,58 +233,64 @@ export async function useAuthenticatedBlitzContext({
})
const ctx: Ctx = await getBlitzContext()
const userId = ctx.session.userId
const {redirect} = await import("next/navigation").catch(() => {
throw new Error(
"useAuthenticatedBlitzContext() can only be used in React Server Components in Nextjs 13 or higher",
)
})
if (userId) {
debug("[useAuthenticatedBlitzContext] User is authenticated")
if (redirectAuthenticatedTo) {
if (typeof redirectAuthenticatedTo === "function") {
redirectAuthenticatedTo = redirectAuthenticatedTo(ctx)
try {
//using eval to avoid bundling next/navigation
const {redirect} = eval("require('next/navigation')")
if (userId) {
debug("[useAuthenticatedBlitzContext] User is authenticated")
if (redirectAuthenticatedTo) {
if (typeof redirectAuthenticatedTo === "function") {
redirectAuthenticatedTo = redirectAuthenticatedTo(ctx)
}
const redirectUrl =
typeof redirectAuthenticatedTo === "string"
? redirectAuthenticatedTo
: formatWithValidation(redirectAuthenticatedTo)
debug("[useAuthenticatedBlitzContext] Redirecting to", redirectUrl)
if (role) {
try {
ctx.session.$authorize(role)
} catch (e) {
log.info("Authentication Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
redirect(redirectUrl)
}
} else {
log.info("Authentication Redirect: " + customChalk.dim("(Authenticated)"), redirectUrl)
redirect(redirectUrl)
}
}
const redirectUrl =
typeof redirectAuthenticatedTo === "string"
? redirectAuthenticatedTo
: formatWithValidation(redirectAuthenticatedTo)
debug("[useAuthenticatedBlitzContext] Redirecting to", redirectUrl)
if (role) {
if (redirectTo && role) {
debug("[useAuthenticatedBlitzContext] redirectTo and role are both defined.")
try {
ctx.session.$authorize(role)
} catch (e) {
log.info("Authentication Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
redirect(redirectUrl)
log.error("Authorization Error: " + (e as Error).message)
if (typeof redirectTo !== "string") {
redirectTo = formatWithValidation(redirectTo)
}
log.info("Authorization Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
redirect(redirectTo)
}
} else {
log.info("Authentication Redirect: " + customChalk.dim("(Authenticated)"), redirectUrl)
redirect(redirectUrl)
}
}
if (redirectTo && role) {
debug("[useAuthenticatedBlitzContext] redirectTo and role are both defined.")
try {
ctx.session.$authorize(role)
} catch (e) {
log.error("Authorization Error: " + (e as Error).message)
} else {
debug("[useAuthenticatedBlitzContext] User is not authenticated")
if (redirectTo) {
if (typeof redirectTo !== "string") {
redirectTo = formatWithValidation(redirectTo)
}
log.info("Authorization Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
log.info("Authentication Redirect: " + customChalk.dim("(Not authenticated)"), redirectTo)
redirect(redirectTo)
}
}
} else {
debug("[useAuthenticatedBlitzContext] User is not authenticated")
if (redirectTo) {
if (typeof redirectTo !== "string") {
redirectTo = formatWithValidation(redirectTo)
}
log.info("Authentication Redirect: " + customChalk.dim("(Not authenticated)"), redirectTo)
redirect(redirectTo)
return ctx as AuthenticatedCtx
} catch (e) {
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
throw new Error(
"Usage of `useAuthenticatedBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.",
)
}
throw e
}
return ctx as AuthenticatedCtx
}

const makeProxyToPublicData = <T extends SessionContextClass>(ctxClass: T): T => {
Expand Down
Loading

0 comments on commit 6f54841

Please sign in to comment.