@peers-app/peers-sdk 0.27.4 → 0.28.0
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/context/user-context.d.ts +7 -1
- package/dist/context/user-context.js +4 -9
- package/dist/contracts/contract-proxy.d.ts +5 -0
- package/dist/contracts/contract-proxy.js +2 -2
- package/dist/contracts/contract-resolvers.d.ts +2 -2
- package/dist/contracts/contract-resolvers.js +14 -15
- package/dist/contracts/contract-transport.d.ts +2 -1
- package/dist/contracts/contract-transport.js +24 -2
- package/dist/contracts/system/network-contracts.d.ts +1 -1
- package/dist/contracts/tool-catalog-service.js +1 -1
- package/dist/contracts/tool-catalog.js +6 -2
- package/dist/data/assistants.d.ts +3 -3
- package/dist/data/authority-write-checks.d.ts +42 -0
- package/dist/data/authority-write-checks.js +324 -0
- package/dist/data/change-tracking.d.ts +4 -4
- package/dist/data/create-group.js +1 -0
- package/dist/data/devices.d.ts +27 -4
- package/dist/data/devices.js +31 -0
- package/dist/data/group-authority.d.ts +111 -0
- package/dist/data/group-authority.js +281 -0
- package/dist/data/group-members.d.ts +8 -2
- package/dist/data/group-members.js +41 -18
- package/dist/data/group-permissions.d.ts +15 -31
- package/dist/data/group-permissions.js +25 -95
- package/dist/data/group-resign.d.ts +38 -0
- package/dist/data/group-resign.js +71 -0
- package/dist/data/groups.d.ts +11 -0
- package/dist/data/groups.js +31 -13
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/invites.d.ts +14 -0
- package/dist/data/orm/index.d.ts +1 -0
- package/dist/data/orm/index.js +1 -0
- package/dist/data/orm/sql.data-source.d.ts +2 -2
- package/dist/data/orm/sql.data-source.js +8 -9
- package/dist/data/orm/table-container.js +14 -8
- package/dist/data/orm/write-checks.d.ts +89 -0
- package/dist/data/orm/write-checks.js +115 -0
- package/dist/data/package-table-identity.d.ts +6 -0
- package/dist/data/package-table-identity.js +51 -0
- package/dist/data/package-version-resolver.d.ts +15 -0
- package/dist/data/package-version-resolver.js +58 -3
- package/dist/data/package-versions.d.ts +20 -14
- package/dist/data/package-versions.js +15 -9
- package/dist/data/packages.d.ts +6 -1
- package/dist/data/packages.js +15 -20
- package/dist/data/persistent-vars.js +2 -39
- package/dist/data/sync-recovery-queue.d.ts +4 -4
- package/dist/data/sync-recovery-queue.js +7 -1
- package/dist/data/tool-access-approvals.d.ts +3 -3
- package/dist/data/user-permissions.d.ts +33 -2
- package/dist/data/user-permissions.js +87 -10
- package/dist/data/users.d.ts +49 -12
- package/dist/data/users.js +21 -1
- package/dist/data/workflows.d.ts +1 -1
- package/dist/device/binary-peer-connection-v2.d.ts +8 -16
- package/dist/device/binary-peer-connection-v2.js +15 -31
- package/dist/device/connection.d.ts +35 -44
- package/dist/device/connection.js +188 -186
- package/dist/device/device.d.ts +1 -8
- package/dist/device/device.js +1 -30
- package/dist/device/frame-cipher.d.ts +1 -1
- package/dist/device/frame-cipher.js +1 -1
- package/dist/device/get-trust-level-fn.js +3 -6
- package/dist/device/socket.type.d.ts +6 -12
- package/dist/device-pairing/types.d.ts +26 -26
- package/dist/identity/user-row-change.d.ts +3 -1
- package/dist/identity/user-row-change.js +33 -18
- package/dist/invites/group-approval.js +23 -24
- package/dist/invites/invite-messages.d.ts +250 -28
- package/dist/invites/invite-token.d.ts +18 -0
- package/dist/invites/invite-token.js +2 -0
- package/dist/keys.d.ts +7 -0
- package/dist/keys.js +27 -0
- package/dist/package-installer/package-installer.d.ts +1 -1
- package/dist/package-installer/package-installer.js +8 -3
- package/dist/package-installer/package-propagation.d.ts +2 -0
- package/dist/package-installer/package-propagation.js +20 -38
- package/dist/package-installer/package-remote-checker.js +15 -3
- package/dist/package-loader/isolated-artifact.js +0 -2
- package/dist/package-loader/package-loader.js +6 -0
- package/dist/types/peer-device.d.ts +2 -0
- package/dist/types/workflow.d.ts +2 -2
- package/package.json +1 -1
- package/dist/data/package-permissions.d.ts +0 -14
- package/dist/data/package-permissions.js +0 -26
- package/dist/data/package-version-permissions.d.ts +0 -14
- package/dist/data/package-version-permissions.js +0 -30
- package/dist/device/binary-peer-connection.d.ts +0 -58
- package/dist/device/binary-peer-connection.js +0 -516
- package/dist/device/streamed-socket.d.ts +0 -23
- package/dist/device/streamed-socket.js +0 -255
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.packageApprovalTables = exports.WRITE_NOT_AUTHORIZED = void 0;
|
|
4
|
+
exports.registerTableWriteCheck = registerTableWriteCheck;
|
|
5
|
+
exports.getTableWriteCheck = getTableWriteCheck;
|
|
6
|
+
exports.checkLocalSave = checkLocalSave;
|
|
7
|
+
exports.signingStamp = signingStamp;
|
|
8
|
+
exports.deletedSignedAt = deletedSignedAt;
|
|
9
|
+
exports.writeNotAuthorized = writeNotAuthorized;
|
|
10
|
+
/**
|
|
11
|
+
* Prefix of every refusal from a {@link TableWriteCheck}. Sync recovery parks a change refused
|
|
12
|
+
* with it, and checks it again only from local history, when the group's authority changes.
|
|
13
|
+
*/
|
|
14
|
+
exports.WRITE_NOT_AUTHORIZED = "Write not authorized";
|
|
15
|
+
const writeChecks = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Register the {@link TableWriteCheck} for a table name. One per table; a second registration
|
|
18
|
+
* replaces the first.
|
|
19
|
+
*
|
|
20
|
+
* @param tableName - Table the check guards.
|
|
21
|
+
* @param check - The rule.
|
|
22
|
+
*/
|
|
23
|
+
function registerTableWriteCheck(tableName, check) {
|
|
24
|
+
writeChecks.set(tableName, check);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The {@link TableWriteCheck} registered for a table, if any.
|
|
28
|
+
*
|
|
29
|
+
* @param tableName - Table name.
|
|
30
|
+
*/
|
|
31
|
+
function getTableWriteCheck(tableName) {
|
|
32
|
+
return writeChecks.get(tableName);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run `check` on a local save to `table`, as the host runs it on every synced row, so a local
|
|
36
|
+
* write and a synced write are judged by the same code. Nothing is checked in the personal
|
|
37
|
+
* context: only the user's own devices write it.
|
|
38
|
+
*
|
|
39
|
+
* @param table - The table being saved to.
|
|
40
|
+
* @param row - The row about to be stored.
|
|
41
|
+
* @param check - The table's rule.
|
|
42
|
+
*/
|
|
43
|
+
async function checkLocalSave(table, row, check) {
|
|
44
|
+
if (!table.groupId)
|
|
45
|
+
return;
|
|
46
|
+
const { getUserContext } = await Promise.resolve().then(() => require("../../context/user-context-singleton"));
|
|
47
|
+
const recordId = row[table.primaryKeyName];
|
|
48
|
+
await check(await table.get(recordId), row, {
|
|
49
|
+
dataContext: (await getUserContext()).getDataContext(table.groupId),
|
|
50
|
+
recordId,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Tables whose approvals are bound to a context (`signedContextId`) and whose deletes are open,
|
|
55
|
+
* so a deleted row's signing time is retained ({@link deletedSignedAt}).
|
|
56
|
+
*/
|
|
57
|
+
exports.packageApprovalTables = new Set(["Packages", "PackageVersions"]);
|
|
58
|
+
/**
|
|
59
|
+
* What a table's `signAndSave` (and a re-sign) puts under the signature: `signedAt`, now or
|
|
60
|
+
* just after the row on record when that was signed by a clock running ahead, so the new
|
|
61
|
+
* signature counts as the later one (and, for a package table, after the deleted row's
|
|
62
|
+
* retained stamp); for a package table also the context the approval is for.
|
|
63
|
+
*
|
|
64
|
+
* @param table - The table being signed for.
|
|
65
|
+
* @param row - The row being signed.
|
|
66
|
+
* @param current - A row already read from storage; membership repair may supply another id's row.
|
|
67
|
+
*/
|
|
68
|
+
async function signingStamp(table, row, current) {
|
|
69
|
+
const { tableName, groupId, primaryKeyName } = table;
|
|
70
|
+
const recordId = row[primaryKeyName];
|
|
71
|
+
current ??= await table.get(recordId);
|
|
72
|
+
const packageTable = exports.packageApprovalTables.has(tableName);
|
|
73
|
+
let floor = current?.signedAt ?? 0;
|
|
74
|
+
if (packageTable && groupId && !current) {
|
|
75
|
+
const { getUserContext } = await Promise.resolve().then(() => require("../../context/user-context-singleton"));
|
|
76
|
+
const dataContext = (await getUserContext()).getDataContext(groupId);
|
|
77
|
+
floor = await deletedSignedAt(tableName, { dataContext, recordId });
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
signedAt: Math.max(Date.now(), floor + 1),
|
|
81
|
+
...(packageTable ? { signedContextId: groupId } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read a deleted authority row's last signing time, optionally retaining the stored row's
|
|
86
|
+
* stamp before deletion. Device-local personal storage survives deletion and log compaction.
|
|
87
|
+
* Only pass `existing` from storage, never an unverified incoming candidate.
|
|
88
|
+
*/
|
|
89
|
+
async function deletedSignedAt(tableName, context, existing) {
|
|
90
|
+
if (!context.dataContext.groupId)
|
|
91
|
+
return 0;
|
|
92
|
+
const { PersistentVars } = await Promise.resolve().then(() => require("../persistent-vars"));
|
|
93
|
+
const vars = PersistentVars(context.dataContext.userContext.userDataContext);
|
|
94
|
+
const name = `authorityOrder:${context.dataContext.groupId}:${tableName}:${context.recordId}`;
|
|
95
|
+
const remembered = await vars.findOne({ name, scope: "device" });
|
|
96
|
+
const floor = Math.max(remembered?.value?.value ?? 0, existing?.signedAt ?? 0);
|
|
97
|
+
if (floor > (remembered?.value?.value ?? 0)) {
|
|
98
|
+
await vars.save({
|
|
99
|
+
persistentVarId: remembered?.persistentVarId ?? "",
|
|
100
|
+
name,
|
|
101
|
+
scope: "device",
|
|
102
|
+
value: { value: floor },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return floor;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* An error refusing a write, carrying the {@link WRITE_NOT_AUTHORIZED} prefix.
|
|
109
|
+
*
|
|
110
|
+
* @param tableName - Table the write was for.
|
|
111
|
+
* @param reason - Why, for logs and the sync recovery queue.
|
|
112
|
+
*/
|
|
113
|
+
function writeNotAuthorized(tableName, reason) {
|
|
114
|
+
return new Error(`${exports.WRITE_NOT_AUTHORIZED}: ${tableName}: ${reason}`);
|
|
115
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ITableDefinitionRecord } from "./table-definitions-table";
|
|
1
2
|
/**
|
|
2
3
|
* Validate a guest-supplied isolated-table or field identifier before it can
|
|
3
4
|
* reach ORM-generated SQL.
|
|
@@ -44,3 +45,8 @@ export declare function packageScopedTableName(packageId: string, logicalName: s
|
|
|
44
45
|
* @throws When package or table identity is invalid.
|
|
45
46
|
*/
|
|
46
47
|
export declare function packageScopedTableId(packageId: string, logicalName: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Validate synced schema metadata before it can name a table, column, or index in SQL.
|
|
50
|
+
* A schema cannot impersonate a system table or supply SQL expressions as identifiers.
|
|
51
|
+
*/
|
|
52
|
+
export declare function validateTableDefinitionRecord(record: ITableDefinitionRecord): void;
|
|
@@ -4,6 +4,7 @@ exports.validateIsolatedSqlIdentifier = validateIsolatedSqlIdentifier;
|
|
|
4
4
|
exports.validatePackageTableName = validatePackageTableName;
|
|
5
5
|
exports.packageScopedTableName = packageScopedTableName;
|
|
6
6
|
exports.packageScopedTableId = packageScopedTableId;
|
|
7
|
+
exports.validateTableDefinitionRecord = validateTableDefinitionRecord;
|
|
7
8
|
const utils_1 = require("../utils");
|
|
8
9
|
const ISOLATED_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9
10
|
/**
|
|
@@ -69,3 +70,53 @@ function packageScopedTableName(packageId, logicalName) {
|
|
|
69
70
|
function packageScopedTableId(packageId, logicalName) {
|
|
70
71
|
return (0, utils_1.deterministicTableId)(packageScopedTableName(packageId, logicalName));
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate synced schema metadata before it can name a table, column, or index in SQL.
|
|
75
|
+
* A schema cannot impersonate a system table or supply SQL expressions as identifiers.
|
|
76
|
+
*/
|
|
77
|
+
function validateTableDefinitionRecord(record) {
|
|
78
|
+
const meta = record.metaData;
|
|
79
|
+
if (!meta ||
|
|
80
|
+
!(0, utils_1.isid)(record.tableId) ||
|
|
81
|
+
meta.tableId !== record.tableId ||
|
|
82
|
+
(meta.packageId !== undefined && !(0, utils_1.isid)(meta.packageId))) {
|
|
83
|
+
throw new Error("Invalid table definition identity");
|
|
84
|
+
}
|
|
85
|
+
validateIsolatedSqlIdentifier(meta.name, "Table name");
|
|
86
|
+
validateIsolatedSqlIdentifier(meta.primaryKeyName, "Primary key");
|
|
87
|
+
if (record.name !== (0, utils_1.getFullTableName)(meta))
|
|
88
|
+
throw new Error("Table definition name mismatch");
|
|
89
|
+
if (!Number.isSafeInteger(record.versionNumber) ||
|
|
90
|
+
record.versionNumber < 0 ||
|
|
91
|
+
record.versionNumber !== (meta.versionNumber ?? 0)) {
|
|
92
|
+
throw new Error("Invalid table definition version");
|
|
93
|
+
}
|
|
94
|
+
if (meta.localOnly !== undefined && typeof meta.localOnly !== "boolean") {
|
|
95
|
+
throw new Error("Invalid table localOnly policy");
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(meta.fields) || !meta.fields.length)
|
|
98
|
+
throw new Error("Missing table fields");
|
|
99
|
+
const names = new Set();
|
|
100
|
+
for (const field of meta.fields) {
|
|
101
|
+
validateIsolatedSqlIdentifier(field.name, "Column name");
|
|
102
|
+
if (names.has(field.name))
|
|
103
|
+
throw new Error("Duplicate table column");
|
|
104
|
+
names.add(field.name);
|
|
105
|
+
}
|
|
106
|
+
if (!names.has(meta.primaryKeyName))
|
|
107
|
+
throw new Error("Missing primary key column");
|
|
108
|
+
if (meta.indexes !== undefined && !Array.isArray(meta.indexes))
|
|
109
|
+
throw new Error("Invalid indexes");
|
|
110
|
+
for (const index of meta.indexes ?? []) {
|
|
111
|
+
if (!Array.isArray(index.fields) || !index.fields.length)
|
|
112
|
+
throw new Error("Missing index fields");
|
|
113
|
+
for (const field of index.fields) {
|
|
114
|
+
const name = typeof field === "string" ? field : field.name;
|
|
115
|
+
if (!names.has(name))
|
|
116
|
+
throw new Error("Index must name a declared column");
|
|
117
|
+
if (typeof field !== "string" && field.order !== "ASC" && field.order !== "DESC") {
|
|
118
|
+
throw new Error("Invalid index order");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -18,6 +18,8 @@ import { type PersistentVar } from "./persistent-vars";
|
|
|
18
18
|
export interface IDevicePackagePrefs {
|
|
19
19
|
/** Which PV this device currently runs */
|
|
20
20
|
activePackageVersionId?: string;
|
|
21
|
+
/** Tag when this device explicitly selected the version; a later retag cannot opt it into dev. */
|
|
22
|
+
activePackageVersionTag?: string;
|
|
21
23
|
/** Newer selected PV temporarily bypassed because this host could not run it. */
|
|
22
24
|
compatibilityFallbackForPackageVersionId?: string;
|
|
23
25
|
/** If true, never auto-update this package */
|
|
@@ -53,6 +55,19 @@ export declare function packagePrefsVar(packageId: string, dataContext?: DataCon
|
|
|
53
55
|
* omitted the var tracks the user's default group context.
|
|
54
56
|
*/
|
|
55
57
|
export declare function updatePackagePrefs(packageId: string, prefs: Partial<IDevicePackagePrefs>, dataContext?: DataContext): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Check a selected version before evaluating any of its runtime, route or UI bundles.
|
|
60
|
+
* Group releases and shared selections must still have authorized signatures even when
|
|
61
|
+
* already on disk. Dev builds require an explicit device selection made while tagged dev.
|
|
62
|
+
*
|
|
63
|
+
* @param localDev - A host explicitly loading a local development checkout.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assertPackageVersionRunnable(pkg: IPackage, pv: IPackageVersion, dataContext: DataContext, localDev?: boolean): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Verify fetched bundle bytes against the hash in the authorized version. Files rows may be
|
|
68
|
+
* changed by group members, so their own chunk hashes alone cannot authorize executable code.
|
|
69
|
+
*/
|
|
70
|
+
export declare function assertPackageBundleIntegrity(pv: IPackageVersion, kind: "package" | "routes" | "ui", contents: string | Uint8Array): void;
|
|
56
71
|
/**
|
|
57
72
|
* Effective device-local preferences for a package after merging the device's
|
|
58
73
|
* raw prefs with the group-level `IPackage` defaults. Every field is resolved
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.packagePrefsVar = packagePrefsVar;
|
|
12
12
|
exports.updatePackagePrefs = updatePackagePrefs;
|
|
13
|
+
exports.assertPackageVersionRunnable = assertPackageVersionRunnable;
|
|
14
|
+
exports.assertPackageBundleIntegrity = assertPackageBundleIntegrity;
|
|
13
15
|
exports.getEffectivePackagePrefs = getEffectivePackagePrefs;
|
|
14
16
|
exports.resolveDevicePackageVersion = resolveDevicePackageVersion;
|
|
15
17
|
exports.hasDeviceFollowOverride = hasDeviceFollowOverride;
|
|
16
18
|
exports.readStoredPackageLocalPath = readStoredPackageLocalPath;
|
|
17
19
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
18
20
|
const assistants_1 = require("./assistants");
|
|
21
|
+
const file_types_1 = require("./files/file.types");
|
|
19
22
|
const group_member_roles_1 = require("./group-member-roles");
|
|
20
23
|
const group_permissions_1 = require("./group-permissions");
|
|
21
24
|
const package_versions_1 = require("./package-versions");
|
|
@@ -62,6 +65,13 @@ async function updatePackagePrefs(packageId, prefs, dataContext) {
|
|
|
62
65
|
effectivePrefs = rest;
|
|
63
66
|
}
|
|
64
67
|
const next = { ...existing, ...effectivePrefs };
|
|
68
|
+
if ("activePackageVersionId" in effectivePrefs) {
|
|
69
|
+
const dc = dataContext ?? (await (0, user_context_singleton_1.getUserContext)()).defaultDataContext();
|
|
70
|
+
const selected = effectivePrefs.activePackageVersionId
|
|
71
|
+
? await (0, package_versions_1.PackageVersions)(dc).get(effectivePrefs.activePackageVersionId)
|
|
72
|
+
: undefined;
|
|
73
|
+
next.activePackageVersionTag = selected?.versionTag;
|
|
74
|
+
}
|
|
65
75
|
for (const key of Object.keys(next)) {
|
|
66
76
|
if (next[key] === undefined) {
|
|
67
77
|
delete next[key];
|
|
@@ -69,6 +79,47 @@ async function updatePackagePrefs(packageId, prefs, dataContext) {
|
|
|
69
79
|
}
|
|
70
80
|
pvar(next);
|
|
71
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Check a selected version before evaluating any of its runtime, route or UI bundles.
|
|
84
|
+
* Group releases and shared selections must still have authorized signatures even when
|
|
85
|
+
* already on disk. Dev builds require an explicit device selection made while tagged dev.
|
|
86
|
+
*
|
|
87
|
+
* @param localDev - A host explicitly loading a local development checkout.
|
|
88
|
+
*/
|
|
89
|
+
async function assertPackageVersionRunnable(pkg, pv, dataContext, localDev = false) {
|
|
90
|
+
if (!dataContext.groupId)
|
|
91
|
+
return;
|
|
92
|
+
if (pv.packageId !== pkg.packageId)
|
|
93
|
+
throw new Error("Package version belongs to another package");
|
|
94
|
+
const { isPackageRowAdminSigned } = await Promise.resolve().then(() => require("./authority-write-checks"));
|
|
95
|
+
const { getTableWriteCheck } = await Promise.resolve().then(() => require("./orm/write-checks"));
|
|
96
|
+
await getTableWriteCheck("PackageVersions")?.(undefined, pv, {
|
|
97
|
+
dataContext,
|
|
98
|
+
recordId: pv.packageVersionId,
|
|
99
|
+
});
|
|
100
|
+
if (!localDev) {
|
|
101
|
+
const prefs = packagePrefsVar(pkg.packageId, dataContext);
|
|
102
|
+
await prefs.loadingPromise;
|
|
103
|
+
const deviceSelected = prefs()?.activePackageVersionId === pv.packageVersionId;
|
|
104
|
+
if (!deviceSelected && !(await isPackageRowAdminSigned(pkg, dataContext))) {
|
|
105
|
+
throw new Error("A group package selection needs an Admin's signature before it can run");
|
|
106
|
+
}
|
|
107
|
+
if (pv.versionTag === "dev" &&
|
|
108
|
+
(!deviceSelected || prefs()?.activePackageVersionTag !== "dev")) {
|
|
109
|
+
throw new Error("Activate this dev version on this device before running it");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Verify fetched bundle bytes against the hash in the authorized version. Files rows may be
|
|
115
|
+
* changed by group members, so their own chunk hashes alone cannot authorize executable code.
|
|
116
|
+
*/
|
|
117
|
+
function assertPackageBundleIntegrity(pv, kind, contents) {
|
|
118
|
+
const expected = pv[`${kind}BundleFileHash`];
|
|
119
|
+
if (!expected || (0, file_types_1.computeFileHash)(contents) !== expected) {
|
|
120
|
+
throw new Error(`Package ${kind} bundle hash does not match the authorized version`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
72
123
|
/**
|
|
73
124
|
* Pure function: merge raw device prefs with the group-level `IPackage`
|
|
74
125
|
* defaults to produce fully-resolved effective preferences. Used by the
|
|
@@ -221,7 +272,7 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
221
272
|
const allVersions = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
|
|
222
273
|
let bestPv = activePv;
|
|
223
274
|
for (const candidate of allVersions) {
|
|
224
|
-
if (candidate.packageVersionId === effectiveActivePvId)
|
|
275
|
+
if (candidate.packageVersionId === effectiveActivePvId || candidate.versionTag === "dev")
|
|
225
276
|
continue;
|
|
226
277
|
const tagOk = (0, package_versions_1.doesTagMatch)(activePv?.versionTag, candidate.versionTag, followTags, undefined);
|
|
227
278
|
if (!tagOk)
|
|
@@ -297,7 +348,10 @@ async function resolveDevicePackageVersion(pkg, dataContext, opts) {
|
|
|
297
348
|
const role = await (0, group_permissions_1.getUserRole)(groupId, userCtx.userId);
|
|
298
349
|
if (role >= group_member_roles_1.GroupMemberRole.Admin) {
|
|
299
350
|
const freshPkg = await (0, packages_1.Packages)(dataContext).get(pkg.packageId);
|
|
300
|
-
|
|
351
|
+
const { isPackageRowAdminSigned } = await Promise.resolve().then(() => require("./authority-write-checks"));
|
|
352
|
+
// Loading can span a sync that deletes and re-adds this package. Advancing a release
|
|
353
|
+
// must not endorse a Writer's replacement publisher key, URL or follow policy.
|
|
354
|
+
if (freshPkg && (await isPackageRowAdminSigned(freshPkg, dataContext))) {
|
|
301
355
|
if (freshPkg.activePackageVersionId !== bestPv.packageVersionId) {
|
|
302
356
|
freshPkg.activePackageVersionId = bestPv.packageVersionId;
|
|
303
357
|
await (0, packages_1.Packages)(dataContext).signAndSave(freshPkg);
|
|
@@ -326,7 +380,8 @@ async function loadOlderCompatiblePackageVersion(pkg, dataContext, unavailableVe
|
|
|
326
380
|
const versions = await (0, package_versions_1.PackageVersions)(dataContext).list({ packageId: pkg.packageId });
|
|
327
381
|
const candidates = versions
|
|
328
382
|
.filter((candidate) => {
|
|
329
|
-
if (candidate.packageVersionId === unavailableVersion.packageVersionId
|
|
383
|
+
if (candidate.packageVersionId === unavailableVersion.packageVersionId ||
|
|
384
|
+
candidate.versionTag === "dev") {
|
|
330
385
|
return false;
|
|
331
386
|
}
|
|
332
387
|
if ((0, package_versions_1.isNewerVersion)(unavailableVersion.version, candidate.version)) {
|
|
@@ -48,63 +48,69 @@ declare const schema: z.ZodObject<{
|
|
|
48
48
|
by: string;
|
|
49
49
|
}>, "many">>;
|
|
50
50
|
signature: z.ZodString;
|
|
51
|
+
signedContextId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
52
|
+
signedAt: z.ZodOptional<z.ZodNumber>;
|
|
51
53
|
createdBy: z.ZodEffects<z.ZodString, string, string>;
|
|
52
54
|
createdAt: z.ZodString;
|
|
53
55
|
}, "strip", z.ZodTypeAny, {
|
|
54
56
|
version: string;
|
|
55
57
|
signature: string;
|
|
56
58
|
packageId: string;
|
|
57
|
-
createdAt: string;
|
|
58
|
-
createdBy: string;
|
|
59
59
|
packageVersionId: string;
|
|
60
60
|
packageVersionHash: string;
|
|
61
61
|
packageBundleFileId: string;
|
|
62
62
|
packageBundleFileHash: string;
|
|
63
|
+
createdBy: string;
|
|
64
|
+
createdAt: string;
|
|
63
65
|
history?: {
|
|
64
66
|
at: string;
|
|
65
67
|
action: string;
|
|
66
68
|
signature: string;
|
|
67
69
|
by: string;
|
|
68
70
|
}[] | undefined;
|
|
71
|
+
signedAt?: number | undefined;
|
|
72
|
+
signedContextId?: string | undefined;
|
|
73
|
+
versionTag?: string | undefined;
|
|
74
|
+
routesBundleFileId?: string | undefined;
|
|
75
|
+
routesBundleFileHash?: string | undefined;
|
|
76
|
+
uiBundleFileId?: string | undefined;
|
|
77
|
+
uiBundleFileHash?: string | undefined;
|
|
69
78
|
appNavs?: {
|
|
70
79
|
name: string;
|
|
71
80
|
iconClassName: string;
|
|
72
81
|
navigationPath: string;
|
|
73
82
|
displayName?: string | undefined;
|
|
74
83
|
}[] | undefined;
|
|
75
|
-
versionTag?: string | undefined;
|
|
76
|
-
routesBundleFileId?: string | undefined;
|
|
77
|
-
routesBundleFileHash?: string | undefined;
|
|
78
|
-
uiBundleFileId?: string | undefined;
|
|
79
|
-
uiBundleFileHash?: string | undefined;
|
|
80
84
|
packageAuthorSignature?: string | undefined;
|
|
81
85
|
}, {
|
|
82
86
|
version: string;
|
|
83
87
|
signature: string;
|
|
84
88
|
packageId: string;
|
|
85
|
-
createdAt: string;
|
|
86
|
-
createdBy: string;
|
|
87
89
|
packageVersionId: string;
|
|
88
90
|
packageVersionHash: string;
|
|
89
91
|
packageBundleFileId: string;
|
|
90
92
|
packageBundleFileHash: string;
|
|
93
|
+
createdBy: string;
|
|
94
|
+
createdAt: string;
|
|
91
95
|
history?: {
|
|
92
96
|
at: string;
|
|
93
97
|
action: string;
|
|
94
98
|
signature: string;
|
|
95
99
|
by: string;
|
|
96
100
|
}[] | undefined;
|
|
101
|
+
signedAt?: number | undefined;
|
|
102
|
+
signedContextId?: string | undefined;
|
|
103
|
+
versionTag?: string | undefined;
|
|
104
|
+
routesBundleFileId?: string | undefined;
|
|
105
|
+
routesBundleFileHash?: string | undefined;
|
|
106
|
+
uiBundleFileId?: string | undefined;
|
|
107
|
+
uiBundleFileHash?: string | undefined;
|
|
97
108
|
appNavs?: {
|
|
98
109
|
name: string;
|
|
99
110
|
iconClassName: string;
|
|
100
111
|
navigationPath: string;
|
|
101
112
|
displayName?: string | undefined;
|
|
102
113
|
}[] | undefined;
|
|
103
|
-
versionTag?: string | undefined;
|
|
104
|
-
routesBundleFileId?: string | undefined;
|
|
105
|
-
routesBundleFileHash?: string | undefined;
|
|
106
|
-
uiBundleFileId?: string | undefined;
|
|
107
|
-
uiBundleFileHash?: string | undefined;
|
|
108
114
|
packageAuthorSignature?: string | undefined;
|
|
109
115
|
}>;
|
|
110
116
|
/** A signed package build and its bundle hashes, tag, and lifecycle history. */
|
|
@@ -45,11 +45,12 @@ const context_1 = require("../context");
|
|
|
45
45
|
const keys_1 = require("../keys");
|
|
46
46
|
const app_nav_1 = require("../types/app-nav");
|
|
47
47
|
const zod_types_1 = require("../types/zod-types");
|
|
48
|
+
const authority_write_checks_1 = require("./authority-write-checks");
|
|
48
49
|
const decorators_1 = require("./orm/decorators");
|
|
49
50
|
const table_1 = require("./orm/table");
|
|
50
51
|
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
51
52
|
const types_1 = require("./orm/types");
|
|
52
|
-
const
|
|
53
|
+
const write_checks_1 = require("./orm/write-checks");
|
|
53
54
|
const schema = zod_1.z.object({
|
|
54
55
|
packageVersionId: zod_types_1.zodPeerId,
|
|
55
56
|
packageId: zod_types_1.zodPeerId.describe("The package this version belongs to"),
|
|
@@ -81,6 +82,13 @@ const schema = zod_1.z.object({
|
|
|
81
82
|
.optional()
|
|
82
83
|
.describe("Audit trail of lifecycle events for this version"),
|
|
83
84
|
signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
|
|
85
|
+
signedContextId: zod_types_1.zodPeerId
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("The context this version approval was signed for"),
|
|
88
|
+
signedAt: zod_1.z
|
|
89
|
+
.number()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("When the signer signed this row (ms since epoch), under the signature: a copy of an earlier row can't replace a later one"),
|
|
84
92
|
createdBy: zod_types_1.zodPeerId.describe("The user who created this version"),
|
|
85
93
|
createdAt: zod_1.z.string().describe("ISO timestamp of when this version was created"),
|
|
86
94
|
});
|
|
@@ -115,14 +123,9 @@ let PackageVersionsTable = (() => {
|
|
|
115
123
|
return this.save(packageVersion, opts);
|
|
116
124
|
}
|
|
117
125
|
async save(packageVersion, opts) {
|
|
118
|
-
if (PackageVersionsTable.isPassthrough) {
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
if (!this.groupId) {
|
|
122
|
-
// users can do whatever they want to package versions in their personal space
|
|
123
|
-
return super.save(packageVersion, opts);
|
|
126
|
+
if (!PackageVersionsTable.isPassthrough) {
|
|
127
|
+
await (0, write_checks_1.checkLocalSave)(this, packageVersion, authority_write_checks_1.checkPackageVersion);
|
|
124
128
|
}
|
|
125
|
-
await (0, package_version_permissions_1.verifyPackageVersionSignature)(packageVersion, this.groupId);
|
|
126
129
|
return super.save(packageVersion, opts);
|
|
127
130
|
}
|
|
128
131
|
async signAndSave(packageVersion, opts) {
|
|
@@ -130,7 +133,10 @@ let PackageVersionsTable = (() => {
|
|
|
130
133
|
if (!sign) {
|
|
131
134
|
throw new Error("Package version signing is not enabled. Call PackageVersionsTable.enablePackageVersionSigning(fn) to enable it.");
|
|
132
135
|
}
|
|
133
|
-
packageVersion = await sign.call(PackageVersionsTable,
|
|
136
|
+
packageVersion = await sign.call(PackageVersionsTable, {
|
|
137
|
+
...packageVersion,
|
|
138
|
+
...(await (0, write_checks_1.signingStamp)(this, packageVersion)),
|
|
139
|
+
});
|
|
134
140
|
return this.save(packageVersion, opts);
|
|
135
141
|
}
|
|
136
142
|
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
package/dist/data/packages.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ declare const schema: z.ZodObject<{
|
|
|
15
15
|
updateUrl: z.ZodOptional<z.ZodString>;
|
|
16
16
|
publishPublicKey: z.ZodString;
|
|
17
17
|
signature: z.ZodString;
|
|
18
|
+
signedContextId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
19
|
+
signedAt: z.ZodOptional<z.ZodNumber>;
|
|
18
20
|
}, "strip", z.ZodTypeAny, {
|
|
19
21
|
name: string;
|
|
20
22
|
description: string;
|
|
@@ -23,6 +25,8 @@ declare const schema: z.ZodObject<{
|
|
|
23
25
|
createdBy: string;
|
|
24
26
|
publishPublicKey: string;
|
|
25
27
|
disabled?: boolean | undefined;
|
|
28
|
+
signedAt?: number | undefined;
|
|
29
|
+
signedContextId?: string | undefined;
|
|
26
30
|
remoteRepo?: string | undefined;
|
|
27
31
|
activePackageVersionId?: string | undefined;
|
|
28
32
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
@@ -36,6 +40,8 @@ declare const schema: z.ZodObject<{
|
|
|
36
40
|
createdBy: string;
|
|
37
41
|
publishPublicKey: string;
|
|
38
42
|
disabled?: boolean | undefined;
|
|
43
|
+
signedAt?: number | undefined;
|
|
44
|
+
signedContextId?: string | undefined;
|
|
39
45
|
remoteRepo?: string | undefined;
|
|
40
46
|
activePackageVersionId?: string | undefined;
|
|
41
47
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
@@ -53,7 +59,6 @@ export declare class PackagesTable extends Table<IPackage> {
|
|
|
53
59
|
signAndSave(packageObj: IPackage, opts?: ISaveOptions): Promise<IPackage>;
|
|
54
60
|
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
55
61
|
static enablePackageSigning(fn: (packageObj: IPackage) => IPackage): void;
|
|
56
|
-
delete(packageId: string): Promise<void>;
|
|
57
62
|
}
|
|
58
63
|
/** Returns the Packages table for the given context or the current default context. */
|
|
59
64
|
export declare function Packages(dataContext?: DataContext): PackagesTable;
|
package/dist/data/packages.js
CHANGED
|
@@ -39,11 +39,12 @@ exports.Packages = Packages;
|
|
|
39
39
|
const zod_1 = require("zod");
|
|
40
40
|
const context_1 = require("../context");
|
|
41
41
|
const zod_types_1 = require("../types/zod-types");
|
|
42
|
+
const authority_write_checks_1 = require("./authority-write-checks");
|
|
42
43
|
const decorators_1 = require("./orm/decorators");
|
|
43
44
|
const table_1 = require("./orm/table");
|
|
44
45
|
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
45
46
|
const types_1 = require("./orm/types");
|
|
46
|
-
const
|
|
47
|
+
const write_checks_1 = require("./orm/write-checks");
|
|
47
48
|
const persistent_vars_1 = require("./persistent-vars");
|
|
48
49
|
const schema = zod_1.z.object({
|
|
49
50
|
packageId: zod_types_1.zodPeerId,
|
|
@@ -75,6 +76,13 @@ const schema = zod_1.z.object({
|
|
|
75
76
|
.describe("Ed25519 public key (base64url) of the package publisher. " +
|
|
76
77
|
"Set at package creation; used as TOFU anchor for verifying packageAuthorSignature on versions."),
|
|
77
78
|
signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
|
|
79
|
+
signedContextId: zod_types_1.zodPeerId
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("The context this package approval was signed for"),
|
|
82
|
+
signedAt: zod_1.z
|
|
83
|
+
.number()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("When the signer signed this row (ms since epoch), under the signature: a copy of an earlier row can't replace a later one"),
|
|
78
86
|
});
|
|
79
87
|
const metaData = {
|
|
80
88
|
name: "Packages",
|
|
@@ -106,14 +114,8 @@ let PackagesTable = (() => {
|
|
|
106
114
|
return this.save(packageObj, opts);
|
|
107
115
|
}
|
|
108
116
|
async save(packageObj, opts) {
|
|
109
|
-
if (PackagesTable.isPassthrough)
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
if (!this.groupId) {
|
|
113
|
-
// users can do whatever they want to packages in their personal space
|
|
114
|
-
return super.save(packageObj, opts);
|
|
115
|
-
}
|
|
116
|
-
await (0, package_permissions_1.verifyPackageSignature)(packageObj, this.groupId);
|
|
117
|
+
if (!PackagesTable.isPassthrough)
|
|
118
|
+
await (0, write_checks_1.checkLocalSave)(this, packageObj, authority_write_checks_1.checkPackageWrite);
|
|
117
119
|
return super.save(packageObj, opts);
|
|
118
120
|
}
|
|
119
121
|
async signAndSave(packageObj, opts) {
|
|
@@ -121,23 +123,16 @@ let PackagesTable = (() => {
|
|
|
121
123
|
if (!sign) {
|
|
122
124
|
throw new Error("Package signing is not enabled. Call PackagesTable.enablePackageSigning(fn) to enable it.");
|
|
123
125
|
}
|
|
124
|
-
packageObj = await sign.call(PackagesTable,
|
|
126
|
+
packageObj = await sign.call(PackagesTable, {
|
|
127
|
+
...packageObj,
|
|
128
|
+
...(await (0, write_checks_1.signingStamp)(this, packageObj)),
|
|
129
|
+
});
|
|
125
130
|
return this.save(packageObj, opts);
|
|
126
131
|
}
|
|
127
132
|
/** Registers the signer for all SDK copies in this JavaScript realm. Replaces any prior signer. */
|
|
128
133
|
static enablePackageSigning(fn) {
|
|
129
134
|
signingGlobals[signingKey] = fn;
|
|
130
135
|
}
|
|
131
|
-
async delete(packageId) {
|
|
132
|
-
// anyone (with write permissions) can delete packages since that isn't a security risk (unless something else is relying on a package to be present?)
|
|
133
|
-
return super.delete(packageId);
|
|
134
|
-
// const userContext = await getUserContext();
|
|
135
|
-
// const userContextPackages = Packages(userContext.userDataContext);
|
|
136
|
-
// if (userContextPackages === this) {
|
|
137
|
-
// return super.delete(packageId);
|
|
138
|
-
// }
|
|
139
|
-
// throw new Error('Only deleting packages from the user context Packages table is allowed');
|
|
140
|
-
}
|
|
141
136
|
constructor() {
|
|
142
137
|
super(...arguments);
|
|
143
138
|
__runInitializers(this, _instanceExtraInitializers);
|
|
@@ -373,54 +373,17 @@ function persistentVarFactory(name, opts) {
|
|
|
373
373
|
}
|
|
374
374
|
return userContext.userDataContext;
|
|
375
375
|
}
|
|
376
|
-
/**
|
|
377
|
-
* Before storage was unified in the personal DB, a `groupDevice` /
|
|
378
|
-
* `groupUser` var created with an explicit group `dataContext` was
|
|
379
|
-
* written to that group's DB. Return that legacy location so an
|
|
380
|
-
* existing value can be migrated forward, or `undefined` when there is
|
|
381
|
-
* no distinct legacy location for this variable.
|
|
382
|
-
*/
|
|
383
|
-
function getLegacyStorageDataContext() {
|
|
384
|
-
if (scope !== "groupDevice" && scope !== "groupUser") {
|
|
385
|
-
return undefined;
|
|
386
|
-
}
|
|
387
|
-
const pinned = opts?.dataContext;
|
|
388
|
-
if (!pinned || pinned.dataContextId === userContext.userDataContext.dataContextId) {
|
|
389
|
-
return undefined;
|
|
390
|
-
}
|
|
391
|
-
return pinned;
|
|
392
|
-
}
|
|
393
376
|
function getVarNameInDb() {
|
|
394
377
|
const dc = opts?.dataContext || userContext.defaultDataContext();
|
|
395
378
|
return (0, persistent_var_identity_1.persistentVarStorageName)(name, scope, dc?.dataContextId);
|
|
396
379
|
}
|
|
397
|
-
/**
|
|
398
|
-
* Copy a record left in a group DB by the pre-unification storage rule
|
|
399
|
-
* into the personal DB. Secrets are skipped because their ciphertext is
|
|
400
|
-
* bound to the group key and cannot be moved verbatim. The legacy row is
|
|
401
|
-
* left in place: older builds on other devices may still read it, and
|
|
402
|
-
* the personal copy is authoritative from here on.
|
|
403
|
-
*/
|
|
404
|
-
async function migrateLegacyRec(table, name) {
|
|
405
|
-
const legacyContext = getLegacyStorageDataContext();
|
|
406
|
-
if (!legacyContext) {
|
|
407
|
-
return undefined;
|
|
408
|
-
}
|
|
409
|
-
const legacyRec = await PersistentVars(legacyContext).findOne({ name });
|
|
410
|
-
if (!legacyRec || legacyRec.scope !== scope || legacyRec.isSecret) {
|
|
411
|
-
return undefined;
|
|
412
|
-
}
|
|
413
|
-
const { persistentVarId: _legacyId, ...rest } = legacyRec;
|
|
414
|
-
return table.save({ ...rest, persistentVarId: "" }, { weakInsert: true });
|
|
415
|
-
}
|
|
416
380
|
async function loadRecFromDb() {
|
|
417
381
|
const dc = getStorageDataContext();
|
|
418
382
|
const table = PersistentVars(dc);
|
|
419
383
|
const name = getVarNameInDb();
|
|
420
384
|
let dbRec = await table.findOne({ name });
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
385
|
+
// Group rows are member-writable. They cannot bootstrap personal/device settings,
|
|
386
|
+
// including explicit permission to run a package's dev version on this device.
|
|
424
387
|
// Preserve an existing secret unless the caller explicitly changes that setting.
|
|
425
388
|
if (dbRec?.isSecret !== undefined && opts.isSecret === undefined) {
|
|
426
389
|
isSecret = dbRec.isSecret;
|