@parity/product-sdk-auth 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/auth-DR3oM5Tk.d.ts +170 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +323 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +35 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +43 -0
- package/src/allocations.test.ts +75 -0
- package/src/allocations.ts +107 -0
- package/src/auth.test.ts +49 -0
- package/src/auth.ts +485 -0
- package/src/index.ts +55 -0
- package/src/sessionSigner.test.ts +101 -0
- package/src/sessionSigner.ts +31 -0
- package/src/signer.test.ts +97 -0
- package/src/signer.ts +124 -0
- package/src/types.ts +34 -0
- package/src/ui/index.ts +22 -0
- package/src/ui/qr.ts +22 -0
- package/src/ui/status.test.ts +60 -0
- package/src/ui/status.ts +49 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
import { describe, expect, test } from "vitest";
|
|
17
|
+
import { ss58Encode } from "@parity/product-sdk-address";
|
|
18
|
+
import { seedToAccount } from "@parity/product-sdk-keys";
|
|
19
|
+
import type { UserSession } from "@parity/product-sdk-terminal";
|
|
20
|
+
import { createSessionSigner } from "./sessionSigner.js";
|
|
21
|
+
|
|
22
|
+
const DEV_PHRASE = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
23
|
+
// Injected product id (the consumer supplies this via ProductAccountRef). Uses
|
|
24
|
+
// the same value playground derives from so the product account is identical.
|
|
25
|
+
const PRODUCT_ID = "playground.dot";
|
|
26
|
+
|
|
27
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Account equivalence — pins the invariant that every flow which references
|
|
29
|
+
// "the user's account" resolves to the *same* SS58: the product account
|
|
30
|
+
// derived at `mnemonic + "/product/{PRODUCT_ID}/0"`.
|
|
31
|
+
//
|
|
32
|
+
// The signer built by `createSessionSigner(session, {productId, derivationIndex})`
|
|
33
|
+
// must SS58-equal the mobile/playground-app derivation
|
|
34
|
+
// `seedToAccount(mnemonic, "/product/{PRODUCT_ID}/0")`. These tests are the
|
|
35
|
+
// regression guard against the pre-fix bug where the signer used the wallet
|
|
36
|
+
// account (`remoteAccount.accountId`) instead of the product account.
|
|
37
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
describe("session signer account equivalence", () => {
|
|
39
|
+
// Stand-in for the mobile's SSO handshake response: `rootAccountId` is
|
|
40
|
+
// `deriveRootAccount()` on the mobile = the bare-mnemonic keypair pubkey.
|
|
41
|
+
// Other fields aren't read by `createSessionSigner` in the path under test.
|
|
42
|
+
function fakeSession(mnemonic: string): UserSession {
|
|
43
|
+
const root = seedToAccount(mnemonic, "");
|
|
44
|
+
const wallet = seedToAccount(mnemonic, "//SomeWallet"); // user picking a derived account on mobile
|
|
45
|
+
return {
|
|
46
|
+
id: "test",
|
|
47
|
+
localAccount: { accountId: new Uint8Array(32), pin: undefined },
|
|
48
|
+
remoteAccount: {
|
|
49
|
+
accountId: wallet.publicKey,
|
|
50
|
+
publicKey: wallet.publicKey,
|
|
51
|
+
pin: undefined,
|
|
52
|
+
},
|
|
53
|
+
rootAccountId: root.publicKey,
|
|
54
|
+
} as unknown as UserSession;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test("session signer address === product-account derivation address", () => {
|
|
58
|
+
const session = fakeSession(DEV_PHRASE);
|
|
59
|
+
|
|
60
|
+
const cliSigner = createSessionSigner(session, {
|
|
61
|
+
productId: PRODUCT_ID,
|
|
62
|
+
derivationIndex: 0,
|
|
63
|
+
});
|
|
64
|
+
const cliAddress = ss58Encode(cliSigner.publicKey);
|
|
65
|
+
|
|
66
|
+
const mobileDerived = seedToAccount(DEV_PHRASE, `/product/${PRODUCT_ID}/0`);
|
|
67
|
+
const productAccountAddress = ss58Encode(mobileDerived.publicKey);
|
|
68
|
+
|
|
69
|
+
expect(cliAddress).toEqual(productAccountAddress);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("regression: signer does NOT use remoteAccount.accountId (= wallet account)", () => {
|
|
73
|
+
const session = fakeSession(DEV_PHRASE);
|
|
74
|
+
const cliSigner = createSessionSigner(session, {
|
|
75
|
+
productId: PRODUCT_ID,
|
|
76
|
+
derivationIndex: 0,
|
|
77
|
+
});
|
|
78
|
+
const cliAddress = ss58Encode(cliSigner.publicKey);
|
|
79
|
+
const walletAddress = ss58Encode(new Uint8Array(session.remoteAccount.accountId));
|
|
80
|
+
|
|
81
|
+
// Pre-fix bug: signer.publicKey was set from session.remoteAccount.accountId
|
|
82
|
+
// (the user's wallet account), not the product-derived account. The wallet
|
|
83
|
+
// account is what the chain would see as From — different from the funded /
|
|
84
|
+
// allowance-granted product account. This guard ensures we never slip back.
|
|
85
|
+
expect(cliAddress).not.toEqual(walletAddress);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("reports stale sessions without a root account public key", () => {
|
|
89
|
+
const session = {
|
|
90
|
+
...fakeSession(DEV_PHRASE),
|
|
91
|
+
rootAccountId: new Uint8Array(),
|
|
92
|
+
} as UserSession;
|
|
93
|
+
|
|
94
|
+
expect(() =>
|
|
95
|
+
createSessionSigner(session, {
|
|
96
|
+
productId: PRODUCT_ID,
|
|
97
|
+
derivationIndex: 0,
|
|
98
|
+
}),
|
|
99
|
+
).toThrow("Stored login session is missing the root account public key.");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Session-backed signing for a product account.
|
|
18
|
+
*
|
|
19
|
+
* All of this lives in `@parity/product-sdk-terminal` — the session signer, the
|
|
20
|
+
* product-account derivation, and the stale-session guard. This module is a
|
|
21
|
+
* thin re-export so auth consumers get one import surface; nothing is
|
|
22
|
+
* implemented twice.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
createSessionSignerForAccount as createSessionSigner,
|
|
27
|
+
deriveProductPublicKey,
|
|
28
|
+
sessionRootPublicKey,
|
|
29
|
+
INCOMPLETE_SESSION_MESSAGE,
|
|
30
|
+
} from "@parity/product-sdk-terminal";
|
|
31
|
+
export type { ProductAccountRef } from "@parity/product-sdk-terminal";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
import { describe, expect, test } from "vitest";
|
|
17
|
+
import { ss58Encode } from "@parity/product-sdk-address";
|
|
18
|
+
import { getDevPublicKey } from "@parity/product-sdk-tx";
|
|
19
|
+
import { seedToAccount } from "@parity/product-sdk-keys";
|
|
20
|
+
import { resolveSigner, SignerNotAvailableError, parseDevAccountName } from "./signer.js";
|
|
21
|
+
import type { AuthClient } from "./auth.js";
|
|
22
|
+
|
|
23
|
+
const DEV_PHRASE = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
24
|
+
|
|
25
|
+
// Minimal AuthClient stub — only `getSessionSigner` is exercised by resolveSigner.
|
|
26
|
+
function stubAuthClient(session: Awaited<ReturnType<AuthClient["getSessionSigner"]>>): AuthClient {
|
|
27
|
+
return {
|
|
28
|
+
getSessionSigner: async () => session,
|
|
29
|
+
} as unknown as AuthClient;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("parseDevAccountName", () => {
|
|
33
|
+
test("recognizes well-known dev SURIs (exact case, // prefix)", () => {
|
|
34
|
+
expect(parseDevAccountName("//Alice")).toBe("Alice");
|
|
35
|
+
expect(parseDevAccountName("//Bob")).toBe("Bob");
|
|
36
|
+
});
|
|
37
|
+
test("requires the // prefix — a bare name is not a dev SURI", () => {
|
|
38
|
+
expect(parseDevAccountName("Alice")).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
test("is case-sensitive, matching Substrate SURI semantics", () => {
|
|
41
|
+
expect(parseDevAccountName("//alice")).toBeNull();
|
|
42
|
+
expect(parseDevAccountName("//BOB")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
test("returns null for non-dev suris", () => {
|
|
45
|
+
expect(parseDevAccountName("//NotADev")).toBeNull();
|
|
46
|
+
expect(parseDevAccountName(DEV_PHRASE)).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("resolveSigner", () => {
|
|
51
|
+
const noSession = stubAuthClient(null);
|
|
52
|
+
|
|
53
|
+
test("--suri //Alice → dev signer at Alice's address", async () => {
|
|
54
|
+
const r = await resolveSigner(noSession, { suri: "//Alice" });
|
|
55
|
+
expect(r.source).toBe("dev");
|
|
56
|
+
expect(r.address).toBe(ss58Encode(getDevPublicKey("Alice")));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("--suri <mnemonic> → dev signer at the seed-derived address", async () => {
|
|
60
|
+
const r = await resolveSigner(noSession, { suri: DEV_PHRASE });
|
|
61
|
+
expect(r.source).toBe("dev");
|
|
62
|
+
expect(r.address).toBe(ss58Encode(seedToAccount(DEV_PHRASE, "").publicKey));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("--suri <mnemonic>//path → dev signer at the derived sub-account", async () => {
|
|
66
|
+
const r = await resolveSigner(noSession, { suri: `${DEV_PHRASE}//stash` });
|
|
67
|
+
expect(r.source).toBe("dev");
|
|
68
|
+
expect(r.address).toBe(ss58Encode(seedToAccount(DEV_PHRASE, "//stash").publicKey));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("garbage suri → throws 'Unrecognized SURI'", async () => {
|
|
72
|
+
await expect(resolveSigner(noSession, { suri: "not a valid suri !!" })).rejects.toThrow(
|
|
73
|
+
/Unrecognized SURI/,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("no suri + no session → SignerNotAvailableError", async () => {
|
|
78
|
+
await expect(resolveSigner(noSession)).rejects.toBeInstanceOf(SignerNotAvailableError);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("no suri + a session → session signer (source 'session')", async () => {
|
|
82
|
+
const fakeHandle = {
|
|
83
|
+
address: "5SESSION",
|
|
84
|
+
addresses: {
|
|
85
|
+
rootAddress: "r",
|
|
86
|
+
productAddress: "5SESSION",
|
|
87
|
+
productH160: "0xabc" as `0x${string}`,
|
|
88
|
+
},
|
|
89
|
+
signer: {} as never,
|
|
90
|
+
userSession: {} as never,
|
|
91
|
+
destroy() {},
|
|
92
|
+
};
|
|
93
|
+
const r = await resolveSigner(stubAuthClient(fakeHandle));
|
|
94
|
+
expect(r.source).toBe("session");
|
|
95
|
+
expect(r.address).toBe("5SESSION");
|
|
96
|
+
});
|
|
97
|
+
});
|
package/src/signer.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Unified signer resolution — "give me a PolkadotSigner, I don't care how."
|
|
18
|
+
* Lifted from playground-cli `src/utils/signer.ts` (issue #411).
|
|
19
|
+
*
|
|
20
|
+
* Dev accounts (`--suri //Alice`) and QR mobile sessions both produce the same
|
|
21
|
+
* `PolkadotSigner` interface. Consumers call `resolveSigner(authClient)` once at
|
|
22
|
+
* startup and thread the result through all operations. The `authClient` (from
|
|
23
|
+
* `createAuthClient`) supplies the session branch, so this module stays free of
|
|
24
|
+
* product-specific config.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { ss58Encode } from "@parity/product-sdk-address";
|
|
28
|
+
import { createDevSigner, getDevPublicKey, type DevAccountName } from "@parity/product-sdk-tx";
|
|
29
|
+
import { seedToAccount } from "@parity/product-sdk-keys";
|
|
30
|
+
import type { PolkadotSigner } from "polkadot-api";
|
|
31
|
+
import type { AuthClient, SessionAddresses, SessionHandle } from "./auth.js";
|
|
32
|
+
|
|
33
|
+
export type SignerSource = "dev" | "session";
|
|
34
|
+
|
|
35
|
+
export interface ResolvedSigner {
|
|
36
|
+
signer: PolkadotSigner;
|
|
37
|
+
address: string;
|
|
38
|
+
source: SignerSource;
|
|
39
|
+
/** Present for QR/mobile sessions; absent for local dev/SURI signers. */
|
|
40
|
+
userSession?: SessionHandle["userSession"];
|
|
41
|
+
/** Root / product / H160 triple — present for QR/mobile sessions only. */
|
|
42
|
+
addresses?: SessionAddresses;
|
|
43
|
+
/** Tear down session adapter. Call in a finally block. No-op for dev signers. */
|
|
44
|
+
destroy(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SignerOptions {
|
|
48
|
+
/** Secret URI like "//Alice". Takes priority over session. */
|
|
49
|
+
suri?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class SignerNotAvailableError extends Error {
|
|
53
|
+
constructor() {
|
|
54
|
+
super('No signer available. Run "login" to sign in, or pass --suri //Alice for dev.');
|
|
55
|
+
this.name = "SignerNotAvailableError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEV_NAMES: readonly DevAccountName[] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse a well-known dev-account SURI (`//Alice`, `//Bob`, …).
|
|
63
|
+
*
|
|
64
|
+
* Requires the `//` hard-derivation prefix and an exact-case name, matching
|
|
65
|
+
* Substrate SURI semantics (and `createDevSigner`, which derives `//${name}`).
|
|
66
|
+
* A bare `Alice` or a different case is NOT a dev SURI — it falls through to the
|
|
67
|
+
* mnemonic path in `resolveSigner`, where it fails as an invalid mnemonic.
|
|
68
|
+
*/
|
|
69
|
+
export function parseDevAccountName(suri: string): DevAccountName | null {
|
|
70
|
+
if (!suri.startsWith("//")) return null;
|
|
71
|
+
const name = suri.slice(2);
|
|
72
|
+
return DEV_NAMES.find((n) => n === name) ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a PolkadotSigner.
|
|
77
|
+
*
|
|
78
|
+
* 1. `--suri` flag → dev signer (dev name like //Alice, OR a BIP-39 mnemonic
|
|
79
|
+
* with optional `//<path>` derivation suffix)
|
|
80
|
+
* 2. Persisted QR session → session signer (via `authClient.getSessionSigner`)
|
|
81
|
+
* 3. Neither → `SignerNotAvailableError`
|
|
82
|
+
*/
|
|
83
|
+
export async function resolveSigner(
|
|
84
|
+
authClient: AuthClient,
|
|
85
|
+
options?: SignerOptions,
|
|
86
|
+
): Promise<ResolvedSigner> {
|
|
87
|
+
if (options?.suri) {
|
|
88
|
+
const devName = parseDevAccountName(options.suri);
|
|
89
|
+
if (devName) {
|
|
90
|
+
return {
|
|
91
|
+
signer: createDevSigner(devName),
|
|
92
|
+
address: ss58Encode(getDevPublicKey(devName)),
|
|
93
|
+
source: "dev",
|
|
94
|
+
destroy() {},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Mnemonic path. Split off an optional `//<path>` derivation suffix so
|
|
98
|
+
// callers can target a sub-account of the same seed without a separate flag.
|
|
99
|
+
const sepIdx = options.suri.indexOf("//");
|
|
100
|
+
const mnemonic = (sepIdx === -1 ? options.suri : options.suri.slice(0, sepIdx)).trim();
|
|
101
|
+
const path = sepIdx === -1 ? "" : options.suri.slice(sepIdx);
|
|
102
|
+
try {
|
|
103
|
+
const account = seedToAccount(mnemonic, path);
|
|
104
|
+
return {
|
|
105
|
+
signer: account.signer,
|
|
106
|
+
address: ss58Encode(account.publicKey),
|
|
107
|
+
source: "dev",
|
|
108
|
+
destroy() {},
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Unrecognized SURI "${options.suri}". ` +
|
|
113
|
+
`Expected a dev name (${DEV_NAMES.join(", ")}) or a BIP-39 mnemonic.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const session = await authClient.getSessionSigner();
|
|
119
|
+
if (session) {
|
|
120
|
+
return { ...session, source: "session" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new SignerNotAvailableError();
|
|
124
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Per-product configuration injected into `createAuthClient`. Lifting the
|
|
18
|
+
* sign-in glue out of playground-cli (issue #411) means the env-specific
|
|
19
|
+
* constants playground hard-coded in its `config.ts` (DAPP_ID, product id,
|
|
20
|
+
* People-chain endpoints) become consumer-supplied so the same package serves
|
|
21
|
+
* `playground` and `dot` (and future products) unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export interface AuthConfig {
|
|
24
|
+
/** The dApp identity string. Scopes the on-disk session namespace
|
|
25
|
+
* (`~/.polkadot-apps/${dappId}_*`) and the SSO pairing — each product
|
|
26
|
+
* gets its own, independently-revocable session. */
|
|
27
|
+
dappId: string;
|
|
28
|
+
/** Product id used to derive the product account (`/product/{productId}/{index}`). */
|
|
29
|
+
productId: string;
|
|
30
|
+
/** Derivation index of the product account (0 = default). */
|
|
31
|
+
derivationIndex: number;
|
|
32
|
+
/** People-parachain RPC endpoints the terminal adapter connects to. */
|
|
33
|
+
peopleEndpoints: string[];
|
|
34
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `@parity/product-sdk-auth/ui` — terminal UI helpers (QR rendering + login/
|
|
18
|
+
* logout status formatters). Separate entrypoint so headless consumers can
|
|
19
|
+
* import the package root without pulling terminal-rendering deps.
|
|
20
|
+
*/
|
|
21
|
+
export { renderQrCode } from "./qr.js";
|
|
22
|
+
export { renderLoginStatus, renderLogoutStatus } from "./status.js";
|
package/src/ui/qr.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* QR rendering for the terminal. Re-exported from `@parity/product-sdk-terminal`
|
|
18
|
+
* so consumers get the login QR without depending on the terminal package
|
|
19
|
+
* directly. Lives under the `./ui` entrypoint (pulls terminal-rendering deps);
|
|
20
|
+
* headless consumers import the package root instead.
|
|
21
|
+
*/
|
|
22
|
+
export { renderQrCode } from "@parity/product-sdk-terminal";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
import { describe, expect, test } from "vitest";
|
|
17
|
+
import { renderLoginStatus, renderLogoutStatus } from "./status.js";
|
|
18
|
+
|
|
19
|
+
describe("renderLoginStatus", () => {
|
|
20
|
+
test("waiting", () => {
|
|
21
|
+
expect(renderLoginStatus({ step: "waiting" })).toMatch(/scan/i);
|
|
22
|
+
});
|
|
23
|
+
test("paired", () => {
|
|
24
|
+
expect(renderLoginStatus({ step: "paired" })).toMatch(/finishing/i);
|
|
25
|
+
});
|
|
26
|
+
test("pending surfaces the stage", () => {
|
|
27
|
+
expect(renderLoginStatus({ step: "pending", stage: "attestation" })).toContain(
|
|
28
|
+
"attestation",
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
test("success surfaces the address", () => {
|
|
32
|
+
expect(
|
|
33
|
+
renderLoginStatus({
|
|
34
|
+
step: "success",
|
|
35
|
+
address: "5Fy2sypq",
|
|
36
|
+
addresses: { rootAddress: "r", productAddress: "5Fy2sypq", productH160: "0xabc" },
|
|
37
|
+
}),
|
|
38
|
+
).toContain("5Fy2sypq");
|
|
39
|
+
});
|
|
40
|
+
test("error surfaces the message", () => {
|
|
41
|
+
expect(renderLoginStatus({ step: "error", message: "boom" })).toContain("boom");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("renderLogoutStatus", () => {
|
|
46
|
+
test("disconnecting", () => {
|
|
47
|
+
expect(renderLogoutStatus({ step: "disconnecting", address: "5X" })).toContain("5X");
|
|
48
|
+
});
|
|
49
|
+
test("success", () => {
|
|
50
|
+
expect(renderLogoutStatus({ step: "success", address: "5X" })).toMatch(/signed out/i);
|
|
51
|
+
});
|
|
52
|
+
test("partial surfaces the reason", () => {
|
|
53
|
+
expect(renderLogoutStatus({ step: "partial", address: "5X", reason: "ws down" })).toContain(
|
|
54
|
+
"ws down",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
test("error surfaces the message", () => {
|
|
58
|
+
expect(renderLogoutStatus({ step: "error", message: "nope" })).toContain("nope");
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/ui/status.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pure formatters that map the `LoginStatus` / `LogoutStatus` streams to
|
|
18
|
+
* one-line terminal messages. No I/O, no terminal deps — `import type` only,
|
|
19
|
+
* so this module stays unit-testable without the SSO stack.
|
|
20
|
+
*/
|
|
21
|
+
import type { LoginStatus, LogoutStatus } from "../auth.js";
|
|
22
|
+
|
|
23
|
+
export function renderLoginStatus(status: LoginStatus): string {
|
|
24
|
+
switch (status.step) {
|
|
25
|
+
case "waiting":
|
|
26
|
+
return "Waiting for you to scan the QR code with your phone…";
|
|
27
|
+
case "paired":
|
|
28
|
+
return "Paired — finishing sign-in…";
|
|
29
|
+
case "pending":
|
|
30
|
+
return `Working: ${status.stage}…`;
|
|
31
|
+
case "success":
|
|
32
|
+
return `Signed in as ${status.address}`;
|
|
33
|
+
case "error":
|
|
34
|
+
return `Sign-in failed: ${status.message}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function renderLogoutStatus(status: LogoutStatus): string {
|
|
39
|
+
switch (status.step) {
|
|
40
|
+
case "disconnecting":
|
|
41
|
+
return `Signing out ${status.address}…`;
|
|
42
|
+
case "success":
|
|
43
|
+
return `Signed out ${status.address}`;
|
|
44
|
+
case "partial":
|
|
45
|
+
return `Signed out locally (${status.address}); the phone was not notified: ${status.reason}`;
|
|
46
|
+
case "error":
|
|
47
|
+
return `Sign-out error: ${status.message}`;
|
|
48
|
+
}
|
|
49
|
+
}
|