Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Nextjs 13 App Directory Utility Methods #4103

Merged
merged 63 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
317af06
working
siddhsuresh Feb 6, 2023
4529139
fix toolkit app
siddhsuresh Feb 6, 2023
217f623
fix add directive
siddhsuresh Feb 6, 2023
ceb1482
add back secure password
siddhsuresh Feb 6, 2023
97254ca
changeset
siddhsuresh Feb 6, 2023
b5574c0
Merge remote-tracking branch 'upstream/main' into next13-app
siddhsuresh Feb 9, 2023
1de730d
Merge remote-tracking branch 'upstream/main' into next13-app
siddhsuresh Feb 9, 2023
d743053
improve blitz rpc structure
siddhsuresh Feb 9, 2023
b974cc8
make it work
siddhsuresh Feb 9, 2023
956e5c5
fix most integration tests
siddhsuresh Feb 9, 2023
fe52481
fix the tests
siddhsuresh Feb 9, 2023
90321b0
fix import error
siddhsuresh Feb 9, 2023
14968d2
fix unit test
siddhsuresh Feb 9, 2023
eb18279
fix tests
siddhsuresh Feb 9, 2023
76a787a
remove breaking changes
siddhsuresh Feb 10, 2023
a95fb70
remove another breaking change
siddhsuresh Feb 10, 2023
88f9d6f
fix typescript declaration
siddhsuresh Feb 10, 2023
863795a
fix errorboundary redirect issues
siddhsuresh Feb 11, 2023
0be3b43
cleanup
siddhsuresh Feb 11, 2023
eee477e
upgrade to next13
siddhsuresh Feb 11, 2023
1af9819
update pnpm-lock
siddhsuresh Feb 11, 2023
9131a2e
add Server Plugin Export
siddhsuresh Feb 12, 2023
dd3e87a
fix lint error
siddhsuresh Feb 12, 2023
1a95703
fix build errors
siddhsuresh Feb 12, 2023
5a0a952
fixes
siddhsuresh Feb 13, 2023
22480b3
Solve NextRouter is not mounted
siddhsuresh Feb 13, 2023
fd918fd
fix
siddhsuresh Feb 13, 2023
2f72268
fix issues
siddhsuresh Feb 13, 2023
d6d2653
Merge branch 'main' into next13-app
siddhsuresh Feb 13, 2023
1ef8b89
pnpm lock fix
siddhsuresh Feb 13, 2023
d7bb807
Merge branch 'main' into next13-app
siddhsuresh Feb 14, 2023
dc53a25
rename session function and add auth utility
siddhsuresh Feb 15, 2023
2460d40
remove potential implicit warning
siddhsuresh Feb 15, 2023
8cbfb39
fix deps and cleanup
siddhsuresh Feb 15, 2023
c5b236e
fix secure-password missing file
siddhsuresh Feb 16, 2023
5e6cf0a
Merge branch 'main' into next13-app
siddhsuresh Feb 17, 2023
75c298f
fix possibility of crsf token cookie being null
siddhsuresh Feb 17, 2023
e7b81f6
Merge branch 'next13-app' of https://github.com/blitz-js/blitz into n…
siddhsuresh Feb 17, 2023
abccf93
remove secure-password breaking changes
siddhsuresh Feb 20, 2023
0461d29
Update cuddly-singers-perform.md
siddhsuresh Feb 20, 2023
7c72856
rename functions and remove headers
siddhsuresh Mar 5, 2023
d9aa307
Revert "remove secure-password breaking changes"
siddhsuresh Mar 5, 2023
c77b84a
unsupported auth methods in react server components
siddhsuresh Mar 5, 2023
13f6faf
Merge branch 'main' into next13-app
siddhsuresh Mar 18, 2023
46fe8af
remove authorizedContext: To be done in another PR
siddhsuresh Mar 18, 2023
123a33f
add integration test for app dir
siddhsuresh Mar 18, 2023
bfb3aa3
fix duplicate package name
siddhsuresh Mar 18, 2023
c8d9655
Revert "remove authorizedContext: To be done in another PR"
siddhsuresh Mar 18, 2023
851953f
pnpm lock
siddhsuresh Mar 18, 2023
186925a
integration test
siddhsuresh Mar 18, 2023
043ead2
Merge branch 'next13-app' into app-dir-utilities
siddhsuresh Mar 18, 2023
b833376
remove empty then and add void operator
siddhsuresh Mar 18, 2023
f1ce7f0
Merge branch 'next13-app' into app-dir-utilities
siddhsuresh Mar 18, 2023
1f78f6f
add new invokeResolver function
siddhsuresh Mar 18, 2023
0a3e833
fix bug in useAuthenticatedBlitzContext
siddhsuresh Mar 18, 2023
17b6a66
Create .changeset/tall-radios-clean.md
siddhsuresh Mar 20, 2023
7d30ace
Merge branch 'main' into next13-app
siddhsuresh Mar 21, 2023
4f515a0
overload the invoke function for the new RSC usecase
siddhsuresh Mar 23, 2023
4001d39
Merge branch 'main' into next13-app
siddhsuresh Mar 24, 2023
7fb9f78
invoke works without implicit passing of arguments
siddhsuresh Mar 24, 2023
9710119
fix pnpm lock
siddhsuresh Mar 24, 2023
5684602
Merge branch 'next13-app' into app-dir-utilities
siddhsuresh Mar 24, 2023
7ca46ac
Merge branch 'main' into app-dir-utilities
siddhsuresh Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .changeset/tall-radios-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
"next-blitz-auth": patch
"@blitzjs/auth": patch
"@blitzjs/rpc": patch
"blitz": patch
---

