Skip to content

Commit 06881ea

Browse files
committed
feat: add getAuthHeaders api
1 parent 6770661 commit 06881ea

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

src/custom/CortiClient.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CortiClient as BaseCortiClient } from "../Client.js";
22
import type * as environments from "../environments.js";
3+
import * as core from "../core/index.js";
34
import { CortiAuth } from "./auth/CortiAuth.js";
45
import { CustomAgents } from "./agents/CustomAgents.js";
56
import { CustomStream } from "./stream/CustomStream.js";
@@ -89,4 +90,17 @@ export class CortiClient extends BaseCortiClient {
8990
public override get agents(): CustomAgents {
9091
return (this._agents ??= new CustomAgents(this._options));
9192
}
93+
94+
public getAuthHeaders = async (): Promise<Headers> => {
95+
const req = await this._options.authProvider.getAuthRequest();
96+
97+
return new Headers({
98+
...(req.headers ?? {}),
99+
"Tenant-Name": await core.Supplier.get(this._options.tenantName),
100+
});
101+
};
102+
103+
/**
104+
* Patch: removed `auth` getter
105+
*/
92106
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import * as core from "../../src/core/index";
2+
import { CortiClient } from "../../src/custom/CortiClient";
3+
4+
// Type extension to include getAuthHeaders method
5+
type CortiClientWithAuth = CortiClient & {
6+
getAuthHeaders(): Promise<Headers>;
7+
};
8+
9+
// Helper function to create a mock JWT with proper structure
10+
function createMockJWT(
11+
environment: string,
12+
tenant: string,
13+
expiresInSeconds: number = 3600,
14+
): string {
15+
const header = { alg: "RS256", typ: "JWT" };
16+
const payload = {
17+
iss: `https://keycloak.${environment}.corti.app/realms/${tenant}`,
18+
exp: Math.floor(Date.now() / 1000) + expiresInSeconds,
19+
iat: Math.floor(Date.now() / 1000),
20+
};
21+
22+
const base64UrlEncode = (str: string) => {
23+
return Buffer.from(str)
24+
.toString("base64")
25+
.replace(/\+/g, "-")
26+
.replace(/\//g, "_")
27+
.replace(/=/g, "");
28+
};
29+
30+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
31+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
32+
const signature = "mock-signature";
33+
34+
return `${encodedHeader}.${encodedPayload}.${signature}`;
35+
}
36+
37+
describe("cortiClient.getAuthHeaders", () => {
38+
describe("with client credentials", () => {
39+
it("should return headers with Bearer token and tenant name", async () => {
40+
const mockToken = "mock-access-token-123";
41+
const mockTenantName = "test-tenant";
42+
43+
const cortiClient = new CortiClient({
44+
environment: "https://api.test.corti.ai",
45+
tenantName: mockTenantName,
46+
auth: {
47+
clientId: "test-client-id",
48+
clientSecret: "test-client-secret",
49+
},
50+
}) as CortiClientWithAuth;
51+
52+
// Mock the getToken method to return our mock token
53+
vi
54+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
55+
.mockResolvedValue({ headers: { Authorization: `Bearer ${mockToken}` } });
56+
57+
const headers = await cortiClient.getAuthHeaders();
58+
59+
expect(headers.get("Authorization")).toBe(`Bearer ${mockToken}`);
60+
expect(headers.get("Tenant-Name")).toBe(mockTenantName);
61+
});
62+
63+
it("should handle supplier-based tenant name", async () => {
64+
const mockToken = "mock-access-token-456";
65+
const mockTenantName = "dynamic-tenant";
66+
67+
const cortiClient = new CortiClient({
68+
environment: "https://api.test.corti.ai",
69+
tenantName: mockTenantName,
70+
auth: {
71+
clientId: "test-client-id",
72+
clientSecret: "test-client-secret",
73+
},
74+
}) as CortiClientWithAuth;
75+
76+
vi
77+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
78+
.mockResolvedValue({ headers: { Authorization: `Bearer ${mockToken}` } });
79+
80+
const headers = await cortiClient.getAuthHeaders();
81+
82+
expect(headers.get("Authorization")).toBe(`Bearer ${mockToken}`);
83+
expect(headers.get("Tenant-Name")).toBe(mockTenantName);
84+
});
85+
});
86+
87+
describe("with bearer token", () => {
88+
it("should return headers with Bearer token and tenant name", async () => {
89+
const mockEnvironment = "test";
90+
const mockTenantName = "bearer-tenant";
91+
const mockAccessToken = createMockJWT(mockEnvironment, mockTenantName);
92+
const mockRefreshedToken = "refreshed-access-token";
93+
94+
const cortiClient = new CortiClient({
95+
auth: {
96+
accessToken: mockAccessToken,
97+
refreshToken: "mock-refresh-token",
98+
},
99+
}) as CortiClientWithAuth;
100+
101+
// Mock the getToken method to return our mock token
102+
vi
103+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
104+
.mockResolvedValue({ headers: { Authorization: `Bearer ${mockRefreshedToken}` } });
105+
106+
const headers = await cortiClient.getAuthHeaders();
107+
108+
expect(headers.get("Authorization")).toBe(`Bearer ${mockRefreshedToken}`);
109+
expect(headers.get("Tenant-Name")).toBe(mockTenantName);
110+
});
111+
112+
it("should handle async token retrieval", async () => {
113+
const mockEnvironment = "test";
114+
const mockTenantName = "async-tenant";
115+
const mockAccessToken = "async-token";
116+
const initialToken = createMockJWT(mockEnvironment, mockTenantName);
117+
118+
const cortiClient = new CortiClient({
119+
auth: {
120+
accessToken: initialToken,
121+
},
122+
}) as CortiClientWithAuth;
123+
124+
// Mock getAuthRequest to simulate async behavior
125+
vi
126+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
127+
.mockImplementation(
128+
() =>
129+
new Promise((resolve) =>
130+
setTimeout(() => resolve({ headers: { Authorization: `Bearer ${mockAccessToken}` } }), 10),
131+
),
132+
);
133+
134+
const headers = await cortiClient.getAuthHeaders();
135+
136+
expect(headers.get("Authorization")).toBe(`Bearer ${mockAccessToken}`);
137+
expect(headers.get("Tenant-Name")).toBe(mockTenantName);
138+
});
139+
});
140+
141+
describe("error handling", () => {
142+
it("should propagate token retrieval errors", async () => {
143+
const mockTenantName = "error-tenant";
144+
const tokenError = new Error("Failed to retrieve token");
145+
146+
const cortiClient = new CortiClient({
147+
environment: "https://api.test.corti.ai",
148+
tenantName: mockTenantName,
149+
auth: {
150+
clientId: "test-client-id",
151+
clientSecret: "test-client-secret",
152+
},
153+
}) as CortiClientWithAuth;
154+
155+
vi
156+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
157+
.mockRejectedValue(tokenError);
158+
159+
await expect(cortiClient.getAuthHeaders()).rejects.toThrow(
160+
"Failed to retrieve token",
161+
);
162+
});
163+
});
164+
165+
describe("return value structure", () => {
166+
it("should return a Headers instance", async () => {
167+
const mockEnvironment = "test";
168+
const mockTenantName = "test-tenant";
169+
const mockToken = createMockJWT(mockEnvironment, mockTenantName);
170+
171+
const cortiClient = new CortiClient({
172+
auth: {
173+
accessToken: mockToken,
174+
},
175+
}) as CortiClientWithAuth;
176+
177+
vi
178+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
179+
.mockResolvedValue({ headers: { Authorization: `Bearer ${mockToken}` } });
180+
181+
const headers = await cortiClient.getAuthHeaders();
182+
183+
expect(headers).toBeInstanceOf(Headers);
184+
expect(headers.get("Authorization")).toBeTruthy();
185+
expect(headers.get("Tenant-Name")).toBeTruthy();
186+
});
187+
188+
it("should only contain Authorization and Tenant-Name headers", async () => {
189+
const mockEnvironment = "test";
190+
const mockTenantName = "test-tenant";
191+
const mockToken = createMockJWT(mockEnvironment, mockTenantName);
192+
193+
const cortiClient = new CortiClient({
194+
auth: {
195+
accessToken: mockToken,
196+
},
197+
}) as CortiClientWithAuth;
198+
199+
vi
200+
.spyOn(cortiClient["_options"].authProvider, "getAuthRequest")
201+
.mockResolvedValue({ headers: { Authorization: `Bearer ${mockToken}` } });
202+
203+
const headers = await cortiClient.getAuthHeaders();
204+
205+
const headerKeys = Array.from(headers.keys());
206+
expect(headerKeys).toHaveLength(2);
207+
expect(headerKeys).toContain("authorization");
208+
expect(headerKeys).toContain("tenant-name");
209+
});
210+
});
211+
});

0 commit comments

Comments
 (0)