Home / Function/ OAuthAppsList() — supabase Function Reference

OAuthAppsList() — supabase Function Reference

Architecture documentation for the OAuthAppsList() function in OAuthAppsList.tsx from the supabase codebase.

Entity Profile

Dependency Diagram

graph TD
  726abde8_eb5f_57a2_7d63_0eb5c9205822["OAuthAppsList()"]
  1f339641_26f9_a527_6ef9_513d5304cef6["filterOAuthApps()"]
  726abde8_eb5f_57a2_7d63_0eb5c9205822 -->|calls| 1f339641_26f9_a527_6ef9_513d5304cef6
  style 726abde8_eb5f_57a2_7d63_0eb5c9205822 fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/Auth/OAuthApps/OAuthAppsList.tsx lines 63–466

export const OAuthAppsList = () => {
  const { ref: projectRef } = useParams()
  const { data: authConfig, isPending: isAuthConfigLoading } = useAuthConfigQuery({ projectRef })
  const isOAuthServerEnabled = !!authConfig?.OAUTH_SERVER_ENABLED
  const [newOAuthApp, setNewOAuthApp] = useState<OAuthClient | undefined>(undefined)
  const [showRegenerateDialog, setShowRegenerateDialog] = useState(false)
  const [selectedApp, setSelectedApp] = useState<OAuthClient>()
  const [filteredRegistrationTypes, setFilteredRegistrationTypes] = useState<string[]>([])
  const [filteredClientTypes, setFilteredClientTypes] = useState<string[]>([])
  const deletingOAuthAppIdRef = useRef<string | null>(null)

  const { data, isPending: isLoading, isError, error } = useOAuthServerAppsQuery({ projectRef })

  const { mutateAsync: regenerateSecret, isPending: isRegenerating } =
    useOAuthServerAppRegenerateSecretMutation({
      onSuccess: (data) => {
        if (data) {
          setNewOAuthApp(data)
        }
      },
    })

  const { data: endpointData } = useProjectEndpointQuery({ projectRef })

  const oAuthApps = useMemo(() => data?.clients || [], [data])

  const [showCreateSheet, setShowCreateSheet] = useQueryState(
    'new',
    parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
  )

  // Prevent opening the create sheet if OAuth Server is disabled
  useEffect(() => {
    if (!isOAuthServerEnabled && showCreateSheet) {
      setShowCreateSheet(false)
    }
  }, [isOAuthServerEnabled, showCreateSheet, setShowCreateSheet])

  const { setValue: setSelectedAppToEdit, value: appToEdit } = useQueryStateWithSelect({
    urlKey: 'edit',
    select: (client_id: string) =>
      client_id ? oAuthApps?.find((app) => app.client_id === client_id) : undefined,
    enabled: !!oAuthApps?.length,
    onError: (_error, selectedId) =>
      handleErrorOnDelete(deletingOAuthAppIdRef, selectedId, `OAuth App not found`),
  })

  const { setValue: setSelectedAppToDelete, value: appToDelete } = useQueryStateWithSelect({
    urlKey: 'delete',
    select: (client_id: string) =>
      client_id ? oAuthApps?.find((app) => app.client_id === client_id) : undefined,
    enabled: !!oAuthApps?.length,
    onError: (_error, selectedId) =>
      handleErrorOnDelete(deletingOAuthAppIdRef, selectedId, `OAuth App not found`),
  })

  const { mutate: deleteOAuthApp, isPending: isDeletingApp } = useOAuthServerAppDeleteMutation({
    onSuccess: () => {
      toast.success(`Successfully deleted OAuth app`)
      setSelectedAppToDelete(null)
    },
    onError: () => {
      deletingOAuthAppIdRef.current = null
    },
  })

  const [filterString, setFilterString] = useState<string>('')

  const [sort, setSort] = useQueryState(
    'sort',
    parseAsStringLiteral<OAuthAppsSort>(OAUTH_APPS_SORT_VALUES).withDefault('name:asc')
  )

  const filteredAndSortedOAuthApps = useMemo(() => {
    const filtered = filterOAuthApps({
      apps: oAuthApps,
      searchString: filterString,
      registrationTypes: filteredRegistrationTypes,
      clientTypes: filteredClientTypes,
    })

    const [sortCol, sortOrder] = sort.split(':') as [OAuthAppsSortColumn, OAuthAppsSortOrder]
    const orderMultiplier = sortOrder === 'asc' ? 1 : -1

    return filtered.sort((a, b) => {
      if (sortCol === 'name') {
        return a.client_name.localeCompare(b.client_name) * orderMultiplier
      }
      if (sortCol === 'client_type') {
        return a.client_type.localeCompare(b.client_type) * orderMultiplier
      }
      if (sortCol === 'registration_type') {
        return a.registration_type.localeCompare(b.registration_type) * orderMultiplier
      }
      if (sortCol === 'created_at') {
        return (
          (new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) * orderMultiplier
        )
      }
      return 0
    })
  }, [oAuthApps, filterString, filteredRegistrationTypes, filteredClientTypes, sort])

  const hasActiveFilters =
    filterString.length > 0 ||
    filteredRegistrationTypes.length > 0 ||
    filteredClientTypes.length > 0

  const handleResetFilters = () => {
    setFilterString('')
    setFilteredRegistrationTypes([])
    setFilteredClientTypes([])
  }

  const handleSortChange = (column: OAuthAppsSortColumn) => {
    const [currentCol, currentOrder] = sort.split(':') as [OAuthAppsSortColumn, OAuthAppsSortOrder]
    if (currentCol === column) {
      // Cycle through: asc -> desc -> no sort (default)
      if (currentOrder === 'asc') {
        setSort(`${column}:desc` as OAuthAppsSort)
      } else {
        // Reset to default sort (name:asc)
        setSort('name:asc')
      }
    } else {
      // New column, start with asc
      setSort(`${column}:asc` as OAuthAppsSort)
    }
  }

  const isCreateMode = showCreateSheet && isOAuthServerEnabled
  const isEditMode = !!appToEdit
  const isCreateOrUpdateSheetVisible = isCreateMode || isEditMode

  if (isAuthConfigLoading || (isOAuthServerEnabled && isLoading)) {
    return <GenericSkeletonLoader />
  }

  if (isError) {
    return <AlertError error={error} subject="Failed to retrieve OAuth Server apps" />
  }

  return (
    <>
      <div className="flex flex-col gap-y-4">
        {newOAuthApp?.client_secret && (
          <NewOAuthAppBanner oauthApp={newOAuthApp} onClose={() => setNewOAuthApp(undefined)} />
        )}
        {!isOAuthServerEnabled && (
          <Admonition
            type="default"
            layout="horizontal"
            className="mb-8"
            title="OAuth Server is disabled"
            description="Enable OAuth Server to make your project act as an identity provider for third-party applications."
            actions={
              <Button asChild type="default">
                <Link href={`/project/${projectRef}/auth/oauth-server`}>OAuth Server Settings</Link>
              </Button>
            }
          />
        )}
        <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2 flex-wrap">
          <div className="flex flex-col lg:flex-row lg:items-center gap-2">
            <Input
              placeholder="Search OAuth apps"
              size="tiny"
              icon={<Search />}
              value={filterString}
              className="w-full lg:w-52"
              onChange={(e) => setFilterString(e.target.value)}
            />
            <FilterPopover
              name="Registration Type"
              options={OAUTH_APP_REGISTRATION_TYPE_OPTIONS}
              labelKey="name"
              valueKey="value"
              iconKey="icon"
              activeOptions={filteredRegistrationTypes}
              labelClass="text-xs text-foreground-light"
              maxHeightClass="h-[190px]"
              className="w-52"
              onSaveFilters={setFilteredRegistrationTypes}
            />
            <FilterPopover
              name="Client Type"
              options={OAUTH_APP_CLIENT_TYPE_OPTIONS}
              labelKey="name"
              valueKey="value"
              iconKey="icon"
              activeOptions={filteredClientTypes}
              labelClass="text-xs text-foreground-light"
              maxHeightClass="h-[190px]"
              className="w-52"
              onSaveFilters={setFilteredClientTypes}
            />
            {hasActiveFilters && (
              <Button
                type="default"
                size="tiny"
                className="px-1"
                icon={<X />}
                onClick={handleResetFilters}
              />
            )}
          </div>
          <div className="flex items-center gap-x-2">
            <ButtonTooltip
              disabled={!isOAuthServerEnabled}
              icon={<Plus />}
              onClick={() => setShowCreateSheet(true)}
              className="flex-grow"
              tooltip={{
                content: {
                  side: 'bottom',
                  text: !isOAuthServerEnabled
                    ? 'OAuth server must be enabled in settings'
                    : undefined,
                },
              }}
            >
              New OAuth App
            </ButtonTooltip>
          </div>
        </div>

        <div className="w-full overflow-hidden overflow-x-auto">
          <Card className="@container">
            <Table containerProps={{ stickyLastColumn: true }}>
              <TableHeader>
                <TableRow>
                  <TableHead>
                    <TableHeadSort column="name" currentSort={sort} onSortChange={handleSortChange}>
                      Name
                    </TableHeadSort>
                  </TableHead>
                  <TableHead>Client ID</TableHead>
                  <TableHead>
                    <TableHeadSort
                      column="client_type"
                      currentSort={sort}
                      onSortChange={handleSortChange}
                    >
                      Client Type
                    </TableHeadSort>
                  </TableHead>
                  <TableHead>
                    <TableHeadSort
                      column="registration_type"
                      currentSort={sort}
                      onSortChange={handleSortChange}
                    >
                      Registration Type
                    </TableHeadSort>
                  </TableHead>
                  <TableHead>
                    <TableHeadSort
                      column="created_at"
                      currentSort={sort}
                      onSortChange={handleSortChange}
                    >
                      Created
                    </TableHeadSort>
                  </TableHead>
                  <TableHead className="w-8 px-0">
                    <div className="!bg-200 px-4 w-full h-full flex items-center border-l @[944px]:border-l-0" />
                  </TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {filteredAndSortedOAuthApps.length === 0 && (
                  <TableRow>
                    <TableCell colSpan={6}>
                      <p className="text-foreground-lighter">No OAuth apps found</p>
                    </TableCell>
                  </TableRow>
                )}
                {filteredAndSortedOAuthApps.length > 0 &&
                  filteredAndSortedOAuthApps.map((app) => (
                    <TableRow key={app.client_id} className="w-full">
                      <TableCell className="w-48 max-w-48 flex" title={app.client_name}>
                        <Button
                          type="text"
                          className="text-link-table-cell text-sm p-0 hover:bg-transparent title [&>span]:!w-full"
                          onClick={() => setSelectedAppToEdit(app.client_id)}
                          title={app.client_name}
                        >
                          {app.client_name}
                        </Button>
                      </TableCell>
                      <TableCell title={app.client_id}>
                        <Badge className="font-mono">{app.client_id}</Badge>
                      </TableCell>
                      <TableCell className="text-xs text-foreground-light max-w-28 capitalize">
                        {app.client_type}
                      </TableCell>
                      <TableCell className="text-xs text-foreground-light max-w-28 capitalize">
                        {app.registration_type}
                      </TableCell>
                      <TableCell className="text-xs text-foreground-light min-w-28 max-w-40 w-1/6">
                        <TimestampInfo utcTimestamp={app.created_at} labelFormat="D MMM, YYYY" />
                      </TableCell>
                      <TableCell className="max-w-20 bg-surface-100 @[944px]:hover:bg-surface-200 px-6">
                        <div className="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center border-l @[944px]:border-l-0">
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <Button type="default" className="px-1" icon={<MoreVertical />} />
                            </DropdownMenuTrigger>
                            <DropdownMenuContent side="bottom" align="end" className="w-48">
                              <DropdownMenuItem
                                className="space-x-2"
                                onClick={() => {
                                  setSelectedAppToEdit(app.client_id)
                                }}
                              >
                                <Edit size={12} />
                                <p>Update</p>
                              </DropdownMenuItem>
                              {app.client_type === 'confidential' && (
                                <DropdownMenuItem
                                  className="space-x-2"
                                  onClick={() => {
                                    setSelectedApp(app)
                                    setShowRegenerateDialog(true)
                                  }}
                                >
                                  <RotateCw size={12} />
                                  <p>Regenerate client secret</p>
                                </DropdownMenuItem>
                              )}
                              <DropdownMenuItem
                                className="space-x-2"
                                onClick={() => setSelectedAppToDelete(app.client_id)}
                              >
                                <Trash size={12} />
                                <p>Delete</p>
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        </div>
                      </TableCell>
                    </TableRow>
                  ))}
              </TableBody>
            </Table>
          </Card>
        </div>
      </div>

      <CreateOrUpdateOAuthAppSheet
        visible={isCreateOrUpdateSheetVisible}
        appToEdit={appToEdit}
        onSuccess={(app) => {
          const isCreating = !appToEdit
          setShowCreateSheet(false)
          setSelectedAppToEdit(null)
          setSelectedApp(undefined)
          // Only show banner for new apps or regenerated secrets, not for simple edits
          if (isCreating || app.client_secret) {
            setNewOAuthApp(app)
          }
        }}
        onCancel={() => {
          setShowCreateSheet(false)
          setSelectedAppToEdit(null)
          setSelectedApp(undefined)
        }}
      />

      <DeleteOAuthAppModal
        visible={!!appToDelete}
        selectedApp={appToDelete}
        setVisible={setSelectedAppToDelete}
        onDelete={(params: Parameters<typeof deleteOAuthApp>[0]) => {
          deletingOAuthAppIdRef.current = params.clientId ?? null
          deleteOAuthApp(params)
        }}
        isLoading={isDeletingApp}
      />

      <ConfirmationModal
        variant="warning"
        visible={showRegenerateDialog}
        loading={isRegenerating}
        title="Confirm regenerating client secret"
        confirmLabel="Confirm"
        onCancel={() => setShowRegenerateDialog(false)}
        onConfirm={() => {
          regenerateSecret({
            projectRef,
            clientId: selectedApp?.client_id,
            clientEndpoint: endpointData?.endpoint,
          })
          setShowRegenerateDialog(false)
        }}
      >
        <p className="text-sm text-foreground-light">
          Are you sure you wish to regenerate the client secret for "{selectedApp?.client_name}"?
          You'll need to update it in all applications that use it. This action cannot be undone.
        </p>
      </ConfirmationModal>
    </>
  )
}

Subdomains

Frequently Asked Questions

What does OAuthAppsList() do?
OAuthAppsList() is a function in the supabase codebase.
What does OAuthAppsList() call?
OAuthAppsList() calls 1 function(s): filterOAuthApps.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free