@truenas/api-client 9.0.1 → 9.0.3

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/README.md CHANGED
@@ -71,15 +71,18 @@ promise.
71
71
 
72
72
  | Method | Use it for |
73
73
  |---|---|
74
- | `loginWithUserPass(username, password)` | Interactive logins. Resolves with `response_type: 'OTP_REQUIRED'` when two-factor is on. |
74
+ | `loginWithUserPass(username, password, options?)` | Interactive logins. Resolves with `response_type: 'OTP_REQUIRED'` when two-factor is on. Pass `{ rememberPassword: false }` to keep the password out of memory once the login succeeds. |
75
75
  | `loginWithOtp(code)` | The second step of a two-factor login. |
76
76
  | `loginWithApiKey({ username, key })` | Services and scripts. |
77
77
  | `loginWithToken(token)` | Opening another session from a previous login's `reconnect_token`. |
78
78
 
79
79
  All four live on `client.authenticator`. Password and API-key sessions log back
80
- in by themselves after a reconnect. `authenticated$` tracks the session,
81
- `logout()` ends it, and a rejected login errors with an `AuthError` carrying an
82
- `AuthErrorCode`.
80
+ in by themselves after a reconnect, unless the password login passed
81
+ `rememberPassword: false`; a token session is the caller's to restore.
82
+ `authenticated$` tracks the session, `logout()` ends it, and a rejected login
83
+ errors with an `AuthError` carrying an `AuthErrorCode`, plus the refusing
84
+ `response` when middleware answered with one (`response.response_type` tells
85
+ `EXPIRED`, `DENIED` and `REDIRECT` apart from `AUTH_ERR`).
83
86
 
84
87
  ## Calls
85
88
 
@@ -168,6 +171,21 @@ client you get. A named `version` skips discovery and also selects the
168
171
  WebSocket endpoint, so name the version the appliance actually runs. The types
169
172
  follow it when the version is a literal.
170
173
 
174
+ To be written against one version but run against whatever the appliance
175
+ serves, as an appliance's own UI is, keep the `version` and move the socket:
176
+
177
+ ```typescript
178
+ const client = await createTrueNasClient({
179
+ ...options,
180
+ version: 'v28.0.0', // the surface you compile against
181
+ websocketPath: '/api/current', // the appliance's newest version, whichever it is
182
+ });
183
+ ```
184
+
185
+ The client then works against an older or newer release call for call; a
186
+ method or field that release lacks fails when it is called rather than at
187
+ compile time.
188
+
171
189
  For code that must run against any supported release, use `client.ops`. It
172
190
  offers the same calls on every version and maps them onto whatever that version
173
191
  provides:
@@ -2193,9 +2193,16 @@ var AuthErrorCode = /* @__PURE__ */ ((AuthErrorCode2) => {
2193
2193
  return AuthErrorCode2;
2194
2194
  })(AuthErrorCode || {});
