@arbidocs/sdk 0.3.17 → 0.3.18

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/index.cjs CHANGED
@@ -285,6 +285,107 @@ var FileConfigStore = class {
285
285
  return null;
286
286
  }
287
287
  };
288
+
289
+ // src/device-flow.ts
290
+ var device_flow_exports = {};
291
+ __export(device_flow_exports, {
292
+ DeviceFlowAccessDenied: () => DeviceFlowAccessDenied,
293
+ DeviceFlowError: () => DeviceFlowError,
294
+ DeviceFlowExpired: () => DeviceFlowExpired,
295
+ fetchSsoConfig: () => fetchSsoConfig,
296
+ pollForToken: () => pollForToken,
297
+ requestDeviceCode: () => requestDeviceCode
298
+ });
299
+ var DeviceFlowError = class extends Error {
300
+ constructor(message) {
301
+ super(message);
302
+ this.name = "DeviceFlowError";
303
+ }
304
+ };
305
+ var DeviceFlowExpired = class extends DeviceFlowError {
306
+ constructor() {
307
+ super("Device code expired \u2014 please try again");
308
+ this.name = "DeviceFlowExpired";
309
+ }
310
+ };
311
+ var DeviceFlowAccessDenied = class extends DeviceFlowError {
312
+ constructor() {
313
+ super("Authorization was denied by the user");
314
+ this.name = "DeviceFlowAccessDenied";
315
+ }
316
+ };
317
+ async function fetchSsoConfig(baseUrl) {
318
+ const arbi = client.createArbiClient({ baseUrl, deploymentDomain: "", credentials: "omit" });
319
+ const { data, error } = await arbi.fetch.GET("/v1/user/sso-config");
320
+ if (error || !data) {
321
+ throw new DeviceFlowError(`Failed to fetch SSO config`);
322
+ }
323
+ return {
324
+ ssoEnabled: data.sso_enabled,
325
+ domain: data.domain,
326
+ clientId: data.cli_client_id || data.client_id,
327
+ audience: data.audience
328
+ };
329
+ }
330
+ async function requestDeviceCode(domain, clientId, audience, scope = "openid email profile") {
331
+ const body = new URLSearchParams({
332
+ client_id: clientId,
333
+ scope,
334
+ ...audience ? { audience } : {}
335
+ });
336
+ const res = await fetch(`https://${domain}/oauth/device/code`, {
337
+ method: "POST",
338
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
339
+ body
340
+ });
341
+ if (!res.ok) {
342
+ const text = await res.text();
343
+ throw new DeviceFlowError(`Device code request failed: ${res.status} ${text}`);
344
+ }
345
+ return await res.json();
346
+ }
347
+ async function pollForToken(domain, clientId, deviceCode, interval, expiresIn, onPoll) {
348
+ const deadline = Date.now() + expiresIn * 1e3;
349
+ let pollInterval = interval * 1e3;
350
+ while (Date.now() < deadline) {
351
+ await sleep(pollInterval);
352
+ onPoll?.(Date.now());
353
+ const res = await fetch(`https://${domain}/oauth/token`, {
354
+ method: "POST",
355
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
356
+ body: new URLSearchParams({
357
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
358
+ client_id: clientId,
359
+ device_code: deviceCode
360
+ })
361
+ });
362
+ if (res.ok) {
363
+ const data = await res.json();
364
+ return data.access_token;
365
+ }
366
+ const errBody = await res.json().catch(() => ({ error: "unknown" }));
367
+ if (errBody.error === "authorization_pending") {
368
+ continue;
369
+ } else if (errBody.error === "slow_down") {
370
+ pollInterval += 5e3;
371
+ continue;
372
+ } else if (errBody.error === "expired_token") {
373
+ throw new DeviceFlowExpired();
374
+ } else if (errBody.error === "access_denied") {
375
+ throw new DeviceFlowAccessDenied();
376
+ } else {
377
+ throw new DeviceFlowError(
378
+ `Token polling error: ${errBody.error} \u2014 ${errBody.error_description ?? ""}`
379
+ );
380
+ }
381
+ }
382
+ throw new DeviceFlowExpired();
383
+ }
384
+ function sleep(ms) {
385
+ return new Promise((resolve) => setTimeout(resolve, ms));
386
+ }
387
+
388
+ // src/auth.ts
288
389
  function formatWorkspaceChoices(wsList) {
289
390
  return wsList.map((ws) => {
290
391
  const totalDocs = ws.shared_document_count + ws.private_document_count;
@@ -305,7 +406,8 @@ async function createAuthenticatedClient(config, creds, store) {
305
406
  const signingPrivateKey = client.base64ToBytes(creds.signingPrivateKeyBase64);
306
407
  const loginResult = await arbi.auth.loginWithKey({
307
408
  email: creds.email,
308
- signingPrivateKey
409
+ signingPrivateKey,
410
+ ssoToken: creds.ssoToken
309
411
  });
310
412
  store.saveCredentials({
311
413
  ...creds,
@@ -337,6 +439,40 @@ async function performPasswordLogin(config, email, password, store) {
337
439
  });
338
440
  return { arbi, loginResult, config };
339
441
  }
442
+ async function performSsoDeviceFlowLogin(config, email, password, store, callbacks) {
443
+ const ssoConfig = await fetchSsoConfig(config.baseUrl);
444
+ if (!ssoConfig.ssoEnabled) {
445
+ throw new ArbiError("SSO is not enabled on this deployment");
446
+ }
447
+ const dc = await requestDeviceCode(ssoConfig.domain, ssoConfig.clientId, ssoConfig.audience);
448
+ callbacks?.onUserCode?.(dc.user_code, dc.verification_uri_complete);
449
+ const ssoToken = await pollForToken(
450
+ ssoConfig.domain,
451
+ ssoConfig.clientId,
452
+ dc.device_code,
453
+ dc.interval,
454
+ dc.expires_in,
455
+ callbacks?.onPoll
456
+ );
457
+ const arbi = client.createArbiClient({
458
+ baseUrl: config.baseUrl,
459
+ deploymentDomain: config.deploymentDomain,
460
+ credentials: "omit"
461
+ });
462
+ await arbi.crypto.initSodium();
463
+ const loginResult = await arbi.auth.login({ email, password, ssoToken });
464
+ store.saveCredentials({
465
+ email,
466
+ signingPrivateKeyBase64: arbi.crypto.bytesToBase64(loginResult.signingPrivateKey),
467
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey),
468
+ ssoToken,
469
+ accessToken: void 0,
470
+ workspaceKeyHeader: void 0,
471
+ workspaceId: void 0,
472
+ tokenTimestamp: void 0
473
+ });
474
+ return { arbi, loginResult, config };
475
+ }
340
476
  async function selectWorkspace(arbi, workspaceId, wrappedKey, serverSessionKey, signingPrivateKeyBase64) {
341
477
  const signingPrivateKey = client.base64ToBytes(signingPrivateKeyBase64);
342
478
  const ed25519PublicKey = signingPrivateKey.slice(32, 64);
@@ -2097,6 +2233,7 @@ exports.conversations = conversations_exports;
2097
2233
  exports.countCitations = countCitations;
2098
2234
  exports.createAuthenticatedClient = createAuthenticatedClient;
2099
2235
  exports.createDocumentWaiter = createDocumentWaiter;
2236
+ exports.deviceFlow = device_flow_exports;
2100
2237
  exports.dm = dm_exports;
2101
2238
  exports.doctags = doctags_exports;
2102
2239
  exports.documents = documents_exports;
@@ -2114,6 +2251,7 @@ exports.getErrorMessage = getErrorMessage;
2114
2251
  exports.health = health_exports;
2115
2252
  exports.parseSSEEvents = parseSSEEvents;
2116
2253
  exports.performPasswordLogin = performPasswordLogin;
2254
+ exports.performSsoDeviceFlowLogin = performSsoDeviceFlowLogin;
2117
2255
  exports.requireData = requireData;
2118
2256
  exports.requireOk = requireOk;
2119
2257
  exports.resolveAuth = resolveAuth;