@ai-matrx/vault 0.1.11 → 0.1.12
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 +19 -1
- package/dist/index.cjs +210 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -4
- package/dist/index.d.ts +58 -4
- package/dist/index.js +273 -113
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -136,9 +136,11 @@ var init_keepass_xml_worker_client = __esm({
|
|
|
136
136
|
var src_exports = {};
|
|
137
137
|
__export(src_exports, {
|
|
138
138
|
CATEGORIES: () => CATEGORIES,
|
|
139
|
+
CONFIRM_RETURN_KEY: () => CONFIRM_RETURN_KEY,
|
|
139
140
|
DEFAULT_LOCATION: () => DEFAULT_LOCATION,
|
|
140
141
|
GenerateButton: () => GenerateButton,
|
|
141
142
|
HIDDEN_ITEM_TAG: () => HIDDEN_ITEM_TAG,
|
|
143
|
+
IdentityGate: () => IdentityGate,
|
|
142
144
|
LOCKED_MESSAGE: () => LOCKED_MESSAGE,
|
|
143
145
|
VaultApp: () => VaultApp,
|
|
144
146
|
VaultLockedError: () => VaultLockedError,
|
|
@@ -156,7 +158,10 @@ __export(src_exports, {
|
|
|
156
158
|
matchesQuery: () => matchesQuery,
|
|
157
159
|
needsRecentSignIn: () => needsRecentSignIn,
|
|
158
160
|
parseDefinitions: () => parseDefinitions,
|
|
159
|
-
|
|
161
|
+
siteDomainOf: () => siteDomainOf,
|
|
162
|
+
sortItems: () => sortItems,
|
|
163
|
+
useConfirmed: () => useConfirmed,
|
|
164
|
+
wasCancelled: () => wasCancelled
|
|
160
165
|
});
|
|
161
166
|
module.exports = __toCommonJS(src_exports);
|
|
162
167
|
|
|
@@ -384,9 +389,20 @@ var FingerprintGlyph = glyph(/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(impor
|
|
|
384
389
|
var SendGlyph = glyph(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14.5 21.7a.5.5 0 0 0 .9 0L22 3a.5.5 0 0 0-.6-.6L2.3 9.1a.5.5 0 0 0 0 .9l7.9 3.2a2 2 0 0 1 1.1 1.1zM21.9 2.1 10.9 13.1" }), [1, 1]);
|
|
385
390
|
|
|
386
391
|
// src/auth/identity.ts
|
|
392
|
+
var CONFIRM_RETURN_KEY = "matrx-vault:confirming-as";
|
|
393
|
+
var RETURN_WINDOW_MS = 15 * 6e4;
|
|
387
394
|
var SAME_PERSON = "That signed in a different account. Use the account this vault belongs to.";
|
|
388
395
|
function createSupabaseIdentity(auth, options = {}) {
|
|
389
396
|
let me = null;
|
|
397
|
+
const now = options.now ?? Date.now;
|
|
398
|
+
const store = () => {
|
|
399
|
+
if (options.store) return options.store;
|
|
400
|
+
try {
|
|
401
|
+
return globalThis.sessionStorage ?? null;
|
|
402
|
+
} catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
390
406
|
const who = async () => {
|
|
391
407
|
const { data } = await auth.getUser();
|
|
392
408
|
if (!data.user) throw new Error("You are signed out. Sign in again.");
|
|
@@ -403,10 +419,35 @@ function createSupabaseIdentity(auth, options = {}) {
|
|
|
403
419
|
return { email: u.email ?? null, providers: [...names].filter((p) => p !== "email" && p !== "phone"), hasPassword: names.has("email") };
|
|
404
420
|
},
|
|
405
421
|
async withProvider(provider) {
|
|
406
|
-
await who();
|
|
422
|
+
const u = await who();
|
|
423
|
+
store()?.setItem(CONFIRM_RETURN_KEY, JSON.stringify({ id: u.id, email: u.email ?? null, at: now() }));
|
|
407
424
|
const redirectTo = options.redirectTo?.() ?? globalThis.location?.href;
|
|
408
425
|
const { error } = await auth.signInWithOAuth({ provider, ...redirectTo ? { options: { redirectTo } } : {} });
|
|
409
|
-
if (error)
|
|
426
|
+
if (error) {
|
|
427
|
+
store()?.removeItem(CONFIRM_RETURN_KEY);
|
|
428
|
+
throw new Error(error.message);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
async checkReturn() {
|
|
432
|
+
const s = store();
|
|
433
|
+
const raw = s?.getItem(CONFIRM_RETURN_KEY) ?? null;
|
|
434
|
+
if (!s || raw === null) return null;
|
|
435
|
+
s.removeItem(CONFIRM_RETURN_KEY);
|
|
436
|
+
let expected;
|
|
437
|
+
try {
|
|
438
|
+
expected = JSON.parse(raw);
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
if (typeof expected.id !== "string" || typeof expected.at !== "number" || now() - expected.at > RETURN_WINDOW_MS) return null;
|
|
443
|
+
const { data } = await auth.getUser();
|
|
444
|
+
if (!data.user || data.user.id === expected.id) return null;
|
|
445
|
+
const email = typeof expected.email === "string" && expected.email ? expected.email : null;
|
|
446
|
+
const message2 = `That signed in a different account, so it was signed out here.${email ? ` Sign in again as ${email}.` : " Sign in again."}`;
|
|
447
|
+
me = null;
|
|
448
|
+
if (options.onDifferentPerson) await options.onDifferentPerson(message2);
|
|
449
|
+
else await auth.signOut?.({ scope: "local" });
|
|
450
|
+
return message2;
|
|
410
451
|
},
|
|
411
452
|
async withPassword(password) {
|
|
412
453
|
const u = await who();
|
|
@@ -452,6 +493,18 @@ var providerLabel = (p) => PROVIDER_LABEL[p] ?? p.charAt(0).toUpperCase() + p.sl
|
|
|
452
493
|
function IdentityGate({ identity, children }) {
|
|
453
494
|
const [asking, setAsking] = (0, import_react2.useState)(false);
|
|
454
495
|
const waiters = (0, import_react2.useRef)([]);
|
|
496
|
+
const [returnProblem, setReturnProblem] = (0, import_react2.useState)(null);
|
|
497
|
+
(0, import_react2.useEffect)(() => {
|
|
498
|
+
if (!identity?.checkReturn) return;
|
|
499
|
+
let live = true;
|
|
500
|
+
identity.checkReturn().then(
|
|
501
|
+
(problem) => live && problem && setReturnProblem(problem),
|
|
502
|
+
(e) => live && setReturnProblem(e instanceof Error ? e.message : "The account that came back could not be checked.")
|
|
503
|
+
);
|
|
504
|
+
return () => {
|
|
505
|
+
live = false;
|
|
506
|
+
};
|
|
507
|
+
}, [identity]);
|
|
455
508
|
const confirmed = async (run) => {
|
|
456
509
|
try {
|
|
457
510
|
return await run();
|
|
@@ -472,7 +525,14 @@ function IdentityGate({ identity, children }) {
|
|
|
472
525
|
};
|
|
473
526
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Ctx2.Provider, { value: confirmed, children: [
|
|
474
527
|
children,
|
|
475
|
-
identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmIdentityDialog, { open: asking, identity, onDone: () => settle(true), onCancel: () => settle(false) }) : null
|
|
528
|
+
identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmIdentityDialog, { open: asking, identity, onDone: () => settle(true), onCancel: () => settle(false) }) : null,
|
|
529
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.Dialog, { open: returnProblem !== null, onOpenChange: (o) => o ? void 0 : setReturnProblem(null), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.DialogContent, { size: "sm", children: [
|
|
530
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.DialogHeader, { children: [
|
|
531
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.DialogTitle, { children: "Different account" }),
|
|
532
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.DialogDescription, { children: returnProblem })
|
|
533
|
+
] }),
|
|
534
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Button, { variant: "primary", onClick: () => setReturnProblem(null), children: "OK" }) })
|
|
535
|
+
] }) })
|
|
476
536
|
] });
|
|
477
537
|
}
|
|
478
538
|
var wasCancelled = (e) => e instanceof Error && e.message === CANCELLED;
|
|
@@ -536,7 +596,7 @@ function ConfirmIdentityDialog({ open, identity, onDone, onCancel }) {
|
|
|
536
596
|
e.preventDefault();
|
|
537
597
|
if (password) void attempt(() => identity.withPassword(password));
|
|
538
598
|
}, children: [
|
|
539
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Field, { className: "min-w-0 flex-1", type: "password", autoComplete: "current-password", placeholder: "Your password", "aria-label": "Your password", value: password, onChange: (e) => setPassword(e.target.value) }),
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Field, { className: "min-w-0 flex-1", type: "password", autoComplete: "current-password", placeholder: "Your password", "aria-label": "Your password", value: password, onChange: (e) => setPassword(e.target.value), autoFocus: true }),
|
|
540
600
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Button, { variant: "outline", type: "submit", disabled: busy || !password, children: "Confirm" })
|
|
541
601
|
] }) : null,
|
|
542
602
|
account10?.email ? codeSent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { className: "flex gap-2", onSubmit: (e) => {
|
|
@@ -664,6 +724,12 @@ function Panel({ title, open = true, onClose, children, footer, size = "lg" }) {
|
|
|
664
724
|
footer ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.DialogFooter, { children: footer }) : null
|
|
665
725
|
] }) });
|
|
666
726
|
}
|
|
727
|
+
function PanelFooter({ status, children }) {
|
|
728
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex w-full flex-wrap items-center justify-end gap-2", children: [
|
|
729
|
+
status ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "min-w-0 flex-1 basis-full sm:basis-0", children: status }) : null,
|
|
730
|
+
children
|
|
731
|
+
] });
|
|
732
|
+
}
|
|
667
733
|
var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
|
|
668
734
|
|
|
669
735
|
// src/FieldRows.tsx
|
|
@@ -674,7 +740,7 @@ function SecretText({ value, multiline }) {
|
|
|
674
740
|
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)) });
|
|
675
741
|
}
|
|
676
742
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
677
|
-
function SecretRow({ port, item, field, extra }) {
|
|
743
|
+
function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
678
744
|
const confirmed = useConfirmed();
|
|
679
745
|
const copyValue2 = useCopy();
|
|
680
746
|
const secret = useTransientSecret();
|
|
@@ -728,10 +794,27 @@ function SecretRow({ port, item, field, extra }) {
|
|
|
728
794
|
top: multiline && shown !== null,
|
|
729
795
|
actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
730
796
|
extra,
|
|
797
|
+
off && onTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TurnOnButton, { label: field.label, onTurnOn }) : null,
|
|
731
798
|
canSee && !visible ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: secret.value !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": secret.value !== null ? `Hide ${field.label}` : `Show ${field.label}`, disabled: busy, onClick: () => void toggle2() }) : null,
|
|
732
799
|
canSee ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons2.CopyIcon, {}), "aria-label": `Copy ${field.label}`, disabled: busy, onClick: () => void copy() }) : null
|
|
733
800
|
] }),
|
|
734
|
-
children: sealed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LockGlyph, {}), children: "
|
|
801
|
+
children: sealed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LockGlyph, {}), children: "Never shown" }) : off ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-muted-foreground", children: "Turned off" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-destructive", title: error, children: error }) : shown !== null ? visible ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_design_system5.cn)("block", multiline ? "whitespace-pre-wrap break-words" : "truncate"), children: shown || "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SecretText, { value: shown, multiline }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_design_system5.cn)("block truncate text-muted-foreground", !visible && "tracking-widest"), children: visible ? "\u2026" : MASK })
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
function TurnOnButton({ label, onTurnOn }) {
|
|
806
|
+
const [busy, setBusy] = (0, import_react5.useState)(false);
|
|
807
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
808
|
+
import_controls2.Button,
|
|
809
|
+
{
|
|
810
|
+
variant: "outline",
|
|
811
|
+
"aria-label": `Turn on ${label}`,
|
|
812
|
+
disabled: busy,
|
|
813
|
+
onClick: () => {
|
|
814
|
+
setBusy(true);
|
|
815
|
+
void onTurnOn().finally(() => setBusy(false));
|
|
816
|
+
},
|
|
817
|
+
children: "Turn on"
|
|
735
818
|
}
|
|
736
819
|
);
|
|
737
820
|
}
|
|
@@ -870,14 +953,27 @@ function loginSiteOf(item) {
|
|
|
870
953
|
return raw.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? "";
|
|
871
954
|
}
|
|
872
955
|
}
|
|
956
|
+
var TWO_PART_SUFFIX = /* @__PURE__ */ new Set(["co", "com", "org", "net", "gov", "edu", "ac", "ne", "or"]);
|
|
957
|
+
function siteDomainOf(host) {
|
|
958
|
+
const h = host.toLowerCase().replace(/\.$/, "");
|
|
959
|
+
if (!h || /^[\d.]+$/.test(h) || h.includes(":")) return h;
|
|
960
|
+
const labels = h.split(".").filter(Boolean);
|
|
961
|
+
if (labels.length <= 2) return labels.join(".");
|
|
962
|
+
const sld = labels[labels.length - 2] ?? "";
|
|
963
|
+
const tld = labels[labels.length - 1] ?? "";
|
|
964
|
+
const keep = TWO_PART_SUFFIX.has(sld) && tld.length <= 3 ? 3 : 2;
|
|
965
|
+
return labels.slice(-keep).join(".");
|
|
966
|
+
}
|
|
873
967
|
function sortItems(items, sort) {
|
|
874
968
|
const out = [...items];
|
|
875
969
|
const byName = (a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
876
970
|
if (sort === "name") return out.sort(byName);
|
|
877
|
-
if (sort === "website")
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
971
|
+
if (sort === "website")
|
|
972
|
+
return out.sort((a, b) => {
|
|
973
|
+
const x = loginSiteOf(a), y = loginSiteOf(b);
|
|
974
|
+
if (!x || !y) return x ? -1 : y ? 1 : byName(a, b);
|
|
975
|
+
return siteDomainOf(x).localeCompare(siteDomainOf(y)) || x.localeCompare(y) || byName(a, b);
|
|
976
|
+
});
|
|
881
977
|
if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
882
978
|
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
883
979
|
}
|
|
@@ -1297,7 +1393,10 @@ function ActivityPanel({ item, port, onClose }) {
|
|
|
1297
1393
|
{
|
|
1298
1394
|
title: "Activity",
|
|
1299
1395
|
onClose,
|
|
1300
|
-
footer:
|
|
1396
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1397
|
+
hasMore ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls3.Button, { variant: "outline", disabled: loading, onClick: () => setLimit((n) => Math.min(MAX, n * 2)), children: loading ? "Loading\u2026" : "Show more" }) : null,
|
|
1398
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls3.Button, { variant: "primary", onClick: onClose, children: "Done" })
|
|
1399
|
+
] }),
|
|
1301
1400
|
children: [
|
|
1302
1401
|
error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null,
|
|
1303
1402
|
entries === null ? loading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Empty, { children: "Reading activity\u2026" }) : null : entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Empty, { children: "Nothing has happened yet." }) : groupByDay(entries, now).map((day) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VaultSection, { title: day.label, children: collapseRuns(
|
|
@@ -1380,7 +1479,10 @@ function ItemHistoryPanel({ item, port, arg, onClose, onChanged }) {
|
|
|
1380
1479
|
{
|
|
1381
1480
|
title: "Password history",
|
|
1382
1481
|
onClose,
|
|
1383
|
-
footer:
|
|
1482
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1483
|
+
page?.nextBefore ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.Button, { variant: "outline", disabled: more, onClick: () => void loadMore(), children: more ? "Loading\u2026" : "Show earlier" }) : null,
|
|
1484
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.Button, { variant: "primary", onClick: onClose, children: "Done" })
|
|
1485
|
+
] }),
|
|
1384
1486
|
children: !eligible ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Empty, { children: "History is kept only for your own items." }) : loading && !page ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Empty, { children: "Reading history\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1385
1487
|
error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null,
|
|
1386
1488
|
notice ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "status", className: "px-1 text-sm text-success", children: notice }) : null,
|
|
@@ -1985,6 +2087,7 @@ var import_confirm_opener2 = require("@ai-matrx/kit/confirm-opener");
|
|
|
1985
2087
|
|
|
1986
2088
|
// src/authenticator/port.ts
|
|
1987
2089
|
var MFA_METHOD_KEY = "mfa_method";
|
|
2090
|
+
var OTP_SEED_KEY = "totp_seed";
|
|
1988
2091
|
function mfaMethodOf(item) {
|
|
1989
2092
|
const v = item.custom.find((c) => c.key === MFA_METHOD_KEY)?.value;
|
|
1990
2093
|
return v === "sms" ? "sms" : v ? "push_or_other" : null;
|
|
@@ -2343,7 +2446,7 @@ function AuthenticatorView({ port, items, definitions, query, onOpen, onChanged,
|
|
|
2343
2446
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2344
2447
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
2345
2448
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("section", { "aria-label": "One-time passwords", className: "flex h-full min-h-0 flex-col bg-background", children: [
|
|
2346
|
-
header,
|
|
2449
|
+
header ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mx-auto w-full max-w-3xl px-3 pt-3 sm:px-4", children: header }) : null,
|
|
2347
2450
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-12 items-center justify-between gap-2 px-4 pb-1 pt-3", children: [
|
|
2348
2451
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 items-baseline gap-2", children: [
|
|
2349
2452
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { className: "truncate text-sm font-semibold text-foreground", children: "One-time passwords" }),
|
|
@@ -2355,7 +2458,7 @@ function AuthenticatorView({ port, items, definitions, query, onOpen, onChanged,
|
|
|
2355
2458
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ShieldGlyph, { className: "size-6" }) }),
|
|
2356
2459
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: "No one-time passwords yet" }),
|
|
2357
2460
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_controls9.Button, { variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add one-time password" })
|
|
2358
|
-
] }) : groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: "Nothing matches." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6 pt-1 sm:px-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5", children: groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(VaultSection, {
|
|
2461
|
+
] }) : groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: "Nothing matches." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6 pt-1 sm:px-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5", children: groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(VaultSection, { title: g.label, className: "[&>div]:px-1", children: g.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2359
2462
|
OtpListRow,
|
|
2360
2463
|
{
|
|
2361
2464
|
port,
|
|
@@ -2492,9 +2595,9 @@ function sandboxBlocker(envKeyDraft, savedEnvKey) {
|
|
|
2492
2595
|
return null;
|
|
2493
2596
|
}
|
|
2494
2597
|
var HANDLING_WORDS = {
|
|
2495
|
-
visible: { label: "
|
|
2496
|
-
revealable: { label: "
|
|
2497
|
-
sealed: { label: "
|
|
2598
|
+
visible: { label: "Always shown", hint: "Anyone who can use it can show or copy it" },
|
|
2599
|
+
revealable: { label: "Hidden", hint: "Showing or copying it needs reveal access" },
|
|
2600
|
+
sealed: { label: "Never shown", hint: "No one can see it. Automation can still use it." }
|
|
2498
2601
|
};
|
|
2499
2602
|
function handlingChoices(current) {
|
|
2500
2603
|
return current === "sealed" ? ["sealed"] : ["visible", "revealable", "sealed"];
|
|
@@ -2676,7 +2779,7 @@ function FieldSettings({ item, port, field, onClose, onChanged }) {
|
|
|
2676
2779
|
}
|
|
2677
2780
|
) }),
|
|
2678
2781
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VaultRow, { label: "Editable", hint: quietHint(field.editable ? "The value can be changed" : "The value is locked"), children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_controls10.Switch, { "aria-label": "Editable", checked: field.editable, disabled: locked, onCheckedChange: (v) => void update("editable", { editable: v }) }) }),
|
|
2679
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VaultRow, { label: "
|
|
2782
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VaultRow, { label: "Turned on", hint: quietHint(field.active ? "In use" : "Kept, but nothing uses it"), children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_controls10.Switch, { "aria-label": "Turned on", checked: field.active, disabled: locked, onCheckedChange: (v) => void update("active", { active: v }) }) })
|
|
2680
2783
|
] }),
|
|
2681
2784
|
error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Problem, { children: error }) : null
|
|
2682
2785
|
] });
|
|
@@ -2744,7 +2847,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
|
|
|
2744
2847
|
f.envKey
|
|
2745
2848
|
] }) : null,
|
|
2746
2849
|
f.injectIntoSandbox ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground", children: " \xB7 Sandbox" }) : null,
|
|
2747
|
-
!f.active ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-
|
|
2850
|
+
!f.active ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-warning", children: " \xB7 Turned off" }) : null
|
|
2748
2851
|
] })
|
|
2749
2852
|
},
|
|
2750
2853
|
f.id
|
|
@@ -4686,7 +4789,7 @@ var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
|
4686
4789
|
function ItemIcon({ definitionKey, family, site, size = "md", inverted = false }) {
|
|
4687
4790
|
const box = size === "lg" ? "size-10 rounded-xl text-base" : size === "md" ? "size-8 rounded-lg text-sm" : "size-6 rounded-md text-xs";
|
|
4688
4791
|
const tone = inverted ? "bg-primary-foreground text-primary" : "bg-muted text-foreground";
|
|
4689
|
-
const initial = site
|
|
4792
|
+
const initial = siteDomainOf(site).charAt(0).toUpperCase();
|
|
4690
4793
|
if (initial && /[A-Z0-9]/.test(initial)) {
|
|
4691
4794
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { "aria-hidden": true, className: (0, import_design_system12.cn)("flex shrink-0 items-center justify-center font-semibold", box, tone), children: initial });
|
|
4692
4795
|
}
|
|
@@ -4715,7 +4818,7 @@ var TOO_BIG = "Files over 25 MB can't be stored.";
|
|
|
4715
4818
|
var PROTECTION = [
|
|
4716
4819
|
{ value: "visible", label: "Open", title: "Anyone who can use the item can download it" },
|
|
4717
4820
|
{ value: "revealable", label: "Protected", title: "Downloading needs a recent sign-in" },
|
|
4718
|
-
{ value: "sealed", label: "
|
|
4821
|
+
{ value: "sealed", label: "Never shown", title: "Only automation can use it, never a person" }
|
|
4719
4822
|
];
|
|
4720
4823
|
function FilesSection({ item, port, editing, onChanged }) {
|
|
4721
4824
|
const confirmed = useConfirmed();
|
|
@@ -4782,7 +4885,7 @@ function FilesSection({ item, port, editing, onChanged }) {
|
|
|
4782
4885
|
{
|
|
4783
4886
|
label: a.label,
|
|
4784
4887
|
hint: a.description || void 0,
|
|
4785
|
-
actions: a.handling === "sealed" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_controls16.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LockGlyph, {}), children: "
|
|
4888
|
+
actions: a.handling === "sealed" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_controls16.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LockGlyph, {}), children: "Never shown" }) : canDownload(item, a) ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_controls16.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons8.DownloadIcon, {}), "aria-label": `Download ${a.fileName}`, disabled: busy !== null, onClick: () => void download(a) }) : null,
|
|
4786
4889
|
children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FileName, { file: a })
|
|
4787
4890
|
},
|
|
4788
4891
|
a.id
|
|
@@ -5004,7 +5107,7 @@ function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
|
5004
5107
|
canSee ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Button, { variant: "quiet", icon: codes ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EyeGlyph, {}), "aria-label": codes ? "Hide codes" : "Show codes", disabled: busy, onClick: () => void toggle2() }) : null,
|
|
5005
5108
|
canEdit && !sealed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PencilGlyph, {}), "aria-label": "Replace all codes", disabled: busy, onClick: () => setDraft("") }) : null
|
|
5006
5109
|
] }),
|
|
5007
|
-
children: sealed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockGlyph, {}), children: "
|
|
5110
|
+
children: sealed ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Badge, { icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LockGlyph, {}), children: "Never shown" }) : codes ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { className: "flex flex-col gap-0.5", "aria-label": "Recovery codes", children: [
|
|
5008
5111
|
codes.map((code, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex min-h-7 items-center gap-1", children: [
|
|
5009
5112
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("code", { className: "min-w-0 flex-1 break-all font-mono text-sm text-foreground", children: code }),
|
|
5010
5113
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_controls17.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_icons9.CopyIcon, {}), "aria-label": `Copy code ${i + 1}`, onClick: () => void copyValue2(code, "Recovery code") }),
|
|
@@ -5327,10 +5430,7 @@ function MovePanel({ item, port, organizations, onClose, onChanged }) {
|
|
|
5327
5430
|
title: "Move or copy",
|
|
5328
5431
|
size: "md",
|
|
5329
5432
|
onClose,
|
|
5330
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime24.
|
|
5331
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(StatusLine, { error, notice }),
|
|
5332
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_controls19.Button, { variant: "outline", className: "ml-auto", onClick: onClose, children: "Close" })
|
|
5333
|
-
] }),
|
|
5433
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PanelFooter, { status: error || notice ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(StatusLine, { error, notice }) : null, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_controls19.Button, { variant: "primary", onClick: onClose, children: "Done" }) }),
|
|
5334
5434
|
children: nothing ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(VaultSection, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Empty, { children: passkey ? "A passkey stays where it was created." : "Only the owner can move or give this item." }) }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(VaultSection, { children: [
|
|
5335
5435
|
canMove && moves.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5336
5436
|
VaultRow,
|
|
@@ -5445,10 +5545,7 @@ function SharePanel({ item, port, organizations, onClose, onChanged }) {
|
|
|
5445
5545
|
title: "Share",
|
|
5446
5546
|
size: "md",
|
|
5447
5547
|
onClose,
|
|
5448
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime25.
|
|
5449
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StatusLine, { error, notice }),
|
|
5450
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_controls20.Button, { variant: "primary", className: "ml-auto", onClick: onClose, children: "Done" })
|
|
5451
|
-
] }),
|
|
5548
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PanelFooter, { status: error || notice ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StatusLine, { error, notice }) : null, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_controls20.Button, { variant: "primary", onClick: onClose, children: "Done" }) }),
|
|
5452
5549
|
children: [
|
|
5453
5550
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(VaultSection, { title: "Who has access", children: [
|
|
5454
5551
|
isOrg ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AccessLine, { glyph: BuildingGlyph, who: `${orgName} admins`, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "px-2 text-sm text-muted-foreground", children: "Full access" }) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AccessLine, { glyph: PersonGlyph, who: "You", meta: "Owner", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "px-2 text-sm text-muted-foreground", children: "Full access" }) }),
|
|
@@ -5734,6 +5831,21 @@ function ItemPane(props) {
|
|
|
5734
5831
|
if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "The value could not be read.");
|
|
5735
5832
|
}
|
|
5736
5833
|
}
|
|
5834
|
+
const offFields = item ? item.fields.filter((f) => !f.active) : [];
|
|
5835
|
+
const seedField = item?.fields.find((f) => f.key === OTP_SEED_KEY) ?? null;
|
|
5836
|
+
const canTurnOn = Boolean(item && canEdit);
|
|
5837
|
+
async function turnOn(fields) {
|
|
5838
|
+
if (!item || fields.length === 0) return;
|
|
5839
|
+
setError(null);
|
|
5840
|
+
try {
|
|
5841
|
+
for (const f of fields) await confirmed(() => port.fields.updateField(item, f.id, { active: true }));
|
|
5842
|
+
onChanged();
|
|
5843
|
+
} catch (e) {
|
|
5844
|
+
if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "It could not be turned on.");
|
|
5845
|
+
onChanged();
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
const turnOnOne = canTurnOn ? (f) => () => turnOn([f]) : () => void 0;
|
|
5737
5849
|
const declaredKeys = new Set(declared.map((f) => f.key));
|
|
5738
5850
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
5739
5851
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
@@ -5742,6 +5854,10 @@ function ItemPane(props) {
|
|
|
5742
5854
|
const isLogin = (item?.definitionKey ?? definition?.key) === "website_login" || (item?.definitionKey ?? definition?.key) === "username_password";
|
|
5743
5855
|
const typeLabel = definition?.label ?? item?.definitionLabel ?? "";
|
|
5744
5856
|
const shownOwner = mode === "new" ? owner === "me" ? { label: "Personal", glyph: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PersonGlyph, {}) } : owner === "person" ? { label: "Someone else", glyph: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SendGlyph, {}) } : { label: organizations.find((o) => o.id === owner)?.name ?? "Organization", glyph: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BuildingGlyph, {}) } : item ? { label: item.owner.label, glyph: item.owner.kind === "me" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PersonGlyph, {}) : item.owner.kind === "organization" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BuildingGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_icons11.UsersIcon, {}) } : null;
|
|
5857
|
+
const sameNamed = (id) => {
|
|
5858
|
+
const name = organizations.find((o) => o.id === id)?.name.trim().toLowerCase();
|
|
5859
|
+
return Boolean(name) && organizations.filter((o) => o.name.trim().toLowerCase() === name).length > 1;
|
|
5860
|
+
};
|
|
5745
5861
|
const tagsVisible = showTags || draft.tags.length > 0 || tagsText !== "";
|
|
5746
5862
|
const descriptionVisible = showDescription || draft.description !== "";
|
|
5747
5863
|
const header = /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("header", { className: "flex min-h-14 items-center gap-2 border-b border-border px-3 py-2 sm:gap-3 sm:px-5", children: [
|
|
@@ -5825,6 +5941,10 @@ function ItemPane(props) {
|
|
|
5825
5941
|
header,
|
|
5826
5942
|
error ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: error }) : null,
|
|
5827
5943
|
notice && !editing ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { role: "status", className: "border-b border-border bg-muted px-5 py-2 text-sm text-warning", children: notice }) : null,
|
|
5944
|
+
item && !editing && offFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
|
|
5945
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "min-w-0 flex-1 truncate text-warning", children: offFields.length === 1 ? "1 field is turned off" : `${offFields.length} fields are turned off` }),
|
|
5946
|
+
canTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TurnOnButton, { label: "all fields", onTurnOn: () => turnOn(offFields) }) : null
|
|
5947
|
+
] }) : null,
|
|
5828
5948
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5 px-3 py-4 sm:px-6 sm:py-6", children: editing ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
5829
5949
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(VaultSection, { children: [
|
|
5830
5950
|
mode === "new" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(VaultRow, { label: "Save to", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
@@ -5837,11 +5957,11 @@ function ItemPane(props) {
|
|
|
5837
5957
|
searchPlaceholder: "Find an organization",
|
|
5838
5958
|
groups: [
|
|
5839
5959
|
{ heading: "Just you", options: ["me"] },
|
|
5840
|
-
...organizations.length > 0 ? [{ heading: "Organizations", options: organizations.map((o) => o.id) }] : [],
|
|
5960
|
+
...organizations.length > 0 ? [{ heading: "Organizations", options: [...organizations].sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }) || a.id.localeCompare(b.id)).map((o) => o.id) }] : [],
|
|
5841
5961
|
{ heading: "Send", options: ["person"] }
|
|
5842
5962
|
],
|
|
5843
5963
|
getLabel: (v) => v === "me" ? "Personal" : v === "person" ? "Someone else" : organizations.find((o) => o.id === v)?.name ?? v,
|
|
5844
|
-
getHint: (v) => v === "me" ? "Only you" : v === "person" ? "Sent by email" : null,
|
|
5964
|
+
getHint: (v) => v === "me" ? "Only you" : v === "person" ? "Sent by email" : sameNamed(v) ? `ID ${v.slice(0, 8)}` : null,
|
|
5845
5965
|
renderIcon: (v) => v === "me" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PersonGlyph, { className: "size-4" }) : v === "person" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SendGlyph, { className: "size-4" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BuildingGlyph, { className: "size-4" })
|
|
5846
5966
|
}
|
|
5847
5967
|
) }) : null,
|
|
@@ -5916,7 +6036,7 @@ function ItemPane(props) {
|
|
|
5916
6036
|
i
|
|
5917
6037
|
)),
|
|
5918
6038
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
|
|
5919
|
-
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator), item, port, onEnrolled: onChanged }),
|
|
6039
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }),
|
|
5920
6040
|
item && isLogin && !item.authenticator ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TwoFactorMethodRow, { port, item, onChanged }) : null
|
|
5921
6041
|
] }),
|
|
5922
6042
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(VaultSection, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EditRow, { label: "Notes", value: draft.notes, multiline: true, placeholder: "Anything worth remembering", onChange: (v) => patch({ notes: v }) }) }),
|
|
@@ -5949,7 +6069,7 @@ function ItemPane(props) {
|
|
|
5949
6069
|
) : null,
|
|
5950
6070
|
draft.secrets.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CustomEditRow, { label: s.label, value: s.value, secret: true, onLabel: (v) => patch({ secrets: draft.secrets.map((x, j) => j === i ? { ...x, label: v } : x) }), onValue: (v) => patch({ secrets: draft.secrets.map((x, j) => j === i ? { ...x, value: v } : x) }), onRemove: () => patch({ secrets: draft.secrets.filter((_, j) => j !== i) }) }, `s${i}`)),
|
|
5951
6071
|
extraCustom.map((c) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CustomEditRow, { label: c.label, value: c.value, secret: false, onLabel: (v) => patch({ custom: draft.custom.map((x) => x.key === c.key ? { ...x, label: v } : x) }), onValue: (v) => patch({ custom: draft.custom.map((x) => x.key === c.key ? { ...x, value: v } : x) }), onRemove: () => patch({ custom: draft.custom.filter((x) => x.key !== c.key) }) }, c.key)),
|
|
5952
|
-
!descriptionVisible && !tagsVisible && draft.secrets.length === 0 && extraCustom.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: "
|
|
6072
|
+
!descriptionVisible && !tagsVisible && draft.secrets.length === 0 && extraCustom.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: "Tags", onAdd: () => setShowTags(true), children: "Add tags" }) : null
|
|
5953
6073
|
]
|
|
5954
6074
|
}
|
|
5955
6075
|
),
|
|
@@ -5963,11 +6083,15 @@ function ItemPane(props) {
|
|
|
5963
6083
|
}
|
|
5964
6084
|
const field = item.fields.find((x) => x.key === f.key);
|
|
5965
6085
|
if (isRecoveryCodesField(f)) return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RecoveryCodesRow, { item, field, port, onChanged }, f.key);
|
|
5966
|
-
return field ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SecretRow, { port, item, field }, field.id) : null;
|
|
6086
|
+
return field ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SecretRow, { port, item, field, onTurnOn: turnOnOne(field) }, field.id) : null;
|
|
5967
6087
|
}),
|
|
5968
|
-
extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SecretRow, { port, item, field: f }, f.id)),
|
|
6088
|
+
extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SecretRow, { port, item, field: f, onTurnOn: turnOnOne(f) }, f.id)),
|
|
5969
6089
|
item.loginUrls.length > 0 ? item.loginUrls.map((u, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PlainRow, { label: i === 0 ? "Website" : `Website ${i + 1}`, value: u, openUrl: host.openUrl }, `${u}-${i}`)) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: "Website", onAdd: canEdit ? () => onMode("edit") : null, children: "Add website" }),
|
|
5970
|
-
item.authenticator ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TotpRow, { port, item, onChanged }) :
|
|
6090
|
+
item.authenticator ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TotpRow, { port, item, onChanged }) : seedField && !seedField.active ? (
|
|
6091
|
+
// The secret is still there, only off: turning it on brings the codes back. Never "Set up",
|
|
6092
|
+
// which would invite replacing a working two-factor secret.
|
|
6093
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(VaultRow, { label: "One-time password", actions: canTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TurnOnButton, { label: "One-time password", onTurnOn: () => turnOn([seedField]) }) : null, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-muted-foreground", children: "Turned off" }) })
|
|
6094
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: "One-time password", onAdd: canEdit ? () => onMode("edit") : null, children: "Set up" }),
|
|
5971
6095
|
!item.authenticator && item.custom.some((c) => c.key === MFA_METHOD_KEY && c.value) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TwoFactorMethodRow, { port, item, onChanged }) : null
|
|
5972
6096
|
] }),
|
|
5973
6097
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(VaultSection, { children: item.notes ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PlainRow, { label: "Notes", value: item.notes, multiline: true }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AddRow, { label: "Notes", onAdd: canEdit ? () => onMode("edit") : null, children: "Add notes" }) }),
|
|
@@ -5999,7 +6123,7 @@ function subtitleOf(item, withOwner) {
|
|
|
5999
6123
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6000
6124
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6001
6125
|
}
|
|
6002
|
-
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header }) {
|
|
6126
|
+
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner }) {
|
|
6003
6127
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6004
6128
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
6005
6129
|
const ordered = [...pinned, ...rest];
|
|
@@ -6018,7 +6142,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6018
6142
|
listRef.current?.querySelector(`[data-item="${next.id}"]`)?.scrollIntoView({ block: "nearest" });
|
|
6019
6143
|
}
|
|
6020
6144
|
};
|
|
6021
|
-
const mixedOwners = new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
6145
|
+
const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
6022
6146
|
const row = (i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners }, i.id);
|
|
6023
6147
|
const sortLabel = SORTS2.find((s) => s.value === sort)?.label ?? "Name";
|
|
6024
6148
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
|
|
@@ -6236,6 +6360,9 @@ function RestorePanel({ lists, item, restore, onClose }) {
|
|
|
6236
6360
|
|
|
6237
6361
|
// src/lists/TrashView.tsx
|
|
6238
6362
|
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
6363
|
+
function trashLineOf(item) {
|
|
6364
|
+
return [item.definitionLabel || null, item.owner.label, item.deletion?.deletedAt ? `Deleted ${(0, import_format9.formatAbsoluteDate)(item.deletion.deletedAt, DAY_FORMAT, "")}` : null].filter(Boolean).join(" \xB7 ");
|
|
6365
|
+
}
|
|
6239
6366
|
function TrashView({ port, deleted, query, header, onChanged }) {
|
|
6240
6367
|
const confirmed = useConfirmed();
|
|
6241
6368
|
const [busyId, setBusyId] = (0, import_react30.useState)(null);
|
|
@@ -6247,7 +6374,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6247
6374
|
setMessage(null);
|
|
6248
6375
|
try {
|
|
6249
6376
|
const r = await confirmed(() => port.lists.restore(item, deletionId));
|
|
6250
|
-
setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files.
|
|
6377
|
+
setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files. Turn its fields on from the item.` });
|
|
6251
6378
|
onChanged();
|
|
6252
6379
|
return r;
|
|
6253
6380
|
} catch (e) {
|
|
@@ -6265,7 +6392,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6265
6392
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex min-h-12 items-center gap-3 px-3 py-1.5 sm:px-4", children: [
|
|
6266
6393
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
6267
6394
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate text-sm text-foreground", children: item.name }),
|
|
6268
|
-
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate text-xs text-muted-foreground", children:
|
|
6395
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: trashLineOf(item) })
|
|
6269
6396
|
] }),
|
|
6270
6397
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "outline", icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RestoreGlyph, {}), disabled: busyId !== null || !item.deletion, onClick: () => setRestoring(item), children: "Restore" })
|
|
6271
6398
|
] }, item.id)
|
|
@@ -6556,10 +6683,9 @@ function SecureLinkPanel({ item, port, onClose }) {
|
|
|
6556
6683
|
title: "Send securely",
|
|
6557
6684
|
size: "md",
|
|
6558
6685
|
onClose,
|
|
6559
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
6560
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "w-full sm:w-auto sm:min-w-0 sm:flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(StatusLine, { error, notice }) }),
|
|
6686
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(PanelFooter, { status: error || notice ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(StatusLine, { error, notice }) : null, children: [
|
|
6561
6687
|
sameChannelSent && !error ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Badge, { tone: "warning", children: "Both by email" }) : null,
|
|
6562
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Button, { variant: "quiet",
|
|
6688
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Button, { variant: "quiet", onClick: onClose, children: "Close" }),
|
|
6563
6689
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Button, { variant: "primary", disabled: !canSend, onClick: () => void send(), children: busy === "send" ? "Sending\u2026" : "Send link" })
|
|
6564
6690
|
] }),
|
|
6565
6691
|
children: [
|
|
@@ -7815,8 +7941,33 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7815
7941
|
},
|
|
7816
7942
|
[loc, move]
|
|
7817
7943
|
);
|
|
7944
|
+
const keyState = (0, import_react41.useRef)({ loc, go });
|
|
7945
|
+
keyState.current = { loc, go };
|
|
7946
|
+
(0, import_react41.useEffect)(() => {
|
|
7947
|
+
const onKey = (e) => {
|
|
7948
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
7949
|
+
const root = rootRef.current;
|
|
7950
|
+
const at = document.activeElement;
|
|
7951
|
+
if (!root || at && at !== document.body && !root.contains(at)) return;
|
|
7952
|
+
if (at instanceof HTMLElement && (at.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(at.tagName))) return;
|
|
7953
|
+
if (document.querySelector('[role="dialog"], [role="alertdialog"]')) return;
|
|
7954
|
+
const { loc: here, go: to } = keyState.current;
|
|
7955
|
+
if (e.key === "/") {
|
|
7956
|
+
const search = root.querySelector('input[aria-label^="Search"]');
|
|
7957
|
+
if (!search) return;
|
|
7958
|
+
e.preventDefault();
|
|
7959
|
+
search.focus();
|
|
7960
|
+
} else if (e.key === "Escape" && here.itemId && here.mode === "view" && !here.panel) {
|
|
7961
|
+
e.preventDefault();
|
|
7962
|
+
to({ itemId: null, mode: "view", panel: null });
|
|
7963
|
+
}
|
|
7964
|
+
};
|
|
7965
|
+
document.addEventListener("keydown", onKey);
|
|
7966
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
7967
|
+
}, []);
|
|
7818
7968
|
const knobsRef = (0, import_react41.useRef)(host.knobs);
|
|
7819
7969
|
knobsRef.current = host.knobs;
|
|
7970
|
+
const [signedOut, setSignedOut] = (0, import_react41.useState)(false);
|
|
7820
7971
|
const load = (0, import_react41.useCallback)(() => {
|
|
7821
7972
|
setError(null);
|
|
7822
7973
|
port.load().then((s) => {
|
|
@@ -7827,7 +7978,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7827
7978
|
(rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
|
|
7828
7979
|
() => void 0
|
|
7829
7980
|
);
|
|
7830
|
-
}).catch((e) =>
|
|
7981
|
+
}).catch((e) => {
|
|
7982
|
+
setSignedOut(e?.status === 401);
|
|
7983
|
+
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
7984
|
+
});
|
|
7831
7985
|
port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
|
|
7832
7986
|
knobsRef.current?.().catch(() => void 0);
|
|
7833
7987
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
@@ -7841,6 +7995,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7841
7995
|
const orgs = snap?.organizations ?? [];
|
|
7842
7996
|
const definitions = snap?.definitions ?? [];
|
|
7843
7997
|
const inThisVault = (0, import_react41.useMemo)(() => items.filter((i) => inVault(i, loc.vault)), [items, loc.vault]);
|
|
7998
|
+
const vaultMixesOwners = new Set(inThisVault.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
7844
7999
|
const views = (0, import_react41.useMemo)(() => buildViews(inThisVault, definitions, favorites, recents), [inThisVault, definitions, favorites, recents]);
|
|
7845
8000
|
const visible = (0, import_react41.useMemo)(() => {
|
|
7846
8001
|
const filtered = inThisVault.filter((i) => {
|
|
@@ -7881,8 +8036,11 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7881
8036
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { ref: rootRef, role: "alert", className: "flex h-full flex-col items-center justify-center gap-3 bg-background p-6 text-center text-sm", children: [
|
|
7882
8037
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SafeGlyph, { className: "size-8 text-muted-foreground" }),
|
|
7883
8038
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "font-medium", children: "The vault couldn\u2019t open" }),
|
|
7884
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "max-w-xs text-muted-foreground", children: plainLoadError(error) }),
|
|
7885
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.
|
|
8039
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "max-w-xs text-muted-foreground", children: signedOut ? "You\u2019ve been signed out." : plainLoadError(error) }),
|
|
8040
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex gap-2", children: [
|
|
8041
|
+
signedOut && host.signIn ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_controls34.Button, { variant: "primary", onClick: host.signIn, children: "Sign in" }) : null,
|
|
8042
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_controls34.Button, { variant: signedOut && host.signIn ? "outline" : "primary", onClick: load, children: "Try again" })
|
|
8043
|
+
] })
|
|
7886
8044
|
] });
|
|
7887
8045
|
}
|
|
7888
8046
|
if (!snap) {
|
|
@@ -7962,6 +8120,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7962
8120
|
{
|
|
7963
8121
|
title,
|
|
7964
8122
|
items: visible,
|
|
8123
|
+
showOwner: vaultMixesOwners,
|
|
7965
8124
|
definitions,
|
|
7966
8125
|
port,
|
|
7967
8126
|
selectedId: loc.itemId,
|
|
@@ -8734,6 +8893,7 @@ async function readNativeImportLimits(api, organizationId) {
|
|
|
8734
8893
|
var recoveryOf = (w) => w.lifecycle?.vault_recovery;
|
|
8735
8894
|
var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
|
|
8736
8895
|
var MATCH_MODES2 = ["host", "domain", "exact", "never"];
|
|
8896
|
+
var FIELD_WORDS = { totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
|
|
8737
8897
|
var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
|
|
8738
8898
|
function fieldOf(w, def) {
|
|
8739
8899
|
const declared = def?.fields.find((f) => f.key === w.field_key);
|
|
@@ -8741,7 +8901,7 @@ function fieldOf(w, def) {
|
|
|
8741
8901
|
return {
|
|
8742
8902
|
id: w.id,
|
|
8743
8903
|
key: w.field_key,
|
|
8744
|
-
label: declared?.label ?? humanize(w.field_key),
|
|
8904
|
+
label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
|
|
8745
8905
|
kind: declared?.kind ?? (handling === "visible" ? "text" : "secret"),
|
|
8746
8906
|
handling,
|
|
8747
8907
|
editable: w.editable ?? true,
|