@parity/product-sdk-terminal 0.3.1 → 0.3.2
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/testing.d.ts +3 -3
- package/dist/testing.js +38 -29
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
package/dist/testing.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ interface CreateTestSessionOptions {
|
|
|
18
18
|
/** Stable session id. Default: random nanoid(12). */
|
|
19
19
|
sessionId?: string;
|
|
20
20
|
/**
|
|
21
|
-
* Whether to write the encrypted `
|
|
21
|
+
* Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.
|
|
22
22
|
* Set to `false` to exercise recovery paths where a session exists on disk
|
|
23
23
|
* but its secrets are missing.
|
|
24
24
|
*/
|
|
@@ -65,9 +65,9 @@ interface TestSession {
|
|
|
65
65
|
* tracked via on-chain attestation state. See above for how expiry-path
|
|
66
66
|
* tests still work in practice.
|
|
67
67
|
* - **Corrupted-session cases** don't need a helper — write garbage to
|
|
68
|
-
* `<storageDir>/<appId>
|
|
68
|
+
* `<storageDir>/<appId>_SsoSessionsV2.json` with `fs.writeFile` directly.
|
|
69
69
|
* - **Repeated calls replace the session list.** Each call writes a fresh
|
|
70
|
-
* single-entry `
|
|
70
|
+
* single-entry `SsoSessionsV2` file, so calling twice on the same
|
|
71
71
|
* `storageDir`+`appId` leaves only the second session on disk. Use a
|
|
72
72
|
* fresh `mkdtempSync` per test to keep cases isolated.
|
|
73
73
|
*
|
package/dist/testing.js
CHANGED
|
@@ -25,20 +25,22 @@ import {
|
|
|
25
25
|
import { mkdir, writeFile } from "fs/promises";
|
|
26
26
|
import { join } from "path";
|
|
27
27
|
import { nanoid } from "nanoid";
|
|
28
|
-
import { Bytes,
|
|
28
|
+
import { Bytes, Struct, Vector, str } from "scale-ts";
|
|
29
29
|
var storedUserSessionCodec = Struct({
|
|
30
30
|
id: str,
|
|
31
31
|
localAccount: LocalSessionAccountCodec,
|
|
32
32
|
remoteAccount: RemoteSessionAccountCodec,
|
|
33
33
|
rootAccountId: AccountIdCodec,
|
|
34
|
-
identityAccountId:
|
|
35
|
-
identityChatPublicKey:
|
|
34
|
+
identityAccountId: AccountIdCodec,
|
|
35
|
+
identityChatPublicKey: Bytes(65),
|
|
36
|
+
ssoEncPubKey: Bytes(65),
|
|
37
|
+
rootEntropySource: Bytes(32)
|
|
36
38
|
});
|
|
37
39
|
var sessionsCodec = Vector(storedUserSessionCodec);
|
|
38
40
|
var storedUserSecretsCodec = Struct({
|
|
39
41
|
ssSecret: Bytes(),
|
|
40
42
|
encrSecret: Bytes(),
|
|
41
|
-
|
|
43
|
+
identityChatPrivateKey: Bytes(32)
|
|
42
44
|
});
|
|
43
45
|
function encryptSecrets(appId, plaintext) {
|
|
44
46
|
const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });
|
|
@@ -74,13 +76,14 @@ async function createTestSession(options) {
|
|
|
74
76
|
sharedSecret,
|
|
75
77
|
void 0
|
|
76
78
|
),
|
|
77
|
-
// synthesized session: the remote (wallet) account doubles as root.
|
|
78
79
|
rootAccountId: createAccountId(remotePublicKey),
|
|
79
|
-
identityAccountId:
|
|
80
|
-
identityChatPublicKey:
|
|
80
|
+
identityAccountId: createAccountId(remotePublicKey),
|
|
81
|
+
identityChatPublicKey: remoteEncrPublicKey,
|
|
82
|
+
ssoEncPubKey: remoteEncrPublicKey,
|
|
83
|
+
rootEntropySource: remoteEntropy
|
|
81
84
|
};
|
|
82
85
|
await writeFile(
|
|
83
|
-
join(options.storageDir, `${sanitizeKey(options.appId, "
|
|
86
|
+
join(options.storageDir, `${sanitizeKey(options.appId, "SsoSessionsV2")}.json`),
|
|
84
87
|
toHex(sessionsCodec.enc([session])),
|
|
85
88
|
"utf-8"
|
|
86
89
|
);
|
|
@@ -89,12 +92,12 @@ async function createTestSession(options) {
|
|
|
89
92
|
const encoded = storedUserSecretsCodec.enc({
|
|
90
93
|
ssSecret: localSecret,
|
|
91
94
|
encrSecret: localEncrSecret,
|
|
92
|
-
|
|
95
|
+
identityChatPrivateKey: localEncrSecret
|
|
93
96
|
});
|
|
94
97
|
await writeFile(
|
|
95
98
|
join(
|
|
96
99
|
options.storageDir,
|
|
97
|
-
`${sanitizeKey(options.appId, `
|
|
100
|
+
`${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`
|
|
98
101
|
),
|
|
99
102
|
encryptSecrets(options.appId, encoded),
|
|
100
103
|
"utf-8"
|
|
@@ -128,22 +131,22 @@ if (void 0) {
|
|
|
128
131
|
}
|
|
129
132
|
});
|
|
130
133
|
describe("createTestSession", () => {
|
|
131
|
-
test("writes both
|
|
134
|
+
test("writes both SsoSessionsV2 and UserSecretsV2 files by default", async () => {
|
|
132
135
|
const result = await createTestSession({
|
|
133
136
|
appId: "my-app",
|
|
134
137
|
storageDir,
|
|
135
138
|
localMnemonic: LOCAL_MNEMONIC,
|
|
136
139
|
remoteMnemonic: REMOTE_MNEMONIC
|
|
137
140
|
});
|
|
138
|
-
const sessions = await readFile(join(storageDir, "my-
|
|
141
|
+
const sessions = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
139
142
|
expect(sessions).toMatch(/^0x[0-9a-f]+$/);
|
|
140
143
|
const secrets = await readFile(
|
|
141
|
-
join(storageDir, `my-
|
|
144
|
+
join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
|
|
142
145
|
"utf-8"
|
|
143
146
|
);
|
|
144
147
|
expect(secrets).toMatch(/^0x[0-9a-f]+$/);
|
|
145
148
|
});
|
|
146
|
-
test("omits
|
|
149
|
+
test("omits UserSecretsV2 file when includeSecrets is false", async () => {
|
|
147
150
|
const result = await createTestSession({
|
|
148
151
|
appId: "my-app",
|
|
149
152
|
storageDir,
|
|
@@ -152,13 +155,16 @@ if (void 0) {
|
|
|
152
155
|
includeSecrets: false
|
|
153
156
|
});
|
|
154
157
|
await expect(
|
|
155
|
-
readFile(join(storageDir, "my-
|
|
158
|
+
readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8")
|
|
156
159
|
).resolves.toMatch(/^0x/);
|
|
157
160
|
await expect(
|
|
158
|
-
readFile(
|
|
161
|
+
readFile(
|
|
162
|
+
join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
|
|
163
|
+
"utf-8"
|
|
164
|
+
)
|
|
159
165
|
).rejects.toThrow(/ENOENT/);
|
|
160
166
|
});
|
|
161
|
-
test("
|
|
167
|
+
test("SsoSessionsV2 file decodes with the host-papp session codec shape", async () => {
|
|
162
168
|
const result = await createTestSession({
|
|
163
169
|
appId: "my-app",
|
|
164
170
|
storageDir,
|
|
@@ -166,7 +172,7 @@ if (void 0) {
|
|
|
166
172
|
remoteMnemonic: REMOTE_MNEMONIC,
|
|
167
173
|
sessionId: "stable-test-id"
|
|
168
174
|
});
|
|
169
|
-
const hex = await readFile(join(storageDir, "my-
|
|
175
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
170
176
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
171
177
|
expect(decoded).toHaveLength(1);
|
|
172
178
|
expect(decoded[0].id).toBe("stable-test-id");
|
|
@@ -189,7 +195,7 @@ if (void 0) {
|
|
|
189
195
|
expect(result.localAccountId).toEqual(expected);
|
|
190
196
|
expect(result.localAccountId).toHaveLength(32);
|
|
191
197
|
});
|
|
192
|
-
test("
|
|
198
|
+
test("UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape", async () => {
|
|
193
199
|
const appId = "my-app";
|
|
194
200
|
const result = await createTestSession({
|
|
195
201
|
appId,
|
|
@@ -198,25 +204,25 @@ if (void 0) {
|
|
|
198
204
|
remoteMnemonic: REMOTE_MNEMONIC
|
|
199
205
|
});
|
|
200
206
|
const hex = await readFile(
|
|
201
|
-
join(storageDir, `${appId}
|
|
207
|
+
join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),
|
|
202
208
|
"utf-8"
|
|
203
209
|
);
|
|
204
210
|
const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });
|
|
205
211
|
const nonce = blake2b(new TextEncoder().encode("nonce"), { dkLen: 32 });
|
|
206
212
|
const decrypted = gcm(key, nonce).decrypt(fromHex(hex));
|
|
207
213
|
const decoded = storedUserSecretsCodec.dec(decrypted);
|
|
208
|
-
expect(decoded.entropy).toEqual(mnemonicToEntropy(LOCAL_MNEMONIC));
|
|
209
214
|
expect(decoded.ssSecret).toHaveLength(64);
|
|
210
215
|
expect(decoded.encrSecret).toHaveLength(32);
|
|
216
|
+
expect(decoded.identityChatPrivateKey).toHaveLength(32);
|
|
211
217
|
});
|
|
212
218
|
test("different appIds produce files under different prefixes", async () => {
|
|
213
219
|
await createTestSession({ appId: "app-a", storageDir, sessionId: "id" });
|
|
214
220
|
await createTestSession({ appId: "app-b", storageDir, sessionId: "id" });
|
|
215
221
|
await expect(
|
|
216
|
-
readFile(join(storageDir, "app-
|
|
222
|
+
readFile(join(storageDir, "app-a_SsoSessionsV2.json"), "utf-8")
|
|
217
223
|
).resolves.toMatch(/^0x/);
|
|
218
224
|
await expect(
|
|
219
|
-
readFile(join(storageDir, "app-
|
|
225
|
+
readFile(join(storageDir, "app-b_SsoSessionsV2.json"), "utf-8")
|
|
220
226
|
).resolves.toMatch(/^0x/);
|
|
221
227
|
});
|
|
222
228
|
test("sanitizes appIds with special characters", async () => {
|
|
@@ -226,14 +232,14 @@ if (void 0) {
|
|
|
226
232
|
sessionId: "id"
|
|
227
233
|
});
|
|
228
234
|
await expect(
|
|
229
|
-
readFile(join(storageDir, "
|
|
235
|
+
readFile(join(storageDir, "app_with_spaces_SsoSessionsV2.json"), "utf-8")
|
|
230
236
|
).resolves.toMatch(/^0x/);
|
|
231
237
|
});
|
|
232
238
|
test("creates storageDir when it does not yet exist", async () => {
|
|
233
239
|
const nested = join(storageDir, "does", "not", "exist");
|
|
234
240
|
await createTestSession({ appId: "my-app", storageDir: nested });
|
|
235
241
|
await expect(
|
|
236
|
-
readFile(join(nested, "my-
|
|
242
|
+
readFile(join(nested, "my-app_SsoSessionsV2.json"), "utf-8")
|
|
237
243
|
).resolves.toMatch(/^0x/);
|
|
238
244
|
});
|
|
239
245
|
test("generates fresh mnemonics when none are supplied", async () => {
|
|
@@ -251,7 +257,7 @@ if (void 0) {
|
|
|
251
257
|
});
|
|
252
258
|
expect(result.sessionId).toBe("pinned-id");
|
|
253
259
|
await expect(
|
|
254
|
-
readFile(join(storageDir, "my-
|
|
260
|
+
readFile(join(storageDir, "my-app_UserSecretsV2_pinned-id.json"), "utf-8")
|
|
255
261
|
).resolves.toMatch(/^0x/);
|
|
256
262
|
});
|
|
257
263
|
test("respects a custom localDerivation", async () => {
|
|
@@ -279,15 +285,18 @@ if (void 0) {
|
|
|
279
285
|
storageDir,
|
|
280
286
|
sessionId: "second"
|
|
281
287
|
});
|
|
282
|
-
const hex = await readFile(join(storageDir, "my-
|
|
288
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
283
289
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
284
290
|
expect(decoded).toHaveLength(1);
|
|
285
291
|
expect(decoded[0].id).toBe("second");
|
|
286
292
|
await expect(
|
|
287
|
-
readFile(join(storageDir, `my-
|
|
293
|
+
readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), "utf-8")
|
|
288
294
|
).resolves.toMatch(/^0x/);
|
|
289
295
|
await expect(
|
|
290
|
-
readFile(
|
|
296
|
+
readFile(
|
|
297
|
+
join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),
|
|
298
|
+
"utf-8"
|
|
299
|
+
)
|
|
291
300
|
).resolves.toMatch(/^0x/);
|
|
292
301
|
});
|
|
293
302
|
test("AccountIdCodec is Bytes(32) \u2014 keep this invariant if upstream changes", () => {
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Option, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the `Bytes(65)` chat key must match.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: Option(AccountIdCodec),\n identityChatPublicKey: Option(Bytes(65)),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n entropy: Bytes(),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecrets_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessions.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessions` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n // synthesized session: the remote (wallet) account doubles as root.\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: undefined,\n identityChatPublicKey: undefined,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessions\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n entropy: localEntropy,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecrets_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessions and UserSecrets files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecrets_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecrets file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${result.sessionId}.json`), \"utf-8\"),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessions file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecrets file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecrets_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n expect(decoded.entropy).toEqual(mnemonicToEntropy(LOCAL_MNEMONIC));\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecrets_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecrets file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${second.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the fixed-width fields must match.\n//\n// host-papp 0.8.6 (RFC-0007, PR #205) dropped the `Option` wrapper on\n// `identityAccountId`, `identityChatPublicKey`, and `ssoEncPubKey` — all\n// three are now required — and appended `rootEntropySource: Bytes(32)`,\n// the layer-1 entropy the host consumes via `deriveProductEntropyFromSource`.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: AccountIdCodec,\n identityChatPublicKey: Bytes(65),\n ssoEncPubKey: Bytes(65),\n rootEntropySource: Bytes(32),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\n//\n// host-papp 0.8.6 dropped `entropy` (root entropy moved onto the session as\n// `rootEntropySource`) and added the V2 `identityChatPrivateKey` field.\n// The storage key was also renamed `UserSecrets` → `UserSecretsV2`.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n identityChatPrivateKey: Bytes(32),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessionsV2.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessionsV2` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n // host-papp 0.8.6 (RFC-0007) requires all of identityAccountId,\n // identityChatPublicKey, ssoEncPubKey, and rootEntropySource on the\n // persisted session. For a synthesized test session we collapse the\n // identity-side keys onto the remote account (the wallet doubles as\n // identity), reuse the peer's P-256 encryption pubkey for both chat\n // and SSO transports, and use the remote entropy as the RFC-0007\n // root entropy source so the value is reproducible.\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: createAccountId(remotePublicKey),\n identityChatPublicKey: remoteEncrPublicKey,\n ssoEncPubKey: remoteEncrPublicKey,\n rootEntropySource: remoteEntropy,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessionsV2\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n // host-papp 0.8.6 dropped `entropy` (now lives on the session as\n // `rootEntropySource`) and requires a 32-byte `identityChatPrivateKey`\n // (V2 — P-256 raw scalar). Reuse the local P-256 encryption secret\n // for both — same domain (P-256, 32-byte scalar).\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n identityChatPrivateKey: localEncrSecret,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessionsV2 and UserSecretsV2 files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecretsV2 file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n ),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessionsV2 file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n // V2 chat private key (P-256 raw scalar) is 32 bytes.\n expect(decoded.identityChatPrivateKey).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecretsV2_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecretsV2 file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),\n \"utf-8\",\n ),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-terminal",
|
|
3
3
|
"description": "QR code login and signing for CLI/terminal apps via Polkadot mobile wallet pairing",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@noble/ciphers": "^2.1.0",
|
|
32
32
|
"@noble/curves": "^2.0.1",
|
|
33
33
|
"@noble/hashes": "^2.2.0",
|
|
34
|
-
"@novasamatech/host-papp": "^0.8.
|
|
35
|
-
"@novasamatech/statement-store": "^0.8.
|
|
36
|
-
"@novasamatech/storage-adapter": "^0.8.
|
|
34
|
+
"@novasamatech/host-papp": "^0.8.6",
|
|
35
|
+
"@novasamatech/statement-store": "^0.8.6",
|
|
36
|
+
"@novasamatech/storage-adapter": "^0.8.6",
|
|
37
37
|
"@polkadot-api/substrate-bindings": "^0.20.2",
|
|
38
38
|
"@polkadot-api/utils": "^0.4.0",
|
|
39
39
|
"@polkadot-api/ws-provider": "^0.9.0",
|