diff --git a/.changeset/short-eagles-search.md b/.changeset/short-eagles-search.md
new file mode 100644
index 0000000000..956ec761ab
--- /dev/null
+++ b/.changeset/short-eagles-search.md
@@ -0,0 +1,20 @@
+---
+'@clerk/chrome-extension': minor
+'@clerk/clerk-js': minor
+'@clerk/backend': minor
+'@clerk/nextjs': minor
+'@clerk/clerk-react': minor
+'@clerk/types': minor
+---
+
+Introduce Protect for authorization.
+Changes in public APIs:
+- Rename Gate to Protect
+- Support for permission checks. (Previously only roles could be used)
+- Remove the `experimental` tags and prefixes
+- 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 case behaves as ``, if no authorization props are passed.
+- `has` will throw an error if neither `permission` or `role` is passed.
+- `auth().protect()` for Nextjs App Router. Allow per page protection in app router. This utility will automatically throw a 404 error if user is not authorized or authenticated.
+ - 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
diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts
index 54e57809f8..13aa72582b 100644
--- a/packages/backend/src/tokens/authObjects.ts
+++ b/packages/backend/src/tokens/authObjects.ts
@@ -1,7 +1,9 @@
import type {
ActClaim,
- experimental__CheckAuthorizationWithoutPermission,
+ CheckAuthorizationWithCustomPermissions,
JwtPayload,
+ OrganizationCustomPermissionKey,
+ OrganizationCustomRoleKey,
ServerGetToken,
ServerGetTokenOptions,
} from '@clerk/types';
@@ -28,14 +30,12 @@ export type SignedInAuthObject = {
userId: string;
user: User | undefined;
orgId: string | undefined;
- orgRole: string | undefined;
+ orgRole: OrganizationCustomRoleKey | undefined;
orgSlug: string | undefined;
+ orgPermissions: OrganizationCustomPermissionKey[] | undefined;
organization: Organization | undefined;
getToken: ServerGetToken;
- /**
- * @experimental The method is experimental and subject to change in future releases.
- */
- experimental__has: experimental__CheckAuthorizationWithoutPermission;
+ has: CheckAuthorizationWithCustomPermissions;
debug: AuthObjectDebug;
};
@@ -49,12 +49,10 @@ export type SignedOutAuthObject = {
orgId: null;
orgRole: null;
orgSlug: null;
+ orgPermissions: null;
organization: null;
getToken: ServerGetToken;
- /**
- * @experimental The method is experimental and subject to change in future releases.
- */
- experimental__has: experimental__CheckAuthorizationWithoutPermission;
+ has: CheckAuthorizationWithCustomPermissions;
debug: AuthObjectDebug;
};
@@ -80,6 +78,7 @@ export function signedInAuthObject(
org_id: orgId,
org_role: orgRole,
org_slug: orgSlug,
+ org_permissions: orgPermissions,
sub: userId,
} = sessionClaims;
const { secretKey, apiUrl, apiVersion, token, session, user, organization } = options;
@@ -105,9 +104,10 @@ export function signedInAuthObject(
orgId,
orgRole,
orgSlug,
+ orgPermissions,
organization,
getToken,
- experimental__has: createHasAuthorization({ orgId, orgRole, userId }),
+ has: createHasAuthorization({ orgId, orgRole, orgPermissions, userId }),
debug: createDebug({ ...options, ...debugData }),
};
}
@@ -123,9 +123,10 @@ export function signedOutAuthObject(debugData?: AuthObjectDebugData): SignedOutA
orgId: null,
orgRole: null,
orgSlug: null,
+ orgPermissions: null,
organization: null,
getToken: () => Promise.resolve(null),
- experimental__has: () => false,
+ has: () => false,
debug: createDebug(debugData),
};
}
@@ -171,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;
};
@@ -200,27 +201,30 @@ const createHasAuthorization =
orgId,
orgRole,
userId,
+ orgPermissions,
}: {
userId: string;
orgId: string | undefined;
orgRole: string | undefined;
- }): experimental__CheckAuthorizationWithoutPermission =>
+ orgPermissions: string[] | undefined;
+ }): CheckAuthorizationWithCustomPermissions =>
params => {
- if (!orgId || !userId) {
+ if (!params?.permission && !params?.role) {
+ throw new Error(
+ 'Missing parameters. `has` from `auth` or `getAuth` requires a permission or role key to be passed. Example usage: `has({permission: "org:posts:edit"`',
+ );
+ }
+
+ if (!orgId || !userId || !orgRole || !orgPermissions) {
return false;
}
- if (params.role) {
- return orgRole === params.role;
+ if (params.permission) {
+ return orgPermissions.includes(params.permission);
}
- if (params.some) {
- return !!params.some.find(permObj => {
- if (permObj.role) {
- return orgRole === permObj.role;
- }
- return false;
- });
+ if (params.role) {
+ return orgRole === params.role;
}
return false;
diff --git a/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap b/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap
index 5850b5cf88..a8554f210c 100644
--- a/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap
+++ b/packages/chrome-extension/src/__snapshots__/exports.test.ts.snap
@@ -8,11 +8,11 @@ exports[`public exports should not include a breaking change 1`] = `
"ClerkProvider",
"CreateOrganization",
"EmailLinkErrorCode",
- "Experimental__Gate",
"MultisessionAppSupport",
"OrganizationList",
"OrganizationProfile",
"OrganizationSwitcher",
+ "Protect",
"RedirectToCreateOrganization",
"RedirectToOrganizationProfile",
"RedirectToSignIn",
diff --git a/packages/clerk-js/src/core/resources/OrganizationMembership.ts b/packages/clerk-js/src/core/resources/OrganizationMembership.ts
index cc327fcd0f..a6815fb896 100644
--- a/packages/clerk-js/src/core/resources/OrganizationMembership.ts
+++ b/packages/clerk-js/src/core/resources/OrganizationMembership.ts
@@ -1,12 +1,11 @@
import type {
- Autocomplete,
ClerkPaginatedResponse,
ClerkResourceReloadParams,
GetUserOrganizationMembershipParams,
MembershipRole,
OrganizationMembershipJSON,
OrganizationMembershipResource,
- OrganizationPermission,
+ OrganizationPermissionKey,
} from '@clerk/types';
import { unixEpochToDate } from '../../utils/date';
@@ -18,10 +17,7 @@ export class OrganizationMembership extends BaseResource implements Organization
publicMetadata: OrganizationMembershipPublicMetadata = {};
publicUserData!: PublicUserData;
organization!: Organization;
- /**
- * @experimental The property is experimental and subject to change in future releases.
- */
- permissions: Autocomplete[] = [];
+ permissions: OrganizationPermissionKey[] = [];
role!: MembershipRole;
createdAt!: Date;
updatedAt!: Date;
@@ -37,7 +33,7 @@ export class OrganizationMembership extends BaseResource implements Organization
method: 'GET',
// `paginated` is used in some legacy endpoints to support clerk paginated responses
// The parameter will be dropped in FAPI v2
- search: convertPageToOffset({ ...retrieveMembershipsParams, paginated: true }) as any,
+ search: convertPageToOffset({ ...retrieveMembershipsParams, paginated: true }),
})
.then(res => {
if (!res?.response) {
diff --git a/packages/clerk-js/src/core/resources/Session.test.ts b/packages/clerk-js/src/core/resources/Session.test.ts
index b3f5c889d8..f705632e76 100644
--- a/packages/clerk-js/src/core/resources/Session.test.ts
+++ b/packages/clerk-js/src/core/resources/Session.test.ts
@@ -73,7 +73,7 @@ describe('Session', () => {
updated_at: new Date().getTime(),
} as SessionJSON);
- const isAuthorized = await session.experimental__checkAuthorization({ permission: 'org:sys_profile:delete' });
+ const isAuthorized = await session.checkAuthorization({ permission: 'org:sys_profile:delete' });
expect(isAuthorized).toBe(true);
});
@@ -93,7 +93,7 @@ describe('Session', () => {
updated_at: new Date().getTime(),
} as SessionJSON);
- const isAuthorized = await session.experimental__checkAuthorization({ permission: 'org:sys_profile:delete' });
+ const isAuthorized = await session.checkAuthorization({ permission: 'org:sys_profile:delete' });
expect(isAuthorized).toBe(false);
});
diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts
index a153414c41..60860feccf 100644
--- a/packages/clerk-js/src/core/resources/Session.ts
+++ b/packages/clerk-js/src/core/resources/Session.ts
@@ -75,10 +75,7 @@ export class Session extends BaseResource implements SessionResource {
});
};
- /**
- * @experimental The method is experimental and subject to change in future releases.
- */
- experimental__checkAuthorization: CheckAuthorization = params => {
+ checkAuthorization: CheckAuthorization = params => {
// if there is no active organization user can not be authorized
if (!this.lastActiveOrganizationId || !this.user) {
return false;
@@ -103,18 +100,6 @@ export class Session extends BaseResource implements SessionResource {
return activeOrganizationRole === params.role;
}
- if (params.some) {
- return !!params.some.find(permObj => {
- if (permObj.permission) {
- return activeOrganizationPermissions.includes(permObj.permission);
- }
- if (permObj.role) {
- return activeOrganizationRole === permObj.role;
- }
- return false;
- });
- }
-
return false;
};
diff --git a/packages/clerk-js/src/ui.retheme/common/Gate.tsx b/packages/clerk-js/src/ui.retheme/common/Gate.tsx
index 1644b6c7b0..9aefb2801d 100644
--- a/packages/clerk-js/src/ui.retheme/common/Gate.tsx
+++ b/packages/clerk-js/src/ui.retheme/common/Gate.tsx
@@ -1,13 +1,29 @@
import { useSession } from '@clerk/shared/react';
-import type { CheckAuthorization } from '@clerk/types';
+import type { CheckAuthorization, MembershipRole, OrganizationPermissionKey } from '@clerk/types';
import type { ComponentType, PropsWithChildren, ReactNode } from 'react';
import React, { useEffect } from 'react';
import { useRouter } from '../router';
-type GateParams = Parameters[0];
+type GateParams = Parameters[0] | ((has: CheckAuthorization) => boolean);
type GateProps = PropsWithChildren<
- GateParams & {
+ (
+ | {
+ condition?: never;
+ role: MembershipRole;
+ permission?: never;
+ }
+ | {
+ condition?: never;
+ role?: never;
+ permission: OrganizationPermissionKey;
+ }
+ | {
+ condition: (has: CheckAuthorization) => boolean;
+ role?: never;
+ permission?: never;
+ }
+ ) & {
fallback?: ReactNode;
redirectTo?: string;
}
@@ -16,15 +32,31 @@ type GateProps = PropsWithChildren<
export const useGate = (params: GateParams) => {
const { session } = useSession();
+ if (!session?.id) {
+ return { isAuthorizedUser: false };
+ }
+
+ /**
+ * if a function is passed and returns false then throw not found
+ */
+ if (typeof params === 'function') {
+ if (params(session.checkAuthorization)) {
+ return { isAuthorizedUser: true };
+ }
+ return { isAuthorizedUser: false };
+ }
+
return {
- isAuthorizedUser: session?.experimental__checkAuthorization(params),
+ isAuthorizedUser: session?.checkAuthorization(params),
};
};
export const Gate = (gateProps: GateProps) => {
const { children, fallback, redirectTo, ...restAuthorizedParams } = gateProps;
- const { isAuthorizedUser } = useGate(restAuthorizedParams);
+ const { isAuthorizedUser } = useGate(
+ typeof restAuthorizedParams.condition === 'function' ? restAuthorizedParams.condition : restAuthorizedParams,
+ );
const { navigate } = useRouter();
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx
index 36ab17339d..6f7a90db4b 100644
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx
+++ b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileNavbar.tsx
@@ -14,16 +14,12 @@ export const OrganizationProfileNavbar = (
const { organization } = useOrganization();
const { pages } = useOrganizationProfileContext();
- const { isAuthorizedUser: allowMembersRoute } = useGate({
- some: [
- {
+ const { isAuthorizedUser: allowMembersRoute } = useGate(
+ has =>
+ has({
permission: 'org:sys_memberships:read',
- },
- {
- permission: 'org:sys_memberships:manage',
- },
- ],
- });
+ }) || has({ permission: 'org:sys_memberships:manage' }),
+ );
if (!organization) {
return null;
diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx
index ae2799d57a..ed257be5c4 100644
--- a/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx
+++ b/packages/clerk-js/src/ui.retheme/components/OrganizationProfile/OrganizationProfileRoutes.tsx
@@ -77,7 +77,7 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent
@@ -130,8 +130,10 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent
+ has({ permission: 'org:sys_memberships:read' }) || has({ permission: 'org:sys_memberships:manage' })
+ }
+ redirectTo={isSettingsPageRoot ? '../' : './organization-settings'}
>
diff --git a/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts
index 8f4d5e38ac..c509469eff 100644
--- a/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts
+++ b/packages/clerk-js/src/ui.retheme/utils/test/mockHelpers.ts
@@ -35,7 +35,7 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked {
mockMethodsOf(session, {
- exclude: ['experimental__checkAuthorization'],
+ exclude: ['checkAuthorization'],
});
mockMethodsOf(session.user);
session.user?.emailAddresses.forEach(m => mockMethodsOf(m));
diff --git a/packages/clerk-js/src/ui/common/Gate.tsx b/packages/clerk-js/src/ui/common/Gate.tsx
index 1644b6c7b0..9aefb2801d 100644
--- a/packages/clerk-js/src/ui/common/Gate.tsx
+++ b/packages/clerk-js/src/ui/common/Gate.tsx
@@ -1,13 +1,29 @@
import { useSession } from '@clerk/shared/react';
-import type { CheckAuthorization } from '@clerk/types';
+import type { CheckAuthorization, MembershipRole, OrganizationPermissionKey } from '@clerk/types';
import type { ComponentType, PropsWithChildren, ReactNode } from 'react';
import React, { useEffect } from 'react';
import { useRouter } from '../router';
-type GateParams = Parameters[0];
+type GateParams = Parameters[0] | ((has: CheckAuthorization) => boolean);
type GateProps = PropsWithChildren<
- GateParams & {
+ (
+ | {
+ condition?: never;
+ role: MembershipRole;
+ permission?: never;
+ }
+ | {
+ condition?: never;
+ role?: never;
+ permission: OrganizationPermissionKey;
+ }
+ | {
+ condition: (has: CheckAuthorization) => boolean;
+ role?: never;
+ permission?: never;
+ }
+ ) & {
fallback?: ReactNode;
redirectTo?: string;
}
@@ -16,15 +32,31 @@ type GateProps = PropsWithChildren<
export const useGate = (params: GateParams) => {
const { session } = useSession();
+ if (!session?.id) {
+ return { isAuthorizedUser: false };
+ }
+
+ /**
+ * if a function is passed and returns false then throw not found
+ */
+ if (typeof params === 'function') {
+ if (params(session.checkAuthorization)) {
+ return { isAuthorizedUser: true };
+ }
+ return { isAuthorizedUser: false };
+ }
+
return {
- isAuthorizedUser: session?.experimental__checkAuthorization(params),
+ isAuthorizedUser: session?.checkAuthorization(params),
};
};
export const Gate = (gateProps: GateProps) => {
const { children, fallback, redirectTo, ...restAuthorizedParams } = gateProps;
- const { isAuthorizedUser } = useGate(restAuthorizedParams);
+ const { isAuthorizedUser } = useGate(
+ typeof restAuthorizedParams.condition === 'function' ? restAuthorizedParams.condition : restAuthorizedParams,
+ );
const { navigate } = useRouter();
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx
index b8300940aa..03d2ab8cdd 100644
--- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx
@@ -13,16 +13,12 @@ export const OrganizationProfileNavbar = (
const { organization } = useOrganization();
const { pages } = useOrganizationProfileContext();
- const { isAuthorizedUser: allowMembersRoute } = useGate({
- some: [
- {
+ const { isAuthorizedUser: allowMembersRoute } = useGate(
+ has =>
+ has({
permission: 'org:sys_memberships:read',
- },
- {
- permission: 'org:sys_memberships:manage',
- },
- ],
- });
+ }) || has({ permission: 'org:sys_memberships:manage' }),
+ );
if (!organization) {
return null;
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx
index 3a56d86fda..ed257be5c4 100644
--- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx
@@ -77,7 +77,7 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent
@@ -130,7 +130,9 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent
+ has({ permission: 'org:sys_memberships:read' }) || has({ permission: 'org:sys_memberships:manage' })
+ }
redirectTo={isSettingsPageRoot ? '../' : './organization-settings'}
>
diff --git a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
index 8f4d5e38ac..c509469eff 100644
--- a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
+++ b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
@@ -35,7 +35,7 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked {
mockMethodsOf(session, {
- exclude: ['experimental__checkAuthorization'],
+ exclude: ['checkAuthorization'],
});
mockMethodsOf(session.user);
session.user?.emailAddresses.forEach(m => mockMethodsOf(m));
diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts
index 8d8563a90a..4629f0f470 100644
--- a/packages/nextjs/src/app-router/server/auth.ts
+++ b/packages/nextjs/src/app-router/server/auth.ts
@@ -1,12 +1,80 @@
+import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
+import type { CheckAuthorizationWithCustomPermissions } 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';
+type AuthSignedIn = AuthObjectWithoutResources<
+ SignedInAuthObject & {
+ /**
+ * @experimental
+ * 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?:
+ | Parameters[0]
+ | ((has: CheckAuthorizationWithCustomPermissions) => boolean),
+ ) => AuthObjectWithoutResources;
+ }
+>;
+
+type AuthSignedOut = AuthObjectWithoutResources<
+ SignedOutAuthObject & {
+ /**
+ * @experimental
+ * 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: never;
+ }
+>;
+
export const auth = () => {
- return createGetAuth({
+ const authObject = createGetAuth({
debugLoggerName: 'auth()',
noAuthStatusMessage: authAuthHeaderMissing(),
})(buildRequestLike());
+
+ (authObject as AuthSignedIn).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 };
+ }
+ notFound();
+ }
+
+ /**
+ * Checking if user is authorized when permission or role is passed
+ */
+ if (authObject.has(params)) {
+ return { ...authObject };
+ }
+
+ notFound();
+ };
+
+ return authObject as AuthSignedIn | AuthSignedOut;
};
export const initialState = () => {
diff --git a/packages/nextjs/src/app-router/server/controlComponents.tsx b/packages/nextjs/src/app-router/server/controlComponents.tsx
index ac506a1ec8..7455bb5769 100644
--- a/packages/nextjs/src/app-router/server/controlComponents.tsx
+++ b/packages/nextjs/src/app-router/server/controlComponents.tsx
@@ -1,5 +1,4 @@
-import type { experimental__CheckAuthorizationWithoutPermission } from '@clerk/types';
-import { redirect } from 'next/navigation';
+import type { Protect as ProtectClientComponent } from '@clerk/clerk-react';
import React from 'react';
import { auth } from './auth';
@@ -16,37 +15,55 @@ export function SignedOut(props: React.PropsWithChildren) {
return userId ? null : <>{children}>;
}
-type GateServerComponentProps = React.PropsWithChildren<
- Parameters[0] & {
- fallback?: React.ReactNode;
- redirectTo?: string;
- }
->;
+type ProtectServerComponentProps = React.ComponentProps;
/**
- * @experimental The component is experimental and subject to change in future releases.
+ * Use `` in order to prevent unauthenticated or unauthorized users from accessing the children passed to the component.
+ *
+ * Examples:
+ * ```
+ *
+ *
+ * has({permission:"a_permission_key"})} />
+ * has({role:"a_role_key"})} />
+ * Unauthorized
} />
+ * ```
*/
-export function experimental__Gate(gateProps: GateServerComponentProps) {
- const { children, fallback, redirectTo, ...restAuthorizedParams } = gateProps;
- const { experimental__has } = auth();
+export function Protect(props: ProtectServerComponentProps) {
+ const { children, fallback, ...restAuthorizedParams } = props;
+ const { has, userId } = auth();
- const isAuthorizedUser = experimental__has(restAuthorizedParams);
+ /**
+ * Fallback to UI provided by user or `null` if authorization checks failed
+ */
+ const unauthorized = <>{fallback ?? null}>;
- const handleFallback = () => {
- if (!redirectTo && !fallback) {
- throw new Error('Provide `` with a `fallback` or `redirectTo`');
- }
+ const authorized = <>{children}>;
- if (redirectTo) {
- return redirect(redirectTo);
- }
+ if (!userId) {
+ return unauthorized;
+ }
- return <>{fallback}>;
- };
+ /**
+ * Check against the results of `has` called inside the callback
+ */
+ if (typeof restAuthorizedParams.condition === 'function') {
+ if (restAuthorizedParams.condition(has)) {
+ return authorized;
+ }
+ return unauthorized;
+ }
- if (!isAuthorizedUser) {
- return handleFallback();
+ if (restAuthorizedParams.role || restAuthorizedParams.permission) {
+ if (has(restAuthorizedParams)) {
+ return authorized;
+ }
+ return unauthorized;
}
- return <>{children}>;
+ /**
+ * If neither of the authorization params are passed behave as the ``.
+ * If fallback is present render that instead of rendering nothing.
+ */
+ return authorized;
}
diff --git a/packages/nextjs/src/client-boundary/controlComponents.ts b/packages/nextjs/src/client-boundary/controlComponents.ts
index 2ce6bb6333..58eec00a1f 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 3bdb446014..aac3f82f65 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 e002e1b086..f73c8cc91c 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 a4cd040d5d..038b6e2f51 100644
--- a/packages/nextjs/src/index.ts
+++ b/packages/nextjs/src/index.ts
@@ -89,11 +89,7 @@ export const ClerkProvider = ComponentsModule.ClerkProvider as ServerComponentsS
export const SignedIn = ComponentsModule.SignedIn as ServerComponentsServerModuleTypes['SignedIn'];
export const SignedOut = ComponentsModule.SignedOut as ServerComponentsServerModuleTypes['SignedOut'];
-/**
- * @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/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;
diff --git a/packages/react/src/components/controlComponents.tsx b/packages/react/src/components/controlComponents.tsx
index 983a647210..4b05c0cfd3 100644
--- a/packages/react/src/components/controlComponents.tsx
+++ b/packages/react/src/components/controlComponents.tsx
@@ -1,4 +1,9 @@
-import type { experimental__CheckAuthorizationWithoutPermission, HandleOAuthCallbackParams } from '@clerk/types';
+import type {
+ CheckAuthorizationWithCustomPermissions,
+ HandleOAuthCallbackParams,
+ OrganizationCustomPermissionKey,
+ OrganizationCustomRoleKey,
+} from '@clerk/types';
import React from 'react';
import { useAuthContext } from '../contexts/AuthContext';
@@ -41,23 +46,88 @@ export const ClerkLoading = ({ children }: React.PropsWithChildren): JS
return <>{children}>;
};
-type GateProps = React.PropsWithChildren<
- Parameters[0] & {
+type ProtectProps = React.PropsWithChildren<
+ (
+ | {
+ condition?: never;
+ role: OrganizationCustomRoleKey;
+ permission?: never;
+ }
+ | {
+ condition?: never;
+ role?: never;
+ permission: OrganizationCustomPermissionKey;
+ }
+ | {
+ 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.
+ * Use `` in order to prevent unauthenticated or unauthorized users from accessing the children passed to the component.
+ *
+ * Examples:
+ * ```
+ *
+ *
+ * has({permission:"a_permission_key"})} />
+ * has({role:"a_role_key"})} />
+ * Unauthorized} />
+ * ```
*/
-export const experimental__Gate = ({ children, fallback, ...restAuthorizedParams }: GateProps) => {
- const { experimental__has } = useAuth();
+export const Protect = ({ children, fallback, ...restAuthorizedParams }: ProtectProps) => {
+ const { isLoaded, has, userId } = useAuth();
- if (experimental__has(restAuthorizedParams)) {
- return <>{children}>;
+ /**
+ * Avoid flickering children or fallback while clerk is loading sessionId or userId
+ */
+ if (!isLoaded) {
+ return null;
+ }
+
+ /**
+ * Fallback to UI provided by user or `null` if authorization checks failed
+ */
+ const unauthorized = <>{fallback ?? null}>;
+
+ const authorized = <>{children}>;
+
+ if (!userId) {
+ return unauthorized;
+ }
+
+ /**
+ * Check against the results of `has` called inside the callback
+ */
+ if (typeof restAuthorizedParams.condition === 'function') {
+ if (restAuthorizedParams.condition(has)) {
+ return authorized;
+ }
+ return unauthorized;
+ }
+
+ if (restAuthorizedParams.role || restAuthorizedParams.permission) {
+ if (has(restAuthorizedParams)) {
+ return authorized;
+ }
+ return unauthorized;
}
- return <>{fallback ?? null}>;
+ /**
+ * If neither of the authorization params are passed behave as the ``.
+ * If fallback is present render that instead of rendering nothing.
+ */
+ return authorized;
};
export const RedirectToSignIn = withClerk(({ clerk, ...props }: WithClerkProp) => {
diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts
index 55c0949dbb..439452bb52 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/contexts/AuthContext.ts b/packages/react/src/contexts/AuthContext.ts
index ce346388e4..d829bee2dd 100644
--- a/packages/react/src/contexts/AuthContext.ts
+++ b/packages/react/src/contexts/AuthContext.ts
@@ -1,5 +1,5 @@
import { createContextAndHook } from '@clerk/shared/react';
-import type { ActJWTClaim, MembershipRole } from '@clerk/types';
+import type { ActJWTClaim, MembershipRole, OrganizationCustomPermission } from '@clerk/types';
export const [AuthContext, useAuthContext] = createContextAndHook<{
userId: string | null | undefined;
@@ -8,4 +8,5 @@ export const [AuthContext, useAuthContext] = createContextAndHook<{
orgId: string | null | undefined;
orgRole: MembershipRole | null | undefined;
orgSlug: string | null | undefined;
+ orgPermissions: OrganizationCustomPermission[] | null | undefined;
}>('AuthContext');
diff --git a/packages/react/src/contexts/ClerkContextProvider.tsx b/packages/react/src/contexts/ClerkContextProvider.tsx
index 9cd14735eb..a31bcb18ed 100644
--- a/packages/react/src/contexts/ClerkContextProvider.tsx
+++ b/packages/react/src/contexts/ClerkContextProvider.tsx
@@ -35,10 +35,11 @@ export function ClerkContextProvider(props: ClerkContextProvider): JSX.Element |
const clerkCtx = React.useMemo(() => ({ value: clerk }), [clerkLoaded]);
const clientCtx = React.useMemo(() => ({ value: state.client }), [state.client]);
- const { sessionId, session, userId, user, orgId, actor, organization, orgRole, orgSlug } = derivedState;
+ const { sessionId, session, userId, user, orgId, actor, organization, orgRole, orgSlug, orgPermissions } =
+ derivedState;
const authCtx = React.useMemo(() => {
- const value = { sessionId, userId, actor, orgId, orgRole, orgSlug };
+ const value = { sessionId, userId, actor, orgId, orgRole, orgSlug, orgPermissions };
return { value };
}, [sessionId, userId, actor, orgId, orgRole, orgSlug]);
const userCtx = React.useMemo(() => ({ value: user }), [userId, user]);
diff --git a/packages/react/src/errors.ts b/packages/react/src/errors.ts
index 498e47d280..6f94acf99b 100644
--- a/packages/react/src/errors.ts
+++ b/packages/react/src/errors.ts
@@ -43,3 +43,6 @@ export const customPageWrongProps = (componentName: string) =>
export const customLinkWrongProps = (componentName: string) =>
`Missing props. <${componentName}.Link /> component requires the following props: url, label and labelIcon.`;
+
+export const useAuthHasRequiresRoleOrPermission =
+ 'Missing parameters. `has` from `useAuth` requires a permission or role key to be passed. Example usage: `has({permission: "org:posts:edit"`';
diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts
index d1f735fea5..b8e60b3d1e 100644
--- a/packages/react/src/hooks/useAuth.ts
+++ b/packages/react/src/hooks/useAuth.ts
@@ -1,6 +1,6 @@
import type {
ActJWTClaim,
- experimental__CheckAuthorizationWithoutPermission,
+ CheckAuthorizationWithCustomPermissions,
GetToken,
MembershipRole,
SignOut,
@@ -9,13 +9,12 @@ import { useCallback } from 'react';
import { useAuthContext } from '../contexts/AuthContext';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
-import { invalidStateError } from '../errors';
+import { invalidStateError, useAuthHasRequiresRoleOrPermission } from '../errors';
import { errorThrower } from '../utils';
import { createGetToken, createSignOut } from './utils';
-type experimental__CheckAuthorizationSignedOut = (
- params?: Parameters[0],
-) => false;
+type CheckAuthorizationSignedOut = undefined;
+type CheckAuthorizationWithoutOrgOrUser = (params?: Parameters[0]) => false;
type UseAuthReturn =
| {
@@ -27,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;
}
@@ -43,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: CheckAuthorizationWithoutOrgOrUser;
signOut: SignOut;
getToken: GetToken;
}
@@ -59,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: CheckAuthorizationWithoutOrgOrUser;
signOut: SignOut;
getToken: GetToken;
}
@@ -75,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__CheckAuthorizationWithoutPermission;
+ has: CheckAuthorizationWithCustomPermissions;
signOut: SignOut;
getToken: GetToken;
};
@@ -122,28 +109,33 @@ type UseAuth = () => UseAuthReturn;
* }
*/
export const useAuth: UseAuth = () => {
- const { sessionId, userId, actor, orgId, orgRole, orgSlug } = useAuthContext();
+ const { sessionId, userId, actor, orgId, orgRole, orgSlug, orgPermissions } = useAuthContext();
const isomorphicClerk = useIsomorphicClerkContext();
const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]);
const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]);
const has = useCallback(
- (params?: Parameters[0]) => {
- if (!orgId || !userId || !orgRole) {
- return false;
+ (params: Parameters[0]) => {
+ if (!params?.permission && !params?.role) {
+ errorThrower.throw(useAuthHasRequiresRoleOrPermission);
}
- if (!params) {
+ if (!orgId || !userId || !orgRole || !orgPermissions) {
return false;
}
+ if (params.permission) {
+ return orgPermissions.includes(params.permission);
+ }
+
if (params.role) {
return orgRole === params.role;
}
+
return false;
},
- [orgId, orgRole, userId],
+ [orgId, orgRole, userId, orgPermissions],
);
if (sessionId === undefined && userId === undefined) {
@@ -156,7 +148,7 @@ export const useAuth: UseAuth = () => {
orgId: undefined,
orgRole: undefined,
orgSlug: undefined,
- experimental__has: () => false,
+ has: undefined,
signOut,
getToken,
};
@@ -172,7 +164,7 @@ export const useAuth: UseAuth = () => {
orgId: null,
orgRole: null,
orgSlug: null,
- experimental__has: () => false,
+ has: () => false,
signOut,
getToken,
};
@@ -188,7 +180,7 @@ export const useAuth: UseAuth = () => {
orgId,
orgRole,
orgSlug: orgSlug || null,
- experimental__has: has,
+ has,
signOut,
getToken,
};
@@ -204,7 +196,7 @@ export const useAuth: UseAuth = () => {
orgId: null,
orgRole: null,
orgSlug: null,
- experimental__has: () => false,
+ has: () => false,
signOut,
getToken,
};
diff --git a/packages/react/src/utils/deriveState.ts b/packages/react/src/utils/deriveState.ts
index d699efcaf9..db7c31e71c 100644
--- a/packages/react/src/utils/deriveState.ts
+++ b/packages/react/src/utils/deriveState.ts
@@ -2,6 +2,7 @@ import type {
ActiveSessionResource,
InitialState,
MembershipRole,
+ OrganizationCustomPermission,
OrganizationResource,
Resources,
UserResource,
@@ -22,6 +23,7 @@ const deriveFromSsrInitialState = (initialState: InitialState) => {
const organization = initialState.organization as OrganizationResource;
const orgId = initialState.orgId;
const orgRole = initialState.orgRole as MembershipRole;
+ const orgPermissions = initialState.orgPermissions as OrganizationCustomPermission[];
const orgSlug = initialState.orgSlug;
const actor = initialState.actor;
@@ -33,6 +35,7 @@ const deriveFromSsrInitialState = (initialState: InitialState) => {
organization,
orgId,
orgRole,
+ orgPermissions,
orgSlug,
actor,
};
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index 1bce0fc2f1..6e42a887d7 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -7,7 +7,7 @@ import type { ActJWTClaim } from './jwt';
import type { OAuthProvider } from './oauth';
import type { OrganizationDomainVerificationStatus, OrganizationEnrollmentMode } from './organizationDomain';
import type { OrganizationInvitationStatus } from './organizationInvitation';
-import type { MembershipRole, OrganizationPermission } from './organizationMembership';
+import type { MembershipRole, OrganizationPermissionKey } from './organizationMembership';
import type { OrganizationSettingsJSON } from './organizationSettings';
import type { OrganizationSuggestionStatus } from './organizationSuggestion';
import type { SamlIdpSlug } from './saml';
@@ -17,7 +17,7 @@ import type { SignUpField, SignUpIdentificationField, SignUpStatus } from './sig
import type { OAuthStrategy } from './strategies';
import type { BoxShadow, Color, EmUnit, FontWeight, HexColor } from './theme';
import type { UserSettingsJSON } from './userSettings';
-import type { Autocomplete, CamelToSnake } from './utils';
+import type { CamelToSnake } from './utils';
import type { VerificationStatus } from './verification';
export interface ClerkResourceJSON {
@@ -300,10 +300,7 @@ export interface OrganizationMembershipJSON extends ClerkResourceJSON {
object: 'organization_membership';
id: string;
organization: OrganizationJSON;
- /**
- * @experimental The property is experimental and subject to change in future releases.
- */
- permissions: Autocomplete[];
+ permissions: OrganizationPermissionKey[];
public_metadata: OrganizationMembershipPublicMetadata;
public_user_data: PublicUserDataJSON;
role: MembershipRole;
diff --git a/packages/types/src/jwtv2.ts b/packages/types/src/jwtv2.ts
index e7f1db949c..f1acea3d9b 100644
--- a/packages/types/src/jwtv2.ts
+++ b/packages/types/src/jwtv2.ts
@@ -1,4 +1,4 @@
-import type { MembershipRole } from './organizationMembership';
+import type { MembershipRole, OrganizationCustomPermissionKey } from './organizationMembership';
export interface Jwt {
header: JwtHeader;
@@ -96,6 +96,11 @@ export interface JwtPayload extends CustomJwtSessionClaims {
*/
org_role?: MembershipRole;
+ /**
+ * Active organization role
+ */
+ org_permissions?: OrganizationCustomPermissionKey[];
+
/**
* Any other JWT Claim Set member.
*/
diff --git a/packages/types/src/organizationMembership.ts b/packages/types/src/organizationMembership.ts
index adb18e3bf0..b8d6ea8235 100644
--- a/packages/types/src/organizationMembership.ts
+++ b/packages/types/src/organizationMembership.ts
@@ -1,8 +1,16 @@
-import type { Autocomplete } from 'utils';
-
import type { OrganizationResource } from './organization';
import type { ClerkResource } from './resource';
import type { PublicUserData } from './session';
+import type { Autocomplete } from './utils';
+
+interface Base {
+ permission: string;
+ role: string;
+}
+
+declare global {
+ interface ClerkAuthorization {}
+}
declare global {
/**
@@ -27,10 +35,7 @@ declare global {
export interface OrganizationMembershipResource extends ClerkResource {
id: string;
organization: OrganizationResource;
- /**
- * @experimental The property is experimental and subject to change in future releases.
- */
- permissions: Autocomplete[];
+ permissions: OrganizationPermissionKey[];
publicMetadata: OrganizationMembershipPublicMetadata;
publicUserData: PublicUserData;
role: MembershipRole;
@@ -40,9 +45,30 @@ export interface OrganizationMembershipResource extends ClerkResource {
update: (updateParams: UpdateOrganizationMembershipParams) => Promise;
}
-export type MembershipRole = Autocomplete<'admin' | 'basic_member' | 'guest_member'>;
+export type OrganizationCustomPermissionKey = 'permission' extends keyof ClerkAuthorization
+ ? // @ts-expect-error Typescript cannot infer the existence of the `permission` key even if we checking it above
+ ClerkAuthorization['permission']
+ : Base['permission'];
+
+export type OrganizationCustomRoleKey = 'role' extends keyof ClerkAuthorization
+ ? // @ts-expect-error Typescript cannot infer the existence of the `role` key even if we checking it above
+ ClerkAuthorization['role']
+ : Base['role'];
-export type OrganizationPermission =
+/**
+ * @deprecated This type is deprecated and will be removed in the next major release.
+ * Use `OrganizationCustomRoleKey` instead.
+ * MembershipRole includes `admin`, `basic_member`, `guest_member`. With the introduction of "Custom roles"
+ * these types will no longer match a developer's custom logic.
+ */
+export type MembershipRole = 'role' extends keyof ClerkAuthorization
+ ? // @ts-expect-error Typescript cannot infer the existence of the `role` key even if we checking it above
+ // Disabling eslint rule because the error causes the type to become any when accessing a property that does not exist
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
+ ClerkAuthorization['role'] | 'admin' | 'basic_member' | 'guest_member'
+ : Autocomplete<'admin' | 'basic_member' | 'guest_member'>;
+
+export type OrganizationSystemPermissionKey =
| 'org:sys_domains:manage'
| 'org:sys_profile:manage'
| 'org:sys_profile:delete'
@@ -50,6 +76,17 @@ export type OrganizationPermission =
| 'org:sys_memberships:manage'
| 'org:sys_domains:read';
+/**
+ * OrganizationPermissionKey is a combination of system and custom permissions.
+ * System permissions are only accessible from FAPI and client-side operations/utils
+ */
+export type OrganizationPermissionKey = 'permission' extends keyof ClerkAuthorization
+ ? // @ts-expect-error Typescript cannot infer the existence of the `permission` key even if we checking it above
+ // Disabling eslint rule because the error causes the type to become any when accessing a property that does not exist
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
+ ClerkAuthorization['permission'] | OrganizationSystemPermissionKey
+ : Autocomplete;
+
export type UpdateOrganizationMembershipParams = {
role: MembershipRole;
};
diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts
index d7dd6dc9e9..caafd9c723 100644
--- a/packages/types/src/session.ts
+++ b/packages/types/src/session.ts
@@ -1,53 +1,39 @@
-import type { Autocomplete } from 'utils';
-
import type { ActJWTClaim } from './jwt';
-import type { OrganizationPermission } from './organizationMembership';
+import type {
+ MembershipRole,
+ OrganizationCustomPermissionKey,
+ OrganizationCustomRoleKey,
+ OrganizationPermissionKey,
+} from './organizationMembership';
import type { ClerkResource } from './resource';
import type { TokenResource } from './token';
import type { UserResource } from './user';
-export type experimental__CheckAuthorizationWithoutPermission = (
- isAuthorizedParams: CheckAuthorizationParamsWithoutPermission,
-) => boolean;
+export type CheckAuthorizationFn = (isAuthorizedParams: Params) => boolean;
+
+export type CheckAuthorizationWithCustomPermissions =
+ CheckAuthorizationFn;
-type CheckAuthorizationParamsWithoutPermission =
+type CheckAuthorizationParamsWithCustomPermissions =
| {
- some: {
- role: string;
- }[];
- role?: never;
+ role: OrganizationCustomRoleKey;
+ permission?: never;
}
| {
- some?: never;
- role: string;
+ role?: never;
+ permission: OrganizationCustomPermissionKey;
};
-export type CheckAuthorization = (isAuthorizedParams: CheckAuthorizationParams) => boolean;
+export type CheckAuthorization = CheckAuthorizationFn;
type CheckAuthorizationParams =
| {
- some: (
- | {
- role: string;
- permission?: never;
- }
- | {
- role?: never;
- permission: Autocomplete;
- }
- )[];
- role?: never;
- permission?: never;
- }
- | {
- some?: never;
- role: string;
+ role: MembershipRole;
permission?: never;
}
| {
- some?: never;
role?: never;
- permission: Autocomplete;
+ permission: OrganizationPermissionKey;
};
export interface SessionResource extends ClerkResource {
@@ -65,10 +51,7 @@ export interface SessionResource extends ClerkResource {
remove: () => Promise;
touch: () => Promise;
getToken: GetToken;
- /**
- * @experimental The method is experimental and subject to change in future releases.
- */
- experimental__checkAuthorization: CheckAuthorization;
+ checkAuthorization: CheckAuthorization;
clearCache: () => void;
createdAt: Date;
updatedAt: Date;
diff --git a/packages/types/src/ssr.ts b/packages/types/src/ssr.ts
index 3cad74c1de..7f69f07e67 100644
--- a/packages/types/src/ssr.ts
+++ b/packages/types/src/ssr.ts
@@ -1,6 +1,6 @@
import type { ActClaim, JwtPayload } from './jwtv2';
import type { OrganizationResource } from './organization';
-import type { MembershipRole } from './organizationMembership';
+import type { MembershipRole, OrganizationCustomPermissionKey } from './organizationMembership';
import type { SessionResource } from './session';
import type { UserResource } from './user';
import type { Serializable } from './utils';
@@ -18,5 +18,6 @@ export type InitialState = Serializable<{
orgId: string | undefined;
orgRole: MembershipRole | undefined;
orgSlug: string | undefined;
+ orgPermissions: OrganizationCustomPermissionKey[] | undefined;
organization: OrganizationResource | undefined;
}>;