Skip to content

Commit 466d642

Browse files
authored
chore(js,ui): Don't display impersonation for agents (#7933)
1 parent dc886a9 commit 466d642

8 files changed

Lines changed: 123 additions & 3 deletions

File tree

.changeset/cyan-shoes-return.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/shared': minor
4+
'@clerk/ui': minor
5+
---
6+
7+
Don't display impersonation overlay for agents

packages/clerk-js/src/core/resources/Session.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { retry } from '@clerk/shared/retry';
1010
import type {
1111
ActClaim,
12+
AgentActClaim,
1213
CheckAuthorization,
1314
ClientResource,
1415
EmailCodeConfig,
@@ -59,6 +60,7 @@ export class Session extends BaseResource implements SessionResource {
5960
lastActiveToken!: TokenResource | null;
6061
lastActiveOrganizationId!: string | null;
6162
actor!: ActClaim | null;
63+
agent!: AgentActClaim | null;
6264
user!: UserResource | null;
6365
publicUserData!: PublicUserData;
6466
factorVerificationAge: [number, number] | null = null;
@@ -382,6 +384,7 @@ export class Session extends BaseResource implements SessionResource {
382384
this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined);
383385
this.lastActiveOrganizationId = data.last_active_organization_id;
384386
this.actor = data.actor || null;
387+
this.agent = data.actor?.type === 'agent' ? (data.actor as AgentActClaim) : null;
385388
this.createdAt = unixEpochToDate(data.created_at);
386389
this.updatedAt = unixEpochToDate(data.updated_at);
387390
this.user = new User(data.user);

packages/clerk-js/src/core/resources/__tests__/Session.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,4 +1526,57 @@ describe('Session', () => {
15261526
expect(fetchSpy).toHaveBeenCalledTimes(1);
15271527
});
15281528
});
1529+
1530+
describe('agent', () => {
1531+
it('sets agent to null when actor is null', () => {
1532+
const session = new Session({
1533+
status: 'active',
1534+
id: 'session_1',
1535+
object: 'session',
1536+
user: createUser({}),
1537+
last_active_organization_id: null,
1538+
actor: null,
1539+
created_at: new Date().getTime(),
1540+
updated_at: new Date().getTime(),
1541+
} as SessionJSON);
1542+
1543+
expect(session.actor).toBeNull();
1544+
expect(session.agent).toBeNull();
1545+
});
1546+
1547+
it('sets agent to null when actor has no type (impersonation)', () => {
1548+
const actor = { sub: 'user_2' };
1549+
const session = new Session({
1550+
status: 'active',
1551+
id: 'session_1',
1552+
object: 'session',
1553+
user: createUser({}),
1554+
last_active_organization_id: null,
1555+
actor,
1556+
created_at: new Date().getTime(),
1557+
updated_at: new Date().getTime(),
1558+
} as SessionJSON);
1559+
1560+
expect(session.actor).toEqual(actor);
1561+
expect(session.agent).toBeNull();
1562+
});
1563+
1564+
it('sets agent to the actor when actor has type "agent"', () => {
1565+
const actor = { sub: 'user_2', type: 'agent' as const };
1566+
const session = new Session({
1567+
status: 'active',
1568+
id: 'session_1',
1569+
object: 'session',
1570+
user: createUser({}),
1571+
last_active_organization_id: null,
1572+
actor,
1573+
created_at: new Date().getTime(),
1574+
updated_at: new Date().getTime(),
1575+
} as SessionJSON);
1576+
1577+
expect(session.actor).toEqual(actor);
1578+
expect(session.agent).toEqual(actor);
1579+
expect(session.agent?.type).toBe('agent');
1580+
});
1581+
});
15291582
});

packages/shared/src/types/jwtv2.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,29 @@ export type VersionedJwtPayload =
184184

185185
export type JwtPayload = JWTPayloadBase & CustomJwtSessionClaims & VersionedJwtPayload;
186186

