@ai-matrx/vault 0.1.54 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -4
- package/dist/index.cjs +120 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +121 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ import { Look } from '@ai-matrx/design-system';
|
|
|
3
3
|
import { ApiStream, NdjsonEvent, JsonOrStream, FollowRuntimeOperationToEndOptions, FollowRuntimeOperationToEndResult, Api } from '@ai-matrx/api';
|
|
4
4
|
import * as account from '@ai-matrx/api/account';
|
|
5
5
|
import { VaultPasswordHealthScanRequest, VaultPasswordHealthScanReferenceData, PasswordHealthDurableResult } from '@ai-matrx/api/account';
|
|
6
|
+
import { VaultPasswordHealthDiscoveryPageData } from '@ai-matrx/api/stream';
|
|
6
7
|
import { OperationStatusResult } from '@ai-matrx/api/architecture';
|
|
7
8
|
import { VaultIdentity } from './identity.cjs';
|
|
8
9
|
export { CONFIRM_RETURN_KEY, Confirmed, IdentityAccount, IdentityGate, IdentityMethod, ReturnStore, SupabaseAuthLike, SupabaseIdentityOptions, createSupabaseIdentity, needsRecentSignIn, useConfirmed, wasCancelled } from './identity.cjs';
|
|
@@ -89,6 +90,7 @@ interface VaultItem {
|
|
|
89
90
|
fields: VaultField[];
|
|
90
91
|
custom: VaultCustomField[];
|
|
91
92
|
authenticator: VaultAuthenticator | null;
|
|
93
|
+
authenticatorState: "known" | "pending" | "unavailable";
|
|
92
94
|
capabilities: VaultItemCapabilities;
|
|
93
95
|
attachments: VaultAttachment[];
|
|
94
96
|
uriMatchMode: UriMatchMode;
|
|
@@ -222,6 +224,7 @@ type HealthScanOptions = VaultPasswordHealthScanRequest;
|
|
|
222
224
|
type HealthResult = PasswordHealthDurableResult;
|
|
223
225
|
type HealthReference = VaultPasswordHealthScanReferenceData;
|
|
224
226
|
interface HealthPort {
|
|
227
|
+
discover(cursor: string | undefined, signal: AbortSignal): Promise<VaultPasswordHealthDiscoveryPageData>;
|
|
225
228
|
start(requestId: string, signal: AbortSignal, options: HealthScanOptions): Promise<ApiStream<NdjsonEvent>>;
|
|
226
229
|
resume(executionId: string, requestId: string, signal: AbortSignal): Promise<JsonOrStream<HealthReference, NdjsonEvent>>;
|
|
227
230
|
read(executionId: string, signal: AbortSignal): Promise<HealthResult>;
|
|
@@ -321,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
|
|
|
321
324
|
*/
|
|
322
325
|
interface AuthenticatorPort {
|
|
323
326
|
/** Every account the signed-in person holds a one-time password for. */
|
|
324
|
-
list(): Promise<
|
|
327
|
+
list(): Promise<{
|
|
328
|
+
entries: AuthenticatorEntry[];
|
|
329
|
+
unavailableScopes: {
|
|
330
|
+
type: "user" | "organization" | "shared";
|
|
331
|
+
organizationId?: string | null;
|
|
332
|
+
}[];
|
|
333
|
+
}>;
|
|
325
334
|
/** null when the item has none. */
|
|
326
335
|
get(item: ItemRef): Promise<AuthenticatorEntry | null>;
|
|
327
336
|
/** The current code and how long it lives. */
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Look } from '@ai-matrx/design-system';
|
|
|
3
3
|
import { ApiStream, NdjsonEvent, JsonOrStream, FollowRuntimeOperationToEndOptions, FollowRuntimeOperationToEndResult, Api } from '@ai-matrx/api';
|
|
4
4
|
import * as account from '@ai-matrx/api/account';
|
|
5
5
|
import { VaultPasswordHealthScanRequest, VaultPasswordHealthScanReferenceData, PasswordHealthDurableResult } from '@ai-matrx/api/account';
|
|
6
|
+
import { VaultPasswordHealthDiscoveryPageData } from '@ai-matrx/api/stream';
|
|
6
7
|
import { OperationStatusResult } from '@ai-matrx/api/architecture';
|
|
7
8
|
import { VaultIdentity } from './identity.js';
|
|
8
9
|
export { CONFIRM_RETURN_KEY, Confirmed, IdentityAccount, IdentityGate, IdentityMethod, ReturnStore, SupabaseAuthLike, SupabaseIdentityOptions, createSupabaseIdentity, needsRecentSignIn, useConfirmed, wasCancelled } from './identity.js';
|
|
@@ -89,6 +90,7 @@ interface VaultItem {
|
|
|
89
90
|
fields: VaultField[];
|
|
90
91
|
custom: VaultCustomField[];
|
|
91
92
|
authenticator: VaultAuthenticator | null;
|
|
93
|
+
authenticatorState: "known" | "pending" | "unavailable";
|
|
92
94
|
capabilities: VaultItemCapabilities;
|
|
93
95
|
attachments: VaultAttachment[];
|
|
94
96
|
uriMatchMode: UriMatchMode;
|
|
@@ -222,6 +224,7 @@ type HealthScanOptions = VaultPasswordHealthScanRequest;
|
|
|
222
224
|
type HealthResult = PasswordHealthDurableResult;
|
|
223
225
|
type HealthReference = VaultPasswordHealthScanReferenceData;
|
|
224
226
|
interface HealthPort {
|
|
227
|
+
discover(cursor: string | undefined, signal: AbortSignal): Promise<VaultPasswordHealthDiscoveryPageData>;
|
|
225
228
|
start(requestId: string, signal: AbortSignal, options: HealthScanOptions): Promise<ApiStream<NdjsonEvent>>;
|
|
226
229
|
resume(executionId: string, requestId: string, signal: AbortSignal): Promise<JsonOrStream<HealthReference, NdjsonEvent>>;
|
|
227
230
|
read(executionId: string, signal: AbortSignal): Promise<HealthResult>;
|
|
@@ -321,7 +324,13 @@ type ItemRef = Pick<VaultItem, "id" | "owner">;
|
|
|
321
324
|
*/
|
|
322
325
|
interface AuthenticatorPort {
|
|
323
326
|
/** Every account the signed-in person holds a one-time password for. */
|
|
324
|
-
list(): Promise<
|
|
327
|
+
list(): Promise<{
|
|
328
|
+
entries: AuthenticatorEntry[];
|
|
329
|
+
unavailableScopes: {
|
|
330
|
+
type: "user" | "organization" | "shared";
|
|
331
|
+
organizationId?: string | null;
|
|
332
|
+
}[];
|
|
333
|
+
}>;
|
|
325
334
|
/** null when the item has none. */
|
|
326
335
|
get(item: ItemRef): Promise<AuthenticatorEntry | null>;
|
|
327
336
|
/** The current code and how long it lives. */
|
package/dist/index.js
CHANGED
|
@@ -146,7 +146,7 @@ function PasswordHealthView(props) {
|
|
|
146
146
|
const latest = useRef(props);
|
|
147
147
|
latest.current = props;
|
|
148
148
|
const [result, setResult] = useState2(null);
|
|
149
|
-
const [status, setStatus] = useState2(
|
|
149
|
+
const [status, setStatus] = useState2("Checking status");
|
|
150
150
|
const [error, setError] = useState2(null);
|
|
151
151
|
const [progress, setProgress] = useState2(null);
|
|
152
152
|
const [busy, setBusy] = useState2(false);
|
|
@@ -156,9 +156,13 @@ function PasswordHealthView(props) {
|
|
|
156
156
|
}, [port]);
|
|
157
157
|
const [retry, setRetry] = useState2(0);
|
|
158
158
|
const active = useRef(null);
|
|
159
|
-
const starting = useRef(
|
|
159
|
+
const starting = useRef(null);
|
|
160
160
|
const ownedAddress = useRef(null);
|
|
161
161
|
const writeReference = (request, execution) => {
|
|
162
|
+
if (starting.current?.controller === active.current) {
|
|
163
|
+
starting.current.request = request;
|
|
164
|
+
starting.current.execution = execution;
|
|
165
|
+
}
|
|
162
166
|
ownedAddress.current = { request, execution, items: latest.current.items, retry };
|
|
163
167
|
latest.current.onReference(request, execution);
|
|
164
168
|
};
|
|
@@ -186,7 +190,7 @@ function PasswordHealthView(props) {
|
|
|
186
190
|
const value = await port.read(id, controller.signal);
|
|
187
191
|
if (owns(controller)) {
|
|
188
192
|
setStatus(STATUS_LABEL[value.execution_status]);
|
|
189
|
-
setResult({ value, items: inventory });
|
|
193
|
+
setResult({ value, items: inventory, port });
|
|
190
194
|
}
|
|
191
195
|
return value;
|
|
192
196
|
};
|
|
@@ -204,6 +208,27 @@ function PasswordHealthView(props) {
|
|
|
204
208
|
}
|
|
205
209
|
writeReference(request, id);
|
|
206
210
|
}
|
|
211
|
+
if (!id && !request) {
|
|
212
|
+
let cursor;
|
|
213
|
+
const seen = /* @__PURE__ */ new Set();
|
|
214
|
+
do {
|
|
215
|
+
const page = await port.discover(cursor, controller.signal);
|
|
216
|
+
if (!owns(controller)) return;
|
|
217
|
+
const reference = page.references[0];
|
|
218
|
+
if (reference) {
|
|
219
|
+
id = reference.execution_id;
|
|
220
|
+
writeReference(reference.request_id, reference.execution_id);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
cursor = page.continuation ?? void 0;
|
|
224
|
+
if (cursor && seen.has(cursor)) throw new Error("Report unavailable");
|
|
225
|
+
if (cursor) seen.add(cursor);
|
|
226
|
+
} while (cursor);
|
|
227
|
+
if (!id) {
|
|
228
|
+
setStatus("No report");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
207
232
|
if (!id || !owns(controller)) return;
|
|
208
233
|
const first = await read(id, controller);
|
|
209
234
|
if (!owns(controller) || terminal(first.execution_status)) return;
|
|
@@ -213,7 +238,7 @@ function PasswordHealthView(props) {
|
|
|
213
238
|
if (!owns(controller)) return;
|
|
214
239
|
const detail = event.detail;
|
|
215
240
|
if (event.kind === "note" && detail?.label === "password_health_progress" && detail.__kind === "password_health_progress" && typeof detail.checked === "number" && Number.isSafeInteger(detail.checked) && detail.checked >= 0 && typeof detail.skipped === "number" && Number.isSafeInteger(detail.skipped) && detail.skipped >= 0) {
|
|
216
|
-
setProgress({ checked: detail.checked, skipped: detail.skipped });
|
|
241
|
+
setProgress({ checked: detail.checked, skipped: detail.skipped, port });
|
|
217
242
|
}
|
|
218
243
|
const lifecycle = event.kind === "started" || event.kind === "resumed" ? "running" : event.kind === "created" ? "pending" : event.kind;
|
|
219
244
|
const label = STATUS_LABEL[lifecycle];
|
|
@@ -228,14 +253,16 @@ function PasswordHealthView(props) {
|
|
|
228
253
|
if (!inventoryReady) {
|
|
229
254
|
ownedAddress.current = null;
|
|
230
255
|
setResult(null);
|
|
231
|
-
if (!starting.current) active.current?.abort();
|
|
256
|
+
if (!starting.current || !owns(starting.current.controller)) active.current?.abort();
|
|
232
257
|
return;
|
|
233
258
|
}
|
|
234
|
-
if (!port
|
|
259
|
+
if (!port) return;
|
|
260
|
+
const start = starting.current;
|
|
261
|
+
if (start && owns(start.controller) && start.request === (requestId ?? "") && start.execution === (executionId ?? "")) return;
|
|
235
262
|
const own = ownedAddress.current;
|
|
236
263
|
if (own && own.request === requestId && own.execution === executionId && own.items === items && own.retry === retry && active.current && !active.current.signal.aborted) return;
|
|
237
264
|
const controller = fresh();
|
|
238
|
-
setStatus(
|
|
265
|
+
setStatus("Checking status");
|
|
239
266
|
void recover(requestId, executionId, controller).catch((e) => {
|
|
240
267
|
if (owns(controller)) {
|
|
241
268
|
setResult(null);
|
|
@@ -257,7 +284,7 @@ function PasswordHealthView(props) {
|
|
|
257
284
|
const run = async (resume2) => {
|
|
258
285
|
if (!port) return;
|
|
259
286
|
const controller = fresh();
|
|
260
|
-
starting.current =
|
|
287
|
+
starting.current = { controller, request: "", execution: "" };
|
|
261
288
|
const request = crypto.randomUUID();
|
|
262
289
|
const options = { check_breaches: checkBreaches };
|
|
263
290
|
let actualRequest = request;
|
|
@@ -286,7 +313,7 @@ function PasswordHealthView(props) {
|
|
|
286
313
|
if (data && typeof data === "object" && "type" in data) {
|
|
287
314
|
if (data.type === "vault_password_health_scan_reference") accept(data);
|
|
288
315
|
if (data.type === "vault_password_health_progress") {
|
|
289
|
-
setProgress(data);
|
|
316
|
+
setProgress({ ...data, port });
|
|
290
317
|
setStatus("Running");
|
|
291
318
|
}
|
|
292
319
|
}
|
|
@@ -330,20 +357,22 @@ function PasswordHealthView(props) {
|
|
|
330
357
|
setError(problem(e));
|
|
331
358
|
setStatus((value) => value === "Checking status" ? "Unavailable" : value);
|
|
332
359
|
} finally {
|
|
333
|
-
starting.current =
|
|
360
|
+
if (starting.current?.controller === controller) starting.current = null;
|
|
334
361
|
if (owns(controller)) {
|
|
335
362
|
setBusy(false);
|
|
336
363
|
if (startedItems !== latest.current.items) setRetry((v) => v + 1);
|
|
337
364
|
}
|
|
338
365
|
}
|
|
339
366
|
};
|
|
340
|
-
const
|
|
341
|
-
const
|
|
367
|
+
const ownedResult = port && result?.port === port ? result : null;
|
|
368
|
+
const shownProgress = port && progress?.port === port ? progress : null;
|
|
369
|
+
const current = inventoryReady && ownedResult?.items === items && ownedResult.value.availability === "current" ? ownedResult.value.snapshot : null;
|
|
370
|
+
const available = ownedResult?.value.availability;
|
|
342
371
|
const outcomes = current?.check_breaches ? current.breach_outcomes ?? [] : [];
|
|
343
372
|
const compromised = outcomes.filter((outcome) => outcome.status === "compromised").length;
|
|
344
373
|
const notFound = outcomes.filter((outcome) => outcome.status === "not_found").length;
|
|
345
374
|
const unknown = outcomes.filter((outcome) => outcome.status === "unknown").length;
|
|
346
|
-
const resume =
|
|
375
|
+
const resume = ownedResult && ["failed", "cancelled"].includes(ownedResult.value.execution_status);
|
|
347
376
|
return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col gap-4 overflow-auto p-4", "aria-label": "Password health", children: [
|
|
348
377
|
props.header,
|
|
349
378
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
|
|
@@ -377,9 +406,9 @@ function PasswordHealthView(props) {
|
|
|
377
406
|
/* @__PURE__ */ jsx2(InfoHint, { text: "The breach lookup could not establish a result." })
|
|
378
407
|
] }), children: unknown })
|
|
379
408
|
] }) : null,
|
|
380
|
-
!current &&
|
|
381
|
-
/* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children:
|
|
382
|
-
/* @__PURE__ */ jsx2(VaultRow, { label: "Skipped", children:
|
|
409
|
+
!current && shownProgress ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
410
|
+
/* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children: shownProgress.checked }),
|
|
411
|
+
/* @__PURE__ */ jsx2(VaultRow, { label: "Skipped", children: shownProgress.skipped })
|
|
383
412
|
] }) : null,
|
|
384
413
|
current ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
385
414
|
/* @__PURE__ */ jsx2(VaultRow, { label: "Checked", children: current.checked_subjects.length }),
|
|
@@ -2137,7 +2166,7 @@ function normalizeWebsite(raw) {
|
|
|
2137
2166
|
}
|
|
2138
2167
|
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
|
|
2139
2168
|
const confirmed = useConfirmed();
|
|
2140
|
-
const candidates = items.filter((i) => !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
|
|
2169
|
+
const candidates = items.filter((i) => i.authenticatorState === "known" && !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
|
|
2141
2170
|
const [input, setInput] = useState11("");
|
|
2142
2171
|
const [image, setImage] = useState11(null);
|
|
2143
2172
|
const [target, setTarget] = useState11(itemId && candidates.some((c) => c.id === itemId) ? itemId : NEW);
|
|
@@ -2176,7 +2205,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2176
2205
|
}
|
|
2177
2206
|
const it = item;
|
|
2178
2207
|
const entry = await confirmed(() => image ? port.authenticator.enrolQrImage(it, image, image.name) : port.authenticator.enrol(it, input));
|
|
2179
|
-
let next = { ...it, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
|
|
2208
|
+
let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
|
|
2180
2209
|
const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
|
|
2181
2210
|
if (!isNew && better !== it.name) {
|
|
2182
2211
|
try {
|
|
@@ -2314,6 +2343,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2314
2343
|
useEffect10(() => {
|
|
2315
2344
|
if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
|
|
2316
2345
|
}, [listed, added]);
|
|
2346
|
+
const [incomplete, setIncomplete] = useState12(true);
|
|
2317
2347
|
const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
|
|
2318
2348
|
const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
|
|
2319
2349
|
const [adding, setAdding] = useState12(false);
|
|
@@ -2323,8 +2353,15 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2323
2353
|
useEffect10(() => {
|
|
2324
2354
|
let live = true;
|
|
2325
2355
|
port.authenticator.list().then(
|
|
2326
|
-
(list) =>
|
|
2327
|
-
|
|
2356
|
+
(list) => {
|
|
2357
|
+
if (live) {
|
|
2358
|
+
setEntries(new Map(list.entries.map((e) => [e.itemId, e])));
|
|
2359
|
+
setIncomplete(list.unavailableScopes.length > 0);
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
() => {
|
|
2363
|
+
if (live) setIncomplete(true);
|
|
2364
|
+
}
|
|
2328
2365
|
);
|
|
2329
2366
|
return () => {
|
|
2330
2367
|
live = false;
|
|
@@ -2342,7 +2379,14 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2342
2379
|
] }),
|
|
2343
2380
|
total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
|
|
2344
2381
|
] }),
|
|
2345
|
-
total
|
|
2382
|
+
total > 0 && incomplete ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
|
|
2383
|
+
"Some codes unavailable ",
|
|
2384
|
+
/* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2385
|
+
] }) : null,
|
|
2386
|
+
total === 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: [
|
|
2387
|
+
"Codes unavailable ",
|
|
2388
|
+
/* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2389
|
+
] }) : total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
|
|
2346
2390
|
/* @__PURE__ */ jsx11("span", { className: "flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx11(ShieldGlyph, { className: "size-6" }) }),
|
|
2347
2391
|
/* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground", children: "No authenticator codes yet" }),
|
|
2348
2392
|
/* @__PURE__ */ jsx11(Button9, { variant: "primary", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add authenticator" })
|
|
@@ -6392,7 +6436,10 @@ function ItemPane(props) {
|
|
|
6392
6436
|
i
|
|
6393
6437
|
)),
|
|
6394
6438
|
/* @__PURE__ */ jsx25(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
|
|
6395
|
-
/* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }),
|
|
6439
|
+
!item || item.authenticatorState === "known" ? /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) : /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
|
|
6440
|
+
/* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
|
|
6441
|
+
item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
|
|
6442
|
+
] }),
|
|
6396
6443
|
item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
|
|
6397
6444
|
!item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
|
|
6398
6445
|
recoveryEditing ? /* @__PURE__ */ jsx25(
|
|
@@ -6471,7 +6518,10 @@ function ItemPane(props) {
|
|
|
6471
6518
|
}),
|
|
6472
6519
|
extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ jsx25(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ jsx25(SecretRow, { port, item, field: f, onTurnOn: turnOnOne(f), extra: fieldSettings(f) }, f.id)),
|
|
6473
6520
|
item.loginUrls.length > 0 ? item.loginUrls.map((u, i) => /* @__PURE__ */ jsx25(PlainRow, { label: i === 0 ? "Website" : `Website ${i + 1}`, value: u, openUrl: host.openUrl }, `${u}-${i}`)) : /* @__PURE__ */ jsx25(AddRow, { label: "Website", onAdd: canEdit ? () => onMode("edit") : null, children: "Add website" }),
|
|
6474
|
-
item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) :
|
|
6521
|
+
item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
|
|
6522
|
+
/* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
|
|
6523
|
+
item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
|
|
6524
|
+
] }) : seedField && !seedField.active ? (
|
|
6475
6525
|
// The secret is still there, only off: turning it on brings the codes back. Never "Set up",
|
|
6476
6526
|
// which would invite replacing a working two-factor secret.
|
|
6477
6527
|
/* @__PURE__ */ jsx25(VaultRow, { label: "Authenticator", actions: canTurnOn ? /* @__PURE__ */ jsx25(TurnOnButton, { label: "Authenticator", onTurnOn: () => turnOn([seedField]) }) : null, children: /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: "Turned off" }) })
|
|
@@ -9150,10 +9200,34 @@ function Blank({ children }) {
|
|
|
9150
9200
|
|
|
9151
9201
|
// src/health/api.ts
|
|
9152
9202
|
import { followRuntimeOperationToEndWithApi } from "@ai-matrx/api";
|
|
9153
|
-
import { startPasswordHealthScan, getPasswordHealthScan, resumePasswordHealthScan } from "@ai-matrx/api/account";
|
|
9203
|
+
import { startPasswordHealthScan, getPasswordHealthScan, resumePasswordHealthScan, discoverPasswordHealthScans } from "@ai-matrx/api/account";
|
|
9154
9204
|
import { operationStatus } from "@ai-matrx/api/architecture";
|
|
9155
9205
|
function createHealthPort(api) {
|
|
9156
9206
|
return {
|
|
9207
|
+
discover: async (cursor, signal) => {
|
|
9208
|
+
const stream = await discoverPasswordHealthScans(api, cursor ? { query: { cursor } } : void 0, { signal, totalTimeoutMs: null });
|
|
9209
|
+
let page = null;
|
|
9210
|
+
let ended = false;
|
|
9211
|
+
try {
|
|
9212
|
+
for await (const event of stream) {
|
|
9213
|
+
if (signal.aborted || ended || event.event === "error") throw new Error("Report unavailable");
|
|
9214
|
+
const data = event.data;
|
|
9215
|
+
if (event.event === "completion" && data && typeof data === "object" && "status" in data && data.status !== "success") throw new Error("Report unavailable");
|
|
9216
|
+
if (event.event === "end") {
|
|
9217
|
+
if (!data || typeof data !== "object" || !("reason" in data) || data.reason !== "complete") throw new Error("Report unavailable");
|
|
9218
|
+
ended = true;
|
|
9219
|
+
}
|
|
9220
|
+
if (event.event !== "data") continue;
|
|
9221
|
+
if (!data || typeof data !== "object" || !("type" in data) || data.type !== "vault_password_health_discovery_page") continue;
|
|
9222
|
+
if (page || !("__kind" in data) || data.__kind !== "password_health_discovery_page" || !("references" in data) || !Array.isArray(data.references) || data.references.some((ref) => !ref || typeof ref !== "object" || ref.__kind !== "password_health_discovery_reference" || typeof ref.request_id !== "string" || !ref.request_id || typeof ref.execution_id !== "string" || !ref.execution_id) || !("continuation" in data) || data.continuation !== null && (typeof data.continuation !== "string" || !data.continuation)) throw new Error("Report unavailable");
|
|
9223
|
+
page = data;
|
|
9224
|
+
}
|
|
9225
|
+
if (signal.aborted || !ended || !page) throw new Error("Report unavailable");
|
|
9226
|
+
return page;
|
|
9227
|
+
} finally {
|
|
9228
|
+
stream.cancel();
|
|
9229
|
+
}
|
|
9230
|
+
},
|
|
9157
9231
|
start: (requestId, signal, options) => startPasswordHealthScan(api, { body: options.check_breaches ? { check_breaches: true } : {} }, { requestId, signal, totalTimeoutMs: null }),
|
|
9158
9232
|
resume: (executionId, requestId, signal) => resumePasswordHealthScan(api, { path: { execution_id: executionId } }, { requestId, signal, totalTimeoutMs: null }),
|
|
9159
9233
|
read: (executionId, signal) => getPasswordHealthScan(api, { path: { execution_id: executionId } }, { signal }),
|
|
@@ -9276,7 +9350,7 @@ function createAuthenticatorPort(ctx) {
|
|
|
9276
9350
|
return {
|
|
9277
9351
|
async list() {
|
|
9278
9352
|
const r = await apps.listAuthenticators(api);
|
|
9279
|
-
return (r.entries ?? []).map(entryOf);
|
|
9353
|
+
return { entries: (r.entries ?? []).map(entryOf), unavailableScopes: (r.unavailable_scopes ?? []).map((s) => ({ type: s.type, organizationId: s.organization_id ?? null })) };
|
|
9280
9354
|
},
|
|
9281
9355
|
async get(item) {
|
|
9282
9356
|
try {
|
|
@@ -9490,6 +9564,7 @@ function trashItem(r, ctx) {
|
|
|
9490
9564
|
fields: [],
|
|
9491
9565
|
custom: [],
|
|
9492
9566
|
authenticator: null,
|
|
9567
|
+
authenticatorState: "known",
|
|
9493
9568
|
capabilities: { canUse: false, canEdit: false, canReveal: false, canManage: true },
|
|
9494
9569
|
attachments: [],
|
|
9495
9570
|
uriMatchMode: "host",
|
|
@@ -9846,7 +9921,8 @@ function fieldOf(w, def) {
|
|
|
9846
9921
|
active: w.is_active ?? true
|
|
9847
9922
|
};
|
|
9848
9923
|
}
|
|
9849
|
-
|
|
9924
|
+
var scopeUnavailable = (owner, scopes) => scopes.some((s) => s.type === (owner.kind === "me" ? "user" : owner.kind === "organization" ? "organization" : "shared") && (s.type !== "organization" || !s.organization_id || s.organization_id === owner.organizationId));
|
|
9925
|
+
function itemOf(w, owner, defs, auth, state = "known", unavailable = [], inventoryOwner = owner) {
|
|
9850
9926
|
const def = defs.get(w.definition_key ?? "");
|
|
9851
9927
|
const a = auth.get(w.id);
|
|
9852
9928
|
const cap = w.capabilities ?? {};
|
|
@@ -9864,6 +9940,7 @@ function itemOf(w, owner, defs, auth) {
|
|
|
9864
9940
|
fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
|
|
9865
9941
|
custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
|
|
9866
9942
|
authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
|
|
9943
|
+
authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
|
|
9867
9944
|
capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
|
|
9868
9945
|
attachments: (w.attachments ?? []).map((a2) => ({ id: a2.id, label: a2.label, description: a2.description ?? "", fileName: a2.file_name, mediaType: a2.media_type ?? "application/octet-stream", sizeBytes: a2.size_bytes, handling: handlingOf(a2.handling), createdAt: a2.created_at, updatedAt: a2.updated_at })),
|
|
9869
9946
|
uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
|
|
@@ -9900,6 +9977,9 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9900
9977
|
};
|
|
9901
9978
|
let defs = /* @__PURE__ */ new Map();
|
|
9902
9979
|
let auth = /* @__PURE__ */ new Map();
|
|
9980
|
+
let authState = "pending";
|
|
9981
|
+
let authUnavailable = [];
|
|
9982
|
+
let inventoryOwners = /* @__PURE__ */ new Map();
|
|
9903
9983
|
let orgs = [];
|
|
9904
9984
|
const orgOf = (id, userId, mine) => {
|
|
9905
9985
|
if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
|
|
@@ -9913,7 +9993,7 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9913
9993
|
};
|
|
9914
9994
|
async function refetch(id, owner) {
|
|
9915
9995
|
const w = await account9.getItem(api, { path: { item_id: id } });
|
|
9916
|
-
return itemOf(w, owner, defs, auth);
|
|
9996
|
+
return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
|
|
9917
9997
|
}
|
|
9918
9998
|
const visibleCache = /* @__PURE__ */ new Map();
|
|
9919
9999
|
const ctx = {
|
|
@@ -9940,6 +10020,10 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9940
10020
|
fields: createFieldsPort(ctx),
|
|
9941
10021
|
lists: createListsPort(ctx, options.userState),
|
|
9942
10022
|
async load(onProgress) {
|
|
10023
|
+
authState = "pending";
|
|
10024
|
+
auth = /* @__PURE__ */ new Map();
|
|
10025
|
+
authUnavailable = [];
|
|
10026
|
+
inventoryOwners = /* @__PURE__ */ new Map();
|
|
9943
10027
|
const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
|
|
9944
10028
|
const authP = retrying(() => apps2.listAuthenticators(api));
|
|
9945
10029
|
const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
|
|
@@ -9962,9 +10046,10 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9962
10046
|
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
9963
10047
|
const seen = /* @__PURE__ */ new Map();
|
|
9964
10048
|
const deleted = /* @__PURE__ */ new Map();
|
|
9965
|
-
const take = (w, owner) => {
|
|
10049
|
+
const take = (w, owner, inventoryOwner = owner) => {
|
|
9966
10050
|
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
9967
|
-
|
|
10051
|
+
inventoryOwners.set(w.id, inventoryOwner);
|
|
10052
|
+
const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
|
|
9968
10053
|
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
9969
10054
|
};
|
|
9970
10055
|
for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
|
|
@@ -9972,7 +10057,7 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9972
10057
|
const via = orgs[i];
|
|
9973
10058
|
for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
|
|
9974
10059
|
});
|
|
9975
|
-
for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null });
|
|
10060
|
+
for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
|
|
9976
10061
|
const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
|
|
9977
10062
|
return {
|
|
9978
10063
|
unavailable: [...unavailable],
|
|
@@ -10031,9 +10116,14 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10031
10116
|
})();
|
|
10032
10117
|
quiet(orgListsReady);
|
|
10033
10118
|
const authDone = authP.then((a) => {
|
|
10034
|
-
|
|
10119
|
+
authUnavailable = a.unavailable_scopes ?? [];
|
|
10120
|
+
const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
|
|
10121
|
+
auth = fresh;
|
|
10122
|
+
authState = "known";
|
|
10123
|
+
if (authUnavailable.length) unavailable.push("One-time codes");
|
|
10035
10124
|
progress();
|
|
10036
10125
|
}, () => {
|
|
10126
|
+
authState = "unavailable";
|
|
10037
10127
|
unavailable.push("One-time codes");
|
|
10038
10128
|
});
|
|
10039
10129
|
const sharedDone = sharedP.then((sh) => {
|