@milaboratories/pl-client 3.15.0 → 3.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/capabilities.cjs.map +1 -1
- package/dist/core/capabilities.d.ts +1 -1
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/ll_client.cjs +35 -18
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +12 -5
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +35 -18
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/transaction.cjs +2 -1
- package/dist/core/transaction.cjs.map +1 -1
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +2 -1
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/unauth_client.cjs +79 -32
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +49 -14
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +79 -32
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +150 -18
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +127 -9
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +150 -18
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-rest/plapi.d.ts +8 -2
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/core/capabilities.ts +4 -0
- package/src/core/ll_client.ts +25 -13
- package/src/core/transaction.ts +2 -0
- package/src/core/unauth_client.test.ts +69 -1
- package/src/core/unauth_client.ts +108 -34
- package/src/core/unauth_client_branch.test.ts +416 -5
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +225 -15
- package/src/proto-rest/plapi.ts +8 -2
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
import { test, expect, vi } from "vitest";
|
|
2
2
|
import { UnauthenticatedPlClient } from "./unauth_client";
|
|
3
|
+
import { LLPlClient } from "./ll_client";
|
|
4
|
+
import { PlatformClient as GrpcPlApiClient } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client";
|
|
3
5
|
import type { BackendCapability } from "./capabilities";
|
|
6
|
+
import {
|
|
7
|
+
AuthAPI_ListMethods_SSOAuthMethod_FlowType,
|
|
8
|
+
type AuthAPI_ListMethods_Response,
|
|
9
|
+
} from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api";
|
|
4
10
|
|
|
5
11
|
type Scheme = "basic" | "token" | "none";
|
|
12
|
+
type MethodInfo = AuthAPI_ListMethods_Response["methods"][number];
|
|
6
13
|
|
|
7
|
-
function makeStub(opts: { hasAuthV2: boolean; scheme?: Scheme }) {
|
|
14
|
+
function makeStub(opts: { hasAuthV2: boolean; scheme?: Scheme; methods?: MethodInfo[] }) {
|
|
8
15
|
const scheme = opts.scheme ?? "basic";
|
|
9
16
|
const methods =
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
opts.methods ??
|
|
18
|
+
(scheme === "basic"
|
|
19
|
+
? [
|
|
20
|
+
{
|
|
21
|
+
id: "basic",
|
|
22
|
+
title: "basic",
|
|
23
|
+
description: "",
|
|
24
|
+
method: { oneofKind: "basic", basic: {} },
|
|
25
|
+
},
|
|
26
|
+
]
|
|
12
27
|
: scheme === "token"
|
|
13
|
-
? [
|
|
14
|
-
|
|
28
|
+
? [
|
|
29
|
+
{
|
|
30
|
+
id: "token",
|
|
31
|
+
title: "token",
|
|
32
|
+
description: "",
|
|
33
|
+
method: { oneofKind: "token", token: {} },
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
: []);
|
|
15
37
|
const ll = {
|
|
16
38
|
hasCapability: vi.fn(
|
|
17
39
|
(capability: BackendCapability) => capability === "auth:v2" && opts.hasAuthV2,
|
|
@@ -19,6 +41,8 @@ function makeStub(opts: { hasAuthV2: boolean; scheme?: Scheme }) {
|
|
|
19
41
|
loginBasic: vi.fn().mockResolvedValue("jwt-from-loginBasic"),
|
|
20
42
|
loginWithToken: vi.fn().mockResolvedValue("jwt-from-loginWithToken"),
|
|
21
43
|
getJwtToken: vi.fn().mockResolvedValue("jwt-from-getJwtToken"),
|
|
44
|
+
beginSSOLogin: vi.fn().mockResolvedValue({ nonce: "nonce", expiresAt: new Date() }),
|
|
45
|
+
loginSSO: vi.fn().mockResolvedValue("jwt-from-loginSSO"),
|
|
22
46
|
authMethodsSync: { methods },
|
|
23
47
|
conf: { authTTLSeconds: 100 },
|
|
24
48
|
};
|
|
@@ -67,3 +91,390 @@ test("hasCapability proxies to underlying LLPlClient", () => {
|
|
|
67
91
|
expect(client.hasCapability("treeFilter:v2")).toBe(false);
|
|
68
92
|
expect(ll.hasCapability).toHaveBeenCalledWith("auth:v2");
|
|
69
93
|
});
|
|
94
|
+
|
|
95
|
+
function ssoMethod(overrides: {
|
|
96
|
+
id: string;
|
|
97
|
+
title?: string;
|
|
98
|
+
description: string;
|
|
99
|
+
issuer: string;
|
|
100
|
+
flowType?: AuthAPI_ListMethods_SSOAuthMethod_FlowType;
|
|
101
|
+
clientId?: string;
|
|
102
|
+
scopes?: string;
|
|
103
|
+
resource?: string;
|
|
104
|
+
prompt?: string;
|
|
105
|
+
redirectPorts?: number[];
|
|
106
|
+
subjectTokenSource?: string;
|
|
107
|
+
userIdClaim?: string;
|
|
108
|
+
groupsClaim?: string;
|
|
109
|
+
accessType?: string;
|
|
110
|
+
}): MethodInfo {
|
|
111
|
+
return {
|
|
112
|
+
id: overrides.id,
|
|
113
|
+
title: overrides.title ?? overrides.id,
|
|
114
|
+
description: overrides.description,
|
|
115
|
+
method: {
|
|
116
|
+
oneofKind: "sso",
|
|
117
|
+
sso: {
|
|
118
|
+
issuer: overrides.issuer,
|
|
119
|
+
clientId: overrides.clientId ?? "client-id",
|
|
120
|
+
scopes: overrides.scopes ?? "openid",
|
|
121
|
+
resource: overrides.resource ?? "",
|
|
122
|
+
prompt: overrides.prompt ?? "",
|
|
123
|
+
redirectPorts: overrides.redirectPorts ?? [12345],
|
|
124
|
+
subjectTokenSource: overrides.subjectTokenSource ?? "id_token",
|
|
125
|
+
userIdClaim: overrides.userIdClaim ?? "sub",
|
|
126
|
+
groupsClaim: overrides.groupsClaim ?? "",
|
|
127
|
+
flowType: overrides.flowType ?? AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE,
|
|
128
|
+
accessType: overrides.accessType,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function basicMethod(id: string, description = "Basic auth", title = id): MethodInfo {
|
|
135
|
+
return { id, title, description, method: { oneofKind: "basic", basic: {} } };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function tokenMethod(id: string, description = "Static token", title = id): MethodInfo {
|
|
139
|
+
return { id, title, description, method: { oneofKind: "token", token: {} } };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- loginMethods() ---
|
|
143
|
+
|
|
144
|
+
test("loginMethods lists every advertised method, of every kind, in advertised order", () => {
|
|
145
|
+
const methods: MethodInfo[] = [
|
|
146
|
+
ssoMethod({ id: "sso-a", description: "First IdP", issuer: "https://first.example" }),
|
|
147
|
+
ssoMethod({ id: "sso-b", description: "Second IdP", issuer: "https://second.example" }),
|
|
148
|
+
basicMethod("htpasswd-a", "htpasswd"),
|
|
149
|
+
basicMethod("htpasswd-b", "htpasswd"),
|
|
150
|
+
tokenMethod("token-a", "static token"),
|
|
151
|
+
];
|
|
152
|
+
const { client } = makeStub({ hasAuthV2: true, methods });
|
|
153
|
+
|
|
154
|
+
const listed = client.loginMethods();
|
|
155
|
+
|
|
156
|
+
expect(listed.map((m) => m.kind)).toEqual(["sso", "sso", "basic", "basic", "token"]);
|
|
157
|
+
expect(listed.map((m) => m.id)).toEqual([
|
|
158
|
+
"sso-a",
|
|
159
|
+
"sso-b",
|
|
160
|
+
"htpasswd-a",
|
|
161
|
+
"htpasswd-b",
|
|
162
|
+
"token-a",
|
|
163
|
+
]);
|
|
164
|
+
expect(listed.map((m) => m.title)).toEqual([
|
|
165
|
+
"sso-a",
|
|
166
|
+
"sso-b",
|
|
167
|
+
"htpasswd-a",
|
|
168
|
+
"htpasswd-b",
|
|
169
|
+
"token-a",
|
|
170
|
+
]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("loginMethods falls back to description, then id, when the backend omits title", () => {
|
|
174
|
+
const methods: MethodInfo[] = [
|
|
175
|
+
{
|
|
176
|
+
id: "no-title",
|
|
177
|
+
title: "",
|
|
178
|
+
description: "Untitled backend",
|
|
179
|
+
method: { oneofKind: "basic", basic: {} },
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "no-title-no-description",
|
|
183
|
+
title: "",
|
|
184
|
+
description: "",
|
|
185
|
+
method: { oneofKind: "token", token: {} },
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
const { client } = makeStub({ hasAuthV2: true, methods });
|
|
189
|
+
|
|
190
|
+
const listed = client.loginMethods();
|
|
191
|
+
|
|
192
|
+
expect(listed.map((m) => m.title)).toEqual(["Untitled backend", "no-title-no-description"]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("loginMethods keeps same-kind methods distinct while supportedAuthSchemes still collapses them", () => {
|
|
196
|
+
const methods: MethodInfo[] = [
|
|
197
|
+
basicMethod("htpasswd-a", "htpasswd"),
|
|
198
|
+
basicMethod("htpasswd-b", "htpasswd"),
|
|
199
|
+
tokenMethod("token-a"),
|
|
200
|
+
];
|
|
201
|
+
const { client } = makeStub({ hasAuthV2: true, methods });
|
|
202
|
+
|
|
203
|
+
const listed = client.loginMethods().filter((m) => m.kind === "basic");
|
|
204
|
+
|
|
205
|
+
expect(listed.map((m) => m.id)).toEqual(["htpasswd-a", "htpasswd-b"]);
|
|
206
|
+
expect(client.supportedAuthSchemes).toEqual({ basic: true, token: true });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("loginMethods maps every field of an SSO projection, and drops an empty accessType", () => {
|
|
210
|
+
const method = ssoMethod({
|
|
211
|
+
id: "sso-1",
|
|
212
|
+
title: "Corporate SSO",
|
|
213
|
+
description: "Corp SSO",
|
|
214
|
+
issuer: "https://idp.example",
|
|
215
|
+
clientId: "the-client-id",
|
|
216
|
+
scopes: "openid profile",
|
|
217
|
+
resource: "https://api.example",
|
|
218
|
+
prompt: "consent",
|
|
219
|
+
redirectPorts: [11000, 11001],
|
|
220
|
+
subjectTokenSource: "access_token",
|
|
221
|
+
userIdClaim: "email",
|
|
222
|
+
groupsClaim: "groups",
|
|
223
|
+
accessType: "",
|
|
224
|
+
});
|
|
225
|
+
const { client } = makeStub({ hasAuthV2: true, methods: [method] });
|
|
226
|
+
|
|
227
|
+
const [entry] = client.loginMethods();
|
|
228
|
+
|
|
229
|
+
expect(entry).toEqual({
|
|
230
|
+
kind: "sso",
|
|
231
|
+
id: "sso-1",
|
|
232
|
+
title: "Corporate SSO",
|
|
233
|
+
description: "Corp SSO",
|
|
234
|
+
issuer: "https://idp.example",
|
|
235
|
+
clientId: "the-client-id",
|
|
236
|
+
scopes: "openid profile",
|
|
237
|
+
resource: "https://api.example",
|
|
238
|
+
prompt: "consent",
|
|
239
|
+
redirectPorts: [11000, 11001],
|
|
240
|
+
subjectTokenSource: "access_token",
|
|
241
|
+
userIdClaim: "email",
|
|
242
|
+
groupsClaim: "groups",
|
|
243
|
+
flowType: "public_pkce",
|
|
244
|
+
accessType: undefined,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("loginMethods drops an advertised method with no usable arm", () => {
|
|
249
|
+
const armless: MethodInfo = {
|
|
250
|
+
id: "legacy",
|
|
251
|
+
title: "legacy",
|
|
252
|
+
description: "",
|
|
253
|
+
method: { oneofKind: undefined },
|
|
254
|
+
};
|
|
255
|
+
const { client } = makeStub({ hasAuthV2: true, methods: [armless, basicMethod("htpasswd-a")] });
|
|
256
|
+
|
|
257
|
+
expect(client.loginMethods().map((m) => m.id)).toEqual(["htpasswd-a"]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("loginMethods drops an SSO entry whose flow type is unsupported, keeping every other entry", () => {
|
|
261
|
+
const unsupported = ssoMethod({
|
|
262
|
+
id: "sso-legacy",
|
|
263
|
+
description: "Corp SSO",
|
|
264
|
+
issuer: "https://idp.example",
|
|
265
|
+
flowType: 99 as AuthAPI_ListMethods_SSOAuthMethod_FlowType,
|
|
266
|
+
});
|
|
267
|
+
const { client } = makeStub({
|
|
268
|
+
hasAuthV2: true,
|
|
269
|
+
methods: [unsupported, basicMethod("htpasswd-a")],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(client.loginMethods().map((m) => m.id)).toEqual(["htpasswd-a"]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("beginSSOLogin forwards the picked id to the low-level client", async () => {
|
|
276
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "none" });
|
|
277
|
+
|
|
278
|
+
await client.beginSSOLogin("sso-a");
|
|
279
|
+
|
|
280
|
+
expect(ll.beginSSOLogin).toHaveBeenCalledWith("sso-a");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("beginSSOLogin omits the argument when no id is picked", async () => {
|
|
284
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "none" });
|
|
285
|
+
|
|
286
|
+
await client.beginSSOLogin();
|
|
287
|
+
|
|
288
|
+
expect(ll.beginSSOLogin).toHaveBeenCalledWith();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("loginSSO carries the picked id to the low-level client alongside the token response", async () => {
|
|
292
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "none" });
|
|
293
|
+
const tokenResponse = new Uint8Array([1, 2, 3]);
|
|
294
|
+
|
|
295
|
+
await client.loginSSO({ tokenResponse, idP: "sso-a" });
|
|
296
|
+
|
|
297
|
+
expect(ll.loginSSO).toHaveBeenCalledWith(tokenResponse, "sso-a");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("loginSSO sends only the token response when no id is picked", async () => {
|
|
301
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "none" });
|
|
302
|
+
const tokenResponse = new Uint8Array([1, 2, 3]);
|
|
303
|
+
|
|
304
|
+
await client.loginSSO({ tokenResponse });
|
|
305
|
+
|
|
306
|
+
expect(ll.loginSSO).toHaveBeenCalledWith(tokenResponse);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("login forwards the picked id into loginBasic's options bag", async () => {
|
|
310
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "basic" });
|
|
311
|
+
|
|
312
|
+
await client.login("alice", "pw", "htpasswd-b");
|
|
313
|
+
|
|
314
|
+
expect(ll.loginBasic).toHaveBeenCalledWith("alice", "pw", { idP: "htpasswd-b" });
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("login drops the picked id on the token branch", async () => {
|
|
318
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "token" });
|
|
319
|
+
|
|
320
|
+
const info = await client.login("alice", "opaque-token", "some-id");
|
|
321
|
+
|
|
322
|
+
expect(ll.loginWithToken).toHaveBeenCalledWith("opaque-token");
|
|
323
|
+
expect(info.jwtToken).toBe("jwt-from-loginWithToken");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("login routes to loginWithToken when the picked id names a token method, even though the backend also advertises basic", async () => {
|
|
327
|
+
const { client, ll } = makeStub({
|
|
328
|
+
hasAuthV2: true,
|
|
329
|
+
methods: [
|
|
330
|
+
{ id: "ldap-a", title: "ldap-a", description: "", method: { oneofKind: "basic", basic: {} } },
|
|
331
|
+
{
|
|
332
|
+
id: "htpasswd",
|
|
333
|
+
title: "htpasswd",
|
|
334
|
+
description: "",
|
|
335
|
+
method: { oneofKind: "token", token: {} },
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const info = await client.login("alice", "opaque-token", "htpasswd");
|
|
341
|
+
|
|
342
|
+
expect(ll.loginWithToken).toHaveBeenCalledWith("opaque-token");
|
|
343
|
+
expect(ll.loginBasic).not.toHaveBeenCalled();
|
|
344
|
+
expect(info.jwtToken).toBe("jwt-from-loginWithToken");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("ssoConfig returns the first sso method and drops the rest", () => {
|
|
348
|
+
const first = ssoMethod({
|
|
349
|
+
id: "sso-first",
|
|
350
|
+
description: "First IdP",
|
|
351
|
+
issuer: "https://first.example",
|
|
352
|
+
});
|
|
353
|
+
const second = ssoMethod({
|
|
354
|
+
id: "sso-second",
|
|
355
|
+
description: "Second IdP",
|
|
356
|
+
issuer: "https://second.example",
|
|
357
|
+
});
|
|
358
|
+
const { client } = makeStub({ hasAuthV2: true, methods: [first, second] });
|
|
359
|
+
|
|
360
|
+
const sso = client.ssoConfig();
|
|
361
|
+
|
|
362
|
+
expect(sso).toMatchObject({
|
|
363
|
+
id: "sso-first",
|
|
364
|
+
description: "First IdP",
|
|
365
|
+
issuer: "https://first.example",
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("ssoConfig carries id, title and description onto the returned SSOAuthMethod", () => {
|
|
370
|
+
const method = ssoMethod({
|
|
371
|
+
id: "sso-1",
|
|
372
|
+
title: "Corporate IdP",
|
|
373
|
+
description: "Corp SSO",
|
|
374
|
+
issuer: "https://idp.example",
|
|
375
|
+
});
|
|
376
|
+
const { client } = makeStub({ hasAuthV2: true, methods: [method] });
|
|
377
|
+
|
|
378
|
+
const sso = client.ssoConfig();
|
|
379
|
+
|
|
380
|
+
expect(sso?.id).toBe("sso-1");
|
|
381
|
+
expect(sso?.title).toBe("Corporate IdP");
|
|
382
|
+
expect(sso?.description).toBe("Corp SSO");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("ssoConfig throws when the sso method's flow type is not PUBLIC_PKCE", () => {
|
|
386
|
+
const method = ssoMethod({
|
|
387
|
+
id: "sso-1",
|
|
388
|
+
description: "Corp SSO",
|
|
389
|
+
issuer: "https://idp.example",
|
|
390
|
+
// 99 is not a defined FlowType value; casts an out-of-range value the
|
|
391
|
+
// proto currently has none for, to pin the guard against a future flow.
|
|
392
|
+
flowType: 99 as AuthAPI_ListMethods_SSOAuthMethod_FlowType,
|
|
393
|
+
});
|
|
394
|
+
const { client } = makeStub({ hasAuthV2: true, methods: [method] });
|
|
395
|
+
|
|
396
|
+
expect(() => client.ssoConfig()).toThrow("ssoConfig: unsupported SSO flow type");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("supportedAuthSchemes collapses a basic+token+sso method list to scheme booleans", () => {
|
|
400
|
+
const methods: MethodInfo[] = [
|
|
401
|
+
{ id: "basic", title: "basic", description: "", method: { oneofKind: "basic", basic: {} } },
|
|
402
|
+
{ id: "token", title: "token", description: "", method: { oneofKind: "token", token: {} } },
|
|
403
|
+
ssoMethod({ id: "sso-1", description: "Corp SSO", issuer: "https://idp.example" }),
|
|
404
|
+
];
|
|
405
|
+
const { client } = makeStub({ hasAuthV2: true, methods });
|
|
406
|
+
|
|
407
|
+
expect(client.supportedAuthSchemes).toEqual({ basic: true, token: true });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// --- LLPlClient.loginBasic carries the id on both transports ---
|
|
411
|
+
|
|
412
|
+
function makeLLStub(cl: { login?: ReturnType<typeof vi.fn>; POST?: ReturnType<typeof vi.fn> }) {
|
|
413
|
+
return Object.assign(Object.create(LLPlClient.prototype) as object, {
|
|
414
|
+
clientProvider: { get: () => cl },
|
|
415
|
+
conf: { authTTLSeconds: 100 },
|
|
416
|
+
}) as LLPlClient;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
test("loginBasic carries idP in the request body on the gRPC transport", async () => {
|
|
420
|
+
const login = vi.fn(() => ({ response: Promise.resolve({ token: "jwt" }) }));
|
|
421
|
+
const cl = Object.assign(Object.create(GrpcPlApiClient.prototype) as object, { login });
|
|
422
|
+
const ll = makeLLStub(cl);
|
|
423
|
+
|
|
424
|
+
await ll.loginBasic("alice", "pw", { idP: "htpasswd-b" });
|
|
425
|
+
|
|
426
|
+
expect(login).toHaveBeenCalledWith(
|
|
427
|
+
expect.objectContaining({
|
|
428
|
+
credentials: {
|
|
429
|
+
oneofKind: "basic",
|
|
430
|
+
basic: { login: "alice", password: "pw", idp: "htpasswd-b" },
|
|
431
|
+
},
|
|
432
|
+
}),
|
|
433
|
+
expect.anything(),
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("loginBasic without idP produces the same gRPC request body as today", async () => {
|
|
438
|
+
const login = vi.fn(() => ({ response: Promise.resolve({ token: "jwt" }) }));
|
|
439
|
+
const cl = Object.assign(Object.create(GrpcPlApiClient.prototype) as object, { login });
|
|
440
|
+
const ll = makeLLStub(cl);
|
|
441
|
+
|
|
442
|
+
await ll.loginBasic("alice", "pw");
|
|
443
|
+
|
|
444
|
+
expect(login).toHaveBeenCalledWith(
|
|
445
|
+
expect.objectContaining({
|
|
446
|
+
credentials: { oneofKind: "basic", basic: { login: "alice", password: "pw" } },
|
|
447
|
+
}),
|
|
448
|
+
expect.anything(),
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("loginBasic carries idP in the request body on the REST transport", async () => {
|
|
453
|
+
const POST = vi.fn(() => Promise.resolve({ data: { token: "jwt" } }));
|
|
454
|
+
const ll = makeLLStub({ POST });
|
|
455
|
+
|
|
456
|
+
await ll.loginBasic("alice", "pw", { idP: "htpasswd-b" });
|
|
457
|
+
|
|
458
|
+
expect(POST).toHaveBeenCalledWith(
|
|
459
|
+
"/v1/auth/login",
|
|
460
|
+
expect.objectContaining({
|
|
461
|
+
body: expect.objectContaining({
|
|
462
|
+
basic: { login: "alice", password: "pw", idp: "htpasswd-b" },
|
|
463
|
+
}),
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("loginBasic without idP produces the same REST request body as today", async () => {
|
|
469
|
+
const POST = vi.fn(() => Promise.resolve({ data: { token: "jwt" } }));
|
|
470
|
+
const ll = makeLLStub({ POST });
|
|
471
|
+
|
|
472
|
+
await ll.loginBasic("alice", "pw");
|
|
473
|
+
|
|
474
|
+
expect(POST).toHaveBeenCalledWith(
|
|
475
|
+
"/v1/auth/login",
|
|
476
|
+
expect.objectContaining({
|
|
477
|
+
body: expect.objectContaining({ basic: { login: "alice", password: "pw" } }),
|
|
478
|
+
}),
|
|
479
|
+
);
|
|
480
|
+
});
|