Skip to content

Commit

Permalink
feat(types,nextjs,clerk-react,backend): Rename Gate to Protect
Browse files Browse the repository at this point in the history
- 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 `<SignedIn>` if no authorization props are passed.
- `has` will throw an error if neither `permission` or `role` is passed.
  • Loading branch information
panteliselef committed Dec 3, 2023
1 parent 9dfe2b3 commit 5d942e6
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 107 deletions.
40 changes: 14 additions & 26 deletions packages/backend/src/tokens/authObjects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {
ActClaim,
experimental__CheckAuthorizationWithCustomPermissions,
CheckAuthorizationWithCustomPermissions,
JwtPayload,
OrganizationCustomPermission,
OrganizationCustomRole,
Expand Down Expand Up @@ -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;
};

Expand All @@ -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;
};

Expand Down Expand Up @@ -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 }),
};
}
Expand All @@ -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),
};
}
Expand Down Expand Up @@ -178,7 +172,7 @@ export function sanitizeAuthObject<T extends Record<any, any>>(authObject: T): T
export const makeAuthObjectSerializable = <T extends Record<string, unknown>>(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;
};

Expand All @@ -202,6 +196,7 @@ const createGetToken: CreateGetToken = params => {
};
};

//MAYBE move this to @shared
const createHasAuthorization =
({
orgId,
Expand All @@ -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;
}

Expand All @@ -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;
};
58 changes: 53 additions & 5 deletions packages/nextjs/src/app-router/server/controlComponents.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,21 +20,65 @@ export function SignedOut(props: React.PropsWithChildren) {
}

type GateServerComponentProps = React.PropsWithChildren<
Parameters<experimental__CheckAuthorizationWithCustomPermissions>[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;
}
>;

/**
* @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 `<SignedIn/>`
*/
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}</>;
}
2 changes: 1 addition & 1 deletion packages/nextjs/src/client-boundary/controlComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export {
ClerkLoading,
SignedOut,
SignedIn,
Experimental__Gate,
Protect,
RedirectToSignIn,
RedirectToSignUp,
RedirectToUserProfile,
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/components.client.ts
Original file line number Diff line number Diff line change
@@ -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';
6 changes: 3 additions & 3 deletions packages/nextjs/src/components.server.ts
Original file line number Diff line number Diff line change
@@ -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;
};
3 changes: 1 addition & 2 deletions packages/nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down
64 changes: 55 additions & 9 deletions packages/react/src/components/controlComponents.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -41,22 +46,63 @@ export const ClerkLoading = ({ children }: React.PropsWithChildren<unknown>): JS
return <>{children}</>;
};

type GateProps = React.PropsWithChildren<
Parameters<experimental__CheckAuthorizationWithCustomPermissions>[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 `<SignedIn/>`
*/
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}</>;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export {
ClerkLoading,
SignedOut,
SignedIn,
experimental__Gate as Experimental__Gate,
Protect,
RedirectToSignIn,
RedirectToSignUp,
RedirectToUserProfile,
Expand Down
Loading

0 comments on commit 5d942e6

Please sign in to comment.