@ai-matrx/vault 0.1.55 → 0.1.56

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.d.cts CHANGED
@@ -90,6 +90,7 @@ interface VaultItem {
90
90
  fields: VaultField[];
91
91
  custom: VaultCustomField[];
92
92
  authenticator: VaultAuthenticator | null;
93
+ authenticatorState: "known" | "pending" | "unavailable";
93
94
  capabilities: VaultItemCapabilities;
94
95
  attachments: VaultAttachment[];
95
96
  uriMatchMode: UriMatchMode;
@@ -323,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
323
324
  */
324
325
  interface AuthenticatorPort {
325
326
  /** Every account the signed-in person holds a one-time password for. */
326
- list(): Promise<AuthenticatorEntry[]>;
327
+ list(): Promise<{
328
+ entries: AuthenticatorEntry[];
329
+ unavailableScopes: {
330
+ type: "user" | "organization" | "shared";
331
+ organizationId?: string | null;
332
+ }[];
333
+ }>;
327
334
  /** null when the item has none. */
328
335
  get(item: ItemRef): Promise<AuthenticatorEntry | null>;
329
336
  /** The current code and how long it lives. */
package/dist/index.d.ts CHANGED
@@ -90,6 +90,7 @@ interface VaultItem {
90
90
  fields: VaultField[];
91
91
  custom: VaultCustomField[];
92
92
  authenticator: VaultAuthenticator | null;
93
+ authenticatorState: "known" | "pending" | "unavailable";
93
94
  capabilities: VaultItemCapabilities;
94
95
  attachments: VaultAttachment[];
95
96
  uriMatchMode: UriMatchMode;
@@ -323,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
323
324
  */
324
325
  interface AuthenticatorPort {
325
326
  /** Every account the signed-in person holds a one-time password for. */
326
- list(): Promise<AuthenticatorEntry[]>;
327
+ list(): Promise<{
328
+ entries: AuthenticatorEntry[];
329
+ unavailableScopes: {
330
+ type: "user" | "organization" | "shared";
331
+ organizationId?: string | null;
332
+ }[];
333
+ }>;
327
334
  /** null when the item has none. */
328
335
  get(item: ItemRef): Promise<AuthenticatorEntry | null>;
329
336
  /** The current code and how long it lives. */
package/dist/index.js CHANGED
@@ -2166,7 +2166,7 @@ function normalizeWebsite(raw) {
2166
2166
  }
2167
2167
  function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2168
2168
  const confirmed = useConfirmed();
2169
- const candidates = items.filter((i) => !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2169
+ const candidates = items.filter((i) => i.authenticatorState === "known" && !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2170
2170
  const [input, setInput] = useState11("");
2171
2171
  const [image, setImage] = useState11(null);
2172
2172
  const [target, setTarget] = useState11(itemId && candidates.some((c) => c.id === itemId) ? itemId : NEW);
@@ -2205,7 +2205,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2205
2205
  }
2206
2206
  const it = item;
2207
2207
  const entry = await confirmed(() => image ? port.authenticator.enrolQrImage(it, image, image.name) : port.authenticator.enrol(it, input));
2208
- let next = { ...it, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
2208
+ let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
2209
2209
  const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
2210
2210
  if (!isNew && better !== it.name) {
2211
2211
  try {
@@ -2343,6 +2343,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2343
2343
  useEffect10(() => {
2344
2344
  if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
2345
2345
  }, [listed, added]);
2346
+ const [incomplete, setIncomplete] = useState12(true);
2346
2347
  const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
2347
2348
  const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
2348
2349
  const [adding, setAdding] = useState12(false);
@@ -2352,8 +2353,15 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2352
2353
  useEffect10(() => {
2353
2354
  let live = true;
2354
2355
  port.authenticator.list().then(
2355
- (list) => live && setEntries(new Map(list.map((e) => [e.itemId, e]))),
2356
- (e) => console.error("One-time password accounts could not be read", e)
2356
+ (list) => {
2357
+ if (live) {
2358
+ setEntries(new Map(list.entries.map((e) => [e.itemId, e])));
2359
+ setIncomplete(list.unavailableScopes.length > 0);
2360
+ }
2361
+ },
2362
+ () => {
2363
+ if (live) setIncomplete(true);
2364
+ }
2357
2365
  );
2358
2366
  return () => {
2359
2367
  live = false;
@@ -2371,7 +2379,14 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2371
2379
  ] }),
2372
2380
  total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
2373
2381
  ] }),
2374
- total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2382
+ total > 0 && incomplete ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
2383
+ "Some codes unavailable ",
2384
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2385
+ ] }) : null,
2386
+ total === 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: [
2387
+ "Codes unavailable ",
2388
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2389
+ ] }) : total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2375
2390
  /* @__PURE__ */ jsx11("span", { className: "flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx11(ShieldGlyph, { className: "size-6" }) }),
2376
2391
  /* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground", children: "No authenticator codes yet" }),
2377
2392
  /* @__PURE__ */ jsx11(Button9, { variant: "primary", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add authenticator" })
@@ -6421,7 +6436,10 @@ function ItemPane(props) {
6421
6436
  i
6422
6437
  )),
6423
6438
  /* @__PURE__ */ jsx25(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
6424
- /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }),
6439
+ !item || item.authenticatorState === "known" ? /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) : /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6440
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6441
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6442
+ ] }),
6425
6443
  item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
