@ai-matrx/vault 0.1.19 → 0.1.21
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 +30 -1
- package/dist/index.cjs +163 -78
- 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 +184 -99
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.21
|
|
4
4
|
|
|
5
5
|
UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
|
|
6
6
|
Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault/src`.
|
|
7
7
|
|
|
8
|
+
## 0.1.20
|
|
9
|
+
|
|
10
|
+
- **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.
|
|
11
|
+
- **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.
|
|
12
|
+
- **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.
|
|
13
|
+
- **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`.
|
|
14
|
+
- **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.
|
|
15
|
+
- **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).
|
|
16
|
+
- **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.
|
|
17
|
+
- **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.
|
|
18
|
+
- **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.
|
|
19
|
+
- **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.
|
|
20
|
+
|
|
21
|
+
## 0.1.19
|
|
22
|
+
|
|
23
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
24
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
25
|
+
Diff: `git diff ff560b1be4 05488c8d62 -- apps/shared/vault/src`
|
|
26
|
+
|
|
27
|
+
Changed shipped source files (8):
|
|
28
|
+
- `apps/shared/vault/src/ItemList.tsx`
|
|
29
|
+
- `apps/shared/vault/src/VaultApp.tsx`
|
|
30
|
+
- `apps/shared/vault/src/authenticator/AddOtpPanel.tsx`
|
|
31
|
+
- `apps/shared/vault/src/authenticator/Setup.tsx`
|
|
32
|
+
- `apps/shared/vault/src/io/ImportPanel.tsx`
|
|
33
|
+
- `apps/shared/vault/src/io/import-run.ts`
|
|
34
|
+
- `apps/shared/vault/src/model.ts`
|
|
35
|
+
- `apps/shared/vault/src/port.ts`
|
|
36
|
+
|
|
8
37
|
## 0.1.18
|
|
9
38
|
|
|
10
39
|
- The Authenticator list keeps the code and its countdown ring apart at every width (the code column never squeezes), the list header lines up with the list on wide screens, and the Add authenticator panel's camera button is icon-only so the setup-key box has room on a phone. Consumer action: none.
|
package/dist/index.cjs
CHANGED
|
@@ -622,6 +622,8 @@ function ConfirmIdentityDialog({ open, identity, onDone, onCancel }) {
|
|
|
622
622
|
var import_react5 = require("react");
|
|
623
623
|
var import_controls2 = require("@ai-matrx/design-system/controls");
|
|
624
624
|
var import_design_system5 = require("@ai-matrx/design-system");
|
|
625
|
+
var import_outbound = require("@ai-matrx/kit/outbound");
|
|
626
|
+
var import_outbound_react = require("@ai-matrx/kit/outbound-react");
|
|
625
627
|
var import_icons2 = require("@ai-matrx/design-system/icons");
|
|
626
628
|
|
|
627
629
|
// src/hooks.ts
|
|
@@ -740,7 +742,7 @@ var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
|
|
|
740
742
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
741
743
|
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
742
744
|
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 });
|
|
745
|
+
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
746
|
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
747
|
}
|
|
746
748
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
@@ -833,13 +835,13 @@ function PlainRow({ label, value, hint: hint2, openUrl, extra, multiline = false
|
|
|
833
835
|
top: multiline,
|
|
834
836
|
actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
835
837
|
extra,
|
|
836
|
-
isUrl && openUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExternalGlyph, {}), "aria-label": `Open ${value}`, onClick: () => openUrl(value) }) : null,
|
|
838
|
+
isUrl && openUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExternalGlyph, {}), "aria-label": `Open ${value}`, onClick: () => openUrl((0, import_outbound.withReferral)(value)) }) : null,
|
|
837
839
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons2.CopyIcon, {}), "aria-label": `Copy ${typeof label === "string" ? label : "value"}`, onClick: () => void copyValue2(value, typeof label === "string" && label ? label : "Value", { secret: false }) })
|
|
838
840
|
] }),
|
|
839
841
|
children: isUrl && openUrl ? (
|
|
840
842
|
// A real link (right-click, copy link address, open in a new tab); a click goes through the host's opener.
|
|
841
843
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
842
|
-
|
|
844
|
+
import_outbound_react.OutboundLink,
|
|
843
845
|
{
|
|
844
846
|
href: value,
|
|
845
847
|
target: "_blank",
|
|
@@ -847,7 +849,7 @@ function PlainRow({ label, value, hint: hint2, openUrl, extra, multiline = false
|
|
|
847
849
|
className: "block max-w-full truncate text-primary hover:underline",
|
|
848
850
|
onClick: (e) => {
|
|
849
851
|
e.preventDefault();
|
|
850
|
-
openUrl(value);
|
|
852
|
+
openUrl((0, import_outbound.withReferral)(value));
|
|
851
853
|
},
|
|
852
854
|
children: value.replace(/^https?:\/\//i, "").replace(/\/$/, "")
|
|
853
855
|
}
|
|
@@ -876,18 +878,19 @@ function EditRow({ label, hint: hint2, value, onChange, placeholder, secret = fa
|
|
|
876
878
|
}
|
|
877
879
|
);
|
|
878
880
|
}
|
|
879
|
-
function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue }) {
|
|
881
|
+
function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue, multiline = false }) {
|
|
880
882
|
const [shown, setShown] = (0, import_react5.useState)(false);
|
|
881
883
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
882
884
|
VaultRow,
|
|
883
885
|
{
|
|
884
886
|
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" }),
|
|
887
|
+
top: multiline,
|
|
885
888
|
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,
|
|
889
|
+
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
890
|
extra,
|
|
888
891
|
/* @__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
892
|
] }),
|
|
890
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
893
|
+
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
894
|
import_controls2.Field,
|
|
892
895
|
{
|
|
893
896
|
className: "w-full",
|
|
@@ -1006,6 +1009,7 @@ function sortItems(items, sort, recents) {
|
|
|
1006
1009
|
if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1007
1010
|
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1008
1011
|
}
|
|
1012
|
+
var SECURE_NOTE_KEY = "secure_notes";
|
|
1009
1013
|
function draftOf(item) {
|
|
1010
1014
|
return {
|
|
1011
1015
|
name: item?.name ?? "",
|
|
@@ -1056,7 +1060,9 @@ function rebaseDraft(oldBase, newBase, draft) {
|
|
|
1056
1060
|
secrets: draft.secrets,
|
|
1057
1061
|
custom,
|
|
1058
1062
|
removed: draft.removed,
|
|
1059
|
-
authenticator: draft.authenticator
|
|
1063
|
+
authenticator: draft.authenticator,
|
|
1064
|
+
...draft.browser ? { browser: draft.browser } : {},
|
|
1065
|
+
...draft.fieldSettings ? { fieldSettings: draft.fieldSettings } : {}
|
|
1060
1066
|
};
|
|
1061
1067
|
}
|
|
1062
1068
|
function panesShown(mode, specialView, showingDetail) {
|
|
@@ -1084,7 +1090,8 @@ var FAMILY_LABELS = {
|
|
|
1084
1090
|
signing_files: "Signing & files"
|
|
1085
1091
|
};
|
|
1086
1092
|
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)/;
|
|
1093
|
+
var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes|(^|_)notes?($|_))/;
|
|
1094
|
+
var isMultilineKey = (key) => MULTILINE.test(key);
|
|
1088
1095
|
var SECRET = /(password|secret|token|api_key|private|passphrase|credential|seed|key$|_key)/;
|
|
1089
1096
|
var URLISH = /(url|uri|endpoint|webhook|hook)/;
|
|
1090
1097
|
function humanize(key) {
|
|
@@ -2298,7 +2305,7 @@ function normalizeWebsite(raw) {
|
|
|
2298
2305
|
return null;
|
|
2299
2306
|
}
|
|
2300
2307
|
}
|
|
2301
|
-
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onDone }) {
|
|
2308
|
+
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
|
|
2302
2309
|
const confirmed = useConfirmed();
|
|
2303
2310
|
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
2311
|
const [input, setInput] = (0, import_react12.useState)("");
|
|
@@ -2351,6 +2358,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2351
2358
|
setInput("");
|
|
2352
2359
|
setImage(null);
|
|
2353
2360
|
setSaved(next);
|
|
2361
|
+
onAdded?.(next);
|
|
2354
2362
|
onChanged();
|
|
2355
2363
|
} catch (e) {
|
|
2356
2364
|
setError(wasCancelled(e) ? "Not confirmed, so nothing was added." : otpActionError(e, "add it"));
|
|
@@ -2459,7 +2467,23 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
|
|
|
2459
2467
|
const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
|
|
2460
2468
|
return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
|
|
2461
2469
|
}
|
|
2462
|
-
function
|
|
2470
|
+
function withAdded(items, added) {
|
|
2471
|
+
if (added.size === 0) return items;
|
|
2472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2473
|
+
const out = items.map((i) => {
|
|
2474
|
+
seen.add(i.id);
|
|
2475
|
+
const mine = added.get(i.id);
|
|
2476
|
+
return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
|
|
2477
|
+
});
|
|
2478
|
+
for (const [id, a] of added) if (!seen.has(id)) out.push(a);
|
|
2479
|
+
return out;
|
|
2480
|
+
}
|
|
2481
|
+
function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
|
|
2482
|
+
const [added, setAdded] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
|
|
2483
|
+
const items = withAdded(listed, added);
|
|
2484
|
+
(0, import_react13.useEffect)(() => {
|
|
2485
|
+
if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
|
|
2486
|
+
}, [listed, added]);
|
|
2463
2487
|
const [entries, setEntries] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
|
|
2464
2488
|
const [hidden, setHidden] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
|
|
2465
2489
|
const [adding, setAdding] = (0, import_react13.useState)(false);
|
|
@@ -2475,7 +2499,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
|
|
|
2475
2499
|
return () => {
|
|
2476
2500
|
live = false;
|
|
2477
2501
|
};
|
|
2478
|
-
}, [port,
|
|
2502
|
+
}, [port, listed, added]);
|
|
2479
2503
|
const groups = otpGroups(items, entries, query, hidden);
|
|
2480
2504
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2481
2505
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
@@ -2521,6 +2545,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
|
|
|
2521
2545
|
definitions,
|
|
2522
2546
|
onClose: () => setAdding(false),
|
|
2523
2547
|
onChanged,
|
|
2548
|
+
onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
|
|
2524
2549
|
onDone: (id) => {
|
|
2525
2550
|
setAdding(false);
|
|
2526
2551
|
onOpen(id);
|
|
@@ -5245,14 +5270,12 @@ var import_confirm_opener7 = require("@ai-matrx/kit/confirm-opener");
|
|
|
5245
5270
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5246
5271
|
var HANDLINGS = ["visible", "revealable", "sealed"];
|
|
5247
5272
|
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 ?? "";
|
|
5273
|
+
var saysMore = (s, defaultHandling) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== defaultHandling);
|
|
5274
|
+
function FieldSettingsRows({ name, value, defaultHandling = "revealable", onChange }) {
|
|
5275
|
+
const env = value.envKey ?? "";
|
|
5252
5276
|
const envProblem = envKeyProblem(env);
|
|
5253
5277
|
const envOk = env.trim() !== "" && !envProblem;
|
|
5254
|
-
const handling =
|
|
5255
|
-
const name = field.label.trim() || "Secret";
|
|
5278
|
+
const handling = value.handling ?? defaultHandling;
|
|
5256
5279
|
const chooseHandling = async (next) => {
|
|
5257
5280
|
if (next === handling) return;
|
|
5258
5281
|
if (next === "sealed") {
|
|
@@ -5266,6 +5289,38 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
|
5266
5289
|
}
|
|
5267
5290
|
onChange({ handling: next });
|
|
5268
5291
|
};
|
|
5292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
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 && value.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
|
|
5316
|
+
] });
|
|
5317
|
+
}
|
|
5318
|
+
var hasSecretSettings = (s) => saysMore(s, "revealable");
|
|
5319
|
+
var hasDeclaredSettings = (s, defaultHandling) => Boolean(s && saysMore(s, defaultHandling));
|
|
5320
|
+
function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
5321
|
+
const [open, setOpen] = (0, import_react23.useState)(hasSecretSettings(field));
|
|
5322
|
+
const handling = field.handling ?? "revealable";
|
|
5323
|
+
const name = field.label.trim() || "Secret";
|
|
5269
5324
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
5270
5325
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5271
5326
|
CustomEditRow,
|
|
@@ -5273,47 +5328,26 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
|
|
|
5273
5328
|
label: field.label,
|
|
5274
5329
|
value: field.value,
|
|
5275
5330
|
secret: true,
|
|
5331
|
+
multiline: field.multiline === true,
|
|
5276
5332
|
onLabel: (v) => onChange({ label: v }),
|
|
5277
5333
|
onValue: (v) => onChange({ value: v }),
|
|
5278
5334
|
onRemove,
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5335
|
+
...field.multiline ? {} : {
|
|
5336
|
+
onPasteValue: (text) => {
|
|
5337
|
+
const a = parseEnvAssignment(text);
|
|
5338
|
+
if (!a) return false;
|
|
5339
|
+
onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
|
|
5340
|
+
setOpen(true);
|
|
5341
|
+
return true;
|
|
5342
|
+
}
|
|
5285
5343
|
},
|
|
5286
5344
|
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,
|
|
5345
|
+
handling !== "sealed" && !field.multiline ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
|
|
5288
5346
|
/* @__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
5347
|
] })
|
|
5290
5348
|
}
|
|
5291
5349
|
),
|
|
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
|
|
5350
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldSettingsRows, { name, value: field, onChange }) : null
|
|
5317
5351
|
] });
|
|
5318
5352
|
}
|
|
5319
5353
|
function withMfaMethod(custom, v) {
|
|
@@ -5943,6 +5977,8 @@ function SharingSummary({ item, port, className }) {
|
|
|
5943
5977
|
// src/ItemPane.tsx
|
|
5944
5978
|
var import_format8 = require("@ai-matrx/kit/format");
|
|
5945
5979
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
5980
|
+
var ENV_VALUE_KEY = "env_value";
|
|
5981
|
+
var secureNoteDraft = () => ({ key: SECURE_NOTE_KEY, label: "Secure note", value: "", multiline: true, description: "Encrypted notes" });
|
|
5946
5982
|
var USERNAME_KEYS = /^(username|user_name|email|login|username_or_email|account_email)$/;
|
|
5947
5983
|
function ItemPane(props) {
|
|
5948
5984
|
const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
|
|
@@ -6133,6 +6169,16 @@ function ItemPane(props) {
|
|
|
6133
6169
|
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
6170
|
const turnOnOne = canTurnOn ? (f) => () => turnOn([f]) : () => void 0;
|
|
6135
6171
|
const declaredKeys = new Set(declared.map((f) => f.key));
|
|
6172
|
+
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);
|
|
6173
|
+
const [settingsToggled, setSettingsToggled] = (0, import_react28.useState)(/* @__PURE__ */ new Set());
|
|
6174
|
+
const settingsOpen = (f) => settingsToggled.has(f.key) !== (definition?.key === ENV_VALUE_KEY || hasDeclaredSettings(draft.fieldSettings?.[f.key], f.handling));
|
|
6175
|
+
const toggleSettings = (key) => setSettingsToggled((cur) => {
|
|
6176
|
+
const next = new Set(cur);
|
|
6177
|
+
if (next.has(key)) next.delete(key);
|
|
6178
|
+
else next.add(key);
|
|
6179
|
+
return next;
|
|
6180
|
+
});
|
|
6181
|
+
const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
|
|
6136
6182
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
6137
6183
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
6138
6184
|
const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && c.key !== MFA_METHOD_KEY && !isAuthenticatorStorage(c.key));
|
|
@@ -6265,24 +6311,29 @@ function ItemPane(props) {
|
|
|
6265
6311
|
const secretKind = f.storage === "encrypted" && f.kind === "secret";
|
|
6266
6312
|
const value = f.storage === "metadata" ? metaValue(f.key) : draft.values[f.key] ?? "";
|
|
6267
6313
|
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
|
-
|
|
6314
|
+
const newSecret = mode === "new" && f.storage === "encrypted" && owner !== "person";
|
|
6315
|
+
const open = newSecret && settingsOpen(f);
|
|
6316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react28.Fragment, { children: [
|
|
6317
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6318
|
+
EditRow,
|
|
6319
|
+
{
|
|
6320
|
+
label: f.label,
|
|
6321
|
+
value,
|
|
6322
|
+
placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
|
|
6323
|
+
secret: secretKind,
|
|
6324
|
+
multiline: f.kind === "multiline" || value.includes("\n"),
|
|
6325
|
+
inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
|
|
6326
|
+
onChange: (v) => setValue(f.key, v),
|
|
6327
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
6328
|
+
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,
|
|
6329
|
+
secretKind ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
|
|
6330
|
+
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,
|
|
6331
|
+
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
|
|
6332
|
+
] })
|
|
6333
|
+
}
|
|
6334
|
+
),
|
|
6335
|
+
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
|
|
6336
|
+
] }, f.key);
|
|
6286
6337
|
}),
|
|
6287
6338
|
extraFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
6288
6339
|
EditRow,
|
|
@@ -6291,7 +6342,7 @@ function ItemPane(props) {
|
|
|
6291
6342
|
value: draft.values[f.key] ?? "",
|
|
6292
6343
|
placeholder: f.handling === "visible" || f.key in loaded ? "" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
6293
6344
|
secret: f.handling !== "visible" && !isRecoveryCodesField(f),
|
|
6294
|
-
multiline: isRecoveryCodesField(f),
|
|
6345
|
+
multiline: f.kind === "multiline" || isRecoveryCodesField(f) || (draft.values[f.key] ?? "").includes("\n"),
|
|
6295
6346
|
onChange: (v) => setValue(f.key, v),
|
|
6296
6347
|
onRemove: () => patch({ removed: [...draft.removed, f.id] }),
|
|
6297
6348
|
removeLabel: `Delete ${f.label}`,
|
|
@@ -6360,6 +6411,7 @@ function ItemPane(props) {
|
|
|
6360
6411
|
/* @__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
6412
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_design_system17.DropdownMenuContent, { align: "end", children: [
|
|
6362
6413
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, { key: "", label: "", value: "" }] }), children: "Secret field" }),
|
|
6414
|
+
!hasSecureNote ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, secureNoteDraft()] }), children: "Secure note" }) : null,
|
|
6363
6415
|
/* @__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
6416
|
!tagsVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowTags(true), children: "Tags" }) : null,
|
|
6365
6417
|
!descriptionVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowDescription(true), children: "Description" }) : null,
|
|
@@ -9128,6 +9180,28 @@ function trashItem(r, ctx) {
|
|
|
9128
9180
|
createdAt: ""
|
|
9129
9181
|
};
|
|
9130
9182
|
}
|
|
9183
|
+
async function withTypes(items, door, ctx) {
|
|
9184
|
+
if (!door.schema || items.length === 0) return items;
|
|
9185
|
+
const keys = /* @__PURE__ */ new Map();
|
|
9186
|
+
const CHUNK = 100;
|
|
9187
|
+
try {
|
|
9188
|
+
for (let i = 0; i < items.length; i += CHUNK) {
|
|
9189
|
+
const ids = items.slice(i, i + CHUNK).map((x) => x.id);
|
|
9190
|
+
const { data, error } = await door.schema("users").from("credential_items").select("id,definition_key").in("id", ids);
|
|
9191
|
+
if (error) throw new Error(error.message || "credential_items read failed");
|
|
9192
|
+
for (const r of Array.isArray(data) ? data : []) {
|
|
9193
|
+
if (typeof r.id === "string" && typeof r.definition_key === "string" && r.definition_key) keys.set(r.id, r.definition_key);
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
} catch (e) {
|
|
9197
|
+
console.warn("[vault] Trash item types could not be read; rows say Credential", e);
|
|
9198
|
+
return items;
|
|
9199
|
+
}
|
|
9200
|
+
return items.map((it) => {
|
|
9201
|
+
const key = keys.get(it.id);
|
|
9202
|
+
return key ? { ...it, definitionKey: key, definitionLabel: ctx.definition(key)?.label ?? humanize(key) } : it;
|
|
9203
|
+
});
|
|
9204
|
+
}
|
|
9131
9205
|
var previewOf = async (door, id) => parseRecoveryPreview(await call(door, "vault_recovery_preview", { p_id: id }));
|
|
9132
9206
|
function createListsPort(ctx, door) {
|
|
9133
9207
|
return {
|
|
@@ -9160,7 +9234,8 @@ function createListsPort(ctx, door) {
|
|
|
9160
9234
|
if (rows.length < PAGE2) break;
|
|
9161
9235
|
}
|
|
9162
9236
|
const items = all.map((r) => trashItem(r, ctx)).filter((i) => i !== null);
|
|
9163
|
-
|
|
9237
|
+
const unique = [...new Map(items.map((i) => [i.id, i])).values()];
|
|
9238
|
+
return withTypes(unique, door, ctx);
|
|
9164
9239
|
}
|
|
9165
9240
|
},
|
|
9166
9241
|
favorites: {
|
|
@@ -9183,16 +9258,25 @@ function createListsPort(ctx, door) {
|
|
|
9183
9258
|
var account6 = __toESM(require("@ai-matrx/api/account"), 1);
|
|
9184
9259
|
|
|
9185
9260
|
// src/secret-field-in.ts
|
|
9186
|
-
function
|
|
9261
|
+
function settingsIn(s) {
|
|
9187
9262
|
const envKey = s.envKey?.trim() || null;
|
|
9188
9263
|
return {
|
|
9189
|
-
field_key: fieldKeyOf(s.label),
|
|
9190
|
-
value: s.value,
|
|
9191
|
-
handling: s.handling ?? "revealable",
|
|
9192
9264
|
...envKey ? { env_key: envKey } : {},
|
|
9193
9265
|
...envKey && s.injectIntoSandbox ? { inject_into_sandbox: true } : {}
|
|
9194
9266
|
};
|
|
9195
9267
|
}
|
|
9268
|
+
function secretFieldIn(s) {
|
|
9269
|
+
return {
|
|
9270
|
+
field_key: s.key.trim() || fieldKeyOf(s.label),
|
|
9271
|
+
value: s.value,
|
|
9272
|
+
handling: s.handling ?? "revealable",
|
|
9273
|
+
...s.description ? { description: s.description } : {},
|
|
9274
|
+
...settingsIn(s)
|
|
9275
|
+
};
|
|
9276
|
+
}
|
|
9277
|
+
function declaredFieldIn(f, value, settings) {
|
|
9278
|
+
return { field_key: f.key, value, handling: settings?.handling ?? f.handling, ...settingsIn(settings ?? {}) };
|
|
9279
|
+
}
|
|
9196
9280
|
|
|
9197
9281
|
// src/sharing/api.ts
|
|
9198
9282
|
function grantOf(w) {
|
|
@@ -9293,7 +9377,7 @@ function createSharingPort(ctx, options = {}) {
|
|
|
9293
9377
|
initiation: "user",
|
|
9294
9378
|
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
9379
|
fields: [
|
|
9296
|
-
...encrypted.map((f) => (
|
|
9380
|
+
...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
|
|
9297
9381
|
...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn)
|
|
9298
9382
|
]
|
|
9299
9383
|
}
|
|
@@ -9422,7 +9506,7 @@ async function readNativeImportLimits(api, organizationId) {
|
|
|
9422
9506
|
var recoveryOf = (w) => w.lifecycle?.vault_recovery;
|
|
9423
9507
|
var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
|
|
9424
9508
|
var MATCH_MODES2 = ["host", "domain", "exact", "never"];
|
|
9425
|
-
var FIELD_WORDS = { totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
|
|
9509
|
+
var FIELD_WORDS = { secure_notes: "Secure note", totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
|
|
9426
9510
|
var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
|
|
9427
9511
|
function fieldOf(w, def) {
|
|
9428
9512
|
const declared = def?.fields.find((f) => f.key === w.field_key);
|
|
@@ -9431,7 +9515,8 @@ function fieldOf(w, def) {
|
|
|
9431
9515
|
id: w.id,
|
|
9432
9516
|
key: w.field_key,
|
|
9433
9517
|
label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
|
|
9434
|
-
|
|
9518
|
+
// A field no type declares still edits as several lines when its key says so (an old "Secure note").
|
|
9519
|
+
kind: declared?.kind ?? (isMultilineKey(w.field_key) ? "multiline" : handling === "visible" ? "text" : "secret"),
|
|
9435
9520
|
handling,
|
|
9436
9521
|
editable: w.editable ?? true,
|
|
9437
9522
|
envKey: w.env_key ?? null,
|
|
@@ -9586,7 +9671,7 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9586
9671
|
initiation: "user",
|
|
9587
9672
|
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
9673
|
fields: [
|
|
9589
|
-
...encrypted.map((f) => (
|
|
9674
|
+
...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
|
|
9590
9675
|
...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn),
|
|
9591
9676
|
// Backup codes on a type that does not declare them (a website login): their own encrypted field.
|
|
9592
9677
|
...!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" }] : []
|