@noy-db/hub 0.1.0-pre.8 → 0.1.0-pre.9
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/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +2 -2
- package/dist/blobs/index.d.ts +2 -2
- package/dist/blobs/index.js +2 -2
- package/dist/bundle/index.d.cts +2 -2
- package/dist/bundle/index.d.ts +2 -2
- package/dist/bundle/index.js +3 -3
- package/dist/{chunk-R2ZTGEVP.js → chunk-2CSJGFCB.js} +2 -2
- package/dist/{chunk-TOQK4KAN.js → chunk-4PWAI7Q4.js} +3 -3
- package/dist/{chunk-HC7Z5EQZ.js → chunk-AVVPZ4BC.js} +2 -2
- package/dist/{chunk-WN6UK7PM.js → chunk-EXHNQEV4.js} +2 -2
- package/dist/{chunk-2WGMYBYS.js → chunk-MDDTIZUO.js} +3 -3
- package/dist/{chunk-RSPLI376.js → chunk-PTVMYYON.js} +2 -2
- package/dist/{chunk-Y4CMTMUW.js → chunk-QAVUREFT.js} +2 -2
- package/dist/{chunk-7XBQS42M.js → chunk-QGZRWRSL.js} +2 -2
- package/dist/{chunk-PJK6IOBC.js → chunk-RKJ6OL7K.js} +1 -1
- package/dist/{chunk-PJK6IOBC.js.map → chunk-RKJ6OL7K.js.map} +1 -1
- package/dist/{chunk-YVFTBQHL.js → chunk-WDM5XGGS.js} +39 -2
- package/dist/chunk-WDM5XGGS.js.map +1 -0
- package/dist/consent/index.d.cts +2 -2
- package/dist/consent/index.d.ts +2 -2
- package/dist/{dev-unlock-BygpnIWe.d.ts → dev-unlock-BdPp68qn.d.ts} +1 -1
- package/dist/{dev-unlock-BZKx666y.d.cts → dev-unlock-Da1B0TIK.d.cts} +1 -1
- package/dist/{hash-CIyfmKsg.d.cts → hash-BEfzPKwo.d.cts} +1 -1
- package/dist/{hash-B0eU2Qv9.d.ts → hash-lsoL3eEW.d.ts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +3 -3
- package/dist/history/index.d.ts +3 -3
- package/dist/history/index.js +2 -2
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +2 -2
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +3 -3
- package/dist/{index-Dp4tKCjX.d.ts → index-8QDuznDr.d.ts} +1 -1
- package/dist/{index-DsVbTDZI.d.cts → index-CywCC1qZ.d.cts} +1 -1
- package/dist/index.cjs +206 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +182 -13
- package/dist/index.js.map +1 -1
- package/dist/{ledger-UQIMMKO5.js → ledger-QZTTHQAQ.js} +3 -3
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +2 -2
- package/dist/periods/index.d.ts +2 -2
- package/dist/periods/index.js +3 -3
- package/dist/{public-envelope-3QTQADDW.js → public-envelope-6JTACYJV.js} +3 -3
- package/dist/session/index.d.cts +3 -3
- package/dist/session/index.d.ts +3 -3
- package/dist/shadow/index.d.cts +2 -2
- package/dist/shadow/index.d.ts +2 -2
- package/dist/store/index.d.cts +2 -2
- package/dist/store/index.d.ts +2 -2
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.js +2 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +2 -2
- package/dist/team/index.d.ts +2 -2
- package/dist/team/index.js +4 -4
- package/dist/tx/index.d.cts +2 -2
- package/dist/tx/index.d.ts +2 -2
- package/dist/{types-arFMsCtn.d.cts → types-Bnb82f5R.d.cts} +176 -4
- package/dist/{types-DD9eKKNc.d.ts → types-Bo7NSXJr.d.ts} +176 -4
- package/package.json +1 -1
- package/dist/chunk-YVFTBQHL.js.map +0 -1
- /package/dist/{chunk-R2ZTGEVP.js.map → chunk-2CSJGFCB.js.map} +0 -0
- /package/dist/{chunk-TOQK4KAN.js.map → chunk-4PWAI7Q4.js.map} +0 -0
- /package/dist/{chunk-HC7Z5EQZ.js.map → chunk-AVVPZ4BC.js.map} +0 -0
- /package/dist/{chunk-WN6UK7PM.js.map → chunk-EXHNQEV4.js.map} +0 -0
- /package/dist/{chunk-2WGMYBYS.js.map → chunk-MDDTIZUO.js.map} +0 -0
- /package/dist/{chunk-RSPLI376.js.map → chunk-PTVMYYON.js.map} +0 -0
- /package/dist/{chunk-Y4CMTMUW.js.map → chunk-QAVUREFT.js.map} +0 -0
- /package/dist/{chunk-7XBQS42M.js.map → chunk-QGZRWRSL.js.map} +0 -0
- /package/dist/{ledger-UQIMMKO5.js.map → ledger-QZTTHQAQ.js.map} +0 -0
- /package/dist/{public-envelope-3QTQADDW.js.map → public-envelope-6JTACYJV.js.map} +0 -0
package/dist/i18n/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as I18nStrategy } from '../types-
|
|
2
|
-
export { D as DICT_COLLECTION_PREFIX, a as DictEntry, b as DictKeyDescriptor, c as DictionaryHandle, d as DictionaryOptions, e as I18nTextDescriptor, f as I18nTextOptions, g as applyI18nLocale, h as dictCollectionName, i as dictKey, j as i18nText, k as isDictCollectionName, l as isDictKeyDescriptor, m as isI18nTextDescriptor, r as resolveI18nText, v as validateI18nTextValue } from '../types-
|
|
1
|
+
import { I as I18nStrategy } from '../types-Bnb82f5R.cjs';
|
|
2
|
+
export { D as DICT_COLLECTION_PREFIX, a as DictEntry, b as DictKeyDescriptor, c as DictionaryHandle, d as DictionaryOptions, e as I18nTextDescriptor, f as I18nTextOptions, g as applyI18nLocale, h as dictCollectionName, i as dictKey, j as i18nText, k as isDictCollectionName, l as isDictKeyDescriptor, m as isI18nTextDescriptor, r as resolveI18nText, v as validateI18nTextValue } from '../types-Bnb82f5R.cjs';
|
|
3
3
|
import '../lazy-builder-CZVLKh0Z.cjs';
|
|
4
4
|
import '../predicate-SBHmi6D0.cjs';
|
|
5
5
|
import '../strategy-D-SrOLCl.cjs';
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as I18nStrategy } from '../types-
|
|
2
|
-
export { D as DICT_COLLECTION_PREFIX, a as DictEntry, b as DictKeyDescriptor, c as DictionaryHandle, d as DictionaryOptions, e as I18nTextDescriptor, f as I18nTextOptions, g as applyI18nLocale, h as dictCollectionName, i as dictKey, j as i18nText, k as isDictCollectionName, l as isDictKeyDescriptor, m as isI18nTextDescriptor, r as resolveI18nText, v as validateI18nTextValue } from '../types-
|
|
1
|
+
import { I as I18nStrategy } from '../types-Bo7NSXJr.js';
|
|
2
|
+
export { D as DICT_COLLECTION_PREFIX, a as DictEntry, b as DictKeyDescriptor, c as DictionaryHandle, d as DictionaryOptions, e as I18nTextDescriptor, f as I18nTextOptions, g as applyI18nLocale, h as dictCollectionName, i as dictKey, j as i18nText, k as isDictCollectionName, l as isDictKeyDescriptor, m as isI18nTextDescriptor, r as resolveI18nText, v as validateI18nTextValue } from '../types-Bo7NSXJr.js';
|
|
3
3
|
import '../lazy-builder-BwEoBQZ9.js';
|
|
4
4
|
import '../predicate-SBHmi6D0.js';
|
|
5
5
|
import '../strategy-D-SrOLCl.js';
|
package/dist/i18n/index.js
CHANGED
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
isI18nTextDescriptor,
|
|
11
11
|
resolveI18nText,
|
|
12
12
|
validateI18nTextValue
|
|
13
|
-
} from "../chunk-
|
|
14
|
-
import "../chunk-
|
|
13
|
+
} from "../chunk-MDDTIZUO.js";
|
|
14
|
+
import "../chunk-WDM5XGGS.js";
|
|
15
15
|
import "../chunk-CIMZBAZB.js";
|
|
16
|
-
import "../chunk-
|
|
16
|
+
import "../chunk-RKJ6OL7K.js";
|
|
17
17
|
import "../chunk-MR4424N3.js";
|
|
18
18
|
import "../chunk-ACLDOTNQ.js";
|
|
19
19
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { aS as PublicEnvelope, b0 as BundleRecipient, aY as Vault } from './types-
|
|
1
|
+
import { aS as PublicEnvelope, b0 as BundleRecipient, aY as Vault } from './types-Bo7NSXJr.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `.noydb` container format — byte layout, header schema, validators.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { aS as PublicEnvelope, b0 as BundleRecipient, aY as Vault } from './types-
|
|
1
|
+
import { aS as PublicEnvelope, b0 as BundleRecipient, aY as Vault } from './types-Bnb82f5R.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `.noydb` container format — byte layout, header schema, validators.
|
package/dist/index.cjs
CHANGED
|
@@ -5012,6 +5012,11 @@ function canRevoke(callerRole, targetRole) {
|
|
|
5012
5012
|
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
5013
5013
|
return false;
|
|
5014
5014
|
}
|
|
5015
|
+
function canUpdateRole(callerRole, targetRole) {
|
|
5016
|
+
if (callerRole === "owner") return true;
|
|
5017
|
+
if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
|
|
5018
|
+
return false;
|
|
5019
|
+
}
|
|
5015
5020
|
async function loadKeyring(adapter, vault, userId, passphrase) {
|
|
5016
5021
|
const envelope = await adapter.get(vault, "_keyring", userId);
|
|
5017
5022
|
if (!envelope) {
|
|
@@ -5205,6 +5210,37 @@ async function revoke(adapter, vault, callerKeyring, options) {
|
|
|
5205
5210
|
await rotateKeys(adapter, vault, callerKeyring, [...affectedCollections]);
|
|
5206
5211
|
}
|
|
5207
5212
|
}
|
|
5213
|
+
async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
|
|
5214
|
+
if (options.role === void 0 && options.displayName === void 0 && options.permissions === void 0) {
|
|
5215
|
+
throw new ValidationError(
|
|
5216
|
+
`updateUser: at least one of role / displayName / permissions must be provided (userId: "${options.userId}").`
|
|
5217
|
+
);
|
|
5218
|
+
}
|
|
5219
|
+
const env = await adapter.get(vault, "_keyring", options.userId);
|
|
5220
|
+
if (!env) {
|
|
5221
|
+
throw new NoAccessError(
|
|
5222
|
+
`updateUser: user "${options.userId}" has no keyring in vault "${vault}".`
|
|
5223
|
+
);
|
|
5224
|
+
}
|
|
5225
|
+
const target = JSON.parse(env._data);
|
|
5226
|
+
if (!canUpdateRole(callerKeyring.role, target.role)) {
|
|
5227
|
+
throw new PermissionDeniedError(
|
|
5228
|
+
`Role "${callerKeyring.role}" cannot update a keyring with role "${target.role}"`
|
|
5229
|
+
);
|
|
5230
|
+
}
|
|
5231
|
+
if (options.role !== void 0 && options.role !== target.role && !canUpdateRole(callerKeyring.role, options.role)) {
|
|
5232
|
+
throw new PermissionDeniedError(
|
|
5233
|
+
`Role "${callerKeyring.role}" cannot promote target to role "${options.role}"`
|
|
5234
|
+
);
|
|
5235
|
+
}
|
|
5236
|
+
const next = {
|
|
5237
|
+
...target,
|
|
5238
|
+
...options.role !== void 0 && { role: options.role },
|
|
5239
|
+
...options.displayName !== void 0 && { display_name: options.displayName },
|
|
5240
|
+
...options.permissions !== void 0 && { permissions: options.permissions }
|
|
5241
|
+
};
|
|
5242
|
+
await writeKeyringFile(adapter, vault, options.userId, next);
|
|
5243
|
+
}
|
|
5208
5244
|
async function rotateKeys(adapter, vault, callerKeyring, collections) {
|
|
5209
5245
|
const newDeks = /* @__PURE__ */ new Map();
|
|
5210
5246
|
for (const collName of collections) {
|
|
@@ -5505,6 +5541,38 @@ async function enrollAuthenticator(store, vault, keyring, options) {
|
|
|
5505
5541
|
await persistKeyring(store, vault, next);
|
|
5506
5542
|
return next;
|
|
5507
5543
|
}
|
|
5544
|
+
async function updateAuthenticator(store, vault, keyring, slotId, options) {
|
|
5545
|
+
if (options.meta === void 0) {
|
|
5546
|
+
throw new ValidationError(
|
|
5547
|
+
`updateAuthenticator: at least one of meta must be provided (slotId: "${slotId}").`
|
|
5548
|
+
);
|
|
5549
|
+
}
|
|
5550
|
+
const idx = keyring.authenticators.findIndex((a) => a.id === slotId);
|
|
5551
|
+
if (idx === -1) {
|
|
5552
|
+
throw new NoAccessError(
|
|
5553
|
+
`updateAuthenticator: slot "${slotId}" not found in vault "${vault}".`
|
|
5554
|
+
);
|
|
5555
|
+
}
|
|
5556
|
+
const existing = keyring.authenticators[idx];
|
|
5557
|
+
const mergedMeta = { ...existing.meta };
|
|
5558
|
+
for (const [k, v] of Object.entries(options.meta)) {
|
|
5559
|
+
if (v === void 0) continue;
|
|
5560
|
+
if (v === null) {
|
|
5561
|
+
delete mergedMeta[k];
|
|
5562
|
+
continue;
|
|
5563
|
+
}
|
|
5564
|
+
mergedMeta[k] = v;
|
|
5565
|
+
}
|
|
5566
|
+
const next = { ...existing, meta: mergedMeta };
|
|
5567
|
+
const nextSlots = [...keyring.authenticators];
|
|
5568
|
+
nextSlots[idx] = next;
|
|
5569
|
+
const nextKeyring = {
|
|
5570
|
+
...keyring,
|
|
5571
|
+
authenticators: nextSlots
|
|
5572
|
+
};
|
|
5573
|
+
await persistKeyring(store, vault, nextKeyring);
|
|
5574
|
+
return nextKeyring;
|
|
5575
|
+
}
|
|
5508
5576
|
async function removeAuthenticator(store, vault, keyring, slotId) {
|
|
5509
5577
|
const filtered = keyring.authenticators.filter((a) => a.id !== slotId);
|
|
5510
5578
|
if (filtered.length === keyring.authenticators.length) {
|
|
@@ -5959,6 +6027,17 @@ var UserApi = class {
|
|
|
5959
6027
|
* the envelope on first call. Optimistic-concurrency safe — a stale
|
|
5960
6028
|
* `_v` (parallel writer on another device) throws `ConflictError`.
|
|
5961
6029
|
*
|
|
6030
|
+
* Patch semantics (#57):
|
|
6031
|
+
* - `undefined` (or omitted key) — skip; existing value preserved
|
|
6032
|
+
* - `null` — delete the field from the merged result
|
|
6033
|
+
* - any other value — overwrite (deep-merge for plain objects,
|
|
6034
|
+
* replace for primitives / arrays)
|
|
6035
|
+
*
|
|
6036
|
+
* To clear a field, pass `null` rather than `undefined`. Callers
|
|
6037
|
+
* with shape `T = string | null` where `null` is a meaningful value
|
|
6038
|
+
* should use `setMe` for that specific field instead — `null` here
|
|
6039
|
+
* always means delete.
|
|
6040
|
+
*
|
|
5962
6041
|
* Gated by the `edit-own-profile` policy gate (default `minTier: 3`).
|
|
5963
6042
|
* Pass `presented` to satisfy tightened policies that require a
|
|
5964
6043
|
* factor proof (e.g. STRICT_POLICY's TOTP requirement).
|
|
@@ -6130,9 +6209,17 @@ function deepMerge(source, patch) {
|
|
|
6130
6209
|
}
|
|
6131
6210
|
const out = { ...source };
|
|
6132
6211
|
for (const [key, patchVal] of Object.entries(patch)) {
|
|
6212
|
+
if (patchVal === void 0) {
|
|
6213
|
+
continue;
|
|
6214
|
+
}
|
|
6215
|
+
if (patchVal === null) {
|
|
6216
|
+
delete out[key];
|
|
6217
|
+
continue;
|
|
6218
|
+
}
|
|
6133
6219
|
const sourceVal = source[key];
|
|
6134
|
-
if (isPlainObject(
|
|
6135
|
-
|
|
6220
|
+
if (isPlainObject(patchVal)) {
|
|
6221
|
+
const recurseSource = isPlainObject(sourceVal) ? sourceVal : {};
|
|
6222
|
+
out[key] = deepMerge(recurseSource, patchVal);
|
|
6136
6223
|
} else {
|
|
6137
6224
|
out[key] = patchVal;
|
|
6138
6225
|
}
|
|
@@ -13656,6 +13743,12 @@ var PERSONAL_POLICY = Object.freeze({
|
|
|
13656
13743
|
},
|
|
13657
13744
|
"enroll-authenticator": { minTier: 1 },
|
|
13658
13745
|
"remove-authenticator": { minTier: 1 },
|
|
13746
|
+
// update-authenticator: meta-only mutation (slot rename, label
|
|
13747
|
+
// changes). Symmetric with enroll/remove under PERSONAL — tier-1
|
|
13748
|
+
// unlock alone. The structural anti-slot-swap guard inside the
|
|
13749
|
+
// implementation enforces wrap-material/id/method immutability
|
|
13750
|
+
// regardless of this gate's settings.
|
|
13751
|
+
"update-authenticator": { minTier: 1 },
|
|
13659
13752
|
"rotate-unlock": { minTier: 2 },
|
|
13660
13753
|
"enroll-user": { minTier: 1 },
|
|
13661
13754
|
"revoke-user": { minTier: 1 },
|
|
@@ -13665,6 +13758,12 @@ var PERSONAL_POLICY = Object.freeze({
|
|
|
13665
13758
|
// virtue of being a co-owner). Tier-1 unlock is the floor; the
|
|
13666
13759
|
// STRICT preset adds a recovery/email-OTP requirement.
|
|
13667
13760
|
"peer-recover-user": { minTier: 1 },
|
|
13761
|
+
// update-user: post-grant identity mutation (role/displayName/
|
|
13762
|
+
// permissions). PERSONAL_POLICY treats this on par with enroll-user
|
|
13763
|
+
// / revoke-user — tier-1 unlock alone. The role-elevation guard
|
|
13764
|
+
// inside the implementation is the structural backstop that this
|
|
13765
|
+
// gate's settings cannot weaken.
|
|
13766
|
+
"update-user": { minTier: 1 },
|
|
13668
13767
|
"export-bundle": { minTier: 1 },
|
|
13669
13768
|
"export-plaintext": {
|
|
13670
13769
|
minTier: 1,
|
|
@@ -13709,6 +13808,15 @@ var STRICT_POLICY = Object.freeze({
|
|
|
13709
13808
|
minTier: 1,
|
|
13710
13809
|
factors: [{ anyOf: ["totp", "email-otp"] }]
|
|
13711
13810
|
},
|
|
13811
|
+
// STRICT update-authenticator: same factor floor as enroll/remove.
|
|
13812
|
+
// Even though meta changes don't touch wrap material, a malicious
|
|
13813
|
+
// rename could mislead the user about which device a slot
|
|
13814
|
+
// corresponds to ("MacBook Touch ID" → "iPhone Touch ID" on a
|
|
13815
|
+
// shared workstation). STRICT requires a fresh factor proof.
|
|
13816
|
+
"update-authenticator": {
|
|
13817
|
+
minTier: 1,
|
|
13818
|
+
factors: [{ anyOf: ["totp", "email-otp"] }]
|
|
13819
|
+
},
|
|
13712
13820
|
"rotate-unlock": { minTier: 1 },
|
|
13713
13821
|
"enroll-user": {
|
|
13714
13822
|
minTier: 1,
|
|
@@ -13731,6 +13839,18 @@ var STRICT_POLICY = Object.freeze({
|
|
|
13731
13839
|
minTier: 1,
|
|
13732
13840
|
factors: [{ anyOf: ["recovery", "totp", "email-otp", "webauthn-roaming"] }]
|
|
13733
13841
|
},
|
|
13842
|
+
// STRICT update-user: matches the enroll-user / revoke-user shape
|
|
13843
|
+
// (off-device factor required). Update-user is admin-shaped — it
|
|
13844
|
+
// mutates someone else's role/permissions; STRICT requires a fresh
|
|
13845
|
+
// off-device factor proof so the operator affirmatively re-asserts
|
|
13846
|
+
// identity at the moment of mutation. Platform-bound factors
|
|
13847
|
+
// (Touch ID / password / PIN) intentionally excluded: same logic as
|
|
13848
|
+
// peer-recover-user — the off-device requirement is the whole
|
|
13849
|
+
// point under STRICT.
|
|
13850
|
+
"update-user": {
|
|
13851
|
+
minTier: 1,
|
|
13852
|
+
factors: [{ anyOf: ["totp", "email-otp"] }]
|
|
13853
|
+
},
|
|
13734
13854
|
"export-bundle": {
|
|
13735
13855
|
minTier: 1,
|
|
13736
13856
|
factors: [{ anyOf: ["totp", "email-otp"] }],
|
|
@@ -14125,6 +14245,56 @@ var Noydb = class {
|
|
|
14125
14245
|
const keyring = await this.getKeyring(vault);
|
|
14126
14246
|
await revoke(this.options.store, vault, keyring, options);
|
|
14127
14247
|
}
|
|
14248
|
+
/**
|
|
14249
|
+
* Mutate post-grant identity fields on an existing keyring — `role`,
|
|
14250
|
+
* `displayName`, and/or `permissions`. Pure plaintext-header rewrite:
|
|
14251
|
+
* no DEK rewrap, no KEK required, no authenticator slots touched.
|
|
14252
|
+
* Tier-2 enrollments and recovery codes survive.
|
|
14253
|
+
*
|
|
14254
|
+
* Different from `db.revoke + db.grant`:
|
|
14255
|
+
*
|
|
14256
|
+
* - Same `userId`, same DEK wrappings, same `granted_by`, same
|
|
14257
|
+
* `_users/<keyringId>` envelope. Only the specified header
|
|
14258
|
+
* fields move. Last-write-wins via the standard keyring put.
|
|
14259
|
+
* - No cascade on role demotion (admins demoted to operator keep
|
|
14260
|
+
* the keyrings they previously granted; the cascade rules are
|
|
14261
|
+
* a `db.revoke` concern, not `db.updateUser`).
|
|
14262
|
+
* - Tier-2 slots NOT dropped — the wrapping is unaffected.
|
|
14263
|
+
*
|
|
14264
|
+
* Role-elevation guard: BOTH the old and new role must satisfy
|
|
14265
|
+
* `db.grant`'s hierarchy. Owner can do anything; admin manages
|
|
14266
|
+
* admin/operator/viewer/client laterally; admin cannot promote to
|
|
14267
|
+
* owner OR demote from owner. The guard runs regardless of the
|
|
14268
|
+
* `update-user` policy gate's settings — gates can only be more
|
|
14269
|
+
* permissive than the structural floor, never less.
|
|
14270
|
+
*
|
|
14271
|
+
* Gated by `update-user`. `STRICT_POLICY` requires a TOTP/email-OTP
|
|
14272
|
+
* factor proof so the operator affirmatively re-asserts identity at
|
|
14273
|
+
* the moment of mutation; `PERSONAL_POLICY` accepts a tier-1 unlock
|
|
14274
|
+
* alone.
|
|
14275
|
+
*
|
|
14276
|
+
* ```ts
|
|
14277
|
+
* await db.updateUser('acme', {
|
|
14278
|
+
* userId: 'bob',
|
|
14279
|
+
* role: 'operator', // promote
|
|
14280
|
+
* permissions: { invoices: 'rw' },
|
|
14281
|
+
* }, { factors: [{ kind: 'totp' }] })
|
|
14282
|
+
* ```
|
|
14283
|
+
*
|
|
14284
|
+
* @throws `NoAccessError` when no keyring exists for the target.
|
|
14285
|
+
* @throws `PermissionDeniedError` when the role hierarchy rejects.
|
|
14286
|
+
* @throws `ValidationError` when no field is provided.
|
|
14287
|
+
*
|
|
14288
|
+
* @see #54
|
|
14289
|
+
*/
|
|
14290
|
+
async updateUser(vault, options, factors) {
|
|
14291
|
+
await this.checkGate(vault, "update-user", factors);
|
|
14292
|
+
const keyring = await this.getKeyring(vault);
|
|
14293
|
+
await updateKeyringIdentity(this.options.store, vault, keyring, options);
|
|
14294
|
+
if (options.userId === this.options.user) {
|
|
14295
|
+
this.keyringCache.delete(vault);
|
|
14296
|
+
}
|
|
14297
|
+
}
|
|
14128
14298
|
/**
|
|
14129
14299
|
* Rotate the DEKs for the given collections in a vault.
|
|
14130
14300
|
*
|
|
@@ -14668,6 +14838,40 @@ var Noydb = class {
|
|
|
14668
14838
|
const keyring = await this.getKeyring(vault);
|
|
14669
14839
|
return keyring.authenticators;
|
|
14670
14840
|
}
|
|
14841
|
+
/**
|
|
14842
|
+
* Mutate the `meta` blob on an existing authenticator slot — slot
|
|
14843
|
+
* rename, label change, attachment of UI hints. The slot's `id`,
|
|
14844
|
+
* `method`, and wrap material (`wrapped_kek` / `wrapped_deks` + `iv`)
|
|
14845
|
+
* are immutable through this method. Anti-slot-swap is structural,
|
|
14846
|
+
* not gate-driven.
|
|
14847
|
+
*
|
|
14848
|
+
* `meta` patch semantics (#57-aligned):
|
|
14849
|
+
* - Top-level merge — absent keys preserved
|
|
14850
|
+
* - `null` value — delete that meta key
|
|
14851
|
+
* - Other values — replace verbatim
|
|
14852
|
+
*
|
|
14853
|
+
* Use case: per-slot nickname for "iPhone Touch ID" vs "MacBook
|
|
14854
|
+
* Touch ID" disambiguation in admin UIs. The slot id (auto-derived
|
|
14855
|
+
* from credentialId prefix) is not human-friendly; `meta.nickname`
|
|
14856
|
+
* is.
|
|
14857
|
+
*
|
|
14858
|
+
* Gated by `update-authenticator`. PERSONAL_POLICY: tier-1 unlock
|
|
14859
|
+
* alone (matches enroll/remove). STRICT_POLICY: tier-1 +
|
|
14860
|
+
* TOTP/email-OTP factor proof — a malicious rename on a shared
|
|
14861
|
+
* workstation could mislead the user about which device a slot
|
|
14862
|
+
* corresponds to, so STRICT requires fresh factor binding.
|
|
14863
|
+
*
|
|
14864
|
+
* @throws `NoAccessError` when no slot with the given id exists.
|
|
14865
|
+
* @throws `ValidationError` when no patch field is provided.
|
|
14866
|
+
*
|
|
14867
|
+
* @see #55
|
|
14868
|
+
*/
|
|
14869
|
+
async updateAuthenticator(vault, slotId, options, presented) {
|
|
14870
|
+
await this.checkGate(vault, "update-authenticator", presented);
|
|
14871
|
+
const keyring = await this.getKeyring(vault);
|
|
14872
|
+
const next = await updateAuthenticator(this.options.store, vault, keyring, slotId, options);
|
|
14873
|
+
this.keyringCache.set(vault, next);
|
|
14874
|
+
}
|
|
14671
14875
|
/**
|
|
14672
14876
|
* Native WebAuthn enrollment using the **real** internal keyring (#16).
|
|
14673
14877
|
*
|