From c4c8fb86ddb12a2cda76f26af7dc9c9cca92ba41 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 12 Dec 2023 22:19:37 +0200 Subject: [PATCH 1/5] feat(nextjs): Accept `redirectUrl` as option for `auth().protect()` --- .changeset/thirty-chicken-divide.md | 5 +++++ packages/nextjs/src/app-router/server/auth.ts | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .changeset/thirty-chicken-divide.md diff --git a/.changeset/thirty-chicken-divide.md b/.changeset/thirty-chicken-divide.md new file mode 100644 index 00000000000..fe3c7b55125 --- /dev/null +++ b/.changeset/thirty-chicken-divide.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Accept `redirectUrl` as option for `auth().protect()` diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index 2195cf4b87b..71ba582a7ff 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -3,7 +3,7 @@ import type { CheckAuthorizationParamsWithCustomPermissions, CheckAuthorizationWithCustomPermissions, } from '@clerk/types'; -import { notFound } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; import { authAuthHeaderMissing } from '../../server/errors'; import { buildClerkProps, createGetAuth } from '../../server/getAuth'; @@ -21,6 +21,7 @@ type AuthSignedIn = AuthObjectWithoutResources< params?: | CheckAuthorizationParamsWithCustomPermissions | ((has: CheckAuthorizationWithCustomPermissions) => boolean), + options?: { redirectUrl: string }, ) => AuthObjectWithoutResources; } >; @@ -42,12 +43,18 @@ export const auth = () => { noAuthStatusMessage: authAuthHeaderMissing(), })(buildRequestLike()); - (authObject as AuthSignedIn).protect = params => { + (authObject as AuthSignedIn).protect = (params, options) => { + const handleUnauthorized = (): never => { + if (options?.redirectUrl) { + redirect(options.redirectUrl); + } + notFound(); + }; /** * User is not authenticated */ if (!authObject.userId) { - notFound(); + return handleUnauthorized(); } /** @@ -64,7 +71,7 @@ export const auth = () => { if (params(authObject.has)) { return { ...authObject }; } - notFound(); + return handleUnauthorized(); } /** @@ -74,7 +81,7 @@ export const auth = () => { return { ...authObject }; } - notFound(); + return handleUnauthorized(); }; return authObject as AuthSignedIn | AuthSignedOut; From ee5708a9c2260993fed4ddaf2f7faa72b2e07ec8 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 13 Dec 2023 11:18:57 +0200 Subject: [PATCH 2/5] feat(nextjs): Allow for redirectUrl to be the first parameter --- packages/nextjs/src/app-router/server/auth.ts | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index 71ba582a7ff..d3f3f21579f 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -17,15 +17,23 @@ type AuthSignedIn = AuthObjectWithoutResources< * This function is experimental as it throws a Nextjs notFound error if user is not authenticated or authorized. * In the future we would investigate a way to throw a more appropriate error that clearly describes the not authorized of authenticated status. */ - protect: ( - params?: - | CheckAuthorizationParamsWithCustomPermissions - | ((has: CheckAuthorizationWithCustomPermissions) => boolean), - options?: { redirectUrl: string }, - ) => AuthObjectWithoutResources; + protect: { + ( + params?: + | CheckAuthorizationParamsWithCustomPermissions + | ((has: CheckAuthorizationWithCustomPermissions) => boolean), + options?: { redirectUrl: string }, + ): AuthObjectWithoutResources; + + (params?: { redirectUrl: string }): AuthObjectWithoutResources; + }; } >; +type ProtectGeneric = { + protect: (params?: unknown, options?: unknown) => AuthObjectWithoutResources; +}; + type AuthSignedOut = AuthObjectWithoutResources< SignedOutAuthObject & { /** @@ -43,13 +51,21 @@ export const auth = () => { noAuthStatusMessage: authAuthHeaderMissing(), })(buildRequestLike()); - (authObject as AuthSignedIn).protect = (params, options) => { + (authObject as unknown as ProtectGeneric).protect = (params: any, options: any) => { + const paramsOrFunction = params?.redirectUrl + ? undefined + : (params as + | CheckAuthorizationParamsWithCustomPermissions + | ((has: CheckAuthorizationWithCustomPermissions) => boolean)); + const redirectUrl = (params?.redirectUrl || options?.redirectUrl) as string | undefined; + const handleUnauthorized = (): never => { - if (options?.redirectUrl) { - redirect(options.redirectUrl); + if (redirectUrl) { + redirect(redirectUrl); } notFound(); }; + /** * User is not authenticated */ @@ -60,15 +76,15 @@ export const auth = () => { /** * User is authenticated */ - if (!params) { + if (!paramsOrFunction) { return { ...authObject }; } /** * if a function is passed and returns false then throw not found */ - if (typeof params === 'function') { - if (params(authObject.has)) { + if (typeof paramsOrFunction === 'function') { + if (paramsOrFunction(authObject.has)) { return { ...authObject }; } return handleUnauthorized(); @@ -77,7 +93,7 @@ export const auth = () => { /** * Checking if user is authorized when permission or role is passed */ - if (authObject.has(params)) { + if (authObject.has(paramsOrFunction)) { return { ...authObject }; } From 2b0f9424f25c56e1f89e133d0ebabd1147f24d01 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 13 Dec 2023 11:43:38 +0200 Subject: [PATCH 3/5] Revert "feat(nextjs): Allow for redirectUrl to be the first parameter" This reverts commit ee5708a9c2260993fed4ddaf2f7faa72b2e07ec8. --- packages/nextjs/src/app-router/server/auth.ts | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index d3f3f21579f..71ba582a7ff 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -17,23 +17,15 @@ type AuthSignedIn = AuthObjectWithoutResources< * This function is experimental as it throws a Nextjs notFound error if user is not authenticated or authorized. * In the future we would investigate a way to throw a more appropriate error that clearly describes the not authorized of authenticated status. */ - protect: { - ( - params?: - | CheckAuthorizationParamsWithCustomPermissions - | ((has: CheckAuthorizationWithCustomPermissions) => boolean), - options?: { redirectUrl: string }, - ): AuthObjectWithoutResources; - - (params?: { redirectUrl: string }): AuthObjectWithoutResources; - }; + protect: ( + params?: + | CheckAuthorizationParamsWithCustomPermissions + | ((has: CheckAuthorizationWithCustomPermissions) => boolean), + options?: { redirectUrl: string }, + ) => AuthObjectWithoutResources; } >; -type ProtectGeneric = { - protect: (params?: unknown, options?: unknown) => AuthObjectWithoutResources; -}; - type AuthSignedOut = AuthObjectWithoutResources< SignedOutAuthObject & { /** @@ -51,21 +43,13 @@ export const auth = () => { noAuthStatusMessage: authAuthHeaderMissing(), })(buildRequestLike()); - (authObject as unknown as ProtectGeneric).protect = (params: any, options: any) => { - const paramsOrFunction = params?.redirectUrl - ? undefined - : (params as - | CheckAuthorizationParamsWithCustomPermissions - | ((has: CheckAuthorizationWithCustomPermissions) => boolean)); - const redirectUrl = (params?.redirectUrl || options?.redirectUrl) as string | undefined; - + (authObject as AuthSignedIn).protect = (params, options) => { const handleUnauthorized = (): never => { - if (redirectUrl) { - redirect(redirectUrl); + if (options?.redirectUrl) { + redirect(options.redirectUrl); } notFound(); }; - /** * User is not authenticated */ @@ -76,15 +60,15 @@ export const auth = () => { /** * User is authenticated */ - if (!paramsOrFunction) { + if (!params) { return { ...authObject }; } /** * if a function is passed and returns false then throw not found */ - if (typeof paramsOrFunction === 'function') { - if (paramsOrFunction(authObject.has)) { + if (typeof params === 'function') { + if (params(authObject.has)) { return { ...authObject }; } return handleUnauthorized(); @@ -93,7 +77,7 @@ export const auth = () => { /** * Checking if user is authorized when permission or role is passed */ - if (authObject.has(paramsOrFunction)) { + if (authObject.has(params)) { return { ...authObject }; } From 80b0c451e0bc981b78d619fd85bf19641f97d460 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 13 Dec 2023 11:59:01 +0200 Subject: [PATCH 4/5] chore(nextjs): Update changeset --- .changeset/thirty-chicken-divide.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.changeset/thirty-chicken-divide.md b/.changeset/thirty-chicken-divide.md index fe3c7b55125..8e8b8f22891 100644 --- a/.changeset/thirty-chicken-divide.md +++ b/.changeset/thirty-chicken-divide.md @@ -2,4 +2,15 @@ '@clerk/nextjs': patch --- -Accept `redirectUrl` as option for `auth().protect()` +Accept `redirectUrl` as an option for `auth().protect()`. + +For example: + +```ts +// Authorization +auth().protect({ role:'org:admin' }, { redirectUrl: "/any-page" }) +auth().protect({ permission:'org:settings:manage' }, { redirectUrl: "/any-page" }) + +// Authentication +auth().protect({ redirectUrl: "/any-page" }) +``` From 5efed207cc371f9aeaa67c0eb8c6bfa86d1be4b8 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 13 Dec 2023 12:03:49 +0200 Subject: [PATCH 5/5] Revert "Revert "feat(nextjs): Allow for redirectUrl to be the first parameter"" This reverts commit 2b0f9424f25c56e1f89e133d0ebabd1147f24d01. --- packages/nextjs/src/app-router/server/auth.ts | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index 71ba582a7ff..d3f3f21579f 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -17,15 +17,23 @@ type AuthSignedIn = AuthObjectWithoutResources< * This function is experimental as it throws a Nextjs notFound error if user is not authenticated or authorized. * In the future we would investigate a way to throw a more appropriate error that clearly describes the not authorized of authenticated status. */ - protect: ( - params?: - | CheckAuthorizationParamsWithCustomPermissions - | ((has: CheckAuthorizationWithCustomPermissions) => boolean), - options?: { redirectUrl: string }, - ) => AuthObjectWithoutResources; + protect: { + ( + params?: + | CheckAuthorizationParamsWithCustomPermissions + | ((has: CheckAuthorizationWithCustomPermissions) => boolean), + options?: { redirectUrl: string }, + ): AuthObjectWithoutResources; + + (params?: { redirectUrl: string }): AuthObjectWithoutResources; + }; } >; +type ProtectGeneric = { + protect: (params?: unknown, options?: unknown) => AuthObjectWithoutResources; +}; + type AuthSignedOut = AuthObjectWithoutResources< SignedOutAuthObject & { /** @@ -43,13 +51,21 @@ export const auth = () => { noAuthStatusMessage: authAuthHeaderMissing(), })(buildRequestLike()); - (authObject as AuthSignedIn).protect = (params, options) => { + (authObject as unknown as ProtectGeneric).protect = (params: any, options: any) => { + const paramsOrFunction = params?.redirectUrl + ? undefined + : (params as + | CheckAuthorizationParamsWithCustomPermissions + | ((has: CheckAuthorizationWithCustomPermissions) => boolean)); + const redirectUrl = (params?.redirectUrl || options?.redirectUrl) as string | undefined; + const handleUnauthorized = (): never => { - if (options?.redirectUrl) { - redirect(options.redirectUrl); + if (redirectUrl) { + redirect(redirectUrl); } notFound(); }; + /** * User is not authenticated */ @@ -60,15 +76,15 @@ export const auth = () => { /** * User is authenticated */ - if (!params) { + if (!paramsOrFunction) { return { ...authObject }; } /** * if a function is passed and returns false then throw not found */ - if (typeof params === 'function') { - if (params(authObject.has)) { + if (typeof paramsOrFunction === 'function') { + if (paramsOrFunction(authObject.has)) { return { ...authObject }; } return handleUnauthorized(); @@ -77,7 +93,7 @@ export const auth = () => { /** * Checking if user is authorized when permission or role is passed */ - if (authObject.has(params)) { + if (authObject.has(paramsOrFunction)) { return { ...authObject }; }