Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ token.companyId; // string | undefined
token.internalUserId; // string | undefined
token.isClientUser; // boolean
token.isInternalUser; // boolean
token.isProxying; // boolean — internal user previewing as a client (both IDs present)

const client = token.ensureIsClient(); // ClientTokenPayload (throws if not client)
const internal = token.ensureIsInternalUser(); // InternalUserTokenPayload (throws if not internal)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,9 @@ token.tokenId; // string | undefined — present in some marketplace tokens
token.baseUrl; // string | undefined — overrides the API base URL if set

// Identity checks
token.isClientUser; // true if clientId + companyId are present
token.isClientUser; // true if clientId is present
token.isInternalUser; // true if internalUserId is present
token.isProxying; // true if BOTH are present — internal user previewing as a client via CRM

// Throwing guards — return narrowed payload type or throw AssemblyUnauthorizedError
const clientPayload = token.ensureIsClient(); // ClientTokenPayload
Expand Down
9 changes: 9 additions & 0 deletions src/token/assembly-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ export class AssemblyToken {
return this.payload.internalUserId !== undefined;
}

/**
* `true` when an internal user is proxying as a client (CRM "preview as client"):
* both `internalUserId` and `clientId` are present. Both `isClientUser` and
* `isInternalUser` are also `true` in this case — branch on this getter first.
*/
get isProxying(): boolean {
return this.payload.internalUserId !== undefined && this.payload.clientId !== undefined;
}

/**
* Assert that the token belongs to a client user and return the narrowed payload.
* @throws {AssemblyUnauthorizedError} If the token does not represent a client user.
Expand Down
10 changes: 10 additions & 0 deletions tests/fixtures/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ if (paddedWsId === undefined) {
throw new Error("workspaceId missing in padded payload");
}
export const BLOCK_ALIGNED_WORKSPACE_ID: string = paddedWsId;

// ---------------------------------------------------------------------------
// Proxy token: internal user acting as a client via CRM
// ---------------------------------------------------------------------------
export const PROXY_TOKEN: string = encryptPayload(TEST_API_KEY, {
clientId: TEST_CLIENT_ID,
companyId: TEST_COMPANY_ID,
internalUserId: TEST_INTERNAL_USER_ID,
workspaceId: TEST_WORKSPACE_ID,
});
17 changes: 17 additions & 0 deletions tests/token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BLOCK_ALIGNED_WORKSPACE_ID,
CLIENT_TOKEN,
INTERNAL_USER_TOKEN,
PROXY_TOKEN,
TEST_API_KEY,
TEST_BASE_URL,
TEST_CLIENT_ID,
Expand Down Expand Up @@ -192,6 +193,22 @@ describe("AssemblyToken getters", () => {
// Identity checks
// ---------------------------------------------------------------------------
describe("AssemblyToken identity checks", () => {
it("proxy token (internalUserId + clientId) is flagged as proxying", () => {
const t = new AssemblyToken({ apiKey: TEST_API_KEY, token: PROXY_TOKEN });
expect(t.isProxying).toBe(true);
expect(t.isClientUser).toBe(true);
expect(t.isInternalUser).toBe(true);
expect(t.ensureIsClient().clientId).toBe(TEST_CLIENT_ID);
expect(t.ensureIsInternalUser().internalUserId).toBe(TEST_INTERNAL_USER_ID);
});

it("isProxying is false for single-identity tokens", () => {
expect(new AssemblyToken({ apiKey: TEST_API_KEY, token: CLIENT_TOKEN }).isProxying).toBe(false);
expect(new AssemblyToken({ apiKey: TEST_API_KEY, token: INTERNAL_USER_TOKEN }).isProxying).toBe(
false,
);
});

it("isClientUser is true for client tokens", () => {
const t = new AssemblyToken({ apiKey: TEST_API_KEY, token: CLIENT_TOKEN });
expect(t.isClientUser).toBe(true);
Expand Down
Loading