From bda54281dc6e24fdcb1478987e84f8bfbf9ff4a5 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Sun, 3 Dec 2023 16:53:49 +0200 Subject: [PATCH] feat(nextjs): Introduce `auth().protect()` for App Router Allow per page protection in app router. This utility will automatically throw a 404 error if user is not authorized or authenticated. When `auth().protect()` is called - inside a page or layout file it will render the nearest `not-found` component set by the developer - inside a route handler it will return empty response body with a 404 status code --- packages/nextjs/src/app-router/server/auth.ts | 74 ++++++++++++++++++- packages/nextjs/src/server/getAuth.ts | 6 +- packages/nextjs/src/server/types.ts | 4 +- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index 8d8563a90a..3e8982c534 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -1,12 +1,82 @@ +import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend'; +import type { + CheckAuthorizationWithCustomPermissions, + OrganizationCustomPermission, + OrganizationCustomRole, +} from '@clerk/types'; +import { notFound } from 'next/navigation'; + import { authAuthHeaderMissing } from '../../server/errors'; import { buildClerkProps, createGetAuth } from '../../server/getAuth'; +import type { AuthObjectWithoutResources } from '../../server/types'; import { buildRequestLike } from './utils'; export const auth = () => { - return createGetAuth({ + const authObject = createGetAuth({ debugLoggerName: 'auth()', noAuthStatusMessage: authAuthHeaderMissing(), - })(buildRequestLike()); + })(buildRequestLike()) as + | AuthObjectWithoutResources< + SignedInAuthObject & { + protect: ( + params?: + | { + role: OrganizationCustomRole; + permission?: never; + } + | { + role?: never; + permission: OrganizationCustomPermission; + } + | ((has: CheckAuthorizationWithCustomPermissions) => boolean), + ) => AuthObjectWithoutResources; + } + > + /** + * Add a comment + */ + | AuthObjectWithoutResources< + SignedOutAuthObject & { + protect: never; + } + >; + + authObject.protect = params => { + /** + * User is not authenticated + */ + if (!authObject.userId) { + notFound(); + } + + /** + * User is authenticated + */ + if (!params) { + return { ...authObject }; + } + + /** + * if a function is passed and returns false then throw not found + */ + if (typeof params === 'function') { + if (params(authObject.has)) { + return { ...authObject }; + } + return notFound(); + } + + /** + * Checking if user is authorized when permission or role is passed + */ + if (authObject.has(params)) { + return { ...authObject }; + } + + notFound(); + }; + + return authObject; }; export const initialState = () => { diff --git a/packages/nextjs/src/server/getAuth.ts b/packages/nextjs/src/server/getAuth.ts index 8d088917c0..f6deeeee0d 100644 --- a/packages/nextjs/src/server/getAuth.ts +++ b/packages/nextjs/src/server/getAuth.ts @@ -1,4 +1,4 @@ -import type { AuthObject, Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend'; +import type { Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend'; import { AuthStatus, constants, @@ -12,11 +12,9 @@ import { import { withLogger } from '../utils/debugLogger'; import { API_URL, API_VERSION, SECRET_KEY } from './constants'; import { getAuthAuthHeaderMissing } from './errors'; -import type { RequestLike } from './types'; +import type { AuthObjectWithoutResources, RequestLike } from './types'; import { getAuthKeyFromRequest, getCookie, getHeader, injectSSRStateIntoObject } from './utils'; -type AuthObjectWithoutResources = Omit; - export const createGetAuth = ({ debugLoggerName, noAuthStatusMessage, diff --git a/packages/nextjs/src/server/types.ts b/packages/nextjs/src/server/types.ts index 18e9b9c04c..b960002590 100644 --- a/packages/nextjs/src/server/types.ts +++ b/packages/nextjs/src/server/types.ts @@ -1,4 +1,4 @@ -import type { OptionalVerifyTokenOptions } from '@clerk/backend'; +import type { AuthObject, OptionalVerifyTokenOptions } from '@clerk/backend'; import type { MultiDomainAndOrProxy } from '@clerk/types'; import type { IncomingMessage } from 'http'; import type { NextApiRequest } from 'next'; @@ -20,3 +20,5 @@ export type WithAuthOptions = OptionalVerifyTokenOptions & }; export type NextMiddlewareResult = Awaited>; + +export type AuthObjectWithoutResources = Omit;