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}
</>
)
}
Domain
Subdomains
Calls
Source
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