Skip to content

Commit bbb7f6d

Browse files
committed
Add wrapper client that accepts a u+p and gets a token
1 parent 043cdca commit bbb7f6d

File tree

4 files changed

+130
-6
lines changed

4 files changed

+130
-6
lines changed

.fernignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
# Specify files that shouldn't be modified by Fern
2+
3+
# Manual wrapper client implementation
4+
src/WrapperClient.ts
5+
src/index.ts
6+
7+
# Enhanced auth client with basic auth support
8+
src/api/resources/authtoken/resources/auth/client/Client.ts

src/WrapperClient.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Simple wrapper client that extends phenomlClient with username/password authentication.
3+
* Generates token lazily on first API call when username/password are provided.
4+
*/
5+
6+
import * as environments from "./environments.js";
7+
import * as core from "./core/index.js";
8+
import { phenomlClient } from "./Client.js";
9+
import { Auth } from "./api/resources/authtoken/resources/auth/client/Client.js";
10+
11+
export declare namespace PhenoMLClient {
12+
export interface Options {
13+
environment?: core.Supplier<environments.phenomlEnvironment | string>;
14+
baseUrl?: core.Supplier<string>;
15+
username?: string;
16+
password?: string;
17+
token?: core.Supplier<core.BearerToken>;
18+
headers?: Record<string, string | core.Supplier<string | undefined> | undefined>;
19+
fetcher?: core.FetchFunction;
20+
}
21+
}
22+
23+
// Create a token supplier that generates tokens on-demand
24+
class TokenSupplier {
25+
private _tokenPromise?: Promise<string>;
26+
private _cachedToken?: string;
27+
private _username: string;
28+
private _password: string;
29+
private _baseUrl: string;
30+
private _fetcher?: core.FetchFunction;
31+
32+
constructor(username: string, password: string, baseUrl: string, fetcher?: core.FetchFunction) {
33+
this._username = username;
34+
this._password = password;
35+
this._baseUrl = baseUrl;
36+
this._fetcher = fetcher;
37+
}
38+
39+
async get(): Promise<string> {
40+
if (this._cachedToken) {
41+
return this._cachedToken;
42+
}
43+
44+
if (this._tokenPromise) {
45+
this._cachedToken = await this._tokenPromise;
46+
return this._cachedToken;
47+
}
48+
49+
this._tokenPromise = this._generateToken();
50+
this._cachedToken = await this._tokenPromise;
51+
return this._cachedToken;
52+
}
53+
54+
private async _generateToken(): Promise<string> {
55+
const authClient = new Auth({
56+
baseUrl: this._baseUrl,
57+
fetcher: this._fetcher,
58+
});
59+
60+
const response = await authClient.generateToken({
61+
username: this._username,
62+
password: this._password,
63+
});
64+
65+
return response.token;
66+
}
67+
}
68+
69+
export class PhenoMLClient extends phenomlClient {
70+
constructor(options: PhenoMLClient.Options) {
71+
// Validate auth options
72+
if (!options.token && (!options.username || !options.password)) {
73+
throw new Error("Must provide either 'token' or both 'username' and 'password'");
74+
}
75+
76+
if (options.token && (options.username || options.password)) {
77+
throw new Error("Cannot provide both 'token' and 'username'/'password'");
78+
}
79+
80+
// If token provided, use directly
81+
if (options.token) {
82+
super(options as phenomlClient.Options);
83+
return;
84+
}
85+
86+
// Create lazy token supplier for username/password
87+
const baseUrl = (typeof options.baseUrl === 'string' ? options.baseUrl : undefined) ||
88+
(typeof options.environment === 'string' ? options.environment : undefined) ||
89+
environments.phenomlEnvironment.Default;
90+
91+
const tokenSupplier = new TokenSupplier(
92+
options.username!,
93+
options.password!,
94+
baseUrl,
95+
options.fetcher
96+
);
97+
98+
super({
99+
...options,
100+
token: tokenSupplier.get.bind(tokenSupplier) as core.Supplier<core.BearerToken>,
101+
username: undefined,
102+
password: undefined,
103+
} as phenomlClient.Options);
104+
}
105+
}

src/api/resources/authtoken/resources/auth/client/Client.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export declare namespace Auth {
1313
environment?: core.Supplier<environments.phenomlEnvironment | string>;
1414
/** Specify a custom URL to connect the client to. */
1515
baseUrl?: core.Supplier<string>;
16-
token: core.Supplier<core.BearerToken>;
1716
/** Additional headers to include in requests. */
1817
headers?: Record<string, string | core.Supplier<string | undefined> | undefined>;
1918
fetcher?: core.FetchFunction;
@@ -68,7 +67,7 @@ export class Auth {
6867
): Promise<core.WithRawResponse<phenoml.authtoken.AuthGenerateTokenResponse>> {
6968
let _headers: core.Fetcher.Args["headers"] = mergeHeaders(
7069
this._options?.headers,
71-
mergeOnlyDefinedHeaders({ Authorization: await this._getAuthorizationHeader() }),
70+
mergeOnlyDefinedHeaders({ Authorization: await this._getAuthorizationHeader(request) }),
7271
requestOptions?.headers,
7372
);
7473
const _response = await (this._options.fetcher ?? core.fetcher)({
@@ -83,7 +82,6 @@ export class Auth {
8382
contentType: "application/json",
8483
queryParameters: requestOptions?.queryParams,
8584
requestType: "json",
86-
body: request,
8785
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
8886
maxRetries: requestOptions?.maxRetries,
8987
abortSignal: requestOptions?.abortSignal,
@@ -130,7 +128,21 @@ export class Auth {
130128
}
131129
}
132130

133-
protected async _getAuthorizationHeader(): Promise<string> {
134-
return `Bearer ${await core.Supplier.get(this._options.token)}`;
131+
protected async _getAuthorizationHeader(request?: phenoml.authtoken.AuthGenerateTokenRequest): Promise<string> {
132+
// Auth token endpoint only uses basic auth with username/password
133+
if (!request?.username || !request?.password) {
134+
throw new Error("Must provide both 'username' and 'password'");
135+
}
136+
137+
const basicAuthHeader = core.BasicAuth.toAuthorizationHeader({
138+
username: request.username,
139+
password: request.password
140+
});
141+
142+
if (!basicAuthHeader) {
143+
throw new Error("Failed to create basic auth header from username/password");
144+
}
145+
146+
return basicAuthHeader;
135147
}
136148
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export * as phenoml from "./api/index.js";
22
export { phenomlError, phenomlTimeoutError } from "./errors/index.js";
3-
export { phenomlClient } from "./Client.js";
3+
export { PhenoMLClient } from "./WrapperClient.js";
44
export { phenomlEnvironment } from "./environments.js";

0 commit comments

Comments
 (0)