Skip to content

Commit

Permalink
permission-react: more restrictive typing for usePermission and Permi…
Browse files Browse the repository at this point in the history
…ssionedRoute

Signed-off-by: Mike Lewis <mtlewis@users.noreply.github.com>
  • Loading branch information
mtlewis authored and joeporpeglia committed Mar 23, 2022
1 parent 9da18e4 commit 5bdcb8c
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-beers-kiss.md
@@ -0,0 +1,5 @@
---
'@backstage/plugin-permission-react': minor
---

**BREAKING**: More restrictive typing for `usePermission` hook and `PermissionedRoute` component. It's no longer possible to pass a `resourceRef` unless the permission is of type `ResourcPermission`.
23 changes: 16 additions & 7 deletions plugins/permission-react/api-report.md
Expand Up @@ -12,6 +12,7 @@ import { DiscoveryApi } from '@backstage/core-plugin-api';
import { IdentityApi } from '@backstage/core-plugin-api';
import { Permission } from '@backstage/plugin-permission-common';
import { ReactElement } from 'react';
import { ResourcePermission } from '@backstage/plugin-permission-common';
import { Route } from 'react-router';

// @public (undocumented)
Expand Down Expand Up @@ -44,17 +45,25 @@ export const permissionApiRef: ApiRef<PermissionApi>;
// @public
export const PermissionedRoute: (
props: ComponentProps<typeof Route> & {
permission: Permission;
resourceRef?: string;
errorComponent?: ReactElement | null;
},
} & (
| {
permission: Exclude<Permission, ResourcePermission>;
resourceRef?: never;
}
| {
permission: ResourcePermission;
resourceRef: string | undefined;
}
),
) => JSX.Element;

// @public
export const usePermission: (
permission: Permission,
resourceRef?: string | undefined,
) => AsyncPermissionResult;
export function usePermission(
...[permission, resourceRef]:
| [ResourcePermission, string | undefined]
| [Exclude<Permission, ResourcePermission>]
): AsyncPermissionResult;

// (No @packageDocumentation comment for this package)
```
26 changes: 21 additions & 5 deletions plugins/permission-react/src/components/PermissionedRoute.tsx
Expand Up @@ -18,7 +18,11 @@ import React, { ComponentProps, ReactElement } from 'react';
import { Route } from 'react-router';
import { useApp } from '@backstage/core-plugin-api';
import { usePermission } from '../hooks';
import { Permission } from '@backstage/plugin-permission-common';
import {
isResourcePermission,
Permission,
ResourcePermission,
} from '@backstage/plugin-permission-common';

/**
* Returns a React Router Route which only renders the element when authorized. If unauthorized, the Route will render a
Expand All @@ -28,13 +32,25 @@ import { Permission } from '@backstage/plugin-permission-common';
*/
export const PermissionedRoute = (
props: ComponentProps<typeof Route> & {
permission: Permission;
resourceRef?: string;
errorComponent?: ReactElement | null;
},
} & (
| {
permission: Exclude<Permission, ResourcePermission>;
resourceRef?: never;
}
| {
permission: ResourcePermission;
resourceRef: string | undefined;
}
),
) => {
const { permission, resourceRef, errorComponent, ...otherProps } = props;
const permissionResult = usePermission(permission, resourceRef);

const permissionResult = usePermission(
...(isResourcePermission(permission)
? [permission, resourceRef]
: [permission]),
);
const app = useApp();
const { NotFoundErrorPage } = app.getComponents();

Expand Down
38 changes: 28 additions & 10 deletions plugins/permission-react/src/hooks/usePermission.ts
Expand Up @@ -17,8 +17,11 @@
import { useApi } from '@backstage/core-plugin-api';
import { permissionApiRef } from '../apis';
import {
AuthorizeQuery,
AuthorizeResult,
isResourcePermission,
Permission,
ResourcePermission,
} from '@backstage/plugin-permission-common';
import useSWR from 'swr';

Expand All @@ -30,25 +33,40 @@ export type AsyncPermissionResult = {
};

/**
* React hook utility for authorization. Given a
* {@link @backstage/plugin-permission-common#Permission} and an optional
* resourceRef, it will return whether or not access is allowed (for the given
* resource, if resourceRef is provided). See
* React hook utility for authorization. Given either a non-resource
* {@link @backstage/plugin-permission-common#Permission} or a
* {@link @backstage/plugin-permission-common#ResourcePermission} and an
* optional resourceRef, it will return whether or not access is allowed (for
* the given resource, if resourceRef is provided). See
* {@link @backstage/plugin-permission-common/PermissionClient#authorize} for
* more details.
*
* The resourceRef parameter is optional to allow calling this hook with an
* entity that might be loading asynchronously, but when resourceRef is not
* supplied, the value of `allowed` will always be false.
*
* Note: This hook uses stale-while-revalidate to help avoid flicker in UI
* elements that would be conditionally rendered based on the `allowed` result
* of this hook.
* @public
*/
export const usePermission = (
permission: Permission,
resourceRef?: string,
): AsyncPermissionResult => {
export function usePermission(
...[permission, resourceRef]:
| [ResourcePermission, string | undefined]
| [Exclude<Permission, ResourcePermission>]
): AsyncPermissionResult {
const permissionApi = useApi(permissionApiRef);
const { data, error } = useSWR({ permission, resourceRef }, async args => {
const { result } = await permissionApi.authorize(args);
// We could make the resourceRef parameter required to avoid this check, but
// it would make using this hook difficult in situations where the entity
// must be asynchronously loaded, so instead we short-circuit to a deny when
// no resourceRef is supplied, on the assumption that the resourceRef is
// still loading outside the hook.
if (isResourcePermission(args.permission) && !args.resourceRef) {
return AuthorizeResult.DENY;
}

const { result } = await permissionApi.authorize(args as AuthorizeQuery);
return result;
});

Expand All @@ -59,4 +77,4 @@ export const usePermission = (
return { loading: true, allowed: false };
}
return { loading: false, allowed: data === AuthorizeResult.ALLOW };
};
}

0 comments on commit 5bdcb8c

Please sign in to comment.