2195
2195
  var AuthError = class _AuthError extends Error {
2196
- constructor(code, message) {
2196
+ /**
2197
+ * @param response - the answer middleware refused the login with, when there was one.
2198
+ * Its `response_type` is what tells `EXPIRED`, `DENIED` and `REDIRECT` apart from
2199
+ * `AUTH_ERR`; the code alone says only which mechanism failed. Absent when the login
2200
+ * never got an answer of that kind (an error frame, or a superseded login).
2201
+ */
2202
+ constructor(code, message, response) {
2197
2203
  super(message);
2198
2204
  this.code = code;
2205
+ this.response = response;
2199
2206
  this.name = "AuthError";
2200
2207
  Object.setPrototypeOf(this, _AuthError.prototype);
2201
2208
  }
@@ -2733,7 +2740,7 @@ function getWebSocketPath(version) {
2733
2740
  // src/auth/truenas-authenticator.ts
2734
2741
  var throwOnAuthenticationFailure = (code, message) => rxjs.switchMap((response) => {
2735
2742
  if (response.response_type === "AUTH_ERR" /* AuthErr */) {
2736
- return rxjs.throwError(() => new AuthError(code, message));
2743
+ return rxjs.throwError(() => new AuthError(code, message, response));
2737
2744
  }
2738
2745
  return rxjs.of(response);
2739
2746
  });
@@ -2870,7 +2877,8 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
2870
2877
  }
2871
2878
  this.authenticating$.next(false);
2872
2879
  }
2873
- loginWithUserPass(username, password) {
2880
+ loginWithUserPass(username, password, options = {}) {
2881
+ const { rememberPassword = true } = options;
2874
2882
  const { sentDuring, callerInitiated } = this.beginLogin();
2875
2883
  let answered = false;
2876
2884
  const message = createJsonRpcMessage("auth.login_ex", [
@@ -2906,9 +2914,11 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
2906
2914
  rxjs.tap((res) => {
2907
2915
  if (res.response_type === "SUCCESS" /* Success */) {
2908
2916
  this.assertNotSuperseded(sentDuring);
2909
- this.credentials = { username, password, key: "" };
2917
+ this.credentials = rememberPassword ? { username, password, key: "" } : { username: "", password: "", key: "" };
2910
2918
  this.sessionLifetime = res.user_info?.attributes?.preferences?.lifetime ?? _TrueNasAuthenticator.DefaultSessionLifetime;
2911
2919
  this.authenticated$.next(true);
2920
+ } else if (res.response_type === "OTP_REQUIRED" /* OtpRequired */ && !rememberPassword && this.authEpoch === sentDuring) {
2921
+ this.credentials = { username: "", password: "", key: "" };
2912
2922
  }
2913
2923
  }),
2914
2924
  rxjs.finalize(() => {
@@ -3006,7 +3016,8 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
3006
3016
  if (res.response_type !== "SUCCESS" /* Success */) {
3007
3017
  throw new AuthError(
3008
3018
  "TOKEN_AUTH_FAILED" /* TokenAuthFailed */,
3009
- `TrueNAS token authentication failed (${res.response_type}). The token may have expired or been used, or the account may no longer be permitted to log in.`
3019
+ `TrueNAS token authentication failed (${res.response_type}). The token may have expired or been used, or the account may no longer be permitted to log in.`,
3020
+ res
3010
3021
  );
3011
3022
  }
3012
3023
  return res;
@@ -4297,6 +4308,11 @@ async function createTrueNasClient(opts) {
4297
4308
  `Cannot create client for system ${uuid}: hostnames array is empty`
4298
4309
  );
4299
4310
  }
4311
+ if (opts.websocketPath !== void 0 && !opts.websocketPath.startsWith("/")) {
4312
+ throw new Error(
4313
+ `Cannot create client for system ${uuid}: websocketPath must be an absolute path, got '${opts.websocketPath}'.`
4314
+ );
4315
+ }
4300
4316
  logger.info("Creating versioned API client", {
4301
4317
  uuid: uuid.slice(0, 8),
4302
4318
  hostnames: hostnames.join(", "),
@@ -4326,7 +4342,7 @@ async function createTrueNasClient(opts) {
4326
4342
  logger.info("API version supplied by the caller, skipping discovery", {
4327
4343
  uuid: uuid.slice(0, 8),
4328
4344
  version: known.version,
4329
- websocketPath: known.websocketPath
4345
+ websocketPath: opts.websocketPath ?? known.websocketPath
4330
4346
  });
4331
4347
  return instantiateClientForVersion(known, opts, logger);
4332
4348
  }
@@ -4345,7 +4361,7 @@ async function createTrueNasClient(opts) {
4345
4361
  // of them anyway.
4346
4362
  hostname: winner.hostname,
4347
4363
  version: version.version,
4348
- websocketPath: version.websocketPath
4364
+ websocketPath: opts.websocketPath ?? version.websocketPath
4349
4365
  });
4350
4366
  } catch (error) {
4351
4367
  const errorMessage = errorMessageOrDefault(error, "Unknown error");
@@ -4471,6 +4487,7 @@ function clientClassFor(version) {
4471
4487
  function instantiateClientForVersion(version, opts, logger) {
4472
4488
  const { uuid, hostnames, enabled, systemName } = opts;
4473
4489
  const versionKey = clientVersionKey(version);
4490
+ const endpoint = opts.websocketPath === void 0 ? version : { ...version, websocketPath: opts.websocketPath };
4474
4491
  const Client = CLIENT_BY_VERSION_KEY[versionKey];
4475
4492
  if (!Client) {
4476
4493
  logger.error("No client implementation for version", {
@@ -4485,12 +4502,13 @@ function instantiateClientForVersion(version, opts, logger) {
4485
4502
  logger.info(`Instantiating ${Client.name}`, {
4486
4503
  uuid: uuid.slice(0, 8),
4487
4504
  version: version.version,
4488
- versionKey
4505
+ versionKey,
4506
+ websocketPath: endpoint.websocketPath
4489
4507
  });
4490
4508
  return new Client(
4491
4509
  uuid,
4492
4510
  hostnames,
4493
- version,
4511
+ endpoint,
4494
4512
  enabled,
4495
4513
  systemName,
4496
4514
  logger,
@@ -4557,5 +4575,5 @@ exports.v25_10_4_exports = v25_10_4_exports;
4557
4575
  exports.v25_10_5_exports = v25_10_5_exports;
4558
4576
  exports.v27_0_0_exports = v27_0_0_exports;
4559
4577
  exports.v28_0_0_exports = v28_0_0_exports;
4560
- //# sourceMappingURL=chunk-U6V2Y3AE.cjs.map
4561
- //# sourceMappingURL=chunk-U6V2Y3AE.cjs.map
4578
+ //# sourceMappingURL=chunk-DJCZ545I.cjs.map
4579
+ //# sourceMappingURL=chunk-DJCZ545I.cjs.map