187+
/**
188+
* The type of the actor claim.
189+
*/
190+
export type ActClaimType = 'agent';
191+
187192
/**
188193
* JWT Actor - [RFC8693](https://www.rfc-editor.org/rfc/rfc8693.html#name-act-actor-claim).
189194
*
190195
* @inline
191196
*/
192197
export interface ActClaim {
193198
sub: string;
199+
type?: ActClaimType;
194200
[x: string]: unknown;
195201
}
196202

203+
/**
204+
* ActClaim narrowed to actor type `'agent'`. Use for session.agent.
205+
*
206+
* @inline
207+
*/
208+
export type AgentActClaim = ActClaim & { type: 'agent' };
209+
197210
/**
198211
* The current state of the session which can only be `active` or `pending`.
199212
*/

packages/shared/src/types/session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
PhoneCodeSecondFactorConfig,
1313
TOTPAttempt,
1414
} from './factors';
15-
import type { ActClaim } from './jwtv2';
15+
import type { ActClaim, AgentActClaim } from './jwtv2';
1616
import type {
1717
OrganizationCustomPermissionKey,
1818
OrganizationCustomRoleKey,
@@ -227,6 +227,7 @@ export interface SessionResource extends ClerkResource {
227227
lastActiveOrganizationId: string | null;
228228
lastActiveAt: Date;
229229
actor: ActClaim | null;
230+
agent: AgentActClaim | null;
230231
tasks: Array<SessionTask> | null;
231232
currentTask?: SessionTask;
232233
/**
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { bindCreateFixtures } from '@/test/create-fixtures';
5+
import { render } from '@/test/utils';
6+
7+
import { ImpersonationFab } from '../';
8+
9+
const { createFixtures } = bindCreateFixtures('UserButton');
10+
11+
describe('ImpersonationFab', () => {
12+
it('does not render when user has no actor', async () => {
13+
const { wrapper } = await createFixtures(f => {
14+
f.withUser({ email_addresses: ['test@clerk.com'] });
15+
});
16+
render(<ImpersonationFab />, { wrapper });
17+
expect(document.getElementById('cl-impersonationEye')).toBeNull();
18+
});
19+
20+
it('renders when user has actor without type (impersonation)', async () => {
21+
const { wrapper } = await createFixtures(f => {
22+
f.withUser({
23+
email_addresses: ['test@clerk.com'],
24+
actor: { sub: 'user_impersonated' },
25+
});
26+
});
27+
render(<ImpersonationFab />, { wrapper });
28+
expect(document.getElementById('cl-impersonationEye')).toBeInTheDocument();
29+
});
30+
31+
it('does not render when user has actor with type "agent"', async () => {
32+
const { wrapper } = await createFixtures(f => {
33+
f.withUser({
34+
email_addresses: ['test@clerk.com'],
35+
actor: { sub: 'user_agent', type: 'agent' },
36+
});
37+
});
38+
render(<ImpersonationFab />, { wrapper });
39+
expect(document.getElementById('cl-impersonationEye')).toBeNull();
40+
});
41+
});

packages/ui/src/components/ImpersonationFab/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ const ImpersonationFabInternal = () => {
116116
const { parsedInternalTheme } = useAppearance();
117117
const containerRef = useRef<HTMLDivElement>(null);
118118
const actor = session?.actor;
119-
const isImpersonating = !!actor;
119+
const agent = session?.agent;
120+
const isImpersonating = !!actor && !agent;
120121

121122
//essentials for calcs
122123
const eyeWidth = parsedInternalTheme.sizes.$16;

packages/ui/src/test/fixture-helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => {
5151
external_accounts?: Array<OAuthProvider | Partial<ExternalAccountJSON>>;
5252
organization_memberships?: Array<string | OrgParams>;
5353
tasks?: SessionJSON['tasks'];
54+
actor?: SessionJSON['actor'];
5455
};
5556

5657
const createPublicUserData = (params: WithUserParams) => {
@@ -81,7 +82,7 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => {
8182
id: baseClient.sessions.length.toString(),
8283
object: 'session',
8384
last_active_organization_id: activeOrganization,
84-
actor: null,
85+
actor: params.actor ?? null,
8586
user: createUser(params),
8687
public_user_data: createPublicUserData(params),
8788
created_at: new Date().getTime(),

0 commit comments

Comments
 (0)