feature: Nextjs 13 App Directory Utility Methods

### 🔧 New Blitz Auth Hook `useAuthenticatedBlitzContext`

This hook is implemented as the replacement of the [`BlitzPage` seurity auth utilities](https://blitzjs.com/docs/authorization#secure-your-pages) provided for the pages directory to work with React Server Components in the Nextjs 13 app directory
It can be used in any asynchronous server component be it in `page.ts` or in the layouts in `layout.ts`
It uses the new [`redirect` function](https://beta.nextjs.org/docs/api-reference/redirect) to provide the required authorization in server side

#### API
```ts
useAuthenticatedBlitzContext({
redirectTo,
redirectAuthenticatedTo,
role,
}: {
redirectTo?: string | RouteUrlObject
redirectAuthenticatedTo?: string | RouteUrlObject | ((ctx: Ctx) => string | RouteUrlObject)
role?: string | string[]
}): Promise<void>
```

#### Usage
**Example Usage in React Server Component in `app` directory in Next 13**
```ts
import {getAppSession, useAuthenticatedBlitzContext} from "src/blitz-server"
...
await useAuthenticatedBlitzContext({
redirectTo: "/auth/login",
role: ["admin"],
redirectAuthenticatedTo: "/dashboard",
})
```

### 🔧 New Blitz RPC Hook `invokeResolver`

#### API
```ts
invokeResolver<T extends (...args: any) => any, TInput = FirstParam<T>>(
queryFn: T,
params: TInput,
): Promise<PromiseReturnType<T>>
```

#### Example Usage

```ts
...
import {invokeResolver, useAuthenticatedBlitzContext} from "../src/blitz-server"
import getCurrentUser from "../src/users/queries/getCurrentUser"

export default async function Home() {
await useAuthenticatedBlitzContext({
redirectTo: "/auth/login",
})
const user = await invokeResolver(getCurrentUser, null)
...
```
8 changes: 8 additions & 0 deletions apps/next13/app/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {useAuthenticatedBlitzContext} from "@blitzjs/auth"

export default async function RootLayout({children}: {children: React.ReactNode}) {
await useAuthenticatedBlitzContext({
redirectAuthenticatedTo: "/",
})
return <>{children}</>
}
11 changes: 1 addition & 10 deletions apps/next13/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@ import {useSearchParams} from "next/navigation"
const LoginPage = () => {
const router = useRouter()
const searchParams = useSearchParams()
return (
<LoginForm
onSuccess={(_user) => {
const next = searchParams.get("next")
? decodeURIComponent(searchParams.get("next") as string)
: "/"
return router.push(next)
}}
/>
)
return <LoginForm onSuccess={(_user) => {}} />
}

export default LoginPage
10 changes: 5 additions & 5 deletions apps/next13/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import Link from "next/link"
import styles from "src/styles/Home.module.css"
import Test from "./react-query"
import {getBlitzContext} from "../src/blitz-server"
import {invoke, useAuthenticatedBlitzContext} from "../src/blitz-server"
import getCurrentUser from "../src/users/queries/getCurrentUser"

export default async function Home() {
const ctx = await getBlitzContext()
ctx.session.$create({userId: 1})
console.log("session", ctx.session.userId)
const user = await getCurrentUser(null, ctx)
await useAuthenticatedBlitzContext({
redirectTo: "/auth/login",
})
const user = await invoke(getCurrentUser, null)
console.log("user", user)
return (
<div
Expand Down
6 changes: 4 additions & 2 deletions apps/next13/src/blitz-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
import db from "../prisma"
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
import {BlitzLogger} from "blitz"
import {RpcServerPlugin} from "@blitzjs/rpc"

const {api, getBlitzContext} = setupBlitzServer({
const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} = setupBlitzServer({
plugins: [
AuthServerPlugin({
cookiePrefix: "web-cookie-prefix",
storage: PrismaStorage(db),
isAuthorized: simpleRolesIsAuthorized,
}),
RpcServerPlugin({}),
],
logger: BlitzLogger({}),
})

export {api, getBlitzContext}
export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke}

export const cliConfig: BlitzCliConfig = {
customTemplates: "src/templates",
Expand Down
3 changes: 2 additions & 1 deletion packages/blitz-auth/src/server/auth-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {BlitzServerPlugin, RequestMiddleware, Ctx, createServerPlugin} from "bli
import {assert} from "blitz"
import {IncomingMessage, ServerResponse} from "http"
import {PublicData, SessionModel, SessionConfigMethods} from "../shared/types"
import {getBlitzContext, getSession} from "./auth-sessions"
import {getBlitzContext, getSession, useAuthenticatedBlitzContext} from "./auth-sessions"

interface SessionConfigOptions {
cookiePrefix?: string
Expand Down Expand Up @@ -129,6 +129,7 @@ export const AuthServerPlugin = createServerPlugin((options: AuthPluginOptions)
requestMiddlewares: [authPluginSessionMiddleware()],
exports: () => ({
getBlitzContext,
useAuthenticatedBlitzContext,
}),
}
})
61 changes: 61 additions & 0 deletions packages/blitz-auth/src/server/auth-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
AuthorizationError,
CSRFTokenMismatchError,
log,
baseLogger,
chalk,
} from "blitz"
import {
EmptyPublicData,
Expand Down Expand Up @@ -209,6 +211,65 @@ interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href">
pathname: string
}

export async function useAuthenticatedBlitzContext({
redirectTo,
redirectAuthenticatedTo,
role,
}: {
redirectTo?: string | RouteUrlObject
redirectAuthenticatedTo?: string | RouteUrlObject | ((ctx: Ctx) => string | RouteUrlObject)
role?: string | string[]
}): Promise<void> {
const log = baseLogger().getChildLogger()
const customChalk = new chalk.Instance({
level: log.settings.type === "json" ? 0 : chalk.level,
})
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)
}
const redirectUrl =
typeof redirectAuthenticatedTo === "string"
? redirectAuthenticatedTo
: formatWithValidation(redirectAuthenticatedTo)
debug("[useAuthenticatedBlitzContext] Redirecting to", redirectUrl)
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)
if (typeof redirectTo !== "string") {
redirectTo = formatWithValidation(redirectTo)
}
log.info("Authorization Redirect: " + customChalk.dim(`Role ${role}`), 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)
}
}
}

