diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedList.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedList.tsx index 35e152baab2..e446a04970e 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedList.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedList.tsx @@ -1,6 +1,5 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' -import { useState } from 'react' import { Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { PageSection, @@ -13,29 +12,20 @@ import { import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { OAuthAppsAuthorizedRow } from './OAuthAppsAuthorizedRow' -import { OAuthAppsMemberGrantsDialog } from './OAuthAppsMemberGrantsDialog' -import { OAuthAppsRevokeDialog } from './OAuthAppsRevokeDialog' import { AlertError } from '@/components/ui/AlertError' import { NoPermission } from '@/components/ui/NoPermission' -import { useOAuthAuthorizedAppsQuery } from '@/data/oauth-apps/oauth-apps-authorized-apps-query' -import type { OAuthAppOverviewItem } from '@/data/oauth-apps/types' +import { useOAuthApprovalsQuery } from '@/data/oauth-apps/oauth-apps-approvals-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' export const OAuthAppsAuthorizedList = () => { const { slug } = useParams() - const [selectedAppForGrants, setSelectedAppForGrants] = useState() - const [selectedAppToRevoke, setSelectedAppToRevoke] = useState() const { can: canReadOAuthApps, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.READ, 'approved_oauth_apps' ) - const { can: canRevokeOAuthApps } = useAsyncCheckPermissions( - PermissionAction.DELETE, - 'approved_oauth_apps' - ) - const { data: apps, isPending, isSuccess, isError, error } = useOAuthAuthorizedAppsQuery({ slug }) + const { data: apps, isPending, isSuccess, isError, error } = useOAuthApprovalsQuery({ slug }) return ( @@ -53,7 +43,6 @@ export const OAuthAppsAuthorizedList = () => {
-
)} @@ -69,47 +58,26 @@ export const OAuthAppsAuthorizedList = () => { App - Status - Grants - - Actions - + Access {apps.data.length === 0 ? ( - +

No apps have been authorized in this organization yet.

) : ( - apps.data.map((app) => ( - setSelectedAppForGrants(app)} - onSelectRevoke={() => setSelectedAppToRevoke(app)} - /> - )) + apps.data.map((app) => ) )}
)} - - setSelectedAppForGrants(undefined)} - /> - setSelectedAppToRevoke(undefined)} - />
) } diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedRow.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedRow.tsx index 0db02335204..06f4a527c39 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedRow.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsAuthorizedRow.tsx @@ -1,35 +1,12 @@ -import { MoreVertical } from 'lucide-react' -import { - Badge, - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - TableCell, - TableRow, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' +import { TableCell, TableRow } from 'ui' -import type { OAuthAppOverviewItem } from '@/data/oauth-apps/types' +import type { OAuthApprovalItem } from '@/data/oauth-apps/types' export interface OAuthAppsAuthorizedRowProps { - app: OAuthAppOverviewItem - canRevoke: boolean - onSelectViewGrants: () => void - onSelectRevoke: () => void + app: OAuthApprovalItem } -export const OAuthAppsAuthorizedRow = ({ - app, - canRevoke, - onSelectViewGrants, - onSelectRevoke, -}: OAuthAppsAuthorizedRowProps) => { - const showRevoke = canRevoke && app.status === 'active' - +export const OAuthAppsAuthorizedRow = ({ app }: OAuthAppsAuthorizedRowProps) => { return ( @@ -45,41 +22,7 @@ export const OAuthAppsAuthorizedRow = ({

- - {app.status.toUpperCase()} - - {getGrantsLabel(app)} - - - - - - - - - - ) -} - -function getGrantLabel(grant: OAuthGrantItem) { - if (grant.user) return grant.user.email - return 'Organization-wide' -} diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsRevokeDialog.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsRevokeDialog.tsx deleted file mode 100644 index a03d8e3581c..00000000000 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppsRevokeDialog.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useParams } from 'common' -import { toast } from 'sonner' -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogSection, - DialogTitle, -} from 'ui' - -import { useOAuthAppRevokeMutation } from '@/data/oauth-apps/oauth-apps-revoke-mutation' -import type { OAuthAppOverviewItem } from '@/data/oauth-apps/types' - -export interface OAuthAppsRevokeDialogProps { - app?: OAuthAppOverviewItem - onClose: () => void -} - -export const OAuthAppsRevokeDialog = ({ app, onClose }: OAuthAppsRevokeDialogProps) => { - const { slug } = useParams() - const { mutate: revokeApp, isPending } = useOAuthAppRevokeMutation({ - onSuccess: () => { - toast.success(`Revoked access for ${app?.name}`) - onClose() - }, - }) - - const memberGrantCount = app?.member_grant_count ?? 0 - const hasOrgGrant = Boolean(app?.org_grant) - - return ( - !open && onClose()}> - - - Revoke access for {app?.name} - - - -

- This revokes the app at{' '} - organization level and affects{' '} - all users. -

- -
-

Caveats:

-
    -
  • - – - - {memberGrantCount} {memberGrantCount === 1 ? 'member grant' : 'member grants'}{' '} - will be revoked. - -
  • - {hasOrgGrant && ( -
  • - – - The organization-wide grant will be revoked. -
  • - )} -
  • - – - The app stays blocked for this organization until an admin unblocks it. -
  • -
  • - – - Every member loses access on the apps next request. -
  • -
  • - – - Members will need to authorize again to reconnect. -
  • -
  • - – - - This is not a per-user - revocation. - -
  • -
-
-
- - - - - -
-
- ) -} diff --git a/knip.jsonc b/knip.jsonc index 7a79952a7e0..717c5fc0285 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -65,6 +65,16 @@ // read are documentation of the valid values, not dead code. Scoped to // this file so `enumMembers` keeps working everywhere else. "data/database/constraints-query.ts": ["enumMembers"], + // Org settings authorized-apps data layer, landed in #49476 ahead of the + // tables that consume it. Scoped to the remaining files rather than the whole + // folder so everything else here stays gated. + // TODO(FE-4330): remove — used from #50529, drop this entry in that PR. + "data/oauth-apps/oauth-apps-approvals-query.ts": ["files"], + "data/oauth-apps/oauth-apps-grants-query.ts": ["files"], + "data/oauth-apps/oauth-apps-member-grants-query.ts": ["files"], + "data/oauth-apps/oauth-apps-preflight-validation-query.ts": ["files"], + "data/oauth-apps/oauth-apps-revoke-grant-mutation.ts": ["files"], + "data/oauth-apps/oauth-apps-revoke-mutation.ts": ["files"], "hooks/misc/useTrackExperimentExposure.ts": ["files"], // Staging-only pinned Postgres image + FDW request payload for creating // Warehouse test projects by hand. Nothing imports it yet — it's kept