6426
6444
  !item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
6427
6445
  recoveryEditing ? /* @__PURE__ */ jsx25(
@@ -6500,7 +6518,10 @@ function ItemPane(props) {
6500
6518
  }),
6501
6519
  extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ jsx25(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ jsx25(SecretRow, { port, item, field: f, onTurnOn: turnOnOne(f), extra: fieldSettings(f) }, f.id)),
6502
6520
  item.loginUrls.length > 0 ? item.loginUrls.map((u, i) => /* @__PURE__ */ jsx25(PlainRow, { label: i === 0 ? "Website" : `Website ${i + 1}`, value: u, openUrl: host.openUrl }, `${u}-${i}`)) : /* @__PURE__ */ jsx25(AddRow, { label: "Website", onAdd: canEdit ? () => onMode("edit") : null, children: "Add website" }),
6503
- item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : seedField && !seedField.active ? (
6521
+ item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6522
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6523
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6524
+ ] }) : seedField && !seedField.active ? (
6504
6525
  // The secret is still there, only off: turning it on brings the codes back. Never "Set up",
6505
6526
  // which would invite replacing a working two-factor secret.
6506
6527
  /* @__PURE__ */ jsx25(VaultRow, { label: "Authenticator", actions: canTurnOn ? /* @__PURE__ */ jsx25(TurnOnButton, { label: "Authenticator", onTurnOn: () => turnOn([seedField]) }) : null, children: /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: "Turned off" }) })
