From 5d942e6910f3488ea90f45c124f5e5065a9b1eb0 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Sun, 3 Dec 2023 15:09:47 +0200 Subject: [PATCH] feat(types,nextjs,clerk-react,backend): Rename Gate to Protect - Drop `some` from the `has` utility and Protect. Protect now accepts a `condition` prop where a function is expected with the `has` being exposed as the param. - Protect can now be used without required props. In this chae behaves as `` if no authorization props are passed. - `has` will throw an error if neither `permission` or `role` is passed. --- packages/backend/src/tokens/authObjects.ts | 40 ++++-------- .../app-router/server/controlComponents.tsx | 58 +++++++++++++++-- .../src/client-boundary/controlComponents.ts | 2 +- packages/nextjs/src/components.client.ts | 2 +- packages/nextjs/src/components.server.ts | 6 +- packages/nextjs/src/index.ts | 3 +- .../src/components/controlComponents.tsx | 64 ++++++++++++++++--- packages/react/src/components/index.ts | 2 +- packages/react/src/hooks/useAuth.ts | 54 +++++----------- packages/types/src/session.ts | 24 ++----- 10 files changed, 148 insertions(+), 107 deletions(-) diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index 6241b6d88a1..f9ce3d7b291 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -1,6 +1,6 @@ import type { ActClaim, - experimental__CheckAuthorizationWithCustomPermissions, + CheckAuthorizationWithCustomPermissions, JwtPayload, OrganizationCustomPermission, OrganizationCustomRole, @@ -35,10 +35,7 @@ export type SignedInAuthObject = { orgPermissions: OrganizationCustomPermission[] | undefined; organization: Organization | undefined; getToken: ServerGetToken; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationWithCustomPermissions; + has: CheckAuthorizationWithCustomPermissions; debug: AuthObjectDebug; }; @@ -55,10 +52,7 @@ export type SignedOutAuthObject = { orgPermissions: null; organization: null; getToken: ServerGetToken; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationWithCustomPermissions; + has: CheckAuthorizationWithCustomPermissions; debug: AuthObjectDebug; }; @@ -113,7 +107,7 @@ export function signedInAuthObject( orgPermissions, organization, getToken, - experimental__has: createHasAuthorization({ orgId, orgRole, orgPermissions, userId }), + has: createHasAuthorization({ orgId, orgRole, orgPermissions, userId }), debug: createDebug({ ...options, ...debugData }), }; } @@ -132,7 +126,7 @@ export function signedOutAuthObject(debugData?: AuthObjectDebugData): SignedOutA orgPermissions: null, organization: null, getToken: () => Promise.resolve(null), - experimental__has: () => false, + has: () => false, debug: createDebug(debugData), }; } @@ -178,7 +172,7 @@ export function sanitizeAuthObject>(authObject: T): T export const makeAuthObjectSerializable = >(obj: T): T => { // remove any non-serializable props from the returned object - const { debug, getToken, experimental__has, ...rest } = obj as unknown as AuthObject; + const { debug, getToken, has, ...rest } = obj as unknown as AuthObject; return rest as unknown as T; }; @@ -202,6 +196,7 @@ const createGetToken: CreateGetToken = params => { }; }; +//MAYBE move this to @shared const createHasAuthorization = ({ orgId, @@ -213,9 +208,14 @@ const createHasAuthorization = orgId: string | undefined; orgRole: string | undefined; orgPermissions: string[] | undefined; - }): experimental__CheckAuthorizationWithCustomPermissions => + }): CheckAuthorizationWithCustomPermissions => params => { - if (!orgId || !userId || !orgPermissions) { + // TODO: assert + if (!params?.permission && !params?.role) { + throw 'Permission or role is required'; + } + + if (!orgId || !userId || !orgRole || !orgPermissions) { return false; } @@ -227,17 +227,5 @@ const createHasAuthorization = return orgRole === params.role; } - if (params.some) { - return !!params.some.find(permObj => { - if (permObj.permission) { - return orgPermissions.includes(permObj.permission); - } - if (permObj.role) { - return orgRole === permObj.role; - } - return false; - }); - } - return false; }; diff --git a/packages/nextjs/src/app-router/server/controlComponents.tsx b/packages/nextjs/src/app-router/server/controlComponents.tsx index 12136aaf013..12bf93bf34a 100644 --- a/packages/nextjs/src/app-router/server/controlComponents.tsx +++ b/packages/nextjs/src/app-router/server/controlComponents.tsx @@ -1,4 +1,8 @@ -import type { experimental__CheckAuthorizationWithCustomPermissions } from '@clerk/types'; +import type { + CheckAuthorizationWithCustomPermissions, + OrganizationCustomPermission, + OrganizationCustomRole, +} from '@clerk/types'; import React from 'react'; import { auth } from './auth'; @@ -16,7 +20,28 @@ export function SignedOut(props: React.PropsWithChildren) { } type GateServerComponentProps = React.PropsWithChildren< - Parameters[0] & { + ( + | { + condition?: never; + role: OrganizationCustomRole; + permission?: never; + } + | { + condition?: never; + role?: never; + permission: OrganizationCustomPermission; + } + | { + condition: (has: CheckAuthorizationWithCustomPermissions) => boolean; + role?: never; + permission?: never; + } + | { + condition?: never; + role?: never; + permission?: never; + } + ) & { fallback?: React.ReactNode; } >; @@ -24,13 +49,36 @@ type GateServerComponentProps = React.PropsWithChildren< /** * @experimental The component is experimental and subject to change in future releases. */ -export function experimental__Gate(gateProps: GateServerComponentProps) { +export function Protect(gateProps: GateServerComponentProps) { const { children, fallback, ...restAuthorizedParams } = gateProps; - const { experimental__has } = auth(); + const { has, userId, sessionId } = auth(); - if (experimental__has(restAuthorizedParams)) { + /** + * If neither of the authorization params are passed behave as the `` + */ + if (!restAuthorizedParams.condition && !restAuthorizedParams.role && !restAuthorizedParams.permission) { + if (userId && sessionId) { + return <>{children}; + } + return <>{fallback ?? null}; + } + + /** + * Check against the results of `has` called inside the callback + */ + if (typeof restAuthorizedParams.condition === 'function') { + if (restAuthorizedParams.condition(has)) { + return <>{children}; + } + return <>{fallback ?? null}; + } + + if (has(restAuthorizedParams)) { return <>{children}; } + /** + * Fallback to custom ui or null if authorization checks failed + */ return <>{fallback ?? null}; } diff --git a/packages/nextjs/src/client-boundary/controlComponents.ts b/packages/nextjs/src/client-boundary/controlComponents.ts index 2ce6bb63338..58eec00a1fa 100644 --- a/packages/nextjs/src/client-boundary/controlComponents.ts +++ b/packages/nextjs/src/client-boundary/controlComponents.ts @@ -5,7 +5,7 @@ export { ClerkLoading, SignedOut, SignedIn, - Experimental__Gate, + Protect, RedirectToSignIn, RedirectToSignUp, RedirectToUserProfile, diff --git a/packages/nextjs/src/components.client.ts b/packages/nextjs/src/components.client.ts index 3bdb446014f..aac3f82f65b 100644 --- a/packages/nextjs/src/components.client.ts +++ b/packages/nextjs/src/components.client.ts @@ -1,2 +1,2 @@ export { ClerkProvider } from './client-boundary/ClerkProvider'; -export { SignedIn, SignedOut, Experimental__Gate } from './client-boundary/controlComponents'; +export { SignedIn, SignedOut, Protect } from './client-boundary/controlComponents'; diff --git a/packages/nextjs/src/components.server.ts b/packages/nextjs/src/components.server.ts index e002e1b0865..f73c8cc91c5 100644 --- a/packages/nextjs/src/components.server.ts +++ b/packages/nextjs/src/components.server.ts @@ -1,11 +1,11 @@ import { ClerkProvider } from './app-router/server/ClerkProvider'; -import { experimental__Gate, SignedIn, SignedOut } from './app-router/server/controlComponents'; +import { Protect, SignedIn, SignedOut } from './app-router/server/controlComponents'; -export { ClerkProvider, SignedOut, SignedIn, experimental__Gate as Experimental__Gate }; +export { ClerkProvider, SignedOut, SignedIn, Protect }; export type ServerComponentsServerModuleTypes = { ClerkProvider: typeof ClerkProvider; SignedIn: typeof SignedIn; SignedOut: typeof SignedOut; - Experimental__Gate: typeof experimental__Gate; + Protect: typeof Protect; }; diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index a4cd040d5da..1b2e1442ff5 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -92,8 +92,7 @@ export const SignedOut = ComponentsModule.SignedOut as ServerComponentsServerMod /** * @experimental */ -export const Experimental__Gate = - ComponentsModule.Experimental__Gate as ServerComponentsServerModuleTypes['Experimental__Gate']; +export const Protect = ComponentsModule.Protect as ServerComponentsServerModuleTypes['Protect']; export const auth = ServerHelperModule.auth as ServerHelpersServerModuleTypes['auth']; export const currentUser = ServerHelperModule.currentUser as ServerHelpersServerModuleTypes['currentUser']; diff --git a/packages/react/src/components/controlComponents.tsx b/packages/react/src/components/controlComponents.tsx index 138857dd6ca..4b5ace7e055 100644 --- a/packages/react/src/components/controlComponents.tsx +++ b/packages/react/src/components/controlComponents.tsx @@ -1,4 +1,9 @@ -import type { experimental__CheckAuthorizationWithCustomPermissions, HandleOAuthCallbackParams } from '@clerk/types'; +import type { + CheckAuthorizationWithCustomPermissions, + HandleOAuthCallbackParams, + OrganizationCustomPermission, + OrganizationCustomRole, +} from '@clerk/types'; import React from 'react'; import { useAuthContext } from '../contexts/AuthContext'; @@ -41,22 +46,63 @@ export const ClerkLoading = ({ children }: React.PropsWithChildren): JS return <>{children}; }; -type GateProps = React.PropsWithChildren< - Parameters[0] & { +type ProtectProps = React.PropsWithChildren< + ( + | { + condition?: never; + role: OrganizationCustomRole; + permission?: never; + } + | { + condition?: never; + role?: never; + permission: OrganizationCustomPermission; + } + | { + condition: (has: CheckAuthorizationWithCustomPermissions) => boolean; + role?: never; + permission?: never; + } + | { + condition?: never; + role?: never; + permission?: never; + } + ) & { fallback?: React.ReactNode; } >; -/** - * @experimental The component is experimental and subject to change in future releases. - */ -export const experimental__Gate = ({ children, fallback, ...restAuthorizedParams }: GateProps) => { - const { experimental__has } = useAuth(); +export const Protect = ({ children, fallback, ...restAuthorizedParams }: ProtectProps) => { + const { has, userId, sessionId } = useAuth(); - if (experimental__has(restAuthorizedParams)) { + /** + * If neither of the authorization params are passed behave as the `` + */ + if (!restAuthorizedParams.condition && !restAuthorizedParams.role && !restAuthorizedParams.permission) { + if (userId && sessionId) { + return <>{children}; + } + return <>{fallback ?? null}; + } + + /** + * Check against the results of `has` called inside the callback + */ + if (typeof restAuthorizedParams.condition === 'function') { + if (restAuthorizedParams.condition(has)) { + return <>{children}; + } + return <>{fallback ?? null}; + } + + if (has(restAuthorizedParams)) { return <>{children}; } + /** + * Fallback to custom ui or null if authorization checks failed + */ return <>{fallback ?? null}; }; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 55c0949dbb7..439452bb52f 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -14,7 +14,7 @@ export { ClerkLoading, SignedOut, SignedIn, - experimental__Gate as Experimental__Gate, + Protect, RedirectToSignIn, RedirectToSignUp, RedirectToUserProfile, diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index 91d71ac3dd9..2980cd9caf2 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ import type { ActJWTClaim, - experimental__CheckAuthorizationWithCustomPermissions, + CheckAuthorizationWithCustomPermissions, GetToken, MembershipRole, SignOut, @@ -14,9 +14,7 @@ import type { IsomorphicClerk } from '../isomorphicClerk'; import { errorThrower } from '../utils'; import { createGetToken, createSignOut } from './utils'; -type experimental__CheckAuthorizationSignedOut = ( - params?: Parameters[0], -) => false; +type CheckAuthorizationSignedOut = (params?: Parameters[0]) => false; type UseAuthReturn = | { @@ -28,10 +26,7 @@ type UseAuthReturn = orgId: undefined; orgRole: undefined; orgSlug: undefined; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationSignedOut; + has: CheckAuthorizationSignedOut; signOut: SignOut; getToken: GetToken; } @@ -44,10 +39,7 @@ type UseAuthReturn = orgId: null; orgRole: null; orgSlug: null; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationSignedOut; + has: CheckAuthorizationSignedOut; signOut: SignOut; getToken: GetToken; } @@ -60,10 +52,7 @@ type UseAuthReturn = orgId: null; orgRole: null; orgSlug: null; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationSignedOut; + has: CheckAuthorizationSignedOut; signOut: SignOut; getToken: GetToken; } @@ -76,10 +65,7 @@ type UseAuthReturn = orgId: string; orgRole: MembershipRole; orgSlug: string | null; - /** - * @experimental The method is experimental and subject to change in future releases. - */ - experimental__has: experimental__CheckAuthorizationWithCustomPermissions; + has: CheckAuthorizationWithCustomPermissions; signOut: SignOut; getToken: GetToken; }; @@ -130,12 +116,13 @@ export const useAuth: UseAuth = () => { const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]); const has = useCallback( - (params?: Parameters[0]) => { - if (!orgId || !userId || !orgRole || !orgPermissions) { - return false; + (params: Parameters[0]) => { + // TODO: assert + if (!params?.permission && !params?.role) { + throw 'Permission or role is required'; } - if (!params) { + if (!orgId || !userId || !orgRole || !orgPermissions) { return false; } @@ -147,17 +134,6 @@ export const useAuth: UseAuth = () => { return orgRole === params.role; } - if (params.some) { - return !!params.some.find(permObj => { - if (permObj.permission) { - return orgPermissions.includes(permObj.permission); - } - if (permObj.role) { - return orgRole === permObj.role; - } - return false; - }); - } return false; }, [orgId, orgRole, userId, orgPermissions], @@ -173,7 +149,7 @@ export const useAuth: UseAuth = () => { orgId: undefined, orgRole: undefined, orgSlug: undefined, - experimental__has: () => false, + has: () => false, signOut, getToken, }; @@ -189,7 +165,7 @@ export const useAuth: UseAuth = () => { orgId: null, orgRole: null, orgSlug: null, - experimental__has: () => false, + has: () => false, signOut, getToken, }; @@ -205,7 +181,7 @@ export const useAuth: UseAuth = () => { orgId, orgRole, orgSlug: orgSlug || null, - experimental__has: has, + has, signOut, getToken, }; @@ -221,7 +197,7 @@ export const useAuth: UseAuth = () => { orgId: null, orgRole: null, orgSlug: null, - experimental__has: () => false, + has: () => false, signOut, getToken, }; diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts index 891fea2007a..74070654cff 100644 --- a/packages/types/src/session.ts +++ b/packages/types/src/session.ts @@ -8,38 +8,22 @@ import type { import type { ClerkResource } from './resource'; import type { TokenResource } from './token'; import type { UserResource } from './user'; +export type CheckAuthorizationFn = (isAuthorizedParams: Params) => boolean; -export type experimental__CheckAuthorizationWithCustomPermissions = ( - isAuthorizedParams: CheckAuthorizationParamsWithCustomPermissions, -) => boolean; +export type CheckAuthorizationWithCustomPermissions = + CheckAuthorizationFn; type CheckAuthorizationParamsWithCustomPermissions = | { - some: ( - | { - role: OrganizationCustomRole; - permission?: never; - } - | { - role?: never; - permission: OrganizationCustomPermission; - } - )[]; - role?: never; - permission?: never; - } - | { - some?: never; role: OrganizationCustomRole; permission?: never; } | { - some?: never; role?: never; permission: OrganizationCustomPermission; }; -export type CheckAuthorization = (isAuthorizedParams: CheckAuthorizationParams) => boolean; +export type CheckAuthorization = CheckAuthorizationFn; type CheckAuthorizationParams = | {