Home / Function/ UserImpersonationSelector() — supabase Function Reference

UserImpersonationSelector() — supabase Function Reference

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

Entity Profile

Dependency Diagram

graph TD
  bfdec285_84dd_1236_5696_f43e2eb85a89["UserImpersonationSelector()"]
  3c05a680_a8e3_7a53_e059_ae8513da59ad["getDisplayName()"]
  bfdec285_84dd_1236_5696_f43e2eb85a89 -->|calls| 3c05a680_a8e3_7a53_e059_ae8513da59ad
  style bfdec285_84dd_1236_5696_f43e2eb85a89 fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx lines 37–422

const UserImpersonationSelector = () => {
  const [searchText, setSearchText] = useState('')
  const [aal, setAal] = useState<AuthenticatorAssuranceLevels>('aal1')
  const [externalUserId, setExternalUserId] = useState('')
  const [additionalClaims, setAdditionalClaims] = useState('')

  const { id: tableId } = useParams()
  const [selectedTab, setSelectedTab] = useState<'user' | 'external'>('user')

  const [previousSearches, setPreviousSearches] = useLocalStorage<User[]>(
    LOCAL_STORAGE_KEYS.USER_IMPERSONATION_SELECTOR_PREVIOUS_SEARCHES(tableId!),
    []
  )

  const state = useRoleImpersonationStateSnapshot()
  const debouncedSearchText = useDebounce(searchText, 300)

  const { data: project } = useSelectedProjectQuery()

  const {
    data,
    isSuccess,
    isPending: isLoading,
    isError,
    error,
    isFetching,
    isPlaceholderData,
  } = useUsersInfiniteQuery(
    {
      projectRef: project?.ref,
      connectionString: project?.connectionString,
      keywords: debouncedSearchText.trim().toLocaleLowerCase(),
    },
    {
      placeholderData: keepPreviousData,
    }
  )
  const users = useMemo(() => data?.pages.flatMap((page) => page.result) ?? [], [data?.pages])
  const isSearching = isPlaceholderData && isFetching
  const impersonatingUser =
    state.role?.type === 'postgrest' &&
    state.role.role === 'authenticated' &&
    state.role.userType === 'native' &&
    state.role.user

  // Check if we're currently impersonating an external auth user (e.g. OAuth, SAML)
  // This is used to show the correct UI state and impersonation details
  const isExternalAuthImpersonating =
    state.role?.type === 'postgrest' &&
    state.role.role === 'authenticated' &&
    state.role.userType === 'external' &&
    state.role.externalAuth

  const customAccessTokenHookDetails = useCustomAccessTokenHookDetails(project?.ref)

  const [isImpersonateLoading, setIsImpersonateLoading] = useState(false)

  async function impersonateUser(user: User) {
    setIsImpersonateLoading(true)
    setPreviousSearches((prev) => {
      // Remove if already present
      const filtered = prev.filter((u) => u.id !== user.id)
      // Add new user to the end
      const updated = [...filtered, user]
      // Keep only the last 6
      return updated.slice(-6)
    })

    if (customAccessTokenHookDetails?.type === 'https') {
      toast.info(
        'Please note that HTTPS custom access token hooks are not yet supported in the dashboard.'
      )
    }

    try {
      await state.setRole(
        {
          type: 'postgrest',
          role: 'authenticated',
          userType: 'native',
          user,
          aal,
        },
        customAccessTokenHookDetails
      )
    } catch (error) {
      toast.error(`Failed to impersonate user: ${(error as ResponseError).message}`)
    }

    setIsImpersonateLoading(false)
  }

  // Impersonates an external auth user (e.g. OAuth, SAML) by setting the sub and any additional claims
  // This allows testing RLS policies for external auth users without needing to set up the full OAuth/SAML flow
  async function impersonateExternalUser() {
    setIsImpersonateLoading(true)

    let parsedClaims = {}
    try {
      parsedClaims = additionalClaims ? JSON.parse(additionalClaims) : {}
    } catch (e) {
      toast.error('Invalid JSON in additional claims')
      return
    }
    try {
      await state.setRole(
        {
          type: 'postgrest',
          role: 'authenticated',
          userType: 'external',
          externalAuth: {
            sub: externalUserId,
            additionalClaims: parsedClaims,
          },
          aal,
        },
        customAccessTokenHookDetails
      )
    } catch (error) {
      toast.error(`Failed to impersonate user: ${(error as ResponseError).message}`)
    }

    setIsImpersonateLoading(false)
  }

  function stopImpersonating() {
    state.setRole(undefined)
  }

  function toggleAalState() {
    setAal((prev) => (prev === 'aal2' ? 'aal1' : 'aal2'))
  }

  const displayName = impersonatingUser
    ? getDisplayName(
        impersonatingUser,
        impersonatingUser.email ?? impersonatingUser.phone ?? impersonatingUser.id ?? 'Unknown'
      )
    : isExternalAuthImpersonating
      ? state.role.externalAuth.sub
      : undefined

  // Clear all search history
  function clearSearchHistory() {
    setPreviousSearches([])
  }

  return (
    <>
      <div className="px-5 py-3">
        <p className="text-foreground text-sm">
          {displayName ? `Impersonating ${displayName}` : 'Impersonate a User'}
        </p>
        <p className="text-sm text-foreground-light">
          {!impersonatingUser && !isExternalAuthImpersonating
            ? "Select a user to respect your database's Row-Level Security policies for that particular user."
            : "Results will respect your database's Row-Level Security policies for this user."}
        </p>

        {impersonatingUser && (
          <UserImpersonatingRow
            user={impersonatingUser}
            onClick={stopImpersonating}
            isImpersonating={true}
            aal={aal}
            isLoading={isImpersonateLoading}
          />
        )}
        {isExternalAuthImpersonating && (
          <ExternalAuthImpersonatingRow
            sub={state.role.externalAuth.sub}
            onClick={stopImpersonating}
            aal={aal}
            isLoading={isImpersonateLoading}
          />
        )}

        {!impersonatingUser && !isExternalAuthImpersonating && (
          <Tabs_Shadcn_ value={selectedTab} onValueChange={(value: any) => setSelectedTab(value)}>
            <TabsList_Shadcn_ className="gap-x-3">
              <TabsTrigger_Shadcn_ value="user">Project user</TabsTrigger_Shadcn_>
              <TabsTrigger_Shadcn_ value="external" className="gap-x-1.5">
                External user
                <InfoTooltip side="bottom" className="flex flex-col gap-1 max-w-96">
                  Test RLS policies with external auth providers like Clerk or Auth0 by providing a
                  user ID and optional claims.
                </InfoTooltip>
              </TabsTrigger_Shadcn_>
            </TabsList_Shadcn_>

            <TabsContent_Shadcn_ value="user">
              <div className="flex flex-col gap-y-2">
                <Input
                  size="tiny"
                  className="table-editor-search border-none"
                  icon={
                    isSearching ? (
                      <Loader2
                        className="animate-spin text-foreground-lighter"
                        size={16}
                        strokeWidth={1.5}
                      />
                    ) : (
                      <Search className="text-foreground-lighter" size={16} strokeWidth={1.5} />
                    )
                  }
                  placeholder="Search by id, email, phone, or name..."
                  onChange={(e) => setSearchText(e.target.value)}
                  value={searchText}
                  actions={
                    searchText && (
                      <Button
                        size="tiny"
                        type="text"
                        className="px-1"
                        onClick={() => setSearchText('')}
                      >
                        <X size={12} strokeWidth={2} />
                      </Button>
                    )
                  }
                />
                {isLoading && (
                  <div className="flex flex-col gap-2 items-center justify-center h-24">
                    <Loader2 className="animate-spin" size={24} />
                    <span className="text-foreground-light">Loading users...</span>
                  </div>
                )}

                {isError && <AlertError error={error} subject="Failed to retrieve users" />}

                {isSuccess &&
                  (users.length > 0 ? (
                    <div>
                      <ul className="divide-y max-h-[150px] overflow-y-scroll" role="list">
                        {users.map((user) => (
                          <li key={user.id} role="listitem">
                            <UserRow
                              user={user}
                              onClick={impersonateUser}
                              isLoading={isImpersonateLoading}
                            />
                          </li>
                        ))}
                      </ul>
                    </div>
                  ) : (
                    <div className="flex flex-col gap-2 items-center justify-center h-24">
                      <p className="text-foreground-light text-xs" role="status">
                        No users found
                      </p>
                    </div>
                  ))}

                <>
                  {previousSearches.length > 0 && (
                    <div>
                      {previousSearches.length > 0 ? (
                        <>
                          <Collapsible_Shadcn_ className="relative">
                            <CollapsibleTrigger_Shadcn_ className="group font-normal p-0 [&[data-state=open]>div>svg]:!-rotate-180">
                              <div className="flex items-center gap-x-1 w-full">
                                <p className="text-xs text-foreground-light group-hover:text-foreground transition">
                                  Recents
                                </p>
                                <ChevronDown
                                  className="transition-transform duration-200"
                                  strokeWidth={1.5}
                                  size={14}
                                />
                              </div>
                            </CollapsibleTrigger_Shadcn_>

                            <CollapsibleContent_Shadcn_ className="mt-1 flex flex-col gap-y-4">
                              <Button
                                size="tiny"
                                type="text"
                                className="absolute right-0 top-0 py-2 hover:bg-muted flex items-center text"
                                onClick={clearSearchHistory}
                              >
                                <span className="flex items-center">Clear</span>
                              </Button>
                              <ScrollArea
                                className={cn(previousSearches.length > 3 ? 'h-36' : 'h-auto')}
                              >
                                <ul className="grid gap-2 ">
                                  {previousSearches.map((search) => (
                                    <li key={search.id}>
                                      <UserRow user={search} onClick={impersonateUser} />
                                    </li>
                                  ))}
                                </ul>
                              </ScrollArea>
                            </CollapsibleContent_Shadcn_>
                          </Collapsible_Shadcn_>
                        </>
                      ) : (
                        <div className="p-4 text-center text-muted-foreground">
                          No recent searches
                        </div>
                      )}
                    </div>
                  )}
                </>
              </div>
            </TabsContent_Shadcn_>

            <TabsContent_Shadcn_ value="external">
              <div className="flex flex-col gap-y-4">
                <Input
                  size="small"
                  layout="horizontal"
                  label="External User ID"
                  descriptionText="The user ID from your external auth provider"
                  placeholder="e.g. user_abc123"
                  value={externalUserId}
                  onChange={(e) => setExternalUserId(e.target.value)}
                />
                <Input
                  size="small"
                  layout="horizontal"
                  label="Additional Claims (JSON)"
                  descriptionText="Optional: Add custom claims like org_id or roles"
                  placeholder='e.g. {"app_metadata": {"org_id": "org_456"}}'
                  value={additionalClaims}
                  onChange={(e) => setAdditionalClaims(e.target.value)}
                />
                <div className="flex items-center justify-end">
                  <Button
                    type="default"
                    disabled={!externalUserId}
                    onClick={impersonateExternalUser}
                  >
                    Impersonate
                  </Button>
                </div>
              </div>
            </TabsContent_Shadcn_>
          </Tabs_Shadcn_>
        )}
      </div>

      {/* Check for both regular user and external auth impersonation since they use different data structures but both need to be handled for displaying impersonation UI */}
      {!impersonatingUser && !isExternalAuthImpersonating ? (
        <>
          <DropdownMenuSeparator className="m-0" />
          <div className="px-5 py-2 flex flex-col gap-2 relative">
            <Collapsible_Shadcn_>
              <CollapsibleTrigger_Shadcn_ className="group font-normal p-0 [&[data-state=open]>div>svg]:!-rotate-180">
                <div className="flex items-center gap-x-1 w-full">
                  <p className="text-xs text-foreground-light group-hover:text-foreground transition">
                    Advanced options
                  </p>
                  <ChevronDown
                    className="transition-transform duration-200"
                    strokeWidth={1.5}
                    size={14}
                  />
                </div>
              </CollapsibleTrigger_Shadcn_>
              <CollapsibleContent_Shadcn_ className="mt-1 flex flex-col gap-y-4">
                <div className="flex flex-row items-center gap-x-4 text-sm text-foreground-light">
                  <div className="flex items-center gap-x-1">
                    <h3>MFA assurance level</h3>
                    <InfoTooltip side="top" className="max-w-96">
                      AAL1 verifies users via standard login methods, while AAL2 adds a second
                      authentication factor. If you're not using MFA, you can leave this on AAL1.
                      Learn more about MFA{' '}
                      <InlineLink href={`${DOCS_URL}/guides/auth/auth-mfa`}>here</InlineLink>.
                    </InfoTooltip>
                  </div>

                  <div className="flex flex-row items-center gap-x-2 text-xs font-bold">
                    <p className={aal === 'aal1' ? undefined : 'text-foreground-lighter'}>AAL1</p>
                    <Switch checked={aal === 'aal2'} onCheckedChange={toggleAalState} />
                    <p className={aal === 'aal2' ? undefined : 'text-foreground-lighter'}>AAL2</p>
                  </div>
                </div>
              </CollapsibleContent_Shadcn_>
            </Collapsible_Shadcn_>
          </div>
        </>
      ) : null}
    </>
  )
}

Subdomains

Frequently Asked Questions

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

Analyze Your Own Codebase

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

Try Supermodel Free