@protobase/server 0.57.1 → 0.57.3

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.
@@ -1008,7 +1008,7 @@ export declare const buildAppAuth: (settings: AuthSettings, input: AppAuthInput)
1008
1008
  $Infer: {
1009
1009
  body: {
1010
1010
  permissions: {
1011
- readonly user?: ("create" | "update" | "delete" | "list" | "set-role" | "ban" | "impersonate" | "set-password" | "set-email" | "get" | "impersonate-admins")[] | undefined;
1011
+ readonly user?: ("get" | "create" | "update" | "delete" | "list" | "set-role" | "ban" | "impersonate" | "set-password" | "set-email" | "impersonate-admins")[] | undefined;
1012
1012
  readonly session?: ("delete" | "list" | "revoke")[] | undefined;
1013
1013
  };
1014
1014
  } & {
@@ -1133,7 +1133,7 @@ export declare const createAuth: (options: CreateAuthOptions, plugins?: AuthPlug
1133
1133
  $Infer: {
1134
1134
  body: {
1135
1135
  permissions: {
1136
- readonly user?: ("create" | "update" | "delete" | "list" | "set-role" | "ban" | "impersonate" | "set-password" | "set-email" | "get" | "impersonate-admins")[] | undefined;
1136
+ readonly user?: ("get" | "create" | "update" | "delete" | "list" | "set-role" | "ban" | "impersonate" | "set-password" | "set-email" | "impersonate-admins")[] | undefined;
1137
1137
  readonly session?: ("delete" | "list" | "revoke")[] | undefined;
1138
1138
  };
1139
1139
  } & {
@@ -0,0 +1,24 @@
1
+ import { releaseStatementContext } from "@protobase/schema/internal";
2
+ import { sign } from "node:crypto";
3
+ //#region src/mobile-release/release-statement.ts
4
+ /**
5
+ * The statement file of a release bound to `binding`, signed with `signingKey` (see `releaseStatementFile`). The
6
+ * device verifies the signature over the payload's decoded bytes as they are, then parses those same bytes.
7
+ */
8
+ const releaseStatement = (binding, signingKey) => {
9
+ const { manifest, order, appId, fingerprint, shellVersion } = binding;
10
+ const payload = Buffer.from(JSON.stringify({
11
+ manifest,
12
+ order,
13
+ appId,
14
+ fingerprint,
15
+ shellVersion
16
+ }));
17
+ const signature = sign("sha256", Buffer.concat([Buffer.from(releaseStatementContext), payload]), signingKey);
18
+ return new TextEncoder().encode(JSON.stringify({
19
+ payload: payload.toString("hex"),
20
+ signature: signature.toString("hex")
21
+ }));
22
+ };
23
+ //#endregion
24
+ export { releaseStatement };
@@ -1,3 +1,4 @@
1
+ import { ReleaseBinding } from "@protobase/schema/internal";
1
2
  import { KeyObject } from "node:crypto";
2
3
  //#region src/mobile-release/release.d.ts
3
4
  /** A file of a release: its path inside the app's files, such as `assets/index-abc123.js`, and its bytes. */
@@ -12,13 +13,18 @@ export type ServedReleaseFile = {
12
13
  signature?: string;
13
14
  };
14
15
  /**
15
- * A release of the app's files: its bundle id, a hash of its manifest and of the signing key's public key, and every
16
- * file by `href`, the manifest included.
16
+ * A release of the app's files: its bundle id, a hash of what binds it and of the signing key's public key, and every
17
+ * file by `href`, the manifest and, signed, the statement included.
17
18
  */
18
19
  export type MobileRelease = {
19
20
  bundleId: string;
20
21
  files: Map<string, ServedReleaseFile>;
21
22
  };
23
+ /** How a release is signed: the key, and what its statement binds it to beside the manifest. */
24
+ export type ReleaseSigning = {
25
+ key: KeyObject;
26
+ binding: Omit<ReleaseBinding, 'manifest'>;
27
+ };
22
28
  /** The updater's manifest of `files`: each one's `href`, sha256 in hex and size, in the order of their hrefs. */
23
29
  export declare const releaseManifest: (files: ReleaseFile[]) => {
24
30
  href: string;
@@ -26,8 +32,9 @@ export declare const releaseManifest: (files: ReleaseFile[]) => {
26
32
  sizeInBytes: number;
27
33
  }[];
28
34
  /**
29
- * The release of `files`, each hashed and, with `signingKey`, signed once here. Its manifest is served as a file of
30
- * its own, so a file of the app may not have the manifest's name.
35
+ * The release of `files`, each hashed and, with `signing`, signed once here. Its manifest is served as a file of its
36
+ * own and, signed, so is its statement, so a file of the app may not have their names. Signed, the id hashes the
37
+ * statement, which holds the manifest's hash, so the same files released in another order get another id.
31
38
  */
32
- export declare const composeRelease: (files: ReleaseFile[], signingKey?: KeyObject) => MobileRelease;
39
+ export declare const composeRelease: (files: ReleaseFile[], signing?: ReleaseSigning) => MobileRelease;
33
40
  //#endregion
@@ -1,4 +1,5 @@
1
- import { releaseManifestFile } from "@protobase/schema/internal";
1
+ import { releaseStatement } from "./release-statement.js";
2
+ import { releaseManifestFile, releaseStatementContext, releaseStatementFile } from "@protobase/schema/internal";
2
3
  import { createHash, createPublicKey, sign } from "node:crypto";
3
4
  //#region src/mobile-release/release.ts
4
5
  const sha256 = (content) => createHash("sha256").update(content).digest("hex");
@@ -13,21 +14,34 @@ const signer = (signingKey) => signingKey ? createPublicKey(signingKey).export({
13
14
  type: "spki",
14
15
  format: "der"
15
16
  }) : /* @__PURE__ */ new Uint8Array();
17
+ const context = new TextEncoder().encode(releaseStatementContext);
18
+ const startsWithContext = (content) => content.byteLength >= context.byteLength && context.every((byte, i) => content[i] === byte);
16
19
  /**
17
- * The release of `files`, each hashed and, with `signingKey`, signed once here. Its manifest is served as a file of
18
- * its own, so a file of the app may not have the manifest's name.
20
+ * The release of `files`, each hashed and, with `signing`, signed once here. Its manifest is served as a file of its
21
+ * own and, signed, so is its statement, so a file of the app may not have their names. Signed, the id hashes the
22
+ * statement, which holds the manifest's hash, so the same files released in another order get another id.
19
23
  */
20
- const composeRelease = (files, signingKey) => {
21
- if (files.some((file) => file.href === releaseManifestFile)) throw new Error(`The app's files have a ${releaseManifestFile}, the name of the updater's manifest: rename it`);
24
+ const composeRelease = (files, signing) => {
25
+ for (const name of [releaseManifestFile, releaseStatementFile]) if (files.some((file) => file.href === name)) throw new Error(`The app's files have a ${name}, the name of a file of the updater's: rename it`);
26
+ const signed = files.find((file) => startsWithContext(file.content));
27
+ if (signed) throw new Error(`The app's file ${signed.href} starts as a release statement's signed bytes do: change its first line`);
22
28
  const manifest = new TextEncoder().encode(JSON.stringify(releaseManifest(files)));
29
+ const statement = signing && releaseStatement({
30
+ manifest: sha256(manifest),
31
+ ...signing.binding
32
+ }, signing.key);
23
33
  const served = (content) => ({
24
34
  content,
25
35
  checksum: sha256(content),
26
- ...signingKey && { signature: signature(content, signingKey) }
36
+ ...signing && { signature: signature(content, signing.key) }
27
37
  });
28
38
  return {
29
- bundleId: createHash("sha256").update(manifest).update(signer(signingKey)).digest("hex").slice(0, 16),
30
- files: new Map([...files.map(({ href, content }) => [href, served(content)]), [releaseManifestFile, served(manifest)]])
39
+ bundleId: createHash("sha256").update(statement ?? manifest).update(signer(signing?.key)).digest("hex").slice(0, 16),
40
+ files: new Map([
41
+ ...files.map(({ href, content }) => [href, served(content)]),
42
+ [releaseManifestFile, served(manifest)],
43
+ ...statement ? [[releaseStatementFile, served(statement)]] : []
44
+ ])
31
45
  };
32
46
  };
33
47
  //#endregion
@@ -1,6 +1,6 @@
1
- import { MobileRelease, ReleaseFile, ServedReleaseFile, composeRelease, releaseManifest } from "./mobile-release/release.js";
1
+ import { MobileRelease, ReleaseFile, ReleaseSigning, ServedReleaseFile, composeRelease, releaseManifest } from "./mobile-release/release.js";
2
2
  import { MobileReleaseAppInput, OfferedRelease, mobileReleaseApp } from "./mobile-release/release-app.js";
3
3
  import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
4
4
  import { nativeFingerprint } from "./mobile-release/fingerprint.js";
5
5
  import { withApiOrigin } from "./mobile-release/api-origin-page.js";
6
- export { type MobileRelease, type MobileReleaseAppInput, type OfferedRelease, type ReleaseFile, type ServedReleaseFile, composeRelease, mobileReleaseApp, mobileSigningKeyVariable, nativeFingerprint, readSigningKey, releaseManifest, withApiOrigin };
6
+ export { type MobileRelease, type MobileReleaseAppInput, type OfferedRelease, type ReleaseFile, type ReleaseSigning, type ServedReleaseFile, composeRelease, mobileReleaseApp, mobileSigningKeyVariable, nativeFingerprint, readSigningKey, releaseManifest, withApiOrigin };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.57.1";
2
+ var version = "0.57.3";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.57.1",
3
+ "version": "0.57.3",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,9 +41,9 @@
41
41
  "@better-auth/passkey": "^1.7.12",
42
42
  "@better-auth/sso": "^1.7.12",
43
43
  "@modelcontextprotocol/server": "^2.3.1",
44
- "@protobase/layout": "0.57.1",
45
- "@protobase/query": "0.57.1",
46
- "@protobase/schema": "0.57.1",
44
+ "@protobase/layout": "0.57.3",
45
+ "@protobase/query": "0.57.3",
46
+ "@protobase/schema": "0.57.3",
47
47
  "@react-email/render": "^2.1.0",
48
48
  "@scalar/hono-api-reference": "^0.12.10",
49
49
  "@sentry/core": "^11.7.0",