@@ -9329,7 +9350,7 @@ function createAuthenticatorPort(ctx) {
9329
9350
  return {
9330
9351
  async list() {
9331
9352
  const r = await apps.listAuthenticators(api);
9332
- return (r.entries ?? []).map(entryOf);
9353
+ return { entries: (r.entries ?? []).map(entryOf), unavailableScopes: (r.unavailable_scopes ?? []).map((s) => ({ type: s.type, organizationId: s.organization_id ?? null })) };
9333
9354
  },
9334
9355
  async get(item) {
9335
9356
  try {
@@ -9543,6 +9564,7 @@ function trashItem(r, ctx) {
9543
9564
  fields: [],
9544
9565
  custom: [],
9545
9566
  authenticator: null,
9567
+ authenticatorState: "known",
9546
9568
  capabilities: { canUse: false, canEdit: false, canReveal: false, canManage: true },
9547
9569
  attachments: [],
9548
9570
  uriMatchMode: "host",
@@ -9899,7 +9921,8 @@ function fieldOf(w, def) {
9899
9921
  active: w.is_active ?? true
9900
9922
  };
9901
9923
  }
9902
- function itemOf(w, owner, defs, auth) {
9924
+ var scopeUnavailable = (owner, scopes) => scopes.some((s) => s.type === (owner.kind === "me" ? "user" : owner.kind === "organization" ? "organization" : "shared") && (s.type !== "organization" || !s.organization_id || s.organization_id === owner.organizationId));
9925
+ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], inventoryOwner = owner) {
9903
9926
  const def = defs.get(w.definition_key ?? "");
9904
9927
  const a = auth.get(w.id);
9905
9928
  const cap = w.capabilities ?? {};
@@ -9917,6 +9940,7 @@ function itemOf(w, owner, defs, auth) {
9917
9940
  fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
9918
9941
  custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
9919
9942
  authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
9943
+ authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
9920
9944
  capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
9921
9945
  attachments: (w.attachments ?? []).map((a2) => ({ id: a2.id, label: a2.label, description: a2.description ?? "", fileName: a2.file_name, mediaType: a2.media_type ?? "application/octet-stream", sizeBytes: a2.size_bytes, handling: handlingOf(a2.handling), createdAt: a2.created_at, updatedAt: a2.updated_at })),
9922
9946
  uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
@@ -9953,6 +9977,9 @@ function createApiVaultPort(hostApi, options = {}) {
9953
9977
  };
9954
9978
  let defs = /* @__PURE__ */ new Map();
9955
9979
  let auth = /* @__PURE__ */ new Map();
9980
+ let authState = "pending";
9981
+ let authUnavailable = [];
9982
+ let inventoryOwners = /* @__PURE__ */ new Map();
9956
9983
  let orgs = [];
9957
9984
  const orgOf = (id, userId, mine) => {
9958
9985
  if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
@@ -9966,7 +9993,7 @@ function createApiVaultPort(hostApi, options = {}) {
9966
9993
  };
9967
9994
  async function refetch(id, owner) {
9968
9995
  const w = await account9.getItem(api, { path: { item_id: id } });
9969
- return itemOf(w, owner, defs, auth);
9996
+ return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
9970
9997
  }
9971
9998
  const visibleCache = /* @__PURE__ */ new Map();
9972
9999
  const ctx = {
@@ -9993,6 +10020,10 @@ function createApiVaultPort(hostApi, options = {}) {
9993
10020
  fields: createFieldsPort(ctx),
9994
10021
  lists: createListsPort(ctx, options.userState),
9995
10022
  async load(onProgress) {
10023
+ authState = "pending";
10024
+ auth = /* @__PURE__ */ new Map();
10025
+ authUnavailable = [];
10026
+ inventoryOwners = /* @__PURE__ */ new Map();
9996
10027
  const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
9997
10028
  const authP = retrying(() => apps2.listAuthenticators(api));
9998
10029
  const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
@@ -10015,9 +10046,10 @@ function createApiVaultPort(hostApi, options = {}) {
10015
10046
  const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
10016
10047
  const seen = /* @__PURE__ */ new Map();
10017
10048
  const deleted = /* @__PURE__ */ new Map();
10018
- const take = (w, owner) => {
10049
+ const take = (w, owner, inventoryOwner = owner) => {
10019
10050
  if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
10020
- const item = itemOf(w, owner, defs, auth);
10051
+ inventoryOwners.set(w.id, inventoryOwner);
10052
+ const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
10021
10053
  (isDeleted(w) ? deleted : seen).set(w.id, item);
10022
10054
  };
10023
10055
  for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
@@ -10025,7 +10057,7 @@ function createApiVaultPort(hostApi, options = {}) {
10025
10057
  const via = orgs[i];
10026
10058
  for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
10027
10059
  });
10028
- for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null });
10060
+ for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
10029
10061
  const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
10030
10062
  return {
10031
10063
  unavailable: [...unavailable],
@@ -10084,9 +10116,14 @@ function createApiVaultPort(hostApi, options = {}) {
10084
10116
  })();
10085
10117
  quiet(orgListsReady);
10086
10118
  const authDone = authP.then((a) => {
10087
- auth = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10119
+ authUnavailable = a.unavailable_scopes ?? [];
10120
+ const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10121
+ auth = fresh;
10122
+ authState = "known";
10123
+ if (authUnavailable.length) unavailable.push("One-time codes");
10088
10124
  progress();
10089
10125
  }, () => {
10126
+ authState = "unavailable";
10090
10127
  unavailable.push("One-time codes");
10091
10128
  });
10092
10129
  const sharedDone = sharedP.then((sh) => {