@ai-matrx/vault 0.1.19 → 0.1.20
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/CHANGELOG.md +26 -2
- package/dist/index.cjs +158 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +179 -96
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.20
|
|
4
|
+
|
|
5
|
+
- **A multi-line secret edits as several lines.** A field whose type says multi-line, any field under a notes key (an old **Secure note**, `secure_notes`) and any value that already holds a line break now edit in a multi-line box — a one-line box dropped the line breaks on save. Shown values keep their lines too. Guard: `src/vault-gaps-2026-10-19.test.tsx`. Consumer action: none.
|
|
6
|
+
- **Secure note is back** in the item form's Add menu: an encrypted multi-line field stored as `secure_notes`, hidden until shown (`secureNoteDraft`, `NewSecretField.multiline`/`key`/`description`). Consumer action: none.
|
|
7
|
+
- **A new item's declared secrets carry env key, who can see it and sandbox use to the create call** (`ItemDraft.fieldSettings`, `declaredFieldIn`, `FieldSettingsRows`): behind each secret's sliders, open from the start on an Environment value. Consumer action: none.
|
|
8
|
+
- **Trash rows say each item's type** ("Website login"), read from the item through the state door's `schema("users")` (`VaultUserStateDoor.schema`, `withTypes`), instead of "Credential". Consumer action: none — every host already passes its supabase client as `userState`.
|
|
9
|
+
- **A code added in the Authenticator shows in its list at once**, before the vault's re-read answers (or when an older re-read answers last) (`AddOtpPanel onAdded`, `withAdded`). Consumer action: none.
|
|
10
|
+
- **The list stays current.** A **Refresh** button beside the sort re-reads the list; the list also re-reads itself when the window or panel comes back into focus, and every `host.refreshSeconds` (else the organization's `vault.list.refresh_seconds` knob) while shown — never while hidden, never over an edit or a new item, never two reads at once. A refresh that fails keeps the loaded list and says "Couldn’t refresh. Showing what was loaded." with Try again; the partial-read line's Try again no longer drops to the "couldn't open" screen. Guard: `src/parity-gaps.test.tsx`. **Consumer action:** desktop: pass `refreshSeconds` from its refresh knob. Web app and extension: none (focus refresh is on by itself).
|
|
11
|
+
- **The row menu copies any field** — API key, token, account number, any plain custom field — besides username, password and code (never a sealed field, the authenticator's key or recovery codes). **Right-click on a row opens that menu** in every host, with a host's extra entries (`host.rowMenuItems`). **Consumer action:** web app: pass `rowContextMenu: "host"` so its own v3 right-click menu (agents, Copy, Export) keeps answering on rows. Extension and desktop: none.
|
|
12
|
+
- **After an import**, one **Set up authenticator for <name>** link per imported login that came with a one-time password; it opens the setup for that login over the result (`ImportRunResult.needOtp`). Consumer action: none.
|
|
13
|
+
- **The authenticator setup screen starts the camera by itself** where this browser can scan (`OtpSetupRow autoStartCamera`, used by `AddOtpPanel`; a setup row inside an edit never does). Consumer action: none.
|
|
14
|
+
- **An opener can name the new item**: `VaultLocation.name` (`?name=`) prefills the new item's name over the one guessed from the address. Consumer action: none.
|
|
15
|
+
|
|
3
16
|
## 0.1.19
|
|
4
17
|
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
19
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
20
|
+
Diff: `git diff ff560b1be4 05488c8d62 -- apps/shared/vault/src`
|
|
21
|
+
|
|
22
|
+
Changed shipped source files (8):
|
|
23
|
+
- `apps/shared/vault/src/ItemList.tsx`
|
|
24
|
+
- `apps/shared/vault/src/VaultApp.tsx`
|
|
25
|
+
- `apps/shared/vault/src/authenticator/AddOtpPanel.tsx`
|
|
26
|
+
- `apps/shared/vault/src/authenticator/Setup.tsx`
|
|
27
|
+
- `apps/shared/vault/src/io/ImportPanel.tsx`
|
|
28
|
+
- `apps/shared/vault/src/io/import-run.ts`
|
|
29
|
+
- `apps/shared/vault/src/model.ts`
|
|
30
|
+
- `apps/shared/vault/src/port.ts`
|
|
7
31
|
|
|
8
32
|
## 0.1.18
|
|
9
33
|
|
package/dist/index.cjs
CHANGED
|
@@ -740,7 +740,7 @@ var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
|
|
|
740
740
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
741
741
|
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
742
742
|
function SecretText({ value, multiline }) {
|
|
743
|
-
if (multiline || value.length > 200) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
|
|
743
|
+
if (multiline || value.length > 200 || value.includes("\n")) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
|
|
744
744
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "break-all font-mono text-sm tracking-wide", children: [...value].map((ch, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: /[0-9]/.test(ch) ? "text-primary" : /[^A-Za-z0-9]/.test(ch) ? "text-warning" : void 0, children: ch }, i)) });
|
|
745
745
|
}
|
|
746
746
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
@@ -876,18 +876,19 @@ function EditRow({ label, hint: hint2, value, onChange, placeholder, secret = fa
|
|
|
876
876
|
}
|
|
877
877
|
);
|
|
878
878
|
}
|
|
879
|
-
function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue }) {
|
|
879
|
+
function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue, multiline = false }) {
|
|
880
880
|
const [shown, setShown] = (0, import_react5.useState)(false);
|
|
881
881
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
882
882
|
VaultRow,
|
|
883
883
|
{
|
|
884
884
|
label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Field, { className: "w-full", value: label, onChange: (e) => onLabel(e.target.value), placeholder: "Name", "aria-label": "Field name" }),
|
|
885
|
+
top: multiline,
|
|
885
886
|
actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
886
|
-
secret && value ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: shown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
|
|
887
|
+
secret && value && !multiline ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: shown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
|
|
887
888
|
extra,
|
|
888
889
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", removes: true, icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XGlyph, {}), "aria-label": "Remove field", onClick: onRemove })
|
|
889
890
|
] }),
|
|
890
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
891
|
+
children: multiline ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Textarea, { className: "w-full", value, onChange: (e) => onValue(e.target.value), rows: 4, placeholder: secret ? "Encrypted, hidden until shown" : "Value", autoComplete: "off", spellCheck: !secret, "aria-label": label ? `${label} value` : "Field value" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
891
892
|
import_controls2.Field,
|
|
892
893
|
{
|
|
893
894
|
className: "w-full",
|
|
@@ -1006,6 +1007,7 @@ function sortItems(items, sort, recents) {
|
|
|
1006
1007
|
if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1007
1008
|
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1008
1009
|
}
|
|
1010
|
+
var SECURE_NOTE_KEY = "secure_notes";
|
|
1009
1011
|
function draftOf(item) {
|
|
1010
1012
|
return {
|
|
1011
1013
|
name: item?.name ?? "",
|
|
@@ -1056,7 +1058,9 @@ function rebaseDraft(oldBase, newBase, draft) {
|
|
|
1056
1058
|
secrets: draft.secrets,
|
|
1057
1059
|
custom,
|
|
1058
1060
|
removed: draft.removed,
|
|
1059
|
-
authenticator: draft.authenticator
|
|
1061
|
+
authenticator: draft.authenticator,
|
|
1062
|
+
...draft.browser ? { browser: draft.browser } : {},
|
|
1063
|
+
...draft.fieldSettings ? { fieldSettings: draft.fieldSettings } : {}
|
|
1060
1064
|
};
|
|
1061
1065
|
}
|
|
1062
1066
|
function panesShown(mode, specialView, showingDetail) {
|
|
@@ -1084,7 +1088,8 @@ var FAMILY_LABELS = {
|
|
|
1084
1088
|
signing_files: "Signing & files"
|
|
1085
1089
|
};
|
|
1086
1090
|
var COMMON_DEFINITION_KEYS = ["website_login", "username_password", "api_key", "env_value", "database_login", "custom"];
|
|
1087
|
-
var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes)/;
|
|
1091
|
+
var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes|(^|_)notes?($|_))/;
|
|
1092
|
+
var isMultilineKey = (key) => MULTILINE.test(key);
|
|
1088
1093
|
var SECRET = /(password|secret|token|api_key|private|passphrase|credential|seed|key$|_key)/;
|
|
1089
1094
|
var URLISH = /(url|uri|endpoint|webhook|hook)/;
|
|
1090
1095
|
function humanize(key) {
|
|
@@ -2298,7 +2303,7 @@ function normalizeWebsite(raw) {
|
|
|
2298
2303
|
return null;
|
|
2299
2304
|
}
|
|
2300
2305
|
}
|
|
2301
|
-
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onDone }) {
|
|
2306
|
+
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
|
|
2302
2307
|
const confirmed = useConfirmed();
|
|
2303
2308
|
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" }));
|
|
2304
2309
|
const [input, setInput] = (0, import_react12.useState)("");
|
|
@@ -2351,6 +2356,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2351
2356
|
setInput("");
|
|
2352
2357
|
setImage(null);
|
|
2353
2358
|
setSaved(next);
|
|
2359
|
+
onAdded?.(next);
|
|
2354
2360
|
onChanged();
|
|
2355
2361
|
} catch (e) {
|
|
2356
2362
|
setError(wasCancelled(e) ? "Not confirmed, so nothing was added." : otpActionError(e, "add it"));
|
|
@@ -2459,7 +2465,23 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
|
|
|
2459
2465
|
const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
|
|
2460
2466
|
return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
|
|
2461
2467
|
}
|
|
2462
|
-
function
|
|
2468
|
+
function withAdded(items, added) {
|
|
2469
|
+
if (added.size === 0) return items;
|
|
2470
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2471
|
+
const out = items.map((i) => {
|
|
2472
|
+
seen.add(i.id);
|
|
2473
|
+
const mine = added.get(i.id);
|
|
2474
|
+
return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
|
|
2475
|
+
});
|
|
2476
|
+
for (const [id, a] of added) if (!seen.has(id)) out.push(a);
|
|
2477
|
+
return out;
|
|
2478
|
+
}
|
|
2479
|
+
function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
|
|
2480
|
+
const [added, setAdded] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
|
|
2481
|
+
const items = withAdded(listed, added);
|
|
2482
|
+
(0, import_react13.useEffect)(() => {
|
|
2483
|
+
if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
|
|
2484
|
+
}, [listed, added]);
|
|
2463
2485
|
const [entries, setEntries] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
|
|
2464
2486
|
const [hidden, setHidden] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
|
|
2465
2487
|
const [adding, setAdding] = (0, import_react13.useState)(false);
|
|
@@ -2475,7 +2497,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
|
|
|
2475
2497
|
return () => {
|
|
2476
2498
|
live = false;
|
|
2477
2499
|
};
|
|
2478
|
-
}, [port,
|
|
2500
|
+
}, [port, listed, added]);
|
|
2479
2501
|
const groups = otpGroups(items, entries, query, hidden);
|
|
2480
2502
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2481
2503
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
@@ -2521,6 +2543,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
|
|
|
2521
2543
|
definitions,
|
|
2522
2544
|
onClose: () => setAdding(false),
|
|
2523
2545
|
onChanged,
|
|
2546
|
+
onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
|
|
2524
2547
|
onDone: (id) => {
|
|
2525
2548
|
setAdding(false);
|
|
2526
2549
|
onOpen(id);
|
|
@@ -5245,14 +5268,12 @@ var import_confirm_opener7 = require("@ai-matrx/kit/confirm-opener");
|
|
|
5245
5268
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5246
5269
|
var HANDLINGS = ["visible", "revealable", "sealed"];
|
|
5247
5270
|
var hint = (text, tone = "muted") => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: tone === "destructive" ? "text-destructive" : void 0, title: text, children: text });
|
|
5248
|
-
var
|
|
5249
|
-
function
|
|
5250
|
-
const
|
|
5251
|
-
const env = field.envKey ?? "";
|
|
5271
|
+
var saysMore = (s, defaultHandling) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== defaultHandling);
|
|
5272
|
+
function FieldSettingsRows({ name, value, defaultHandling = "revealable", onChange }) {
|
|
5273
|
+
const env = value.envKey ?? "";
|
|
5252
5274
|
const envProblem = envKeyProblem(env);
|
|
5253
5275
|
const envOk = env.trim() !== "" && !envProblem;
|
|
5254
|
-
const handling =
|
|
5255
|
-
const name = field.label.trim() || "Secret";
|
|
5276
|
+
const handling = value.handling ?? defaultHandling;
|
|
5256
5277
|
const chooseHandling = async (next) => {
|
|
5257
5278
|
if (next === handling) return;
|
|
5258
5279
|
if (next === "sealed") {
|
|
@@ -5266,6 +5287,38 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
|
5266
5287
|
}
|
|
5267
5288
|
onChange({ handling: next });
|
|
5268
5289
|
};
|
|
5290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
5291
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5292
|
+
import_controls18.Field,
|
|
5293
|
+
{
|
|
5294
|
+
className: "w-full font-mono",
|
|
5295
|
+
"aria-label": `${name} env key`,
|
|
5296
|
+
"aria-invalid": Boolean(envProblem),
|
|
5297
|
+
placeholder: "e.g. GITHUB_TOKEN",
|
|
5298
|
+
autoComplete: "off",
|
|
5299
|
+
spellCheck: false,
|
|
5300
|
+
value: env,
|
|
5301
|
+
onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
|
|
5302
|
+
}
|
|
5303
|
+
) }),
|
|
5304
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5305
|
+
import_controls18.SegmentedControl,
|
|
5306
|
+
{
|
|
5307
|
+
"aria-label": `Who can see ${name}`,
|
|
5308
|
+
value: handling,
|
|
5309
|
+
onValueChange: (v) => void chooseHandling(v),
|
|
5310
|
+
data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
|
|
5311
|
+
}
|
|
5312
|
+
) }),
|
|
5313
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Switch, { "aria-label": `Use ${name} in sandbox`, checked: envOk && value.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
|
|
5314
|
+
] });
|
|
5315
|
+
}
|
|
5316
|
+
var hasSecretSettings = (s) => saysMore(s, "revealable");
|
|
5317
|
+
var hasDeclaredSettings = (s, defaultHandling) => Boolean(s && saysMore(s, defaultHandling));
|
|
5318
|
+
function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
5319
|
+
const [open, setOpen] = (0, import_react23.useState)(hasSecretSettings(field));
|
|
5320
|
+
const handling = field.handling ?? "revealable";
|
|
5321
|
+
const name = field.label.trim() || "Secret";
|
|
5269
5322
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
5270
5323
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5271
5324
|
CustomEditRow,
|
|
@@ -5273,47 +5326,26 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
|
5273
5326
|
label: field.label,
|
|
5274
5327
|
value: field.value,
|
|
5275
5328
|
secret: true,
|
|
5329
|
+
multiline: field.multiline === true,
|
|
5276
5330
|
onLabel: (v) => onChange({ label: v }),
|
|
5277
5331
|
onValue: (v) => onChange({ value: v }),
|
|
5278
5332
|
onRemove,
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5333
|
+
...field.multiline ? {} : {
|
|
5334
|
+
onPasteValue: (text) => {
|
|
5335
|
+
const a = parseEnvAssignment(text);
|
|
5336
|
+
if (!a) return false;
|
|
5337
|
+
onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
|
|
5338
|
+
setOpen(true);
|
|
5339
|
+
return true;
|
|
5340
|
+
}
|
|
5285
5341
|
},
|
|
5286
5342
|
extra: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
5287
|
-
handling !== "sealed" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
|
|
5343
|
+
handling !== "sealed" && !field.multiline ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
|
|
5288
5344
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SlidersGlyph, {}), "aria-label": `${name} settings`, pressed: open, onClick: () => setOpen((o) => !o) })
|
|
5289
5345
|
] })
|
|
5290
5346
|
}
|
|
5291
5347
|
),
|
|
5292
|
-
open ? /* @__PURE__ */ (0, import_jsx_runtime22.
|
|
5293
|
-
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5294
|
-
import_controls18.Field,
|
|
5295
|
-
{
|
|
5296
|
-
className: "w-full font-mono",
|
|
5297
|
-
"aria-label": `${name} env key`,
|
|
5298
|
-
"aria-invalid": Boolean(envProblem),
|
|
5299
|
-
placeholder: "e.g. GITHUB_TOKEN",
|
|
5300
|
-
autoComplete: "off",
|
|
5301
|
-
spellCheck: false,
|
|
5302
|
-
value: env,
|
|
5303
|
-
onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
|
|
5304
|
-
}
|
|
5305
|
-
) }),
|
|
5306
|
-
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5307
|
-
import_controls18.SegmentedControl,
|
|
5308
|
-
{
|
|
5309
|
-
"aria-label": `Who can see ${name}`,
|
|
5310
|
-
value: handling,
|
|
5311
|
-
onValueChange: (v) => void chooseHandling(v),
|
|
5312
|
-
data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
|
|
5313
|
-
}
|
|
5314
|
-
) }),
|
|
5315
|
-
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Switch, { "aria-label": `Use ${name} in sandbox`, checked: envOk && field.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
|
|
5316
|
-
] }) : null
|
|
5348
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldSettingsRows, { name, value: field, onChange }) : null
|
|
5317
5349
|
] });
|
|
5318
5350
|
}
|
|
5319
5351
|
function withMfaMethod(custom, v) {
|
|
@@ -5943,6 +5975,8 @@ function SharingSummary({ item, port, className }) {
|
|
|
5943
5975
|
// src/ItemPane.tsx
|
|
5944
5976
|
var import_format8 = require("@ai-matrx/kit/format");
|
|
5945
5977
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
5978
|
+
var ENV_VALUE_KEY = "env_value";
|
|
5979
|
+
var secureNoteDraft = () => ({ key: SECURE_NOTE_KEY, label: "Secure note", value: "", multiline: true, description: "Encrypted notes" });
|
|
5946
5980
|
var USERNAME_KEYS = /^(username|user_name|email|login|username_or_email|account_email)$/;
|
|
5947
5981
|
function ItemPane(props) {
|
|
5948
5982
|
const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
|
|
@@ -6133,6 +6167,16 @@ function ItemPane(props) {
|
|
|
6133
6167
|
const fieldSettings = (f) => canEdit ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SlidersGlyph, {}), "aria-label": `${f.label} settings`, title: `${f.label} settings`, onClick: () => onPanel(`field:${f.id}`) }) : null;
|
|
6134
6168
|
const turnOnOne = canTurnOn ? (f) => () => turnOn([f]) : () => void 0;
|
|
6135
6169
|
const declaredKeys = new Set(declared.map((f) => f.key));
|
|
6170
|
+
const hasSecureNote = declaredKeys.has(SECURE_NOTE_KEY) || Boolean(item?.fields.some((f) => f.key === SECURE_NOTE_KEY)) || draft.secrets.some((x) => x.key === SECURE_NOTE_KEY);
|
|
6171
|
+
const [settingsToggled, setSettingsToggled] = (0, import_react28.useState)(/* @__PURE__ */ new Set());
|
|
6172
|
+
const settingsOpen = (f) => settingsToggled.has(f.key) !== (definition?.key === ENV_VALUE_KEY || hasDeclaredSettings(draft.fieldSettings?.[f.key], f.handling));
|
|
6173
|
+
const toggleSettings = (key) => setSettingsToggled((cur) => {
|
|
6174
|
+
const next = new Set(cur);
|
|
6175
|
+
if (next.has(key)) next.delete(key);
|
|
6176
|
+
else next.add(key);
|
|
6177
|
+
return next;
|
|
6178
|
+
});
|
|
6179
|
+
const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
|
|
6136
6180
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
6137
6181
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
6138
6182
|
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && c.key !== MFA_METHOD_KEY && !isAuthenticatorStorage(c.key));
|
|
@@ -6265,24 +6309,29 @@ function ItemPane(props) {
|
|
|
6265
6309
|
const secretKind = f.storage === "encrypted" && f.kind === "secret";
|
|
6266
6310
|
const value = f.storage === "metadata" ? metaValue(f.key) : draft.values[f.key] ?? "";
|
|
6267
6311
|
const hidden = existing && existing.handling !== "visible" && !(f.key in loaded);
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6312
|
+
const newSecret = mode === "new" && f.storage === "encrypted" && owner !== "person";
|
|
6313
|
+
const open = newSecret && settingsOpen(f);
|
|
6314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react28.Fragment, { children: [
|
|
6315
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6316
|
+
EditRow,
|
|
6317
|
+
{
|
|
6318
|
+
label: f.label,
|
|
6319
|
+
value,
|
|
6320
|
+
placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
|
|
6321
|
+
secret: secretKind,
|
|
6322
|
+
multiline: f.kind === "multiline" || value.includes("\n"),
|
|
6323
|
+
inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
|
|
6324
|
+
onChange: (v) => setValue(f.key, v),
|
|
6325
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
6326
|
+
hidden && existing?.handling === "revealable" && item?.capabilities.canReveal ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
|
|
6327
|
+
secretKind ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
|
|
6328
|
+
existing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_icons11.SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${existing.id}`) }) : null,
|
|
6329
|
+
newSecret ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SlidersGlyph, {}), "aria-label": `${f.label} settings`, pressed: open, onClick: () => toggleSettings(f.key) }) : null
|
|
6330
|
+
] })
|
|
6331
|
+
}
|
|
6332
|
+
),
|
|
6333
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldSettingsRows, { name: f.label, value: draft.fieldSettings?.[f.key] ?? {}, defaultHandling: f.handling, onChange: (p) => patchFieldSettings(f.key, p) }) : null
|
|
6334
|
+
] }, f.key);
|
|
6286
6335
|
}),
|
|
6287
6336
|
extraFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6288
6337
|
EditRow,
|
|
@@ -6291,7 +6340,7 @@ function ItemPane(props) {
|
|
|
6291
6340
|
value: draft.values[f.key] ?? "",
|
|
6292
6341
|
placeholder: f.handling === "visible" || f.key in loaded ? "" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
6293
6342
|
secret: f.handling !== "visible" && !isRecoveryCodesField(f),
|
|
6294
|
-
multiline: isRecoveryCodesField(f),
|
|
6343
|
+
multiline: f.kind === "multiline" || isRecoveryCodesField(f) || (draft.values[f.key] ?? "").includes("\n"),
|
|
6295
6344
|
onChange: (v) => setValue(f.key, v),
|
|
6296
6345
|
onRemove: () => patch({ removed: [...draft.removed, f.id] }),
|
|
6297
6346
|
removeLabel: `Delete ${f.label}`,
|
|
@@ -6360,6 +6409,7 @@ function ItemPane(props) {
|
|
|
6360
6409
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(PlusGlyph, {}), children: "Add" }) }),
|
|
6361
6410
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_design_system17.DropdownMenuContent, { align: "end", children: [
|
|
6362
6411
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, { key: "", label: "", value: "" }] }), children: "Secret field" }),
|
|
6412
|
+
!hasSecureNote ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, secureNoteDraft()] }), children: "Secure note" }) : null,
|
|
6363
6413
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ custom: [...draft.custom, { key: `custom_${draft.custom.length + 1}_${Date.now() % 1e5}`, label: "", value: "" }] }), children: "Text field" }),
|
|
6364
6414
|
!tagsVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowTags(true), children: "Tags" }) : null,
|
|
6365
6415
|
!descriptionVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowDescription(true), children: "Description" }) : null,
|
|
@@ -9128,6 +9178,28 @@ function trashItem(r, ctx) {
|
|
|
9128
9178
|
createdAt: ""
|
|
9129
9179
|
};
|
|
9130
9180
|
}
|
|
9181
|
+
async function withTypes(items, door, ctx) {
|
|
9182
|
+
if (!door.schema || items.length === 0) return items;
|
|
9183
|
+
const keys = /* @__PURE__ */ new Map();
|
|
9184
|
+
const CHUNK = 100;
|
|
9185
|
+
try {
|
|
9186
|
+
for (let i = 0; i < items.length; i += CHUNK) {
|
|
9187
|
+
const ids = items.slice(i, i + CHUNK).map((x) => x.id);
|
|
9188
|
+
const { data, error } = await door.schema("users").from("credential_items").select("id,definition_key").in("id", ids);
|
|
9189
|
+
if (error) throw new Error(error.message || "credential_items read failed");
|
|
9190
|
+
for (const r of Array.isArray(data) ? data : []) {
|
|
9191
|
+
if (typeof r.id === "string" && typeof r.definition_key === "string" && r.definition_key) keys.set(r.id, r.definition_key);
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
} catch (e) {
|
|
9195
|
+
console.warn("[vault] Trash item types could not be read; rows say Credential", e);
|
|
9196
|
+
return items;
|
|
9197
|
+
}
|
|
9198
|
+
return items.map((it) => {
|
|
9199
|
+
const key = keys.get(it.id);
|
|
9200
|
+
return key ? { ...it, definitionKey: key, definitionLabel: ctx.definition(key)?.label ?? humanize(key) } : it;
|
|
9201
|
+
});
|
|
9202
|
+
}
|
|
9131
9203
|
var previewOf = async (door, id) => parseRecoveryPreview(await call(door, "vault_recovery_preview", { p_id: id }));
|
|
9132
9204
|
function createListsPort(ctx, door) {
|
|
9133
9205
|
return {
|
|
@@ -9160,7 +9232,8 @@ function createListsPort(ctx, door) {
|
|
|
9160
9232
|
if (rows.length < PAGE2) break;
|
|
9161
9233
|
}
|
|
9162
9234
|
const items = all.map((r) => trashItem(r, ctx)).filter((i) => i !== null);
|
|
9163
|
-
|
|
9235
|
+
const unique = [...new Map(items.map((i) => [i.id, i])).values()];
|
|
9236
|
+
return withTypes(unique, door, ctx);
|
|
9164
9237
|
}
|
|
9165
9238
|
},
|
|
9166
9239
|
favorites: {
|
|
@@ -9183,16 +9256,25 @@ function createListsPort(ctx, door) {
|
|
|
9183
9256
|
var account6 = __toESM(require("@ai-matrx/api/account"), 1);
|
|
9184
9257
|
|
|
9185
9258
|
// src/secret-field-in.ts
|
|
9186
|
-
function
|
|
9259
|
+
function settingsIn(s) {
|
|
9187
9260
|
const envKey = s.envKey?.trim() || null;
|
|
9188
9261
|
return {
|
|
9189
|
-
field_key: fieldKeyOf(s.label),
|
|
9190
|
-
value: s.value,
|
|
9191
|
-
handling: s.handling ?? "revealable",
|
|
9192
9262
|
...envKey ? { env_key: envKey } : {},
|
|
9193
9263
|
...envKey && s.injectIntoSandbox ? { inject_into_sandbox: true } : {}
|
|
9194
9264
|
};
|
|
9195
9265
|
}
|
|
9266
|
+
function secretFieldIn(s) {
|
|
9267
|
+
return {
|
|
9268
|
+
field_key: s.key.trim() || fieldKeyOf(s.label),
|
|
9269
|
+
value: s.value,
|
|
9270
|
+
handling: s.handling ?? "revealable",
|
|
9271
|
+
...s.description ? { description: s.description } : {},
|
|
9272
|
+
...settingsIn(s)
|
|
9273
|
+
};
|
|
9274
|
+
}
|
|
9275
|
+
function declaredFieldIn(f, value, settings) {
|
|
9276
|
+
return { field_key: f.key, value, handling: settings?.handling ?? f.handling, ...settingsIn(settings ?? {}) };
|
|
9277
|
+
}
|
|
9196
9278
|
|
|
9197
9279
|
// src/sharing/api.ts
|
|
9198
9280
|
function grantOf(w) {
|
|
@@ -9293,7 +9375,7 @@ function createSharingPort(ctx, options = {}) {
|
|
|
9293
9375
|
initiation: "user",
|
|
9294
9376
|
non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
|
|
9295
9377
|
fields: [
|
|
9296
|
-
...encrypted.map((f) => (
|
|
9378
|
+
...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
|
|
9297
9379
|
...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn)
|
|
9298
9380
|
]
|
|
9299
9381
|
}
|
|
@@ -9422,7 +9504,7 @@ async function readNativeImportLimits(api, organizationId) {
|
|
|
9422
9504
|
var recoveryOf = (w) => w.lifecycle?.vault_recovery;
|
|
9423
9505
|
var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
|
|
9424
9506
|
var MATCH_MODES2 = ["host", "domain", "exact", "never"];
|
|
9425
|
-
var FIELD_WORDS = { totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
|
|
9507
|
+
var FIELD_WORDS = { secure_notes: "Secure note", totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
|
|
9426
9508
|
var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
|
|
9427
9509
|
function fieldOf(w, def) {
|
|
9428
9510
|
const declared = def?.fields.find((f) => f.key === w.field_key);
|
|
@@ -9431,7 +9513,8 @@ function fieldOf(w, def) {
|
|
|
9431
9513
|
id: w.id,
|
|
9432
9514
|
key: w.field_key,
|
|
9433
9515
|
label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
|
|
9434
|
-
|
|
9516
|
+
// A field no type declares still edits as several lines when its key says so (an old "Secure note").
|
|
9517
|
+
kind: declared?.kind ?? (isMultilineKey(w.field_key) ? "multiline" : handling === "visible" ? "text" : "secret"),
|
|
9435
9518
|
handling,
|
|
9436
9519
|
editable: w.editable ?? true,
|
|
9437
9520
|
envKey: w.env_key ?? null,
|
|
@@ -9586,7 +9669,7 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9586
9669
|
initiation: "user",
|
|
9587
9670
|
non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
|
|
9588
9671
|
fields: [
|
|
9589
|
-
...encrypted.map((f) => (
|
|
9672
|
+
...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
|
|
9590
9673
|
...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn),
|
|
9591
9674
|
// Backup codes on a type that does not declare them (a website login): their own encrypted field.
|
|
9592
9675
|
...!declared.some((f) => f.key === RECOVERY_CODES_KEY) && (draft.values[RECOVERY_CODES_KEY] ?? "").trim() !== "" ? [{ field_key: RECOVERY_CODES_KEY, value: parseRecoveryCodes(draft.values[RECOVERY_CODES_KEY] ?? "").join("\n"), handling: "revealable", description: "One-time account recovery codes" }] : []
|