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
15 changes: 15 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,21 @@ export class ConfigClient {
return new ConfigWatcher(this.configStub, this.metadata, this.timeout, tenantId);
}

/**
* Replace the bearer token used for all subsequent RPCs (including watcher reconnects).
*
* Switches the client to JWT auth mode: removes any metadata-header credentials
* (x-subject, x-role, x-tenant-id) that may have been set at construction time.
*
* @param token - Raw JWT (without the "Bearer " prefix).
*/
setToken(token: string): void {
this.metadata.remove("x-subject");
this.metadata.remove("x-role");
this.metadata.remove("x-tenant-id");
this.metadata.set("authorization", `Bearer ${token}`);
}

/**
* Close the underlying gRPC channels.
*/
Expand Down
55 changes: 55 additions & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,61 @@ describe("ConfigClient", () => {
});
tenantClient.close();
});

describe("setToken()", () => {
it("subsequent RPCs use the new Bearer token", async () => {
client.setToken("rotated-token");

configStub.getField.mockImplementation(
(_req: unknown, meta: Metadata, _opts: unknown, cb: (...args: unknown[]) => void) => {
expect(meta.get("authorization")).toEqual(["Bearer rotated-token"]);
cb(null, {
value: { fieldPath: "a", value: { stringValue: "v" }, checksum: "c" },
});
},
);

await client.get("tenant-1", "a");
});

it("clears metadata headers when switching from header-mode to token-mode", async () => {
client.setToken("jwt-abc");

configStub.getField.mockImplementation(
(_req: unknown, meta: Metadata, _opts: unknown, cb: (...args: unknown[]) => void) => {
expect(meta.get("x-subject")).toEqual([]);
expect(meta.get("x-role")).toEqual([]);
expect(meta.get("authorization")).toEqual(["Bearer jwt-abc"]);
cb(null, {
value: { fieldPath: "a", value: { stringValue: "v" }, checksum: "c" },
});
},
);

await client.get("tenant-1", "a");
});

it("rotates token on a token-mode client", async () => {
const tokenClient = new ConfigClient("localhost:9090", {
token: "initial-token",
retry: false,
});

tokenClient.setToken("refreshed-token");

configStub.getField.mockImplementation(
(_req: unknown, meta: Metadata, _opts: unknown, cb: (...args: unknown[]) => void) => {
expect(meta.get("authorization")).toEqual(["Bearer refreshed-token"]);
cb(null, {
value: { fieldPath: "a", value: { stringValue: "v" }, checksum: "c" },
});
},
);

await tokenClient.get("tenant-1", "a");
tokenClient.close();
});
});
});

describe("AbortSignal", () => {
Expand Down