@novasamatech/host-papp 0.7.9 → 0.8.0-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/README.md +170 -1
- package/dist/debugTypes.d.ts +3 -5
- package/dist/identity/rpcAdapter.js +12 -5
- package/dist/index.d.ts +2 -1
- package/dist/papp.d.ts +21 -16
- package/dist/papp.js +6 -2
- package/dist/sso/auth/impl.d.ts +25 -7
- package/dist/sso/auth/impl.js +126 -188
- package/dist/sso/auth/scale/handshakeV2.d.ts +260 -0
- package/dist/sso/auth/scale/handshakeV2.js +176 -0
- package/dist/sso/auth/v2/envelope.d.ts +18 -0
- package/dist/sso/auth/v2/envelope.js +23 -0
- package/dist/sso/auth/v2/proposal.d.ts +23 -0
- package/dist/sso/auth/v2/proposal.js +43 -0
- package/dist/sso/auth/v2/service.d.ts +64 -0
- package/dist/sso/auth/v2/service.js +184 -0
- package/dist/sso/auth/v2/state.d.ts +90 -0
- package/dist/sso/auth/v2/state.js +65 -0
- package/dist/sso/auth/v2/topic.d.ts +17 -0
- package/dist/sso/auth/v2/topic.js +21 -0
- package/dist/sso/deviceIdentityStore.d.ts +14 -0
- package/dist/sso/deviceIdentityStore.js +76 -0
- package/dist/sso/sessionManager/userSession.js +1 -4
- package/dist/sso/userSecretRepository.d.ts +1 -0
- package/dist/sso/userSecretRepository.js +16 -1
- package/dist/sso/userSessionRepository.d.ts +29 -1
- package/dist/sso/userSessionRepository.js +22 -5
- package/package.json +9 -6
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handshake V2 state machine — the public-facing observable shape of an
|
|
3
|
+
* in-flight SSO pairing exchange.
|
|
4
|
+
*
|
|
5
|
+
* Maps directly onto the inner `EncryptedHandshakeResponseV2` enum:
|
|
6
|
+
*
|
|
7
|
+
* Idle — no proposal emitted yet
|
|
8
|
+
* Submitted — proposal QR shown, waiting for the first response statement
|
|
9
|
+
* Pending — peer acknowledged; allocating Statement Store allowance on-chain
|
|
10
|
+
* Success — final state; identity keys received, device authorised
|
|
11
|
+
* Failed — final state; peer rejected (declined / duplicate / no-slot / tx-failed)
|
|
12
|
+
*
|
|
13
|
+
* Transitions are unidirectional except for Failed → Idle (user retries).
|
|
14
|
+
* The state object is what UIs render and what the chat layer gates on
|
|
15
|
+
* before submitting any V2 statements.
|
|
16
|
+
*/
|
|
17
|
+
import { deriveIdentityChatPublicKey } from '../scale/handshakeV2.js';
|
|
18
|
+
export const idle = () => ({ tag: 'Idle' });
|
|
19
|
+
export const submitted = () => ({ tag: 'Submitted' });
|
|
20
|
+
/**
|
|
21
|
+
* Translate the length-dispatched-decoded `EncryptedHandshakeResponseV2` into
|
|
22
|
+
* the public state. Pure — no I/O. The caller decrypts the outer envelope and
|
|
23
|
+
* runs `decodeEncryptedHandshakeResponseV2` first.
|
|
24
|
+
*/
|
|
25
|
+
export const fromInnerResponse = (response, peerStatementAccountId = null) => {
|
|
26
|
+
switch (response.tag) {
|
|
27
|
+
case 'Pending':
|
|
28
|
+
// Only AllowanceAllocation today; widen here when the spec adds more variants.
|
|
29
|
+
return { tag: 'Pending', reason: 'AllowanceAllocation' };
|
|
30
|
+
case 'Success':
|
|
31
|
+
return {
|
|
32
|
+
tag: 'Success',
|
|
33
|
+
identityAccountId: response.value.identityAccountId,
|
|
34
|
+
rootAccountId: response.value.rootAccountId,
|
|
35
|
+
identityChatPrivateKey: response.value.identityChatPrivateKey,
|
|
36
|
+
identityChatPublicKey: deriveIdentityChatPublicKey(response.value.identityChatPrivateKey),
|
|
37
|
+
deviceEncPubKey: response.value.deviceEncPubKey,
|
|
38
|
+
peerStatementAccountId,
|
|
39
|
+
};
|
|
40
|
+
case 'Failed':
|
|
41
|
+
return { tag: 'Failed', reason: response.value };
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Forward-only transition guard: rejects regressions like Success → Pending.
|
|
46
|
+
* Idempotent on same-tag transitions (returns current by reference) so callers
|
|
47
|
+
* can use `next === current` to detect "no change" without per-call equality.
|
|
48
|
+
*/
|
|
49
|
+
export const advance = (current, next) => {
|
|
50
|
+
if (isTerminal(current))
|
|
51
|
+
return current;
|
|
52
|
+
if (current.tag === 'Idle' && next.tag !== 'Submitted')
|
|
53
|
+
return current;
|
|
54
|
+
if (current.tag === 'Submitted' && next.tag === 'Idle')
|
|
55
|
+
return current;
|
|
56
|
+
if (current.tag === next.tag)
|
|
57
|
+
return current;
|
|
58
|
+
return next;
|
|
59
|
+
};
|
|
60
|
+
export const isTerminal = (state) => state.tag === 'Success' || state.tag === 'Failed';
|
|
61
|
+
/**
|
|
62
|
+
* True only when the device is authorised to submit V2 statements. The
|
|
63
|
+
* chat-send path gates on this — V2 messages must wait for allowance.
|
|
64
|
+
*/
|
|
65
|
+
export const canSubmitV2Statements = (state) => state.tag === 'Success';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing topic + channel derivation for the V2 SSO handshake.
|
|
3
|
+
*
|
|
4
|
+
* topic = blake2b256_keyed(encryptionPublicKey || "topic", key=statementAccountId)
|
|
5
|
+
* channel = blake2b256_keyed(encryptionPublicKey || "channel", key=statementAccountId)
|
|
6
|
+
*
|
|
7
|
+
* Where:
|
|
8
|
+
* - `statementAccountId` = the host's sr25519 device public key (32 bytes)
|
|
9
|
+
* - `encryptionPublicKey` = the host's P-256 device public key (65 bytes uncompressed)
|
|
10
|
+
*
|
|
11
|
+
* Both sides compute the same topic/channel deterministically from the same
|
|
12
|
+
* pubkeys carried in the QR-coded `VersionedHandshakeProposal::V2`, so they
|
|
13
|
+
* agree on where the response statement is delivered without any shared
|
|
14
|
+
* secret negotiation.
|
|
15
|
+
*/
|
|
16
|
+
export declare const computePairingTopic: (statementAccountId: Uint8Array, encryptionPublicKey: Uint8Array) => Uint8Array;
|
|
17
|
+
export declare const computePairingChannel: (statementAccountId: Uint8Array, encryptionPublicKey: Uint8Array) => Uint8Array;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing topic + channel derivation for the V2 SSO handshake.
|
|
3
|
+
*
|
|
4
|
+
* topic = blake2b256_keyed(encryptionPublicKey || "topic", key=statementAccountId)
|
|
5
|
+
* channel = blake2b256_keyed(encryptionPublicKey || "channel", key=statementAccountId)
|
|
6
|
+
*
|
|
7
|
+
* Where:
|
|
8
|
+
* - `statementAccountId` = the host's sr25519 device public key (32 bytes)
|
|
9
|
+
* - `encryptionPublicKey` = the host's P-256 device public key (65 bytes uncompressed)
|
|
10
|
+
*
|
|
11
|
+
* Both sides compute the same topic/channel deterministically from the same
|
|
12
|
+
* pubkeys carried in the QR-coded `VersionedHandshakeProposal::V2`, so they
|
|
13
|
+
* agree on where the response statement is delivered without any shared
|
|
14
|
+
* secret negotiation.
|
|
15
|
+
*/
|
|
16
|
+
import { khash } from '@novasamatech/statement-store';
|
|
17
|
+
import { mergeUint8 } from '@polkadot-api/utils';
|
|
18
|
+
const TOPIC_SUFFIX = new TextEncoder().encode('topic');
|
|
19
|
+
const CHANNEL_SUFFIX = new TextEncoder().encode('channel');
|
|
20
|
+
export const computePairingTopic = (statementAccountId, encryptionPublicKey) => khash(statementAccountId, mergeUint8([encryptionPublicKey, TOPIC_SUFFIX]));
|
|
21
|
+
export const computePairingChannel = (statementAccountId, encryptionPublicKey) => khash(statementAccountId, mergeUint8([encryptionPublicKey, CHANNEL_SUFFIX]));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
2
|
+
import type { ResultAsync } from 'neverthrow';
|
|
3
|
+
import type { SsSecret } from '../crypto.js';
|
|
4
|
+
import type { DeviceIdentityForPairing } from './auth/v2/service.js';
|
|
5
|
+
export type DeviceIdentity = DeviceIdentityForPairing & {
|
|
6
|
+
statementAccountSecret: SsSecret;
|
|
7
|
+
};
|
|
8
|
+
export type DeviceIdentityStore = {
|
|
9
|
+
loadOrCreate(): ResultAsync<DeviceIdentity, Error>;
|
|
10
|
+
readLastProcessedHandshakeStatement(): ResultAsync<string | null, Error>;
|
|
11
|
+
writeLastProcessedHandshakeStatement(hex: string): ResultAsync<void, Error>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createDeviceIdentityStore(salt: string, storage: StorageAdapter): DeviceIdentityStore;
|
|
14
|
+
export declare const awaitDeviceIdentity: (store: DeviceIdentityStore) => Promise<DeviceIdentity>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { gcm } from '@noble/ciphers/aes.js';
|
|
2
|
+
import { blake2b } from '@noble/hashes/blake2.js';
|
|
3
|
+
import { createSr25519Secret, deriveSr25519PublicKey } from '@novasamatech/statement-store';
|
|
4
|
+
import { errAsync, fromPromise, okAsync } from 'neverthrow';
|
|
5
|
+
import { fromHex, toHex } from 'polkadot-api/utils';
|
|
6
|
+
import { Bytes, Option, Struct, str } from 'scale-ts';
|
|
7
|
+
import { getEncrPub, stringToBytes } from '../crypto.js';
|
|
8
|
+
import { toError } from '../helpers/utils.js';
|
|
9
|
+
const KEY = 'DeviceIdentity';
|
|
10
|
+
// Persisted shape — kept under appId-derived AES-GCM at rest, same pattern as
|
|
11
|
+
// UserSecretRepository.
|
|
12
|
+
const StoredDeviceCodec = Struct({
|
|
13
|
+
statementAccountSeed: Bytes(32),
|
|
14
|
+
encryptionPrivateKey: Bytes(32),
|
|
15
|
+
lastProcessedHandshakeStatement: Option(str),
|
|
16
|
+
});
|
|
17
|
+
export function createDeviceIdentityStore(salt, storage) {
|
|
18
|
+
const aes = () => gcm(blake2b(stringToBytes(salt), { dkLen: 16 }), blake2b(stringToBytes('nonce'), { dkLen: 32 }));
|
|
19
|
+
const decode = (raw) => {
|
|
20
|
+
if (!raw)
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const decrypted = aes().decrypt(fromHex(raw));
|
|
24
|
+
return StoredDeviceCodec.dec(decrypted);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// 0.7.x had no DeviceIdentity key; any decode failure here means a
|
|
28
|
+
// schema rev or tampered blob — drop it and regenerate.
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const encode = (stored) => toHex(aes().encrypt(StoredDeviceCodec.enc(stored)));
|
|
33
|
+
const read = () => storage.read(KEY).map(decode);
|
|
34
|
+
const write = (stored) => storage.write(KEY, encode(stored)).map(() => undefined);
|
|
35
|
+
const expand = (stored) => {
|
|
36
|
+
const statementAccountSecret = createSr25519Secret(stored.statementAccountSeed);
|
|
37
|
+
const statementAccountPublicKey = deriveSr25519PublicKey(statementAccountSecret);
|
|
38
|
+
const encryptionPrivateKey = stored.encryptionPrivateKey;
|
|
39
|
+
const encryptionPublicKey = getEncrPub(encryptionPrivateKey);
|
|
40
|
+
return { statementAccountPublicKey, statementAccountSecret, encryptionPublicKey, encryptionPrivateKey };
|
|
41
|
+
};
|
|
42
|
+
const generate = () => ({
|
|
43
|
+
statementAccountSeed: crypto.getRandomValues(new Uint8Array(32)),
|
|
44
|
+
encryptionPrivateKey: crypto.getRandomValues(new Uint8Array(32)),
|
|
45
|
+
lastProcessedHandshakeStatement: undefined,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
loadOrCreate() {
|
|
49
|
+
return read().andThen(existing => {
|
|
50
|
+
if (existing)
|
|
51
|
+
return okAsync(expand(existing));
|
|
52
|
+
const fresh = generate();
|
|
53
|
+
return write(fresh).map(() => expand(fresh));
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
readLastProcessedHandshakeStatement() {
|
|
57
|
+
return read().map(stored => stored?.lastProcessedHandshakeStatement ?? null);
|
|
58
|
+
},
|
|
59
|
+
writeLastProcessedHandshakeStatement(hex) {
|
|
60
|
+
return read().andThen(existing => {
|
|
61
|
+
if (!existing) {
|
|
62
|
+
// No identity yet — caller will populate via loadOrCreate first.
|
|
63
|
+
return errAsync(new Error('writeLastProcessedHandshakeStatement: no device identity persisted'));
|
|
64
|
+
}
|
|
65
|
+
return write({ ...existing, lastProcessedHandshakeStatement: hex });
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Re-export the awaitable form for convenience, since most call sites already
|
|
71
|
+
// live in async functions.
|
|
72
|
+
export const awaitDeviceIdentity = (store) => fromPromise(Promise.resolve(), toError)
|
|
73
|
+
.andThen(() => store.loadOrCreate())
|
|
74
|
+
.match(ok => ok, err => {
|
|
75
|
+
throw err;
|
|
76
|
+
});
|
|
@@ -78,10 +78,7 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
78
78
|
to: JSON.stringify,
|
|
79
79
|
});
|
|
80
80
|
return {
|
|
81
|
-
|
|
82
|
-
localAccount: userSession.localAccount,
|
|
83
|
-
remoteAccount: userSession.remoteAccount,
|
|
84
|
-
rootAccountId: userSession.rootAccountId,
|
|
81
|
+
...userSession,
|
|
85
82
|
signPayload(payload) {
|
|
86
83
|
return requestQueue.call(() => {
|
|
87
84
|
const messageId = nanoid();
|
|
@@ -7,6 +7,7 @@ declare const StoredUserSecretsCodec: import("scale-ts").Codec<{
|
|
|
7
7
|
ssSecret: SsSecret;
|
|
8
8
|
encrSecret: EncrSecret;
|
|
9
9
|
entropy: Uint8Array<ArrayBufferLike>;
|
|
10
|
+
identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
|
|
10
11
|
}>;
|
|
11
12
|
export type UserSecretRepository = ReturnType<typeof createUserSecretRepository>;
|
|
12
13
|
export declare function createUserSecretRepository(salt: string, storage: StorageAdapter): {
|
|
@@ -9,11 +9,26 @@ const StoredUserSecretsCodec = Struct({
|
|
|
9
9
|
ssSecret: BrandedBytesCodec(),
|
|
10
10
|
encrSecret: BrandedBytesCodec(),
|
|
11
11
|
entropy: Bytes(),
|
|
12
|
+
// V2 addition: user identity chat private key (P-256 raw scalar, 32 bytes).
|
|
13
|
+
// Sensitive — kept encrypted at rest alongside the per-session ss/encr secrets.
|
|
14
|
+
identityChatPrivateKey: Bytes(32),
|
|
12
15
|
});
|
|
13
16
|
export function createUserSecretRepository(salt, storage) {
|
|
14
17
|
const baseKey = 'UserSecrets';
|
|
15
18
|
const encode = fromThrowable(StoredUserSecretsCodec.enc, toError);
|
|
16
|
-
const decode = fromThrowable((value) =>
|
|
19
|
+
const decode = fromThrowable((value) => {
|
|
20
|
+
if (!value)
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
return StoredUserSecretsCodec.dec(value);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// 0.7.x V1 blobs are missing `identityChatPrivateKey` and decode short.
|
|
27
|
+
// Treat as absent — a fresh handshake (required after 0.8.0 upgrade)
|
|
28
|
+
// will overwrite.
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}, toError);
|
|
17
32
|
const encrypt = fromThrowable((value) => {
|
|
18
33
|
const aes = getAes(salt);
|
|
19
34
|
return toHex(aes.encrypt(value));
|
|
@@ -15,8 +15,14 @@ declare const storedUserSessionCodec: import("scale-ts").Codec<{
|
|
|
15
15
|
pin: string | undefined;
|
|
16
16
|
};
|
|
17
17
|
rootAccountId: AccountId;
|
|
18
|
+
identityAccountId: AccountId | undefined;
|
|
19
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
18
20
|
}>;
|
|
19
|
-
|
|
21
|
+
type StoredUserSessionV2Extras = {
|
|
22
|
+
identityAccountId?: AccountId;
|
|
23
|
+
identityChatPublicKey?: Uint8Array;
|
|
24
|
+
};
|
|
25
|
+
export declare function createStoredUserSession(localAccount: LocalSessionAccount, remoteAccount: RemoteSessionAccount, rootAccountId: AccountId, extras?: StoredUserSessionV2Extras): StoredUserSession;
|
|
20
26
|
export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
21
27
|
add(value: {
|
|
22
28
|
id: string;
|
|
@@ -30,6 +36,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
30
36
|
pin: string | undefined;
|
|
31
37
|
};
|
|
32
38
|
rootAccountId: AccountId;
|
|
39
|
+
identityAccountId: AccountId | undefined;
|
|
40
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
33
41
|
}): import("neverthrow").ResultAsync<{
|
|
34
42
|
id: string;
|
|
35
43
|
localAccount: {
|
|
@@ -42,6 +50,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
42
50
|
pin: string | undefined;
|
|
43
51
|
};
|
|
44
52
|
rootAccountId: AccountId;
|
|
53
|
+
identityAccountId: AccountId | undefined;
|
|
54
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
45
55
|
}, Error>;
|
|
46
56
|
filter(fn: (value: {
|
|
47
57
|
id: string;
|
|
@@ -55,6 +65,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
55
65
|
pin: string | undefined;
|
|
56
66
|
};
|
|
57
67
|
rootAccountId: AccountId;
|
|
68
|
+
identityAccountId: AccountId | undefined;
|
|
69
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
58
70
|
}) => boolean): import("neverthrow").ResultAsync<{
|
|
59
71
|
id: string;
|
|
60
72
|
localAccount: {
|
|
@@ -67,6 +79,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
67
79
|
pin: string | undefined;
|
|
68
80
|
};
|
|
69
81
|
rootAccountId: AccountId;
|
|
82
|
+
identityAccountId: AccountId | undefined;
|
|
83
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
70
84
|
}[], Error>;
|
|
71
85
|
mutate(fn: (value: {
|
|
72
86
|
id: string;
|
|
@@ -80,6 +94,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
80
94
|
pin: string | undefined;
|
|
81
95
|
};
|
|
82
96
|
rootAccountId: AccountId;
|
|
97
|
+
identityAccountId: AccountId | undefined;
|
|
98
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
83
99
|
}[]) => {
|
|
84
100
|
id: string;
|
|
85
101
|
localAccount: {
|
|
@@ -92,6 +108,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
92
108
|
pin: string | undefined;
|
|
93
109
|
};
|
|
94
110
|
rootAccountId: AccountId;
|
|
111
|
+
identityAccountId: AccountId | undefined;
|
|
112
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
95
113
|
}[]): import("neverthrow").ResultAsync<{
|
|
96
114
|
id: string;
|
|
97
115
|
localAccount: {
|
|
@@ -104,6 +122,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
104
122
|
pin: string | undefined;
|
|
105
123
|
};
|
|
106
124
|
rootAccountId: AccountId;
|
|
125
|
+
identityAccountId: AccountId | undefined;
|
|
126
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
107
127
|
}[], Error>;
|
|
108
128
|
read(): import("neverthrow").ResultAsync<{
|
|
109
129
|
id: string;
|
|
@@ -117,6 +137,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
117
137
|
pin: string | undefined;
|
|
118
138
|
};
|
|
119
139
|
rootAccountId: AccountId;
|
|
140
|
+
identityAccountId: AccountId | undefined;
|
|
141
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
120
142
|
}[], Error>;
|
|
121
143
|
write(value: {
|
|
122
144
|
id: string;
|
|
@@ -130,6 +152,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
130
152
|
pin: string | undefined;
|
|
131
153
|
};
|
|
132
154
|
rootAccountId: AccountId;
|
|
155
|
+
identityAccountId: AccountId | undefined;
|
|
156
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
133
157
|
}[]): import("neverthrow").ResultAsync<null, Error> | import("neverthrow").ResultAsync<{
|
|
134
158
|
id: string;
|
|
135
159
|
localAccount: {
|
|
@@ -142,6 +166,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
142
166
|
pin: string | undefined;
|
|
143
167
|
};
|
|
144
168
|
rootAccountId: AccountId;
|
|
169
|
+
identityAccountId: AccountId | undefined;
|
|
170
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
145
171
|
}[], Error>;
|
|
146
172
|
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
147
173
|
subscribe(fn: (value: {
|
|
@@ -156,6 +182,8 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
156
182
|
pin: string | undefined;
|
|
157
183
|
};
|
|
158
184
|
rootAccountId: AccountId;
|
|
185
|
+
identityAccountId: AccountId | undefined;
|
|
186
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike> | undefined;
|
|
159
187
|
}[]) => void): VoidFunction;
|
|
160
188
|
};
|
|
161
189
|
export {};
|
|
@@ -2,19 +2,25 @@ import { AccountIdCodec, LocalSessionAccountCodec, RemoteSessionAccountCodec } f
|
|
|
2
2
|
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
4
|
import { fromHex, toHex } from 'polkadot-api/utils';
|
|
5
|
-
import { Struct, Vector, str } from 'scale-ts';
|
|
5
|
+
import { Bytes, Option, Struct, Vector, str } from 'scale-ts';
|
|
6
|
+
// V2 fields trail V1 fields so a future schema rev can append further
|
|
7
|
+
// `Option`-wrapped fields without breaking decode of 0.8.0 blobs.
|
|
6
8
|
const storedUserSessionCodec = Struct({
|
|
7
9
|
id: str,
|
|
8
10
|
localAccount: LocalSessionAccountCodec,
|
|
9
11
|
remoteAccount: RemoteSessionAccountCodec,
|
|
10
12
|
rootAccountId: AccountIdCodec,
|
|
13
|
+
identityAccountId: Option(AccountIdCodec),
|
|
14
|
+
identityChatPublicKey: Option(Bytes(65)),
|
|
11
15
|
});
|
|
12
|
-
export function createStoredUserSession(localAccount, remoteAccount, rootAccountId) {
|
|
16
|
+
export function createStoredUserSession(localAccount, remoteAccount, rootAccountId, extras = {}) {
|
|
13
17
|
return {
|
|
14
18
|
id: nanoid(12),
|
|
15
|
-
localAccount
|
|
16
|
-
remoteAccount
|
|
19
|
+
localAccount,
|
|
20
|
+
remoteAccount,
|
|
17
21
|
rootAccountId,
|
|
22
|
+
identityAccountId: extras.identityAccountId,
|
|
23
|
+
identityChatPublicKey: extras.identityChatPublicKey,
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
export const createUserSessionRepository = (storage) => {
|
|
@@ -22,7 +28,18 @@ export const createUserSessionRepository = (storage) => {
|
|
|
22
28
|
return fieldListView({
|
|
23
29
|
storage,
|
|
24
30
|
key: 'SsoSessions',
|
|
25
|
-
from: x =>
|
|
31
|
+
from: x => {
|
|
32
|
+
try {
|
|
33
|
+
return codec.dec(fromHex(x));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// 0.7.x V1 blobs use the prior codec shape and won't decode against
|
|
37
|
+
// V2's extended struct. Treat as empty so the caller (and the
|
|
38
|
+
// fieldListView mutate machinery) start clean; the next write
|
|
39
|
+
// overwrites the bad blob.
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
},
|
|
26
43
|
to: x => toHex(codec.enc(x)),
|
|
27
44
|
});
|
|
28
45
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-papp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0-0",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -34,16 +34,19 @@
|
|
|
34
34
|
"@noble/ciphers": "2.2.0",
|
|
35
35
|
"@noble/curves": "2.2.0",
|
|
36
36
|
"@noble/hashes": "2.2.0",
|
|
37
|
-
"@novasamatech/host-api": "0.
|
|
38
|
-
"@novasamatech/scale": "0.
|
|
39
|
-
"@novasamatech/statement-store": "0.
|
|
40
|
-
"@novasamatech/storage-adapter": "0.
|
|
37
|
+
"@novasamatech/host-api": "0.8.0-0",
|
|
38
|
+
"@novasamatech/scale": "0.8.0-0",
|
|
39
|
+
"@novasamatech/statement-store": "0.8.0-0",
|
|
40
|
+
"@novasamatech/storage-adapter": "0.8.0-0",
|
|
41
|
+
"@polkadot-api/utils": "^0.4.0",
|
|
41
42
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|
|
42
43
|
"nanoevents": "9.1.0",
|
|
43
44
|
"nanoid": "5.1.9",
|
|
44
45
|
"neverthrow": "^8.2.0",
|
|
45
46
|
"polkadot-api": ">=2",
|
|
46
|
-
"
|
|
47
|
+
"rxjs": "^7.8.2",
|
|
48
|
+
"scale-ts": "1.6.1",
|
|
49
|
+
"verifiablejs": "1.2.0"
|
|
47
50
|
},
|
|
48
51
|
"publishConfig": {
|
|
49
52
|
"access": "public"
|