From 273abfddf9d611bad862581bb1ba912cea3312a6 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 12:01:21 -0400 Subject: [PATCH 1/3] feat(backend): Add actor_token API endpoints --- .../src/api/endpoints/ActorTokenApi.ts | 62 +++++++++++++++++++ packages/backend/src/api/endpoints/index.ts | 1 + packages/backend/src/api/factory.ts | 2 + .../backend/src/api/resources/ActorToken.ts | 28 +++++++++ .../backend/src/api/resources/Deserializer.ts | 3 + packages/backend/src/api/resources/Enums.ts | 7 +++ packages/backend/src/api/resources/JSON.ts | 14 +++++ packages/backend/src/api/resources/index.ts | 1 + packages/backend/src/index.ts | 2 + 9 files changed, 120 insertions(+) create mode 100644 packages/backend/src/api/endpoints/ActorTokenApi.ts create mode 100644 packages/backend/src/api/resources/ActorToken.ts diff --git a/packages/backend/src/api/endpoints/ActorTokenApi.ts b/packages/backend/src/api/endpoints/ActorTokenApi.ts new file mode 100644 index 00000000000..c3dca6a9706 --- /dev/null +++ b/packages/backend/src/api/endpoints/ActorTokenApi.ts @@ -0,0 +1,62 @@ +import { joinPaths } from '../../util/path'; +import type { ActorToken } from '../resources/ActorToken'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/actor_tokens'; + +type ActorTokenActorCreateParams = { + /** + * The ID of the actor. + */ + sub: string; + /** + * Additional properties of the actor. + */ + additionalProperties?: { [k: string]: any }; +}; + +type ActorTokenCreateParams = { + /** + * The ID of the user being impersonated. + */ + userId: string; + /** + * The actor payload. It needs to include a sub property which should contain the ID of the actor. + * + * @remarks + * This whole payload will be also included in the JWT session token. + */ + actor: ActorTokenActorCreateParams; + /** + * Optional parameter to specify the life duration of the actor token in seconds. + * + * @remarks + * By default, the duration is 1 hour. + */ + expiresInSeconds?: number | undefined; + /** + * The maximum duration that the session which will be created by the generated actor token should last. + * + * @remarks + * By default, the duration of a session created via an actor token, lasts 30 minutes. + */ + sessionMaxDurationInSeconds?: number | undefined; +}; + +export class ActorTokenAPI extends AbstractAPI { + public async create(params: ActorTokenCreateParams) { + return this.request({ + method: 'POST', + path: basePath, + bodyParams: params, + }); + } + + public async revoke(actorTokenId: string) { + this.requireId(actorTokenId); + return this.request({ + method: 'POST', + path: joinPaths(basePath, actorTokenId, 'revoke'), + }); + } +} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index f1ea10ac9ce..63af4a56a00 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -1,3 +1,4 @@ +export * from './ActorTokenApi'; export * from './AccountlessApplicationsAPI'; export * from './AbstractApi'; export * from './AllowlistIdentifierApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 47098188dbe..13c250979af 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -1,5 +1,6 @@ import { AccountlessApplicationAPI, + ActorTokenAPI, AllowlistIdentifierAPI, ClientAPI, DomainAPI, @@ -27,6 +28,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { __experimental_accountlessApplications: new AccountlessApplicationAPI( buildRequest({ ...options, requireSecretKey: false }), ), + actorTokens: new ActorTokenAPI(request), allowlistIdentifiers: new AllowlistIdentifierAPI(request), clients: new ClientAPI(request), emailAddresses: new EmailAddressAPI(request), diff --git a/packages/backend/src/api/resources/ActorToken.ts b/packages/backend/src/api/resources/ActorToken.ts new file mode 100644 index 00000000000..c9fca99cace --- /dev/null +++ b/packages/backend/src/api/resources/ActorToken.ts @@ -0,0 +1,28 @@ +import type { ActorTokenStatus } from './Enums'; +import type { ActorTokenJSON } from './JSON'; + +export class ActorToken { + constructor( + readonly id: string, + readonly status: ActorTokenStatus, + readonly userId: string, + readonly actor: Record | null, + readonly token: string | null | undefined, + readonly url: string | null | undefined, + readonly createdAt: number, + readonly updatedAt: number, + ) {} + + static fromJSON(data: ActorTokenJSON): ActorToken { + return new ActorToken( + data.id, + data.status, + data.user_id, + data.actor, + data.token, + data.url, + data.created_at, + data.updated_at, + ); + } +} diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index 067d0ee0693..ff6eacc0b88 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -1,4 +1,5 @@ import { + ActorToken, AllowlistIdentifier, Client, Cookies, @@ -69,6 +70,8 @@ function jsonToObject(item: any): any { switch (item.object) { case ObjectType.AccountlessApplication: return AccountlessApplication.fromJSON(item); + case ObjectType.ActorToken: + return ActorToken.fromJSON(item); case ObjectType.AllowlistIdentifier: return AllowlistIdentifier.fromJSON(item); case ObjectType.Client: diff --git a/packages/backend/src/api/resources/Enums.ts b/packages/backend/src/api/resources/Enums.ts index c0e3c6d3de2..75cc6ab0061 100644 --- a/packages/backend/src/api/resources/Enums.ts +++ b/packages/backend/src/api/resources/Enums.ts @@ -36,3 +36,10 @@ export type SignInStatus = 'needs_identifier' | 'needs_factor_one' | 'needs_fact export type SignUpStatus = 'missing_requirements' | 'complete' | 'abandoned'; export type InvitationStatus = 'pending' | 'accepted' | 'revoked' | 'expired'; + +export const ActorTokenStatus = { + Pending: 'pending', + Accepted: 'accepted', + Revoked: 'revoked', +} as const; +export type ActorTokenStatus = (typeof ActorTokenStatus)[keyof typeof ActorTokenStatus]; diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 5ad5f0cc078..a039abc1f13 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -1,4 +1,5 @@ import type { + ActorTokenStatus, InvitationStatus, OrganizationDomainVerificationStatus, OrganizationDomainVerificationStrategy, @@ -11,6 +12,7 @@ import type { export const ObjectType = { AccountlessApplication: 'accountless_application', + ActorToken: 'actor_token', AllowlistIdentifier: 'allowlist_identifier', Client: 'client', Cookies: 'cookies', @@ -68,6 +70,18 @@ export interface AccountlessApplicationJSON extends ClerkResourceJSON { api_keys_url: string; } +export interface ActorTokenJSON extends ClerkResourceJSON { + object: typeof ObjectType.ActorToken; + id: string; + status: ActorTokenStatus; + user_id: string; + actor: Record | null; + token?: string | null; + url?: string | null; + created_at: number; + updated_at: number; +} + export interface AllowlistIdentifierJSON extends ClerkResourceJSON { object: typeof ObjectType.AllowlistIdentifier; identifier: string; diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index c384c95f8e0..4899e4028de 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -1,3 +1,4 @@ +export * from './ActorToken'; export * from './AccountlessApplication'; export * from './AllowlistIdentifier'; export * from './Client'; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index c9e3e8cd224..0d05c942191 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -54,6 +54,7 @@ export type { VerifyTokenOptions } from './tokens/verify'; * JSON types */ export type { + ActorTokenJSON, AccountlessApplicationJSON, ClerkResourceJSON, TokenJSON, @@ -92,6 +93,7 @@ export type { * Resources */ export type { + ActorToken, AccountlessApplication, AllowlistIdentifier, Client, From e6dbf8874b21022d34ff1dda03741a8127c44857 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 12:07:37 -0400 Subject: [PATCH 2/3] chore(repo): Add changeset --- .changeset/three-badgers-hunt.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/three-badgers-hunt.md diff --git a/.changeset/three-badgers-hunt.md b/.changeset/three-badgers-hunt.md new file mode 100644 index 00000000000..781553e6b74 --- /dev/null +++ b/.changeset/three-badgers-hunt.md @@ -0,0 +1,15 @@ +--- +'@clerk/backend': patch +--- + +Adds the ability to create and revoke actor tokens to the Backend API client. + + +```ts + import { createClerkClient } from '@clerk/backend'; + + const clerkClient = createClerkClient(...); + + const { id } = await clerkClient.actor_tokens.create({...}); + await clerkClient.actor_tokens.revoke(id); +``` \ No newline at end of file From df9b808ccfb2f823321157218c85301019479ac8 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 15:01:00 -0400 Subject: [PATCH 3/3] chore: Update changeset example --- .changeset/three-badgers-hunt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/three-badgers-hunt.md b/.changeset/three-badgers-hunt.md index 781553e6b74..08f26b67bd5 100644 --- a/.changeset/three-badgers-hunt.md +++ b/.changeset/three-badgers-hunt.md @@ -10,6 +10,6 @@ Adds the ability to create and revoke actor tokens to the Backend API client. const clerkClient = createClerkClient(...); - const { id } = await clerkClient.actor_tokens.create({...}); - await clerkClient.actor_tokens.revoke(id); + const { id } = await clerkClient.actorTokens.create({...}); + await clerkClient.actorTokens.revoke(id); ``` \ No newline at end of file