Skip to content

Commit 8a0c404

Browse files
authored
chore(clerk-js,shared): Don't display impersonation for agents (Core 2) (#7934)
1 parent a20d156 commit 8a0c404

8 files changed

Lines changed: 121 additions & 3 deletions

File tree

.changeset/short-taxes-move.md

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ClerkWebAuthnError, is4xxError, MissingExpiredTokenError } from '@clerk
33
import { retry } from '@clerk/shared/retry';
44
import type {
55
ActClaim,
6+
AgentActClaim,
67
CheckAuthorization,
78
EmailCodeConfig,
89
EnterpriseSSOConfig,
@@ -50,6 +51,7 @@ export class Session extends BaseResource implements SessionResource {
5051
lastActiveToken!: TokenResource | null;
5152
lastActiveOrganizationId!: string | null;
5253
actor!: ActClaim | null;
54+
agent!: AgentActClaim | null;
5355
user!: UserResource | null;
5456
publicUserData!: PublicUserData;
5557
factorVerificationAge: [number, number] | null = null;
@@ -311,6 +313,7 @@ export class Session extends BaseResource implements SessionResource {
311313
this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined);
312314
this.lastActiveOrganizationId = data.last_active_organization_id;
313315
this.actor = data.actor || null;
316+
this.agent = data.actor?.type === 'agent' ? (data.actor as AgentActClaim) : null;
314317
this.createdAt = unixEpochToDate(data.created_at);
315318
this.updatedAt = unixEpochToDate(data.updated_at);
316319
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
@@ -1233,4 +1233,57 @@ describe('Session', () => {
12331233
expect(fetchSpy).toHaveBeenCalledTimes(1);
12341234
});
12351235
});
1236+
1237+
describe('agent', () => {
1238+
it('sets agent to null when actor is null', () => {
1239+
const session = new Session({
1240+
status: 'active',
1241+
id: 'session_1',
1242+
object: 'session',
1243+
user: createUser({}),
1244+
last_active_organization_id: null,
1245+
actor: null,
1246+
created_at: new Date().getTime(),
1247+
updated_at: new Date().getTime(),
1248+
} as SessionJSON);
1249+
1250+
expect(session.actor).toBeNull();
1251+
expect(session.agent).toBeNull();
1252+
});
1253+
1254+
it('sets agent to null when actor has no type (impersonation)', () => {
1255+
const actor = { sub: 'user_2' };
1256+
const session = new Session({
1257+
status: 'active',
1258+
id: 'session_1',
1259+
object: 'session',
1260+
user: createUser({}),
1261+
last_active_organization_id: null,
1262+
actor,
1263+
created_at: new Date().getTime(),
1264+
updated_at: new Date().getTime(),
1265+
} as SessionJSON);
1266+
1267+
expect(session.actor).toEqual(actor);
1268+
expect(session.agent).toBeNull();
1269+
});
1270+
1271+
it('sets agent to the actor when actor has type "agent"', () => {
1272+
const actor = { sub: 'user_2', type: 'agent' as const };
1273+
const session = new Session({
1274+
status: 'active',
1275+
id: 'session_1',
1276+
object: 'session',
1277+
user: createUser({}),
1278+
last_active_organization_id: null,
1279+
actor,
1280+
created_at: new Date().getTime(),
1281+
updated_at: new Date().getTime(),
1282+
} as SessionJSON);
1283+
1284+
expect(session.actor).toEqual(actor);
1285+
expect(session.agent).toEqual(actor);
1286+
expect(session.agent?.type).toBe('agent');
1287+
});
1288+
});
12361289
});

packages/clerk-js/src/test/fixture-helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => {
5353
saml_accounts?: Array<Partial<SamlAccountJSON>>;
5454
organization_memberships?: Array<string | OrgParams>;
5555
tasks?: SessionJSON['tasks'];
56+
actor?: SessionJSON['actor'];
5657
};
5758

5859
const createPublicUserData = (params: WithUserParams) => {
@@ -83,7 +84,7 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => {
8384
id: baseClient.sessions.length.toString(),
8485
object: 'session',
8586
last_active_organization_id: activeOrganization,
86-
actor: null,
87+
actor: params.actor ?? null,
8788
user: createUser(params),
8889
public_user_data: createPublicUserData(params),
8990
created_at: new Date().getTime(),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { bindCreateFixtures } from '@/test/create-fixtures';
4+
import { render } from '@/test/utils';
5+
6+
import { ImpersonationFab } from '../';
7+
8+
const { createFixtures } = bindCreateFixtures('UserButton');
9+
10+
describe('ImpersonationFab', () => {
11+
it('does not render when user has no actor', async () => {
12+
const { wrapper } = await createFixtures(f => {
13+
f.withUser({ email_addresses: ['test@clerk.com'] });
14+
});
15+
render(<ImpersonationFab />, { wrapper });
16+
expect(document.getElementById('cl-impersonationEye')).toBeNull();
17+
});
18+
19+
it('renders when user has actor without type (impersonation)', async () => {
20+
const { wrapper } = await createFixtures(f => {
21+
f.withUser({
22+
email_addresses: ['test@clerk.com'],
23+
actor: { sub: 'user_impersonated' },
24+
});
25+
});
26+
render(<ImpersonationFab />, { wrapper });
27+
expect(document.getElementById('cl-impersonationEye')).toBeInTheDocument();
28+
});
29+
30+
it('does not render when user has actor with type "agent"', async () => {
31+
const { wrapper } = await createFixtures(f => {
32+
f.withUser({
33+
email_addresses: ['test@clerk.com'],
34+
actor: { sub: 'user_agent', type: 'agent' },
35+
});
36+
});
37+
render(<ImpersonationFab />, { wrapper });
38+
expect(document.getElementById('cl-impersonationEye')).toBeNull();
39+
});
40+
});

packages/clerk-js/src/ui/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/shared/src/types/jwtv2.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,28 @@ 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
* @inline
190195
*/
191196
export interface ActClaim {
192197
sub: string;
198+
type?: ActClaimType;
193199
[x: string]: unknown;
194200
}
195201

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

packages/shared/src/types/session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
PhoneCodeSecondFactorConfig,
1212
TOTPAttempt,
1313
} from './factors';
14-
import type { ActClaim } from './jwtv2';
14+
import type { ActClaim, AgentActClaim } from './jwtv2';
1515
import type {
1616
OrganizationCustomPermissionKey,
1717
OrganizationCustomRoleKey,
@@ -226,6 +226,7 @@ export interface SessionResource extends ClerkResource {
226226
lastActiveOrganizationId: string | null;
227227
lastActiveAt: Date;
228228
actor: ActClaim | null;
229+
agent: AgentActClaim | null;
229230
tasks: Array<SessionTask> | null;
230231
currentTask?: SessionTask;
231232
/**

0 commit comments

Comments
 (0)