const makeProxyToPublicData = <T extends SessionContextClass>(ctxClass: T): T => {
return new Proxy(ctxClass, {
get(target, prop, receiver) {
Expand Down
24 changes: 22 additions & 2 deletions packages/blitz-rpc/src/client/invoke.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import {FirstParam, PromiseReturnType, isClient, Ctx} from "blitz"
import {RpcClient} from "./rpc"

export function invoke<T extends (...args: any) => any, TInput = FirstParam<T>>(
export async function invoke<T extends (...args: any) => any, TInput = FirstParam<T>>(
queryFn: T,
params: TInput,
): Promise<PromiseReturnType<T>> {
): Promise<T>
export async function invoke<T extends (...args: any) => any, TInput = FirstParam<T>>(
queryFn: T,
params: TInput,
isServer: boolean,
): Promise<T>
export async function invoke<T extends (...args: any) => any, TInput = FirstParam<T>>(
queryFn: T,
params: TInput,
isServer = typeof window === "undefined" ? true : false,
): Promise<T> {
if (typeof queryFn === "undefined") {
throw new Error(
"invoke is missing the first argument - it must be a query or mutation function",
)
}

if (isServer) {
const {getBlitzContext} = await import("@blitzjs/auth").catch((e) => {
throw new Error(
`invoke with isServer parameter can only be used in a Blitz powered Nextjs app directory. Make sure you have installed the @blitzjs/auth package.`,
)
})
const ctx = await getBlitzContext()
return queryFn(params, ctx)
}

if (isClient) {
const fn = queryFn as unknown as RpcClient
return fn(params, {fromInvoke: true}) as ReturnType<T>
Expand Down
1 change: 1 addition & 0 deletions packages/blitz-rpc/src/index-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import chalk from "chalk"

// TODO - optimize end user server bundles by not exporting all client stuff here
export * from "./index-browser"
export {RpcServerPlugin} from "./server/plugin"

export * from "./server/resolvers/resolver"

Expand Down
11 changes: 11 additions & 0 deletions packages/blitz-rpc/src/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {createServerPlugin} from "blitz"
import {invoke} from "../client/invoke"

export const RpcServerPlugin = createServerPlugin(() => {
return {
requestMiddlewares: [],
exports: () => ({
invoke,
}),
}
})