[0];
+type GateProps = PropsWithChildren<
+ GateParams & {
+ fallback?: ReactNode;
+ redirectTo?: string;
+ }
+>;
+
+export const useGate = (params: GateParams) => {
+ const { isAuthorized } = useCoreSession();
+ const { data: isAuthorizedUser } = useFetch(isAuthorized, params);
+
+ return {
+ isAuthorizedUser,
+ };
+};
+
+export const Gate = (gateProps: GateProps) => {
+ const { children, fallback, redirectTo, ...restAuthorizedParams } = gateProps;
+
+ const { isAuthorizedUser } = useGate(restAuthorizedParams);
+
+ const { navigate } = useRouter();
+
+ useEffect(() => {
+ // wait for promise to resolve
+ if (typeof isAuthorizedUser === 'boolean' && !isAuthorizedUser && redirectTo) {
+ void navigate(redirectTo);
+ }
+ }, [isAuthorizedUser, redirectTo]);
+
+ // wait for promise to resolve
+ if (typeof isAuthorizedUser === 'boolean' && !isAuthorizedUser && fallback) {
+ return <>{fallback}>;
+ }
+
+ if (isAuthorizedUser) {
+ return <>{children}>;
+ }
+
+ return null;
+};
+
+export function withGate(Component: ComponentType
, gateProps: GateProps): React.ComponentType
{
+ const displayName = Component.displayName || Component.name || 'Component';
+ const HOC = (props: P) => {
+ return (
+
+
+
+ );
+ };
+
+ HOC.displayName = `withGate(${displayName})`;
+
+ return HOC;
+}
diff --git a/packages/clerk-js/src/ui/common/index.ts b/packages/clerk-js/src/ui/common/index.ts
index bdd3c08c14..b08b98ac79 100644
--- a/packages/clerk-js/src/ui/common/index.ts
+++ b/packages/clerk-js/src/ui/common/index.ts
@@ -2,6 +2,7 @@ export * from './BlockButtons';
export * from './constants';
export * from './CalloutWithAction';
export * from './forms';
+export * from './Gate';
export * from './InfiniteListSpinner';
export * from './redirects';
export * from './verification';
diff --git a/packages/clerk-js/src/ui/hooks/useFetch.ts b/packages/clerk-js/src/ui/hooks/useFetch.ts
index 514ac87f7e..f725b520bb 100644
--- a/packages/clerk-js/src/ui/hooks/useFetch.ts
+++ b/packages/clerk-js/src/ui/hooks/useFetch.ts
@@ -24,11 +24,11 @@ export const useFetch = (
requestStatus.setLoading();
fetcherRef
.current(params)
- .then(domain => {
+ .then(result => {
requestStatus.setIdle();
- if (domain) {
- setData({ ...domain });
- callbacks?.onSuccess?.({ ...domain });
+ if (typeof result !== 'undefined') {
+ setData(typeof result === 'object' ? { ...result } : result);
+ callbacks?.onSuccess?.(typeof result === 'object' ? { ...result } : result);
}
})
.catch(() => {
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index aca6f80b7e..2a896ea8f1 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -8,7 +8,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 } from './organizationMembership';
+import type { MembershipRole, OrganizationPermission } from './organizationMembership';
import type { OrganizationSettingsJSON } from './organizationSettings';
import type { OrganizationSuggestionStatus } from './organizationSuggestion';
import type { SamlIdpSlug } from './saml';
@@ -322,6 +322,12 @@ export interface OrganizationMembershipJSON extends ClerkResourceJSON {
object: 'organization_membership';
id: string;
organization: OrganizationJSON;
+ /**
+ * @experimental The property is experimental and subject to change in future releases.
+ */
+ // Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
+ // eslint-disable-next-line
+ permissions: (OrganizationPermission | (string & {}))[];
public_metadata: OrganizationMembershipPublicMetadata;
public_user_data: PublicUserDataJSON;
role: MembershipRole;
diff --git a/packages/types/src/organizationMembership.ts b/packages/types/src/organizationMembership.ts
index 6e60ee9f20..8786088955 100644
--- a/packages/types/src/organizationMembership.ts
+++ b/packages/types/src/organizationMembership.ts
@@ -25,6 +25,12 @@ declare global {
export interface OrganizationMembershipResource extends ClerkResource {
id: string;
organization: OrganizationResource;
+ /**
+ * @experimental The property is experimental and subject to change in future releases.
+ */
+ // Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
+ // eslint-disable-next-line
+ permissions: (OrganizationPermission | (string & {}))[];
publicMetadata: OrganizationMembershipPublicMetadata;
publicUserData: PublicUserData;
role: MembershipRole;
@@ -36,6 +42,16 @@ export interface OrganizationMembershipResource extends ClerkResource {
export type MembershipRole = 'admin' | 'basic_member' | 'guest_member';
+export type OrganizationPermission =
+ | 'org:domains:manage'
+ | 'org:domains:delete'
+ | 'org:profile:manage'
+ | 'org:profile:delete'
+ | 'org:memberships:read'
+ | 'org:memberships:manage'
+ | 'org:memberships:delete'
+ | 'org:domains:read';
+
export type UpdateOrganizationMembershipParams = {
role: MembershipRole;
};
diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts
index 416acb2ed5..006dfc25dc 100644
--- a/packages/types/src/session.ts
+++ b/packages/types/src/session.ts
@@ -1,8 +1,20 @@
import type { ActJWTClaim } from './jwt';
+import type { OrganizationPermission } from './organizationMembership';
import type { ClerkResource } from './resource';
import type { TokenResource } from './token';
import type { UserResource } from './user';
+export type IsAuthorized = (isAuthorizedParams: IsAuthorizedParams) => Promise;
+
+interface IsAuthorizedParams {
+ // Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
+ // eslint-disable-next-line
+ permission?: OrganizationPermission | (string & {});
+ role?: string;
+}
+
+type IsAuthorizedReturnValues = boolean;
+
export interface SessionResource extends ClerkResource {
id: string;
status: SessionStatus;
@@ -18,6 +30,10 @@ export interface SessionResource extends ClerkResource {
remove: () => Promise;
touch: () => Promise;
getToken: GetToken;
+ /**
+ * @experimental The method is experimental and subject to change in future releases.
+ */
+ isAuthorized: IsAuthorized;
clearCache: () => void;
createdAt: Date;
updatedAt: Date;