@peers-app/peers-sdk 0.18.8 → 0.19.6
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/README.md +74 -1
- package/dist/data/files/file-read-stream.js +7 -0
- package/dist/data/files/file.types.d.ts +6 -0
- package/dist/data/files/file.types.js +18 -0
- package/dist/data/files/files.test.js +50 -7
- package/dist/data/package-version-resolver.d.ts +13 -5
- package/dist/data/package-version-resolver.js +64 -6
- package/dist/data/package-version-resolver.test.d.ts +0 -4
- package/dist/data/package-version-resolver.test.js +127 -5
- package/dist/data/package-versions.d.ts +3 -0
- package/dist/data/package-versions.js +5 -0
- package/dist/data/packages.d.ts +6 -29
- package/dist/data/packages.js +8 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/package-installer/index.d.ts +10 -0
- package/dist/package-installer/index.js +26 -0
- package/dist/package-installer/package-author-signing.d.ts +54 -0
- package/dist/package-installer/package-author-signing.js +82 -0
- package/dist/package-installer/package-author-signing.test.d.ts +1 -0
- package/dist/package-installer/package-author-signing.test.js +189 -0
- package/dist/package-installer/package-cloner.d.ts +16 -0
- package/dist/package-installer/package-cloner.js +115 -0
- package/dist/package-installer/package-cloner.test.d.ts +1 -0
- package/dist/package-installer/package-cloner.test.js +276 -0
- package/dist/package-installer/package-creator.d.ts +22 -0
- package/dist/package-installer/package-creator.js +154 -0
- package/dist/package-installer/package-creator.test.d.ts +1 -0
- package/dist/package-installer/package-creator.test.js +354 -0
- package/dist/package-installer/package-installer.d.ts +32 -0
- package/dist/package-installer/package-installer.js +247 -0
- package/dist/package-installer/package-installer.test.d.ts +1 -0
- package/dist/package-installer/package-installer.test.js +666 -0
- package/dist/package-installer/package-propagation.d.ts +29 -0
- package/dist/package-installer/package-propagation.js +364 -0
- package/dist/package-installer/package-propagation.test.d.ts +1 -0
- package/dist/package-installer/package-propagation.test.js +1145 -0
- package/dist/package-installer/package-publisher.d.ts +55 -0
- package/dist/package-installer/package-publisher.js +71 -0
- package/dist/package-installer/package-publisher.test.d.ts +1 -0
- package/dist/package-installer/package-publisher.test.js +142 -0
- package/dist/package-installer/package-remote-checker.d.ts +54 -0
- package/dist/package-installer/package-remote-checker.js +194 -0
- package/dist/package-installer/package-remote-checker.test.d.ts +1 -0
- package/dist/package-installer/package-remote-checker.test.js +269 -0
- package/dist/package-installer/package-seed-installer.d.ts +45 -0
- package/dist/package-installer/package-seed-installer.js +108 -0
- package/dist/package-installer/package-seed-installer.test.d.ts +1 -0
- package/dist/package-installer/package-seed-installer.test.js +123 -0
- package/dist/package-installer/package-tarball.d.ts +35 -0
- package/dist/package-installer/package-tarball.js +57 -0
- package/dist/package-installer/package-tarball.test.d.ts +1 -0
- package/dist/package-installer/package-tarball.test.js +75 -0
- package/dist/package-installer/types.d.ts +110 -0
- package/dist/package-installer/types.js +2 -0
- package/dist/rpc-types.d.ts +14 -0
- package/dist/rpc-types.js +6 -0
- package/dist/system-ids.d.ts +1 -0
- package/dist/system-ids.js +2 -1
- package/package.json +3 -2
package/dist/data/packages.d.ts
CHANGED
|
@@ -9,26 +9,11 @@ declare const schema: z.ZodObject<{
|
|
|
9
9
|
createdBy: z.ZodEffects<z.ZodString, string, string>;
|
|
10
10
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
11
11
|
remoteRepo: z.ZodOptional<z.ZodString>;
|
|
12
|
-
/** @deprecated Read appNavs from the active IPackageVersion record instead. */
|
|
13
|
-
appNavs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
displayName: z.ZodOptional<z.ZodString>;
|
|
16
|
-
iconClassName: z.ZodString;
|
|
17
|
-
navigationPath: z.ZodString;
|
|
18
|
-
}, "strip", z.ZodTypeAny, {
|
|
19
|
-
name: string;
|
|
20
|
-
iconClassName: string;
|
|
21
|
-
navigationPath: string;
|
|
22
|
-
displayName?: string | undefined;
|
|
23
|
-
}, {
|
|
24
|
-
name: string;
|
|
25
|
-
iconClassName: string;
|
|
26
|
-
navigationPath: string;
|
|
27
|
-
displayName?: string | undefined;
|
|
28
|
-
}>, "many">>;
|
|
29
12
|
activePackageVersionId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
30
13
|
versionFollowRange: z.ZodOptional<z.ZodEnum<["pinned", "patch", "minor", "latest"]>>;
|
|
31
14
|
followVersionTags: z.ZodOptional<z.ZodString>;
|
|
15
|
+
updateUrl: z.ZodOptional<z.ZodString>;
|
|
16
|
+
publishPublicKey: z.ZodString;
|
|
32
17
|
signature: z.ZodString;
|
|
33
18
|
}, "strip", z.ZodTypeAny, {
|
|
34
19
|
name: string;
|
|
@@ -36,34 +21,26 @@ declare const schema: z.ZodObject<{
|
|
|
36
21
|
signature: string;
|
|
37
22
|
packageId: string;
|
|
38
23
|
createdBy: string;
|
|
24
|
+
publishPublicKey: string;
|
|
39
25
|
disabled?: boolean | undefined;
|
|
40
|
-
appNavs?: {
|
|
41
|
-
name: string;
|
|
42
|
-
iconClassName: string;
|
|
43
|
-
navigationPath: string;
|
|
44
|
-
displayName?: string | undefined;
|
|
45
|
-
}[] | undefined;
|
|
46
26
|
remoteRepo?: string | undefined;
|
|
47
27
|
activePackageVersionId?: string | undefined;
|
|
48
28
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
49
29
|
followVersionTags?: string | undefined;
|
|
30
|
+
updateUrl?: string | undefined;
|
|
50
31
|
}, {
|
|
51
32
|
name: string;
|
|
52
33
|
description: string;
|
|
53
34
|
signature: string;
|
|
54
35
|
packageId: string;
|
|
55
36
|
createdBy: string;
|
|
37
|
+
publishPublicKey: string;
|
|
56
38
|
disabled?: boolean | undefined;
|
|
57
|
-
appNavs?: {
|
|
58
|
-
name: string;
|
|
59
|
-
iconClassName: string;
|
|
60
|
-
navigationPath: string;
|
|
61
|
-
displayName?: string | undefined;
|
|
62
|
-
}[] | undefined;
|
|
63
39
|
remoteRepo?: string | undefined;
|
|
64
40
|
activePackageVersionId?: string | undefined;
|
|
65
41
|
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
66
42
|
followVersionTags?: string | undefined;
|
|
43
|
+
updateUrl?: string | undefined;
|
|
67
44
|
}>;
|
|
68
45
|
export type IPackage = z.infer<typeof schema>;
|
|
69
46
|
export declare class PackagesTable extends Table<IPackage> {
|
package/dist/data/packages.js
CHANGED
|
@@ -38,7 +38,6 @@ exports.autoUpdatePeersCore = exports.reloadPackagesOnPageRefresh = exports.pack
|
|
|
38
38
|
exports.Packages = Packages;
|
|
39
39
|
const zod_1 = require("zod");
|
|
40
40
|
const context_1 = require("../context");
|
|
41
|
-
const app_nav_1 = require("../types/app-nav");
|
|
42
41
|
const zod_types_1 = require("../types/zod-types");
|
|
43
42
|
const decorators_1 = require("./orm/decorators");
|
|
44
43
|
const table_1 = require("./orm/table");
|
|
@@ -56,11 +55,6 @@ const schema = zod_1.z.object({
|
|
|
56
55
|
.optional()
|
|
57
56
|
.describe("Whether the package's components should be loaded and included in the app runtime"),
|
|
58
57
|
remoteRepo: zod_1.z.string().optional().describe("The remote repository where the package is stored"),
|
|
59
|
-
/** @deprecated Read appNavs from the active IPackageVersion record instead. */
|
|
60
|
-
appNavs: app_nav_1.appNavSchema
|
|
61
|
-
.array()
|
|
62
|
-
.optional()
|
|
63
|
-
.describe("DEPRECATED: Read appNavs from the active IPackageVersion record instead"),
|
|
64
58
|
activePackageVersionId: zod_types_1.zodPeerId
|
|
65
59
|
.optional()
|
|
66
60
|
.describe("FK to PackageVersions — the currently active version"),
|
|
@@ -72,6 +66,14 @@ const schema = zod_1.z.object({
|
|
|
72
66
|
.string()
|
|
73
67
|
.optional()
|
|
74
68
|
.describe('Tag policy: undefined="current" (follow active tag), "*"=any tag, or CSV like "stable,prod"'),
|
|
69
|
+
updateUrl: zod_1.z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Base URL for remote package seeding. Admin devices check <updateUrl>/latest-<tag>.json on startup."),
|
|
73
|
+
publishPublicKey: zod_1.z
|
|
74
|
+
.string()
|
|
75
|
+
.describe("Ed25519 public key (base64url) of the package publisher. " +
|
|
76
|
+
"Set at package creation; used as TOFU anchor for verifying packageAuthorSignature on versions."),
|
|
75
77
|
signature: zod_1.z.string().describe("The signed hash of this data excluding the signature itself"),
|
|
76
78
|
});
|
|
77
79
|
const metaData = {
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from "./keys";
|
|
|
19
19
|
export * from "./logging";
|
|
20
20
|
export * from "./mentions";
|
|
21
21
|
export * from "./observable";
|
|
22
|
+
export * from "./package-installer";
|
|
22
23
|
export * from "./package-loader";
|
|
23
24
|
export * from "./peers-ui/peers-ui";
|
|
24
25
|
export * from "./peers-ui/peers-ui.types";
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __exportStar(require("./keys"), exports);
|
|
|
40
40
|
__exportStar(require("./logging"), exports);
|
|
41
41
|
__exportStar(require("./mentions"), exports);
|
|
42
42
|
__exportStar(require("./observable"), exports);
|
|
43
|
+
__exportStar(require("./package-installer"), exports);
|
|
43
44
|
__exportStar(require("./package-loader"), exports);
|
|
44
45
|
__exportStar(require("./peers-ui/peers-ui"), exports);
|
|
45
46
|
__exportStar(require("./peers-ui/peers-ui.types"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./package-author-signing";
|
|
2
|
+
export * from "./package-cloner";
|
|
3
|
+
export * from "./package-creator";
|
|
4
|
+
export * from "./package-installer";
|
|
5
|
+
export * from "./package-propagation";
|
|
6
|
+
export * from "./package-publisher";
|
|
7
|
+
export * from "./package-remote-checker";
|
|
8
|
+
export * from "./package-seed-installer";
|
|
9
|
+
export * from "./package-tarball";
|
|
10
|
+
export * from "./types";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./package-author-signing"), exports);
|
|
18
|
+
__exportStar(require("./package-cloner"), exports);
|
|
19
|
+
__exportStar(require("./package-creator"), exports);
|
|
20
|
+
__exportStar(require("./package-installer"), exports);
|
|
21
|
+
__exportStar(require("./package-propagation"), exports);
|
|
22
|
+
__exportStar(require("./package-publisher"), exports);
|
|
23
|
+
__exportStar(require("./package-remote-checker"), exports);
|
|
24
|
+
__exportStar(require("./package-seed-installer"), exports);
|
|
25
|
+
__exportStar(require("./package-tarball"), exports);
|
|
26
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IPackageVersion } from "../data/package-versions";
|
|
2
|
+
import type { IAppNav } from "../types/app-nav";
|
|
3
|
+
/** Prefix for the persistent variable name that stores a package's signing secret key. */
|
|
4
|
+
export declare const PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
5
|
+
/**
|
|
6
|
+
* The subset of PackageVersion fields that the publisher signs.
|
|
7
|
+
* The `publicKey` field is included so verifiers know which key to check against.
|
|
8
|
+
*/
|
|
9
|
+
export interface IAuthorSignedPayload {
|
|
10
|
+
packageId: string;
|
|
11
|
+
packageVersionId: string;
|
|
12
|
+
version: string;
|
|
13
|
+
versionTag: string;
|
|
14
|
+
packageBundleFileHash: string;
|
|
15
|
+
routesBundleFileHash?: string;
|
|
16
|
+
uiBundleFileHash?: string;
|
|
17
|
+
publicKey: string;
|
|
18
|
+
appNavs?: IAppNav[];
|
|
19
|
+
createdBy?: string;
|
|
20
|
+
createdAt?: string;
|
|
21
|
+
}
|
|
22
|
+
/** PV fields required by {@link buildAuthorSignedPayload} and related signing/verification functions. */
|
|
23
|
+
export type AuthorSignableFields = Pick<IPackageVersion, "packageId" | "packageVersionId" | "version" | "versionTag" | "packageBundleFileHash" | "routesBundleFileHash" | "uiBundleFileHash" | "appNavs"> & Partial<Pick<IPackageVersion, "createdBy" | "createdAt">>;
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the signable payload from a PackageVersion record.
|
|
26
|
+
* Only includes optional fields when they have a value (undefined fields are omitted
|
|
27
|
+
* entirely so `stableStringify` produces a consistent output).
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildAuthorSignedPayload(pv: AuthorSignableFields, publicKey: string): IAuthorSignedPayload;
|
|
30
|
+
/**
|
|
31
|
+
* Signs a PackageVersion with the publisher's Ed25519 secret key.
|
|
32
|
+
* @returns A base64url-encoded detached Ed25519 signature over `stableStringify(payload)`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function signPackageAuthor(pv: AuthorSignableFields, secretKey: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Result of verifying a packageAuthorSignature.
|
|
37
|
+
* `publicKey` is the key embedded in the payload (extracted from the signature verification process).
|
|
38
|
+
*/
|
|
39
|
+
export interface IVerifyAuthorSignatureResult {
|
|
40
|
+
valid: boolean;
|
|
41
|
+
publicKey: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Verifies the `packageAuthorSignature` on a PackageVersion record.
|
|
45
|
+
*
|
|
46
|
+
* The signature format is a raw base64url detached Ed25519 signature. The public key
|
|
47
|
+
* is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
|
|
48
|
+
* to reconstruct the payload and verify.
|
|
49
|
+
*
|
|
50
|
+
* @param pv - The PackageVersion record with `packageAuthorSignature` set
|
|
51
|
+
* @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
|
|
52
|
+
* @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
|
|
53
|
+
*/
|
|
54
|
+
export declare function verifyPackageAuthorSignature(pv: AuthorSignableFields & Pick<IPackageVersion, "packageAuthorSignature">, expectedPublicKey: string): IVerifyAuthorSignatureResult;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PACKAGE_SIGNING_KEY_PREFIX = void 0;
|
|
4
|
+
exports.buildAuthorSignedPayload = buildAuthorSignedPayload;
|
|
5
|
+
exports.signPackageAuthor = signPackageAuthor;
|
|
6
|
+
exports.verifyPackageAuthorSignature = verifyPackageAuthorSignature;
|
|
7
|
+
const nacl = require("tweetnacl");
|
|
8
|
+
const tweetnacl_util_1 = require("tweetnacl-util");
|
|
9
|
+
const keys_1 = require("../keys");
|
|
10
|
+
/** Prefix for the persistent variable name that stores a package's signing secret key. */
|
|
11
|
+
exports.PACKAGE_SIGNING_KEY_PREFIX = "packageSigningKey_";
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the signable payload from a PackageVersion record.
|
|
14
|
+
* Only includes optional fields when they have a value (undefined fields are omitted
|
|
15
|
+
* entirely so `stableStringify` produces a consistent output).
|
|
16
|
+
*/
|
|
17
|
+
function buildAuthorSignedPayload(pv, publicKey) {
|
|
18
|
+
const payload = {
|
|
19
|
+
packageId: pv.packageId,
|
|
20
|
+
packageVersionId: pv.packageVersionId,
|
|
21
|
+
version: pv.version,
|
|
22
|
+
versionTag: pv.versionTag ?? "",
|
|
23
|
+
packageBundleFileHash: pv.packageBundleFileHash,
|
|
24
|
+
publicKey,
|
|
25
|
+
};
|
|
26
|
+
if (pv.routesBundleFileHash) {
|
|
27
|
+
payload.routesBundleFileHash = pv.routesBundleFileHash;
|
|
28
|
+
}
|
|
29
|
+
if (pv.uiBundleFileHash) {
|
|
30
|
+
payload.uiBundleFileHash = pv.uiBundleFileHash;
|
|
31
|
+
}
|
|
32
|
+
if (pv.appNavs) {
|
|
33
|
+
payload.appNavs = pv.appNavs;
|
|
34
|
+
}
|
|
35
|
+
if (pv.createdBy) {
|
|
36
|
+
payload.createdBy = pv.createdBy;
|
|
37
|
+
}
|
|
38
|
+
if (pv.createdAt) {
|
|
39
|
+
payload.createdAt = pv.createdAt;
|
|
40
|
+
}
|
|
41
|
+
return payload;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Signs a PackageVersion with the publisher's Ed25519 secret key.
|
|
45
|
+
* @returns A base64url-encoded detached Ed25519 signature over `stableStringify(payload)`.
|
|
46
|
+
*/
|
|
47
|
+
function signPackageAuthor(pv, secretKey) {
|
|
48
|
+
const keys = (0, keys_1.hydrateKeys)(secretKey);
|
|
49
|
+
const payload = buildAuthorSignedPayload(pv, keys.publicKey);
|
|
50
|
+
const message = (0, tweetnacl_util_1.decodeUTF8)((0, keys_1.stableStringify)(payload));
|
|
51
|
+
const sk = (0, keys_1.decodeBase64)(secretKey);
|
|
52
|
+
const signature = nacl.sign.detached(message, sk);
|
|
53
|
+
return (0, keys_1.encodeBase64)(signature);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verifies the `packageAuthorSignature` on a PackageVersion record.
|
|
57
|
+
*
|
|
58
|
+
* The signature format is a raw base64url detached Ed25519 signature. The public key
|
|
59
|
+
* is NOT in the signature itself — it must be provided via `expectedPublicKey` (TOFU check)
|
|
60
|
+
* to reconstruct the payload and verify.
|
|
61
|
+
*
|
|
62
|
+
* @param pv - The PackageVersion record with `packageAuthorSignature` set
|
|
63
|
+
* @param expectedPublicKey - The public key to verify against (from Package.publishPublicKey)
|
|
64
|
+
* @returns `{ valid, publicKey }` — valid is true if signature verifies and key matches
|
|
65
|
+
*/
|
|
66
|
+
function verifyPackageAuthorSignature(pv, expectedPublicKey) {
|
|
67
|
+
const result = { valid: false, publicKey: expectedPublicKey };
|
|
68
|
+
if (!pv.packageAuthorSignature) {
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const payload = buildAuthorSignedPayload(pv, expectedPublicKey);
|
|
73
|
+
const message = (0, tweetnacl_util_1.decodeUTF8)((0, keys_1.stableStringify)(payload));
|
|
74
|
+
const signature = (0, keys_1.decodeBase64)(pv.packageAuthorSignature);
|
|
75
|
+
const pk = (0, keys_1.decodeBase64)(expectedPublicKey);
|
|
76
|
+
result.valid = nacl.sign.detached.verify(message, signature, pk);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
result.valid = false;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const keys_1 = require("../keys");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const package_author_signing_1 = require("./package-author-signing");
|
|
6
|
+
function makePV(overrides) {
|
|
7
|
+
return {
|
|
8
|
+
packageId: (0, utils_1.newid)(),
|
|
9
|
+
packageVersionId: (0, utils_1.newid)(),
|
|
10
|
+
version: "1.2.3",
|
|
11
|
+
versionTag: "stable",
|
|
12
|
+
packageBundleFileHash: "abc123hash",
|
|
13
|
+
routesBundleFileHash: "routes456hash",
|
|
14
|
+
uiBundleFileHash: "ui789hash",
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe("package-author-signing", () => {
|
|
19
|
+
const keys = (0, keys_1.newKeys)();
|
|
20
|
+
describe("PACKAGE_SIGNING_KEY_PREFIX", () => {
|
|
21
|
+
it("has the expected value", () => {
|
|
22
|
+
expect(package_author_signing_1.PACKAGE_SIGNING_KEY_PREFIX).toBe("packageSigningKey_");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("buildAuthorSignedPayload", () => {
|
|
26
|
+
it("extracts the correct fields from a PV", () => {
|
|
27
|
+
const pv = makePV();
|
|
28
|
+
const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pv, keys.publicKey);
|
|
29
|
+
expect(payload).toEqual({
|
|
30
|
+
packageId: pv.packageId,
|
|
31
|
+
packageVersionId: pv.packageVersionId,
|
|
32
|
+
version: "1.2.3",
|
|
33
|
+
versionTag: "stable",
|
|
34
|
+
packageBundleFileHash: "abc123hash",
|
|
35
|
+
routesBundleFileHash: "routes456hash",
|
|
36
|
+
uiBundleFileHash: "ui789hash",
|
|
37
|
+
publicKey: keys.publicKey,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it("omits optional hash fields when undefined", () => {
|
|
41
|
+
const pv = makePV({ routesBundleFileHash: undefined, uiBundleFileHash: undefined });
|
|
42
|
+
const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pv, keys.publicKey);
|
|
43
|
+
expect(payload).not.toHaveProperty("routesBundleFileHash");
|
|
44
|
+
expect(payload).not.toHaveProperty("uiBundleFileHash");
|
|
45
|
+
expect(payload.packageBundleFileHash).toBe("abc123hash");
|
|
46
|
+
});
|
|
47
|
+
it("omits uiBundleFileHash but includes routesBundleFileHash when only ui is undefined", () => {
|
|
48
|
+
const pv = makePV({ uiBundleFileHash: undefined });
|
|
49
|
+
const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pv, keys.publicKey);
|
|
50
|
+
expect(payload.routesBundleFileHash).toBe("routes456hash");
|
|
51
|
+
expect(payload).not.toHaveProperty("uiBundleFileHash");
|
|
52
|
+
});
|
|
53
|
+
it("treats empty string versionTag the same as undefined", () => {
|
|
54
|
+
const pv = makePV({ versionTag: undefined });
|
|
55
|
+
const payload = (0, package_author_signing_1.buildAuthorSignedPayload)(pv, keys.publicKey);
|
|
56
|
+
expect(payload.versionTag).toBe("");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("signPackageAuthor + verifyPackageAuthorSignature round-trip", () => {
|
|
60
|
+
it("produces a valid signature that verifies", () => {
|
|
61
|
+
const pv = makePV();
|
|
62
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
63
|
+
expect(typeof signature).toBe("string");
|
|
64
|
+
expect(signature.length).toBeGreaterThan(0);
|
|
65
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, keys.publicKey);
|
|
66
|
+
expect(result.valid).toBe(true);
|
|
67
|
+
expect(result.publicKey).toBe(keys.publicKey);
|
|
68
|
+
});
|
|
69
|
+
it("works with only the required bundle hash (no routes or ui)", () => {
|
|
70
|
+
const pv = makePV({ routesBundleFileHash: undefined, uiBundleFileHash: undefined });
|
|
71
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
72
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, keys.publicKey);
|
|
73
|
+
expect(result.valid).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("works with undefined versionTag", () => {
|
|
76
|
+
const pv = makePV({ versionTag: undefined });
|
|
77
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
78
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, keys.publicKey);
|
|
79
|
+
expect(result.valid).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("verification fails on tampered fields", () => {
|
|
83
|
+
const pv = makePV();
|
|
84
|
+
let signature;
|
|
85
|
+
beforeAll(() => {
|
|
86
|
+
signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
87
|
+
});
|
|
88
|
+
it("fails when packageBundleFileHash is changed", () => {
|
|
89
|
+
const tampered = {
|
|
90
|
+
...pv,
|
|
91
|
+
packageBundleFileHash: "tampered",
|
|
92
|
+
packageAuthorSignature: signature,
|
|
93
|
+
};
|
|
94
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
95
|
+
expect(result.valid).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it("fails when version is changed", () => {
|
|
98
|
+
const tampered = { ...pv, version: "9.9.9", packageAuthorSignature: signature };
|
|
99
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
100
|
+
expect(result.valid).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
it("fails when versionTag is changed", () => {
|
|
103
|
+
const tampered = { ...pv, versionTag: "beta", packageAuthorSignature: signature };
|
|
104
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
105
|
+
expect(result.valid).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
it("fails when packageId is changed", () => {
|
|
108
|
+
const tampered = { ...pv, packageId: (0, utils_1.newid)(), packageAuthorSignature: signature };
|
|
109
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
110
|
+
expect(result.valid).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it("fails when packageVersionId is changed", () => {
|
|
113
|
+
const tampered = { ...pv, packageVersionId: (0, utils_1.newid)(), packageAuthorSignature: signature };
|
|
114
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
115
|
+
expect(result.valid).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
it("fails when routesBundleFileHash is changed", () => {
|
|
118
|
+
const tampered = {
|
|
119
|
+
...pv,
|
|
120
|
+
routesBundleFileHash: "tampered",
|
|
121
|
+
packageAuthorSignature: signature,
|
|
122
|
+
};
|
|
123
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
124
|
+
expect(result.valid).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
it("fails when uiBundleFileHash is changed", () => {
|
|
127
|
+
const tampered = { ...pv, uiBundleFileHash: "tampered", packageAuthorSignature: signature };
|
|
128
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)(tampered, keys.publicKey);
|
|
129
|
+
expect(result.valid).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("verification fails with wrong key", () => {
|
|
133
|
+
it("rejects when verified against a different key", () => {
|
|
134
|
+
const pv = makePV();
|
|
135
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
136
|
+
const otherKeys = (0, keys_1.newKeys)();
|
|
137
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, otherKeys.publicKey);
|
|
138
|
+
expect(result.valid).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe("TOFU enforcement via expectedPublicKey", () => {
|
|
142
|
+
it("succeeds when expectedPublicKey matches the signer", () => {
|
|
143
|
+
const pv = makePV();
|
|
144
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
145
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, keys.publicKey);
|
|
146
|
+
expect(result.valid).toBe(true);
|
|
147
|
+
expect(result.publicKey).toBe(keys.publicKey);
|
|
148
|
+
});
|
|
149
|
+
it("fails when expectedPublicKey does not match the signer", () => {
|
|
150
|
+
const pv = makePV();
|
|
151
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
152
|
+
const differentKey = (0, keys_1.newKeys)().publicKey;
|
|
153
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, differentKey);
|
|
154
|
+
expect(result.valid).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe("edge cases", () => {
|
|
158
|
+
it("returns invalid when packageAuthorSignature is undefined", () => {
|
|
159
|
+
const pv = makePV();
|
|
160
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: undefined }, keys.publicKey);
|
|
161
|
+
expect(result.valid).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
it("returns invalid when packageAuthorSignature is malformed", () => {
|
|
164
|
+
const pv = makePV();
|
|
165
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: "not-a-valid-signature!!!" }, keys.publicKey);
|
|
166
|
+
expect(result.valid).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
it("returns invalid when expectedPublicKey is malformed", () => {
|
|
169
|
+
const pv = makePV();
|
|
170
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
171
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, "not-a-valid-key");
|
|
172
|
+
expect(result.valid).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
it("signature is deterministic for same inputs", () => {
|
|
175
|
+
const pv = makePV();
|
|
176
|
+
const sig1 = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
177
|
+
const sig2 = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
178
|
+
expect(sig1).toBe(sig2);
|
|
179
|
+
});
|
|
180
|
+
it("derives the correct public key from the secret key", () => {
|
|
181
|
+
const pv = makePV();
|
|
182
|
+
const signature = (0, package_author_signing_1.signPackageAuthor)(pv, keys.secretKey);
|
|
183
|
+
const derived = (0, keys_1.hydrateKeys)(keys.secretKey);
|
|
184
|
+
expect(derived.publicKey).toBe(keys.publicKey);
|
|
185
|
+
const result = (0, package_author_signing_1.verifyPackageAuthorSignature)({ ...pv, packageAuthorSignature: signature }, derived.publicKey);
|
|
186
|
+
expect(result.valid).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DataContext } from "../context/data-context";
|
|
2
|
+
import type { IClonePackageOpts, IClonePackageResult, IPackageInstallerDeps } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Derive a filesystem-safe slug from a git remote URL.
|
|
5
|
+
* "https://github.com/peers-app/groceries.git" → "groceries"
|
|
6
|
+
*/
|
|
7
|
+
export declare function deriveRepoSlug(url: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Clone a remote git repository containing a Peers package, install dependencies,
|
|
10
|
+
* build it, and register the dev bundle in the database.
|
|
11
|
+
*
|
|
12
|
+
* Prerequisites (git, npm) are validated up front with clear error messages.
|
|
13
|
+
* If the target directory already exists and contains a valid package, delegates
|
|
14
|
+
* to {@link installPackageFromBundles} instead of re-cloning.
|
|
15
|
+
*/
|
|
16
|
+
export declare function clonePackage(dataContext: DataContext, deps: IPackageInstallerDeps, opts: IClonePackageOpts): Promise<IClonePackageResult>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveRepoSlug = deriveRepoSlug;
|
|
4
|
+
exports.clonePackage = clonePackage;
|
|
5
|
+
const packages_1 = require("../data/packages");
|
|
6
|
+
const persistent_vars_1 = require("../data/persistent-vars");
|
|
7
|
+
const system_ids_1 = require("../system-ids");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const package_installer_1 = require("./package-installer");
|
|
10
|
+
/**
|
|
11
|
+
* Derive a filesystem-safe slug from a git remote URL.
|
|
12
|
+
* "https://github.com/peers-app/groceries.git" → "groceries"
|
|
13
|
+
*/
|
|
14
|
+
function deriveRepoSlug(url) {
|
|
15
|
+
const lastSegment = url.split("/").pop() ?? "";
|
|
16
|
+
return lastSegment.replace(/\.git$/, "");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Determine whether a package from this remote should start disabled.
|
|
20
|
+
* Packages from the peers-app org are trusted and enabled by default.
|
|
21
|
+
*/
|
|
22
|
+
function autoDetectDisabled(remoteRepo) {
|
|
23
|
+
return !remoteRepo.toLowerCase().startsWith("https://github.com/peers-app/");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Clone a remote git repository containing a Peers package, install dependencies,
|
|
27
|
+
* build it, and register the dev bundle in the database.
|
|
28
|
+
*
|
|
29
|
+
* Prerequisites (git, npm) are validated up front with clear error messages.
|
|
30
|
+
* If the target directory already exists and contains a valid package, delegates
|
|
31
|
+
* to {@link installPackageFromBundles} instead of re-cloning.
|
|
32
|
+
*/
|
|
33
|
+
async function clonePackage(dataContext, deps, opts) {
|
|
34
|
+
// 1. Validate prerequisites
|
|
35
|
+
try {
|
|
36
|
+
await deps.shell.exec("git --version");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error("git is not installed. Please install git to add remote packages.");
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await deps.shell.exec("npm --version");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new Error("npm is not installed. Please install Node.js (which includes npm) to add remote packages.");
|
|
46
|
+
}
|
|
47
|
+
// 2. Resolve location
|
|
48
|
+
const location = opts.location ?? deps.resolvePath(deps.packagesRootDir, deriveRepoSlug(opts.remoteRepo));
|
|
49
|
+
// 3. Check if directory already exists
|
|
50
|
+
if (await deps.fs.exists(location)) {
|
|
51
|
+
const info = await (0, package_installer_1.getPackageInfo)(deps, location);
|
|
52
|
+
if (info.packageId && (0, utils_1.isid)(info.packageId)) {
|
|
53
|
+
const result = await (0, package_installer_1.installPackageFromBundles)(dataContext, deps, info.packageId, {
|
|
54
|
+
localPath: location,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
packageId: info.packageId,
|
|
58
|
+
localPath: location,
|
|
59
|
+
remoteRepo: opts.remoteRepo,
|
|
60
|
+
packageVersion: result.packageVersion,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Directory already exists at ${location} but does not contain a valid Peers package (missing peers.packageId)`);
|
|
64
|
+
}
|
|
65
|
+
// 4. Clone
|
|
66
|
+
await deps.shell.exec(`git clone ${opts.remoteRepo} ${location}`);
|
|
67
|
+
// 5. Install + build (unless skipBuild)
|
|
68
|
+
if (!opts.skipBuild) {
|
|
69
|
+
await deps.shell.exec("npm install", { cwd: location });
|
|
70
|
+
await deps.shell.exec("npm run build", { cwd: location });
|
|
71
|
+
}
|
|
72
|
+
// 6. Read package info and resolve packageId
|
|
73
|
+
const info = await (0, package_installer_1.getPackageInfo)(deps, location);
|
|
74
|
+
let packageId;
|
|
75
|
+
if (info.packageId && (0, utils_1.isid)(info.packageId)) {
|
|
76
|
+
packageId = info.packageId;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// No valid packageId in package.json — generate one and patch it in
|
|
80
|
+
packageId = (0, utils_1.newid)();
|
|
81
|
+
const packageJsonPath = deps.resolvePath(location, "package.json");
|
|
82
|
+
const packageObj = await deps.fs.readJson(packageJsonPath);
|
|
83
|
+
packageObj.peers = packageObj.peers || {};
|
|
84
|
+
packageObj.peers.packageId = packageId;
|
|
85
|
+
await deps.fs.writeFile(packageJsonPath, JSON.stringify(packageObj, null, 2));
|
|
86
|
+
}
|
|
87
|
+
// 7. Register package record
|
|
88
|
+
const disabled = opts.disabled ?? autoDetectDisabled(opts.remoteRepo);
|
|
89
|
+
await (0, packages_1.Packages)(dataContext).signAndSave({
|
|
90
|
+
packageId,
|
|
91
|
+
name: info.name,
|
|
92
|
+
description: info.description ?? "",
|
|
93
|
+
createdBy: system_ids_1.peersRootUserId,
|
|
94
|
+
disabled,
|
|
95
|
+
remoteRepo: opts.remoteRepo,
|
|
96
|
+
publishPublicKey: "",
|
|
97
|
+
signature: "",
|
|
98
|
+
}, { restoreIfDeleted: true, saveAsSnapshot: true });
|
|
99
|
+
// Set the packageLocalPath device var
|
|
100
|
+
const pvar = (0, persistent_vars_1.groupDeviceVar)(`packageLocalPath_${packageId}`, {
|
|
101
|
+
defaultValue: deps.packagesRootDir,
|
|
102
|
+
dataContext,
|
|
103
|
+
});
|
|
104
|
+
pvar(location);
|
|
105
|
+
// 8. Install dev bundle
|
|
106
|
+
const result = await (0, package_installer_1.installPackageFromBundles)(dataContext, deps, packageId, {
|
|
107
|
+
localPath: location,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
packageId,
|
|
111
|
+
localPath: location,
|
|
112
|
+
remoteRepo: opts.remoteRepo,
|
|
113
|
+
packageVersion: result.packageVersion,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|