@ai-matrx/vault 0.1.10 → 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 +42 -1
- package/dist/index.cjs +215 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -4
- package/dist/index.d.ts +71 -4
- package/dist/index.js +278 -114
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -136,8 +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,
|
|
142
|
+
HIDDEN_ITEM_TAG: () => HIDDEN_ITEM_TAG,
|
|
143
|
+
IdentityGate: () => IdentityGate,
|
|
141
144
|
LOCKED_MESSAGE: () => LOCKED_MESSAGE,
|
|
142
145
|
VaultApp: () => VaultApp,
|
|
143
146
|
VaultLockedError: () => VaultLockedError,
|
|
@@ -148,13 +151,17 @@ __export(src_exports, {
|
|
|
148
151
|
createVaultKnobs: () => createVaultKnobs,
|
|
149
152
|
guardPort: () => guardPort,
|
|
150
153
|
inferFromUrl: () => inferFromUrl,
|
|
154
|
+
isHiddenItem: () => isHiddenItem,
|
|
151
155
|
locationFromParams: () => locationFromParams,
|
|
152
156
|
locationToParams: () => locationToParams,
|
|
153
157
|
loginSiteOf: () => loginSiteOf,
|
|
154
158
|
matchesQuery: () => matchesQuery,
|
|
155
159
|
needsRecentSignIn: () => needsRecentSignIn,
|
|
156
160
|
parseDefinitions: () => parseDefinitions,
|
|
157
|
-
|
|
161
|
+
siteDomainOf: () => siteDomainOf,
|
|
162
|
+
sortItems: () => sortItems,
|
|
163
|
+
useConfirmed: () => useConfirmed,
|
|
164
|
+
wasCancelled: () => wasCancelled
|
|
158
165
|
});
|
|
159
166
|
module.exports = __toCommonJS(src_exports);
|
|
160
167
|
|
|
@@ -382,9 +389,20 @@ var FingerprintGlyph = glyph(/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(impor
|
|
|
382
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]);
|
|
383
390
|
|
|
384
391
|
// src/auth/identity.ts
|
|
392
|
+
var CONFIRM_RETURN_KEY = "matrx-vault:confirming-as";
|
|
393
|
+
var RETURN_WINDOW_MS = 15 * 6e4;
|
|
385
394
|
var SAME_PERSON = "That signed in a different account. Use the account this vault belongs to.";
|
|
386
395
|
function createSupabaseIdentity(auth, options = {}) {
|
|
387
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
|
+
};
|
|
388
406
|
const who = async () => {
|
|
389
407
|
const { data } = await auth.getUser();
|
|
390
408
|
if (!data.user) throw new Error("You are signed out. Sign in again.");
|
|
@@ -401,10 +419,35 @@ function createSupabaseIdentity(auth, options = {}) {
|
|
|
401
419
|
return { email: u.email ?? null, providers: [...names].filter((p) => p !== "email" && p !== "phone"), hasPassword: names.has("email") };
|
|
402
420
|
},
|
|
403
421
|
async withProvider(provider) {
|
|
404
|
-
await who();
|
|
422
|
+
const u = await who();
|
|
423
|
+
store()?.setItem(CONFIRM_RETURN_KEY, JSON.stringify({ id: u.id, email: u.email ?? null, at: now() }));
|
|
405
424
|
const redirectTo = options.redirectTo?.() ?? globalThis.location?.href;
|
|
406
425
|
const { error } = await auth.signInWithOAuth({ provider, ...redirectTo ? { options: { redirectTo } } : {} });
|
|
407
|
-
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;
|
|
408
451
|
},
|
|
409
452
|
async withPassword(password) {
|
|
410
453
|
const u = await who();
|
|
@@ -450,6 +493,18 @@ var providerLabel = (p) => PROVIDER_LABEL[p] ?? p.charAt(0).toUpperCase() + p.sl
|
|
|
450
493
|
function IdentityGate({ identity, children }) {
|
|
451
494
|
const [asking, setAsking] = (0, import_react2.useState)(false);
|
|
452
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]);
|
|
453
508
|
const confirmed = async (run) => {
|
|
454
509
|
try {
|
|
455
510
|
return await run();
|
|
@@ -470,7 +525,14 @@ function IdentityGate({ identity, children }) {
|
|
|
470
525
|
};
|
|
471
526
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Ctx2.Provider, { value: confirmed, children: [
|
|
472
527
|
children,
|
|
473
|
-
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
|
+
] }) })
|
|
474
536
|
] });
|
|
475
537
|
}
|
|
476
538
|
var wasCancelled = (e) => e instanceof Error && e.message === CANCELLED;
|
|
@@ -534,7 +596,7 @@ function ConfirmIdentityDialog({ open, identity, onDone, onCancel }) {
|
|
|
534
596
|
e.preventDefault();
|
|
535
597
|
if (password) void attempt(() => identity.withPassword(password));
|
|
536
598
|
}, children: [
|
|
537
|
-
/* @__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 }),
|
|
538
600
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Button, { variant: "outline", type: "submit", disabled: busy || !password, children: "Confirm" })
|
|
539
601
|
] }) : null,
|
|
540
602
|
account10?.email ? codeSent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { className: "flex gap-2", onSubmit: (e) => {
|
|
@@ -662,6 +724,12 @@ function Panel({ title, open = true, onClose, children, footer, size = "lg" }) {
|
|
|
662
724
|
footer ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.DialogFooter, { children: footer }) : null
|
|
663
725
|
] }) });
|
|
664
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
|
+
}
|
|
665
733
|
var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
|
|
666
734
|
|
|
667
735
|
// src/FieldRows.tsx
|
|
@@ -672,7 +740,7 @@ function SecretText({ value, multiline }) {
|
|
|
672
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)) });
|
|
673
741
|
}
|
|
674
742
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
675
|
-
function SecretRow({ port, item, field, extra }) {
|
|
743
|
+
function SecretRow({ port, item, field, extra, onTurnOn }) {
|
|
676
744
|
const confirmed = useConfirmed();
|
|
677
745
|
const copyValue2 = useCopy();
|
|
678
746
|
const secret = useTransientSecret();
|
|
@@ -726,10 +794,27 @@ function SecretRow({ port, item, field, extra }) {
|
|
|
726
794
|
top: multiline && shown !== null,
|
|
727
795
|
actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
728
796
|
extra,
|
|
797
|
+
off && onTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TurnOnButton, { label: field.label, onTurnOn }) : null,
|
|
729
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,
|
|
730
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
|
|
731
800
|
] }),
|
|
732
|
-
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"
|
|
733
818
|
}
|
|
734
819
|
);
|
|
735
820
|
}
|
|
@@ -856,6 +941,8 @@ function matchesQuery(item, query) {
|
|
|
856
941
|
if (!q) return true;
|
|
857
942
|
return [item.name, item.definitionLabel, ...item.tags, ...item.loginUrls, ...item.custom.map((c) => `${c.label} ${c.value}`)].some((h) => h.toLowerCase().includes(q));
|
|
858
943
|
}
|
|
944
|
+
var HIDDEN_ITEM_TAG = "matrx:hidden";
|
|
945
|
+
var isHiddenItem = (item) => (item.tags ?? []).includes(HIDDEN_ITEM_TAG);
|
|
859
946
|
var isAuthenticatorStorage = (key) => key === "totp_seed" || key.startsWith("totp_") || key === "authenticator_last_time_step";
|
|
860
947
|
function loginSiteOf(item) {
|
|
861
948
|
const raw = item.loginUrls[0];
|
|
@@ -866,14 +953,27 @@ function loginSiteOf(item) {
|
|
|
866
953
|
return raw.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? "";
|
|
867
954
|
}
|
|
868
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
|
+
}
|
|
869
967
|
function sortItems(items, sort) {
|
|
870
968
|
const out = [...items];
|
|
871
969
|
const byName = (a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
872
970
|
if (sort === "name") return out.sort(byName);
|
|
873
|
-
if (sort === "website")
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
+
});
|
|
877
977
|
if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
878
978
|
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
879
979
|
}
|
|
@@ -1293,7 +1393,10 @@ function ActivityPanel({ item, port, onClose }) {
|
|
|
1293
1393
|
{
|
|
1294
1394
|
title: "Activity",
|
|
1295
1395
|
onClose,
|
|
1296
|
-
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
|
+
] }),
|
|
1297
1400
|
children: [
|
|
1298
1401
|
error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null,
|
|
1299
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(
|
|
@@ -1376,7 +1479,10 @@ function ItemHistoryPanel({ item, port, arg, onClose, onChanged }) {
|
|
|
1376
1479
|
{
|
|
1377
1480
|
title: "Password history",
|
|
1378
1481
|
onClose,
|
|
1379
|
-
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
|
+
] }),
|
|
1380
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: [
|
|
1381
1487
|
error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null,
|
|
1382
1488
|
notice ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "status", className: "px-1 text-sm text-success", children: notice }) : null,
|
|
@@ -1981,6 +2087,7 @@ var import_confirm_opener2 = require("@ai-matrx/kit/confirm-opener");
|
|
|
1981
2087
|
|
|
1982
2088
|
// src/authenticator/port.ts
|
|
1983
2089
|
var MFA_METHOD_KEY = "mfa_method";
|
|
2090
|
+
var OTP_SEED_KEY = "totp_seed";
|
|
1984
2091
|
function mfaMethodOf(item) {
|
|
1985
2092
|
const v = item.custom.find((c) => c.key === MFA_METHOD_KEY)?.value;
|
|
1986
2093
|
return v === "sms" ? "sms" : v ? "push_or_other" : null;
|
|
@@ -2339,7 +2446,7 @@ function AuthenticatorView({ port, items, definitions, query, onOpen, onChanged,
|
|
|
2339
2446
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2340
2447
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
2341
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: [
|
|
2342
|
-
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,
|
|
2343
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: [
|
|
2344
2451
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 items-baseline gap-2", children: [
|
|
2345
2452
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { className: "truncate text-sm font-semibold text-foreground", children: "One-time passwords" }),
|
|
@@ -2351,7 +2458,7 @@ function AuthenticatorView({ port, items, definitions, query, onOpen, onChanged,
|
|
|
2351
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" }) }),
|
|
2352
2459
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: "No one-time passwords yet" }),
|
|
2353
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" })
|
|
2354
|
-
] }) : 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)(
|
|
2355
2462
|
OtpListRow,
|
|
2356
2463
|
{
|
|
2357
2464
|
port,
|
|
@@ -2488,9 +2595,9 @@ function sandboxBlocker(envKeyDraft, savedEnvKey) {
|
|
|
2488
2595
|
return null;
|
|
2489
2596
|
}
|
|
2490
2597
|
var HANDLING_WORDS = {
|
|
2491
|
-
visible: { label: "
|
|
2492
|
-
revealable: { label: "
|
|
2493
|
-
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." }
|
|
2494
2601
|
};
|
|
2495
2602
|
function handlingChoices(current) {
|
|
2496
2603
|
return current === "sealed" ? ["sealed"] : ["visible", "revealable", "sealed"];
|
|
@@ -2672,7 +2779,7 @@ function FieldSettings({ item, port, field, onClose, onChanged }) {
|
|
|
2672
2779
|
}
|
|
2673
2780
|
) }),
|
|
2674
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 }) }) }),
|
|
2675
|
-
/* @__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 }) }) })
|
|
2676
2783
|
] }),
|
|
2677
2784
|
error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Problem, { children: error }) : null
|
|
2678
2785
|
] });
|
|
@@ -2740,7 +2847,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
|
|
|
2740
2847
|
f.envKey
|
|
2741
2848
|
] }) : null,
|
|
2742
2849
|
f.injectIntoSandbox ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground", children: " \xB7 Sandbox" }) : null,
|
|
2743
|
-
!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
|
|
2744
2851
|
] })
|
|
2745
2852
|
},
|
|
2746
2853
|
f.id
|
|
@@ -4682,7 +4789,7 @@ var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
|
4682
4789
|
function ItemIcon({ definitionKey, family, site, size = "md", inverted = false }) {
|
|
4683
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";
|
|
4684
4791
|
const tone = inverted ? "bg-primary-foreground text-primary" : "bg-muted text-foreground";
|
|
4685
|
-
const initial = site
|
|
4792
|
+
const initial = siteDomainOf(site).charAt(0).toUpperCase();
|
|
4686
4793
|
if (initial && /[A-Z0-9]/.test(initial)) {
|
|
4687
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 });
|
|
4688
4795
|
}
|
|
@@ -4711,7 +4818,7 @@ var TOO_BIG = "Files over 25 MB can't be stored.";
|
|
|
4711
4818
|
var PROTECTION = [
|
|
4712
4819
|
{ value: "visible", label: "Open", title: "Anyone who can use the item can download it" },
|
|
4713
4820
|
{ value: "revealable", label: "Protected", title: "Downloading needs a recent sign-in" },
|
|
4714
|
-
{ value: "sealed", label: "
|
|
4821
|
+
{ value: "sealed", label: "Never shown", title: "Only automation can use it, never a person" }
|
|
4715
4822
|
];
|
|
4716
4823
|
function FilesSection({ item, port, editing, onChanged }) {
|
|
4717
4824
|
const confirmed = useConfirmed();
|
|
@@ -4778,7 +4885,7 @@ function FilesSection({ item, port, editing, onChanged }) {
|
|
|
4778
4885
|
{
|
|
4779
4886
|
label: a.label,
|
|
4780
4887
|
hint: a.description || void 0,
|
|
4781
|
-
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,
|
|
4782
4889
|
children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(FileName, { file: a })
|
|
4783
4890
|
},
|
|
4784
4891
|
a.id
|
|
@@ -5000,7 +5107,7 @@ function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
|
5000
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,
|
|
5001
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
|
|
5002
5109
|
] }),
|
|
5003
|
-
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: [
|
|
5004
5111
|
codes.map((code, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex min-h-7 items-center gap-1", children: [
|
|
5005
5112
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("code", { className: "min-w-0 flex-1 break-all font-mono text-sm text-foreground", children: code }),
|
|
5006
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") }),
|
|
@@ -5323,10 +5430,7 @@ function MovePanel({ item, port, organizations, onClose, onChanged }) {
|
|
|
5323
5430
|
title: "Move or copy",
|
|
5324
5431
|
size: "md",
|
|
5325
5432
|
onClose,
|
|
5326
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime24.
|
|
5327
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(StatusLine, { error, notice }),
|
|
5328
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_controls19.Button, { variant: "outline", className: "ml-auto", onClick: onClose, children: "Close" })
|
|
5329
|
-
] }),
|
|
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" }) }),
|
|
5330
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: [
|
|
5331
5435
|
canMove && moves.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5332
5436
|
VaultRow,
|
|
@@ -5441,10 +5545,7 @@ function SharePanel({ item, port, organizations, onClose, onChanged }) {
|
|
|
5441
5545
|
title: "Share",
|
|
5442
5546
|
size: "md",
|
|
5443
5547
|
onClose,
|
|
5444
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime25.
|
|
5445
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StatusLine, { error, notice }),
|
|
5446
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_controls20.Button, { variant: "primary", className: "ml-auto", onClick: onClose, children: "Done" })
|
|
5447
|
-
] }),
|
|
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" }) }),
|
|
5448
5549
|
children: [
|
|
5449
5550
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(VaultSection, { title: "Who has access", children: [
|
|
5450
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" }) }),
|
|
@@ -5730,6 +5831,21 @@ function ItemPane(props) {
|
|
|
5730
5831
|
if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "The value could not be read.");
|
|
5731
5832
|
}
|
|
5732
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;
|
|
5733
5849
|
const declaredKeys = new Set(declared.map((f) => f.key));
|
|
5734
5850
|
const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
|
|
5735
5851
|
const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
|
|
@@ -5738,6 +5854,10 @@ function ItemPane(props) {
|
|
|
5738
5854
|
const isLogin = (item?.definitionKey ?? definition?.key) === "website_login" || (item?.definitionKey ?? definition?.key) === "username_password";
|
|
5739
5855
|
const typeLabel = definition?.label ?? item?.definitionLabel ?? "";
|
|
5740
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
|
+
};
|
|
5741
5861
|
const tagsVisible = showTags || draft.tags.length > 0 || tagsText !== "";
|
|
5742
5862
|
const descriptionVisible = showDescription || draft.description !== "";
|
|
5743
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: [
|
|
@@ -5821,6 +5941,10 @@ function ItemPane(props) {
|
|
|
5821
5941
|
header,
|
|
5822
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,
|
|
5823
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,
|
|
5824
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: [
|
|
5825
5949
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(VaultSection, { children: [
|
|
5826
5950
|
mode === "new" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(VaultRow, { label: "Save to", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
@@ -5833,11 +5957,11 @@ function ItemPane(props) {
|
|
|
5833
5957
|
searchPlaceholder: "Find an organization",
|
|
5834
5958
|
groups: [
|
|
5835
5959
|
{ heading: "Just you", options: ["me"] },
|
|
5836
|
-
...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) }] : [],
|
|
5837
5961
|
{ heading: "Send", options: ["person"] }
|
|
5838
5962
|
],
|
|
5839
5963
|
getLabel: (v) => v === "me" ? "Personal" : v === "person" ? "Someone else" : organizations.find((o) => o.id === v)?.name ?? v,
|
|
5840
|
-
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,
|
|
5841
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" })
|
|
5842
5966
|
}
|
|
5843
5967
|
) }) : null,
|
|
@@ -5912,7 +6036,7 @@ function ItemPane(props) {
|
|
|
5912
6036
|
i
|
|
5913
6037
|
)),
|
|
5914
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" }),
|
|
5915
|
-
/* @__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 }),
|
|
5916
6040
|
item && isLogin && !item.authenticator ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TwoFactorMethodRow, { port, item, onChanged }) : null
|
|
5917
6041
|
] }),
|
|
5918
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 }) }) }),
|
|
@@ -5945,7 +6069,7 @@ function ItemPane(props) {
|
|
|
5945
6069
|
) : null,
|
|
5946
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}`)),
|
|
5947
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)),
|
|
5948
|
-
!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
|
|
5949
6073
|
]
|
|
5950
6074
|
}
|
|
5951
6075
|
),
|
|
@@ -5959,11 +6083,15 @@ function ItemPane(props) {
|
|
|
5959
6083
|
}
|
|
5960
6084
|
const field = item.fields.find((x) => x.key === f.key);
|
|
5961
6085
|
if (isRecoveryCodesField(f)) return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RecoveryCodesRow, { item, field, port, onChanged }, f.key);
|
|
5962
|
-
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;
|
|
5963
6087
|
}),
|
|
5964
|
-
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)),
|
|
5965
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" }),
|
|
5966
|
-
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" }),
|
|
5967
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
|
|
5968
6096
|
] }),
|
|
5969
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" }) }),
|
|
@@ -5995,7 +6123,7 @@ function subtitleOf(item, withOwner) {
|
|
|
5995
6123
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
5996
6124
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
5997
6125
|
}
|
|
5998
|
-
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 }) {
|
|
5999
6127
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6000
6128
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
6001
6129
|
const ordered = [...pinned, ...rest];
|
|
@@ -6014,7 +6142,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6014
6142
|
listRef.current?.querySelector(`[data-item="${next.id}"]`)?.scrollIntoView({ block: "nearest" });
|
|
6015
6143
|
}
|
|
6016
6144
|
};
|
|
6017
|
-
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;
|
|
6018
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);
|
|
6019
6147
|
const sortLabel = SORTS2.find((s) => s.value === sort)?.label ?? "Name";
|
|
6020
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: [
|
|
@@ -6232,6 +6360,9 @@ function RestorePanel({ lists, item, restore, onClose }) {
|
|
|
6232
6360
|
|
|
6233
6361
|
// src/lists/TrashView.tsx
|
|
6234
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
|
+
}
|
|
6235
6366
|
function TrashView({ port, deleted, query, header, onChanged }) {
|
|
6236
6367
|
const confirmed = useConfirmed();
|
|
6237
6368
|
const [busyId, setBusyId] = (0, import_react30.useState)(null);
|
|
@@ -6243,7 +6374,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6243
6374
|
setMessage(null);
|
|
6244
6375
|
try {
|
|
6245
6376
|
const r = await confirmed(() => port.lists.restore(item, deletionId));
|
|
6246
|
-
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.` });
|
|
6247
6378
|
onChanged();
|
|
6248
6379
|
return r;
|
|
6249
6380
|
} catch (e) {
|
|
@@ -6261,7 +6392,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6261
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: [
|
|
6262
6393
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
6263
6394
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "truncate text-sm text-foreground", children: item.name }),
|
|
6264
|
-
/* @__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) })
|
|
6265
6396
|
] }),
|
|
6266
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" })
|
|
6267
6398
|
] }, item.id)
|
|
@@ -6552,10 +6683,9 @@ function SecureLinkPanel({ item, port, onClose }) {
|
|
|
6552
6683
|
title: "Send securely",
|
|
6553
6684
|
size: "md",
|
|
6554
6685
|
onClose,
|
|
6555
|
-
footer: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
6556
|
-
/* @__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: [
|
|
6557
6687
|
sameChannelSent && !error ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Badge, { tone: "warning", children: "Both by email" }) : null,
|
|
6558
|
-
/* @__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" }),
|
|
6559
6689
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_controls26.Button, { variant: "primary", disabled: !canSend, onClick: () => void send(), children: busy === "send" ? "Sending\u2026" : "Send link" })
|
|
6560
6690
|
] }),
|
|
6561
6691
|
children: [
|
|
@@ -7811,8 +7941,33 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7811
7941
|
},
|
|
7812
7942
|
[loc, move]
|
|
7813
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
|
+
}, []);
|
|
7814
7968
|
const knobsRef = (0, import_react41.useRef)(host.knobs);
|
|
7815
7969
|
knobsRef.current = host.knobs;
|
|
7970
|
+
const [signedOut, setSignedOut] = (0, import_react41.useState)(false);
|
|
7816
7971
|
const load = (0, import_react41.useCallback)(() => {
|
|
7817
7972
|
setError(null);
|
|
7818
7973
|
port.load().then((s) => {
|
|
@@ -7823,7 +7978,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7823
7978
|
(rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
|
|
7824
7979
|
() => void 0
|
|
7825
7980
|
);
|
|
7826
|
-
}).catch((e) =>
|
|
7981
|
+
}).catch((e) => {
|
|
7982
|
+
setSignedOut(e?.status === 401);
|
|
7983
|
+
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
7984
|
+
});
|
|
7827
7985
|
port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
|
|
7828
7986
|
knobsRef.current?.().catch(() => void 0);
|
|
7829
7987
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
@@ -7837,6 +7995,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7837
7995
|
const orgs = snap?.organizations ?? [];
|
|
7838
7996
|
const definitions = snap?.definitions ?? [];
|
|
7839
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;
|
|
7840
7999
|
const views = (0, import_react41.useMemo)(() => buildViews(inThisVault, definitions, favorites, recents), [inThisVault, definitions, favorites, recents]);
|
|
7841
8000
|
const visible = (0, import_react41.useMemo)(() => {
|
|
7842
8001
|
const filtered = inThisVault.filter((i) => {
|
|
@@ -7877,8 +8036,11 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7877
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: [
|
|
7878
8037
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SafeGlyph, { className: "size-8 text-muted-foreground" }),
|
|
7879
8038
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "font-medium", children: "The vault couldn\u2019t open" }),
|
|
7880
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "max-w-xs text-muted-foreground", children: plainLoadError(error) }),
|
|
7881
|
-
/* @__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
|
+
] })
|
|
7882
8044
|
] });
|
|
7883
8045
|
}
|
|
7884
8046
|
if (!snap) {
|
|
@@ -7958,6 +8120,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7958
8120
|
{
|
|
7959
8121
|
title,
|
|
7960
8122
|
items: visible,
|
|
8123
|
+
showOwner: vaultMixesOwners,
|
|
7961
8124
|
definitions,
|
|
7962
8125
|
port,
|
|
7963
8126
|
selectedId: loc.itemId,
|
|
@@ -8730,6 +8893,7 @@ async function readNativeImportLimits(api, organizationId) {
|
|
|
8730
8893
|
var recoveryOf = (w) => w.lifecycle?.vault_recovery;
|
|
8731
8894
|
var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
|
|
8732
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" };
|
|
8733
8897
|
var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
|
|
8734
8898
|
function fieldOf(w, def) {
|
|
8735
8899
|
const declared = def?.fields.find((f) => f.key === w.field_key);
|
|
@@ -8737,7 +8901,7 @@ function fieldOf(w, def) {
|
|
|
8737
8901
|
return {
|
|
8738
8902
|
id: w.id,
|
|
8739
8903
|
key: w.field_key,
|
|
8740
|
-
label: declared?.label ?? humanize(w.field_key),
|
|
8904
|
+
label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
|
|
8741
8905
|
kind: declared?.kind ?? (handling === "visible" ? "text" : "secret"),
|
|
8742
8906
|
handling,
|
|
8743
8907
|
editable: w.editable ?? true,
|
|
@@ -8846,7 +9010,7 @@ function createApiVaultPort(api, options = {}) {
|
|
|
8846
9010
|
lists.forEach((l, i) => {
|
|
8847
9011
|
const sharedList = i === lists.length - 1;
|
|
8848
9012
|
for (const w of l.items) {
|
|
8849
|
-
if (seen.has(w.id) || deleted.has(w.id)) continue;
|
|
9013
|
+
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) continue;
|
|
8850
9014
|
const owner = sharedList ? orgOf(w.organization_id, null, false) : orgOf(w.organization_id, w.user_id, i === 0);
|
|
8851
9015
|
const item = itemOf(w, sharedList && !w.organization_id ? { kind: "shared", label: "Shared with me", organizationId: null } : owner, defs, auth);
|
|
8852
9016
|
(isDeleted(w) ? deleted : seen).set(w.id, item);
|