@novasamatech/host-papp 0.5.0-1 → 0.5.0-10
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/.papi/descriptors/dist/common-types.d.ts +8667 -0
- package/.papi/descriptors/dist/common.d.ts +1 -0
- package/.papi/descriptors/dist/descriptors-UUEW32EL.mjs +27 -0
- package/.papi/descriptors/dist/descriptors.d.ts +1 -0
- package/.papi/descriptors/dist/index.d.ts +10 -0
- package/.papi/descriptors/dist/index.js +237 -0
- package/.papi/descriptors/dist/index.mjs +148 -0
- package/.papi/descriptors/dist/metadataTypes-E4AQJDJR.mjs +6 -0
- package/.papi/descriptors/dist/metadataTypes.d.ts +2 -0
- package/.papi/descriptors/dist/people_lite.d.ts +7757 -0
- package/.papi/descriptors/dist/people_lite_metadata-EIVHV27X.mjs +6 -0
- package/.papi/descriptors/dist/people_lite_metadata.d.ts +2 -0
- package/.papi/descriptors/package.json +24 -0
- package/.papi/metadata/people_lite.scale +0 -0
- package/.papi/polkadot-api.json +15 -0
- package/dist/adapters/identity/rpc.d.ts +6 -4
- package/dist/adapters/identity/rpc.js +96 -26
- package/dist/adapters/identity/types.d.ts +3 -1
- package/dist/adapters/lazyClient/papi.js +5 -0
- package/dist/adapters/lazyClient/types.d.ts +1 -0
- package/dist/adapters/statement/rpc.js +58 -10
- package/dist/adapters/statement/types.d.ts +6 -3
- package/dist/adapters/storage/localStorage.js +26 -4
- package/dist/adapters/storage/memory.js +14 -4
- package/dist/adapters/storage/types.d.ts +5 -2
- package/dist/adapters/storage/types.js +1 -1
- package/dist/components/auth/codec.d.ts +9 -0
- package/dist/components/auth/codec.js +10 -0
- package/dist/components/auth/codecs.d.ts +9 -0
- package/dist/components/auth/codecs.js +10 -0
- package/dist/components/auth/index.d.ts +36 -0
- package/dist/components/auth/index.js +150 -0
- package/dist/components/auth/types.d.ts +15 -0
- package/dist/components/auth/types.js +1 -0
- package/dist/components/session.d.ts +34 -0
- package/dist/components/session.js +54 -0
- package/dist/components/sso/index.d.ts +36 -0
- package/dist/components/sso/index.js +150 -0
- package/dist/components/sso/scale/handshake.d.ts +9 -0
- package/dist/components/sso/scale/handshake.js +10 -0
- package/dist/components/sso/types.d.ts +15 -0
- package/dist/components/sso/types.js +1 -0
- package/dist/components/transport.d.ts +27 -0
- package/dist/components/transport.js +57 -0
- package/dist/components/user/codec.d.ts +16 -0
- package/dist/components/user/codec.js +13 -0
- package/dist/components/user/index.d.ts +22 -0
- package/dist/components/user/index.js +58 -0
- package/dist/components/user/ssoMessageStream.d.ts +10 -0
- package/dist/components/user/ssoMessageStream.js +8 -0
- package/dist/components/user/ssoSession.d.ts +5 -0
- package/dist/components/user/ssoSession.js +5 -0
- package/dist/components/user/storage.d.ts +27 -0
- package/dist/components/user/storage.js +143 -0
- package/dist/components/user/types.d.ts +6 -0
- package/dist/components/user/types.js +1 -0
- package/dist/components/user/userSessionStorage.d.ts +20 -0
- package/dist/components/user/userSessionStorage.js +24 -0
- package/dist/components/user.d.ts +74 -0
- package/dist/components/user.js +188 -0
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +5 -1
- package/dist/crypto.d.ts +29 -0
- package/dist/crypto.js +86 -0
- package/dist/helpers/abortError.d.ts +4 -0
- package/dist/helpers/abortError.js +8 -0
- package/dist/helpers/callbackRaceResolver.d.ts +1 -0
- package/dist/helpers/callbackRaceResolver.js +17 -0
- package/dist/helpers/result.d.ts +12 -0
- package/dist/helpers/result.js +15 -0
- package/dist/helpers/result.spec.d.ts +1 -0
- package/dist/helpers/result.spec.js +23 -0
- package/dist/helpers/state.d.ts +16 -0
- package/dist/helpers/state.js +51 -0
- package/dist/helpers/utils.d.ts +2 -1
- package/dist/helpers/utils.js +11 -2
- package/dist/helpers/zipWith.d.ts +4 -0
- package/dist/helpers/zipWith.js +11 -0
- package/dist/identity/impl.d.ts +6 -0
- package/dist/identity/impl.js +68 -0
- package/dist/identity/rpcAdapter.d.ts +3 -0
- package/dist/identity/rpcAdapter.js +46 -0
- package/dist/identity/types.d.ts +21 -0
- package/dist/identity/types.js +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +2 -7
- package/dist/modules/crypto.d.ts +8 -9
- package/dist/modules/crypto.js +20 -42
- package/dist/modules/secretStorage.d.ts +13 -12
- package/dist/modules/secretStorage.js +34 -43
- package/dist/modules/session/helpers.d.ts +5 -0
- package/dist/modules/session/helpers.js +29 -0
- package/dist/modules/session/session.d.ts +12 -0
- package/dist/modules/session/session.js +50 -0
- package/dist/modules/session/types.d.ts +12 -0
- package/dist/modules/session/types.js +1 -0
- package/dist/modules/signIn.d.ts +32 -11
- package/dist/modules/signIn.js +97 -101
- package/dist/modules/state.d.ts +16 -0
- package/dist/modules/state.js +50 -0
- package/dist/modules/statementStore.d.ts +10 -11
- package/dist/modules/statementStore.js +16 -14
- package/dist/modules/statementTopic.d.ts +34 -0
- package/dist/modules/statementTopic.js +46 -0
- package/dist/modules/storageView.d.ts +25 -0
- package/dist/modules/storageView.js +51 -0
- package/dist/modules/syncStorage.d.ts +25 -0
- package/dist/modules/syncStorage.js +76 -0
- package/dist/modules/transport/codec.d.ts +24 -0
- package/dist/modules/transport/codec.js +36 -0
- package/dist/modules/transport/crypto.d.ts +2 -0
- package/dist/modules/transport/crypto.js +20 -0
- package/dist/modules/transport/transport.d.ts +42 -0
- package/dist/modules/transport/transport.js +66 -0
- package/dist/modules/user.d.ts +67 -0
- package/dist/modules/user.js +188 -0
- package/dist/modules/userManager.d.ts +15 -0
- package/dist/modules/userManager.js +105 -0
- package/dist/modules/userStorage.d.ts +19 -0
- package/dist/modules/userStorage.js +108 -0
- package/dist/modules/userStore.d.ts +15 -0
- package/dist/modules/userStore.js +105 -0
- package/dist/papp.d.ts +25 -13
- package/dist/papp.js +19 -50
- package/dist/sso/auth/attestationService.d.ts +18 -0
- package/dist/sso/auth/attestationService.js +171 -0
- package/dist/sso/auth/impl.d.ts +53 -0
- package/dist/sso/auth/impl.js +161 -0
- package/dist/sso/auth/scale/handshake.d.ts +9 -0
- package/dist/sso/auth/scale/handshake.js +10 -0
- package/dist/sso/auth/types.d.ts +17 -0
- package/dist/sso/auth/types.js +1 -0
- package/dist/sso/session/impl.d.ts +23 -0
- package/dist/sso/session/impl.js +57 -0
- package/dist/sso/session/scale/remoteMessage.d.ts +10 -0
- package/dist/sso/session/scale/remoteMessage.js +13 -0
- package/dist/sso/session/sessionManager.d.ts +23 -0
- package/dist/sso/session/sessionManager.js +58 -0
- package/dist/sso/session/ssoSession.d.ts +8 -0
- package/dist/sso/session/ssoSession.js +5 -0
- package/dist/sso/session/ssoSessionStorage.d.ts +21 -0
- package/dist/sso/session/ssoSessionStorage.js +20 -0
- package/dist/sso/session/types.d.ts +6 -0
- package/dist/sso/session/types.js +1 -0
- package/dist/sso/session/userSessionStorage.d.ts +21 -0
- package/dist/sso/session/userSessionStorage.js +20 -0
- package/dist/sso/sessionManager/attestationService.d.ts +5 -0
- package/dist/sso/sessionManager/attestationService.js +15 -0
- package/dist/sso/sessionManager/impl.d.ts +22 -0
- package/dist/sso/sessionManager/impl.js +71 -0
- package/dist/sso/sessionManager/repository/ssoSessionRepository.d.ts +22 -0
- package/dist/sso/sessionManager/repository/ssoSessionRepository.js +27 -0
- package/dist/sso/sessionManager/scale/hex.d.ts +1 -0
- package/dist/sso/sessionManager/scale/hex.js +3 -0
- package/dist/sso/sessionManager/scale/remoteMessage.d.ts +41 -0
- package/dist/sso/sessionManager/scale/remoteMessage.js +13 -0
- package/dist/sso/sessionManager/scale/signPayloadRequest.d.ts +19 -0
- package/dist/sso/sessionManager/scale/signPayloadRequest.js +19 -0
- package/dist/sso/sessionManager/scale/signPayloadResponse.d.ts +12 -0
- package/dist/sso/sessionManager/scale/signPayloadResponse.js +9 -0
- package/dist/sso/sessionManager/scale/signRequest.d.ts +19 -0
- package/dist/sso/sessionManager/scale/signRequest.js +19 -0
- package/dist/sso/sessionManager/scale/signResponse.d.ts +6 -0
- package/dist/sso/sessionManager/scale/signResponse.js +5 -0
- package/dist/sso/sessionManager/ssoSession.d.ts +23 -0
- package/dist/sso/sessionManager/ssoSession.js +69 -0
- package/dist/sso/sessionManager/ssoSessionProver.d.ts +4 -0
- package/dist/sso/sessionManager/ssoSessionProver.js +35 -0
- package/dist/sso/sessionManager/types.d.ts +6 -0
- package/dist/sso/sessionManager/types.js +1 -0
- package/dist/sso/sessionManager/userSession.d.ts +22 -0
- package/dist/sso/sessionManager/userSession.js +106 -0
- package/dist/sso/ssoSessionProver.d.ts +4 -0
- package/dist/sso/ssoSessionProver.js +35 -0
- package/dist/sso/ssoSessionRepository.d.ts +18 -0
- package/dist/sso/ssoSessionRepository.js +27 -0
- package/dist/sso/userSecretRepository.d.ts +17 -0
- package/dist/sso/userSecretRepository.js +45 -0
- package/dist/sso/userSessionRepository.d.ts +18 -0
- package/dist/sso/userSessionRepository.js +26 -0
- package/dist/structs.d.ts +10 -10
- package/dist/structs.js +17 -13
- package/dist/types.d.ts +1 -1
- package/package.json +14 -7
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { okAsync } from 'neverthrow';
|
|
2
|
+
import { nonNullable } from '../helpers/utils.js';
|
|
3
|
+
import { createState } from './state.js';
|
|
4
|
+
export function storageView({ storage, initial, key, from, to, autosync = true }) {
|
|
5
|
+
const state = createState(to(initial));
|
|
6
|
+
const enhancedStorage = {
|
|
7
|
+
read() {
|
|
8
|
+
return storage
|
|
9
|
+
.read(key)
|
|
10
|
+
.map(state.write)
|
|
11
|
+
.map(x => (nonNullable(x) ? from(x) : initial));
|
|
12
|
+
},
|
|
13
|
+
write(value) {
|
|
14
|
+
const data = to(value);
|
|
15
|
+
if (data !== null) {
|
|
16
|
+
return storage
|
|
17
|
+
.write(key, data)
|
|
18
|
+
.map(() => state.write(data))
|
|
19
|
+
.map(() => value);
|
|
20
|
+
}
|
|
21
|
+
return okAsync(null);
|
|
22
|
+
},
|
|
23
|
+
clear() {
|
|
24
|
+
return storage.clear(key).map(() => state.reset());
|
|
25
|
+
},
|
|
26
|
+
subscribe(fn) {
|
|
27
|
+
return state.subscribe(x => fn(nonNullable(x) ? from(x) : initial));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
if (autosync) {
|
|
31
|
+
enhancedStorage.read();
|
|
32
|
+
}
|
|
33
|
+
state.onFirstSubscribe(() => enhancedStorage.read());
|
|
34
|
+
return enhancedStorage;
|
|
35
|
+
}
|
|
36
|
+
export function storageListView(params) {
|
|
37
|
+
const view = storageView(params);
|
|
38
|
+
const listView = {
|
|
39
|
+
...view,
|
|
40
|
+
add(value) {
|
|
41
|
+
return listView.mutate(list => list.concat(value)).map(() => value);
|
|
42
|
+
},
|
|
43
|
+
mutate(fn) {
|
|
44
|
+
return listView.read().andThen(list => {
|
|
45
|
+
const result = fn(list);
|
|
46
|
+
return listView.write(result).map(() => result);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return listView;
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../adapters/storage/types.js';
|
|
2
|
+
export declare function createSyncStorage<T>(initial: T): {
|
|
3
|
+
touched(): boolean;
|
|
4
|
+
read(): T;
|
|
5
|
+
write(value: T): T;
|
|
6
|
+
reset(): void;
|
|
7
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
8
|
+
onFirstSubscribe(callback: VoidFunction): import("nanoevents").Unsubscribe;
|
|
9
|
+
onLastUnsubscribe(callback: VoidFunction): import("nanoevents").Unsubscribe;
|
|
10
|
+
};
|
|
11
|
+
type ReactiveStorageParams<T> = {
|
|
12
|
+
storage: StorageAdapter;
|
|
13
|
+
key: string;
|
|
14
|
+
initial: T;
|
|
15
|
+
autosync: boolean;
|
|
16
|
+
from(value: string): T;
|
|
17
|
+
to(value: T): string | null;
|
|
18
|
+
};
|
|
19
|
+
export declare function reactiveStorage<T>({ storage, initial, key, autosync, from, to }: ReactiveStorageParams<T>): {
|
|
20
|
+
read(): Promise<import("../helpers/result.js").Result<T, Error>>;
|
|
21
|
+
write(value: T): Promise<import("../helpers/result.js").Result<null, Error> | import("../helpers/result.js").Result<T, Error>>;
|
|
22
|
+
clear(): Promise<import("../helpers/result.js").Result<void, Error>>;
|
|
23
|
+
subscribe(fn: (value: T | null) => void): () => void;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
import { ok } from '../helpers/result.js';
|
|
3
|
+
export function createSyncStorage(initial) {
|
|
4
|
+
const events = createNanoEvents();
|
|
5
|
+
let touched = false;
|
|
6
|
+
let currentValue = initial;
|
|
7
|
+
return {
|
|
8
|
+
touched() {
|
|
9
|
+
return touched;
|
|
10
|
+
},
|
|
11
|
+
read() {
|
|
12
|
+
return currentValue;
|
|
13
|
+
},
|
|
14
|
+
write(value) {
|
|
15
|
+
if (currentValue !== value) {
|
|
16
|
+
touched = true;
|
|
17
|
+
currentValue = value;
|
|
18
|
+
events.emit('value', value);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
},
|
|
22
|
+
reset() {
|
|
23
|
+
if (currentValue !== initial) {
|
|
24
|
+
touched = true;
|
|
25
|
+
currentValue = initial;
|
|
26
|
+
events.emit('value', initial);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
subscribe(fn) {
|
|
30
|
+
if (!events.events.value || events.events.value.length === 0) {
|
|
31
|
+
events.emit('first');
|
|
32
|
+
}
|
|
33
|
+
const unsubscribe = events.on('value', fn);
|
|
34
|
+
return () => {
|
|
35
|
+
unsubscribe();
|
|
36
|
+
if (!events.events.value || events.events.value.length === 0) {
|
|
37
|
+
events.emit('last');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
onFirstSubscribe(callback) {
|
|
42
|
+
return events.on('first', callback);
|
|
43
|
+
},
|
|
44
|
+
onLastUnsubscribe(callback) {
|
|
45
|
+
return events.on('last', callback);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function reactiveStorage({ storage, initial, key, autosync, from, to }) {
|
|
50
|
+
const sync = createSyncStorage(initial);
|
|
51
|
+
const enhancedStorage = {
|
|
52
|
+
async read() {
|
|
53
|
+
const result = await storage.read(key);
|
|
54
|
+
return result.map(x => sync.write(x === null ? initial : from(x)));
|
|
55
|
+
},
|
|
56
|
+
async write(value) {
|
|
57
|
+
const data = to(value);
|
|
58
|
+
if (data !== null) {
|
|
59
|
+
const result = await storage.write(key, data);
|
|
60
|
+
return result.map(() => sync.write(value));
|
|
61
|
+
}
|
|
62
|
+
return ok(null);
|
|
63
|
+
},
|
|
64
|
+
async clear() {
|
|
65
|
+
const result = await storage.clear(key);
|
|
66
|
+
return result.map(() => sync.reset());
|
|
67
|
+
},
|
|
68
|
+
subscribe(fn) {
|
|
69
|
+
return sync.subscribe(fn);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
if (autosync) {
|
|
73
|
+
sync.onFirstSubscribe(() => enhancedStorage.read());
|
|
74
|
+
}
|
|
75
|
+
return enhancedStorage;
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Codec } from 'scale-ts';
|
|
2
|
+
export type TransportError = 'decryptionFailed' | 'decodingFailed' | 'unknown';
|
|
3
|
+
export declare const TransportErrorCodec: Codec<TransportError>;
|
|
4
|
+
export declare const Request: <T>(data: Codec<T>) => Codec<{
|
|
5
|
+
requestId: string;
|
|
6
|
+
data: T[];
|
|
7
|
+
}>;
|
|
8
|
+
export declare const Response: Codec<{
|
|
9
|
+
requestId: string;
|
|
10
|
+
responseCode: TransportError;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const StatementData: <T>(data: Codec<T>) => Codec<{
|
|
13
|
+
tag: "request";
|
|
14
|
+
value: {
|
|
15
|
+
requestId: string;
|
|
16
|
+
data: T[];
|
|
17
|
+
};
|
|
18
|
+
} | {
|
|
19
|
+
tag: "response";
|
|
20
|
+
value: {
|
|
21
|
+
requestId: string;
|
|
22
|
+
responseCode: TransportError;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Enum, Struct, Vector, enhanceCodec, str, u8 } from 'scale-ts';
|
|
2
|
+
export const TransportErrorCodec = enhanceCodec(u8, error => {
|
|
3
|
+
switch (error) {
|
|
4
|
+
case 'decryptionFailed':
|
|
5
|
+
return 1;
|
|
6
|
+
case 'decodingFailed':
|
|
7
|
+
return 2;
|
|
8
|
+
case 'unknown':
|
|
9
|
+
return 255;
|
|
10
|
+
}
|
|
11
|
+
}, code => {
|
|
12
|
+
switch (code) {
|
|
13
|
+
case 1:
|
|
14
|
+
return 'decryptionFailed';
|
|
15
|
+
case 2:
|
|
16
|
+
return 'decodingFailed';
|
|
17
|
+
default:
|
|
18
|
+
return 'unknown';
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
export const Request = (data) => {
|
|
22
|
+
return Struct({
|
|
23
|
+
requestId: str,
|
|
24
|
+
data: Vector(data),
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
export const Response = Struct({
|
|
28
|
+
requestId: str,
|
|
29
|
+
responseCode: TransportErrorCodec,
|
|
30
|
+
});
|
|
31
|
+
export const StatementData = (data) => {
|
|
32
|
+
return Enum({
|
|
33
|
+
request: Request(data),
|
|
34
|
+
response: Response,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { gcm } from '@noble/ciphers/aes.js';
|
|
2
|
+
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
|
+
import { randomBytes } from '@noble/hashes/utils.js';
|
|
5
|
+
import { mergeBytes } from '../crypto.js';
|
|
6
|
+
export function encrypt(secret, cipherText) {
|
|
7
|
+
const nonce = randomBytes(12);
|
|
8
|
+
const salt = new Uint8Array(); // secure enough since P256 random keys provide enough entropy
|
|
9
|
+
const info = new Uint8Array(); // no need to introduce any context
|
|
10
|
+
const aesKey = hkdf(sha256, secret, salt, info, 32);
|
|
11
|
+
const aes = gcm(aesKey, nonce);
|
|
12
|
+
return aes.encrypt(mergeBytes(nonce, cipherText));
|
|
13
|
+
}
|
|
14
|
+
export function decrypt(secret, message) {
|
|
15
|
+
const nonce = message.slice(0, 12);
|
|
16
|
+
const cipherText = message.slice(12);
|
|
17
|
+
const aesKey = hkdf(sha256, secret, new Uint8Array(), new Uint8Array(), 32);
|
|
18
|
+
const aes = gcm(aesKey, nonce);
|
|
19
|
+
return aes.decrypt(cipherText);
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Statement } from '@polkadot-api/sdk-statement';
|
|
2
|
+
import type { ResultAsync } from 'neverthrow';
|
|
3
|
+
import type { Codec } from 'scale-ts';
|
|
4
|
+
import type { StatementAdapter } from '../../adapters/statement/types.js';
|
|
5
|
+
import type { Callback } from '../../types.js';
|
|
6
|
+
import type { SsSecret } from '../crypto.js';
|
|
7
|
+
import type { Account } from '../session/types.js';
|
|
8
|
+
import type { TransportError } from './codec.js';
|
|
9
|
+
export type Transport = ReturnType<typeof createTransport>;
|
|
10
|
+
type RequestMessage<T> = {
|
|
11
|
+
type: 'request';
|
|
12
|
+
requestId: string;
|
|
13
|
+
data: T;
|
|
14
|
+
};
|
|
15
|
+
type ResponseMessage = {
|
|
16
|
+
type: 'response';
|
|
17
|
+
requestId: string;
|
|
18
|
+
code: TransportError;
|
|
19
|
+
};
|
|
20
|
+
type Message<T> = RequestMessage<T> | ResponseMessage;
|
|
21
|
+
type Params = {
|
|
22
|
+
adapter: StatementAdapter;
|
|
23
|
+
};
|
|
24
|
+
export declare function createTransport({ adapter }: Params): {
|
|
25
|
+
subscribeSession(sessionId: Uint8Array, callback: Callback<Statement[]>): VoidFunction;
|
|
26
|
+
subscribe<T>({ ownAccount, peerAccount, codec, }: {
|
|
27
|
+
ownAccount: Account;
|
|
28
|
+
peerAccount: Account;
|
|
29
|
+
codec: Codec<T>;
|
|
30
|
+
}, callback: Callback<Message<T>[]>): VoidFunction;
|
|
31
|
+
handleRequest<T>(params: {
|
|
32
|
+
ownAccount: Account;
|
|
33
|
+
peerAccount: Account;
|
|
34
|
+
codec: Codec<T>;
|
|
35
|
+
}, callback: Callback<RequestMessage<T>>): VoidFunction;
|
|
36
|
+
submitRequest({ ownAccount, peerAccount, secret }: {
|
|
37
|
+
ownAccount: Account;
|
|
38
|
+
peerAccount: Account;
|
|
39
|
+
secret: SsSecret;
|
|
40
|
+
}, message: Uint8Array): ResultAsync<void, Error>;
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Result, fromThrowable, ok } from 'neverthrow';
|
|
2
|
+
import { nonNullable, toError } from '../../helpers/utils.js';
|
|
3
|
+
import { decrypt } from '../crypto.js';
|
|
4
|
+
import { createRequestChannel, createSessionId } from '../session/helpers.js';
|
|
5
|
+
import { createStatement } from '../statementStore.js';
|
|
6
|
+
import { StatementData } from './codec.js';
|
|
7
|
+
const decryptResults = fromThrowable(decrypt, toError);
|
|
8
|
+
function mapMessage(statementData) {
|
|
9
|
+
switch (statementData.tag) {
|
|
10
|
+
case 'request':
|
|
11
|
+
return statementData.value.data.map((data, index) => ({
|
|
12
|
+
type: 'request',
|
|
13
|
+
requestId: `${statementData.value.requestId}-${index.toString()}`,
|
|
14
|
+
data,
|
|
15
|
+
}));
|
|
16
|
+
case 'response':
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
type: 'response',
|
|
20
|
+
requestId: statementData.value.requestId,
|
|
21
|
+
code: statementData.value.responseCode,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function createTransport({ adapter }) {
|
|
27
|
+
const transport = {
|
|
28
|
+
subscribeSession(sessionId, callback) {
|
|
29
|
+
return adapter.subscribeStatements([sessionId], callback);
|
|
30
|
+
},
|
|
31
|
+
subscribe({ ownAccount, peerAccount, codec, }, callback) {
|
|
32
|
+
const sessionId = createSessionId(peerAccount.publicKey, peerAccount, ownAccount);
|
|
33
|
+
const statementDataCodec = StatementData(codec);
|
|
34
|
+
return adapter.subscribeStatements([sessionId], statements => {
|
|
35
|
+
Result.combine(statements.map(statement => {
|
|
36
|
+
if (!statement.data)
|
|
37
|
+
return ok(null);
|
|
38
|
+
return decryptResults(peerAccount.publicKey, statement.data.asBytes())
|
|
39
|
+
.map(statementDataCodec.dec)
|
|
40
|
+
.orElse(() => ok(null));
|
|
41
|
+
}))
|
|
42
|
+
.map(messages => messages.filter(nonNullable).flatMap(mapMessage))
|
|
43
|
+
.andTee(messages => {
|
|
44
|
+
if (messages.length > 0) {
|
|
45
|
+
callback(messages);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
handleRequest(params, callback) {
|
|
51
|
+
return transport.subscribe(params, messages => {
|
|
52
|
+
messages.filter(m => m.type === 'request').forEach(callback);
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
submitRequest({ ownAccount, peerAccount, secret }, message) {
|
|
56
|
+
const sessionId = createSessionId(peerAccount.publicKey, ownAccount, peerAccount);
|
|
57
|
+
return createStatement(secret, {
|
|
58
|
+
channel: createRequestChannel(sessionId),
|
|
59
|
+
priority: 0,
|
|
60
|
+
topics: [sessionId],
|
|
61
|
+
data: message,
|
|
62
|
+
}).andThen(adapter.submitStatement);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return transport;
|
|
66
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { StatementAdapter } from '../adapters/statement/types.js';
|
|
2
|
+
import type { StorageAdapter } from '../adapters/storage/types.js';
|
|
3
|
+
import type { Result } from '../helpers/result.js';
|
|
4
|
+
import type { EncrPublicKey, SsPublicKey } from './crypto.js';
|
|
5
|
+
import type { UserSession } from './userStore.js';
|
|
6
|
+
export declare const HandshakeData: import("scale-ts").Codec<{
|
|
7
|
+
tag: "V1";
|
|
8
|
+
value: [SsPublicKey, EncrPublicKey, string];
|
|
9
|
+
}>;
|
|
10
|
+
export declare const HandshakeResponsePayload: import("scale-ts").Codec<{
|
|
11
|
+
tag: "V1";
|
|
12
|
+
value: [Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>];
|
|
13
|
+
}>;
|
|
14
|
+
export declare const HandshakeResponseSensitiveData: import("scale-ts").Codec<[Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>]>;
|
|
15
|
+
export type AuthentificationStatus = {
|
|
16
|
+
step: 'none';
|
|
17
|
+
} | {
|
|
18
|
+
step: 'initial';
|
|
19
|
+
} | {
|
|
20
|
+
step: 'pairing';
|
|
21
|
+
payload: string;
|
|
22
|
+
} | {
|
|
23
|
+
step: 'error';
|
|
24
|
+
message: string;
|
|
25
|
+
} | {
|
|
26
|
+
step: 'finished';
|
|
27
|
+
user: UserSession;
|
|
28
|
+
};
|
|
29
|
+
type Params = {
|
|
30
|
+
/**
|
|
31
|
+
* Host app Id.
|
|
32
|
+
* CAUTION! This value should be stable.
|
|
33
|
+
*/
|
|
34
|
+
appId: string;
|
|
35
|
+
/**
|
|
36
|
+
* URL for additional metadata that will be displayed during pairing process.
|
|
37
|
+
* Content of provided json shound be
|
|
38
|
+
* ```ts
|
|
39
|
+
* interface Metadata {
|
|
40
|
+
* name: string;
|
|
41
|
+
* icon: string; // url for icon. Icon should be a rasterized image with min size 256x256 px.
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
metadata: string;
|
|
46
|
+
statements: StatementAdapter;
|
|
47
|
+
storage: StorageAdapter;
|
|
48
|
+
};
|
|
49
|
+
export declare function createUserModule({ appId, metadata, statements, storage }: Params): {
|
|
50
|
+
authStatus: {
|
|
51
|
+
read(): AuthentificationStatus;
|
|
52
|
+
write(value: AuthentificationStatus): void;
|
|
53
|
+
reset(): void;
|
|
54
|
+
subscribe(fn: (value: AuthentificationStatus) => void): import("nanoevents").Unsubscribe;
|
|
55
|
+
};
|
|
56
|
+
storage: {
|
|
57
|
+
readSelectedUserSession(): Promise<Result<UserSession | null>>;
|
|
58
|
+
readUserSession(accountId: string): Promise<Result<UserSession | null>>;
|
|
59
|
+
createUserSession(user: UserSession): Promise<Result<UserSession>>;
|
|
60
|
+
removeUserSession(accountId: string): Promise<Result<void, Error>>;
|
|
61
|
+
readAccounts(): Promise<Result<string[], Error>>;
|
|
62
|
+
selectAccount(accountId: string): Promise<Result<void, Error>>;
|
|
63
|
+
};
|
|
64
|
+
authenticate(): Promise<Result<UserSession | null>>;
|
|
65
|
+
abortAuthentication(): void;
|
|
66
|
+
};
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { toHex } from '@polkadot-api/utils';
|
|
2
|
+
import { Bytes, Enum, Tuple, str } from 'scale-ts';
|
|
3
|
+
import { err, fromPromise, ok, seq } from '../helpers/result.js';
|
|
4
|
+
import { isAbortError, toError } from '../helpers/utils.js';
|
|
5
|
+
import { ENCR_SECRET_SEED_SIZE, EncrPubKey, SS_SECRET_SEED_SIZE, SsPubKey, createEncrSecret, createRandomSeed, createSharedSecret, createSsSecret, createSymmetricKey, decrypt, getEncrPub, getSsPub, khash, mergeBytes, stringToBytes, } from './crypto.js';
|
|
6
|
+
import { createSecretStorage } from './secretStorage.js';
|
|
7
|
+
import { createSession } from './statementStore.js';
|
|
8
|
+
import { createSyncStorage } from './syncStorage.js';
|
|
9
|
+
import { createUserStore } from './userStore.js';
|
|
10
|
+
// codecs
|
|
11
|
+
export const HandshakeData = Enum({
|
|
12
|
+
V1: Tuple(SsPubKey, EncrPubKey, str),
|
|
13
|
+
});
|
|
14
|
+
export const HandshakeResponsePayload = Enum({
|
|
15
|
+
// [encrypted, tmp_key]
|
|
16
|
+
V1: Tuple(Bytes(), Bytes(65)),
|
|
17
|
+
});
|
|
18
|
+
export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
|
|
19
|
+
export function createUserModule({ appId, metadata, statements, storage }) {
|
|
20
|
+
const userStorage = createUserStore(appId, storage);
|
|
21
|
+
const secretStorage = createSecretStorage(appId, storage);
|
|
22
|
+
const authStatus = createSyncStorage({ step: 'none' });
|
|
23
|
+
let signInPromise = null;
|
|
24
|
+
let abort = null;
|
|
25
|
+
async function handshake(signal) {
|
|
26
|
+
authStatus.write({ step: 'initial' });
|
|
27
|
+
const secrets = await getSecretKeys(appId, secretStorage);
|
|
28
|
+
return secrets.andThenPromise(async ({ ssPublicKey, encrPublicKey, encrSecret }) => {
|
|
29
|
+
const handshakeTopic = createHandshakeTopic({ encrPublicKey, ssPublicKey });
|
|
30
|
+
const handshakePayload = createHandshakePayloadV1({ ssPublicKey, encrPublicKey, metadata });
|
|
31
|
+
authStatus.write({ step: 'pairing', payload: createDeeplink(handshakePayload) });
|
|
32
|
+
const statementStoreResponse = fromPromise(waitForStatements(statements, handshakeTopic, signal, (statements, resolve) => {
|
|
33
|
+
for (const statement of [...statements].reverse()) {
|
|
34
|
+
if (!statement.data)
|
|
35
|
+
continue;
|
|
36
|
+
const { sessionTopic, accountId } = retrieveSessionTopic({
|
|
37
|
+
payload: statement.data.asBytes(),
|
|
38
|
+
encrSecret,
|
|
39
|
+
ssPublicKey,
|
|
40
|
+
});
|
|
41
|
+
resolve({ sessionTopic, accountId: toHex(accountId) });
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}), toError);
|
|
45
|
+
return statementStoreResponse
|
|
46
|
+
.then(x => x.andThenPromise(userStorage.createUserSession))
|
|
47
|
+
.then(async (result) => result
|
|
48
|
+
.map(user => {
|
|
49
|
+
authStatus.write({ step: 'finished', user });
|
|
50
|
+
return user;
|
|
51
|
+
})
|
|
52
|
+
.orElse(e => {
|
|
53
|
+
const error = toError(e);
|
|
54
|
+
if (isAbortError(error)) {
|
|
55
|
+
authStatus.write({ step: 'none' });
|
|
56
|
+
return ok(null);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
authStatus.write({ step: 'error', message: error.message });
|
|
60
|
+
return err(error);
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const userModule = {
|
|
66
|
+
authStatus,
|
|
67
|
+
storage: userStorage,
|
|
68
|
+
async authenticate() {
|
|
69
|
+
if (signInPromise) {
|
|
70
|
+
return signInPromise;
|
|
71
|
+
}
|
|
72
|
+
abort = new AbortController();
|
|
73
|
+
signInPromise = handshake(abort.signal);
|
|
74
|
+
return signInPromise;
|
|
75
|
+
},
|
|
76
|
+
abortAuthentication() {
|
|
77
|
+
if (abort) {
|
|
78
|
+
signInPromise = null;
|
|
79
|
+
authStatus.reset();
|
|
80
|
+
abort.abort();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return userModule;
|
|
85
|
+
}
|
|
86
|
+
function createHandshakeTopic({ encrPublicKey, ssPublicKey, }) {
|
|
87
|
+
return khash(ssPublicKey, mergeBytes(encrPublicKey, stringToBytes('topic')));
|
|
88
|
+
}
|
|
89
|
+
function createHandshakePayloadV1({ encrPublicKey, ssPublicKey, metadata, }) {
|
|
90
|
+
return HandshakeData.enc({
|
|
91
|
+
tag: 'V1',
|
|
92
|
+
value: [ssPublicKey, encrPublicKey, metadata],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function parseHandshakePayload(payload) {
|
|
96
|
+
const decoded = HandshakeResponsePayload.dec(payload);
|
|
97
|
+
switch (decoded.tag) {
|
|
98
|
+
case 'V1':
|
|
99
|
+
return {
|
|
100
|
+
encrypted: decoded.value[0],
|
|
101
|
+
tmpKey: decoded.value[1],
|
|
102
|
+
};
|
|
103
|
+
default:
|
|
104
|
+
throw new Error('Unsupported handshake payload version');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function retrieveSessionTopic({ payload, encrSecret, ssPublicKey, }) {
|
|
108
|
+
const { encrypted, tmpKey } = parseHandshakePayload(payload);
|
|
109
|
+
const symmetricKey = createSymmetricKey(createSharedSecret(encrSecret, tmpKey));
|
|
110
|
+
const decrypted = decrypt(symmetricKey, encrypted);
|
|
111
|
+
const [pappEncrPublicKey, userPublicKey] = HandshakeResponseSensitiveData.dec(decrypted);
|
|
112
|
+
const sharedSecret = createSharedSecret(encrSecret, pappEncrPublicKey);
|
|
113
|
+
const session = createSession({
|
|
114
|
+
sharedSecret: sharedSecret,
|
|
115
|
+
accountA: ssPublicKey,
|
|
116
|
+
accountB: pappEncrPublicKey,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
accountId: userPublicKey,
|
|
120
|
+
sessionTopic: session.a,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function getSsKeys(appId, secretStorage) {
|
|
124
|
+
return (await secretStorage.readSsSecret())
|
|
125
|
+
.andThenPromise(async (ssSecret) => {
|
|
126
|
+
if (ssSecret) {
|
|
127
|
+
return ok(ssSecret);
|
|
128
|
+
}
|
|
129
|
+
const seed = createRandomSeed(appId, SS_SECRET_SEED_SIZE);
|
|
130
|
+
const newSsSecret = createSsSecret(seed);
|
|
131
|
+
const write = await secretStorage.writeSsSecret(newSsSecret);
|
|
132
|
+
return write.map(() => newSsSecret);
|
|
133
|
+
})
|
|
134
|
+
.then(x => x.map(ssSecret => ({
|
|
135
|
+
ssSecret: ssSecret,
|
|
136
|
+
ssPublicKey: getSsPub(ssSecret),
|
|
137
|
+
})));
|
|
138
|
+
}
|
|
139
|
+
async function getEncrKeys(appId, secretStorage) {
|
|
140
|
+
return (await secretStorage.readEncrSecret())
|
|
141
|
+
.andThenPromise(async (encrSecret) => {
|
|
142
|
+
if (encrSecret) {
|
|
143
|
+
return ok(encrSecret);
|
|
144
|
+
}
|
|
145
|
+
const seed = createRandomSeed(appId, ENCR_SECRET_SEED_SIZE);
|
|
146
|
+
const newEncrSecret = createEncrSecret(seed);
|
|
147
|
+
const write = await secretStorage.writeEncrSecret(newEncrSecret);
|
|
148
|
+
return write.map(() => newEncrSecret);
|
|
149
|
+
})
|
|
150
|
+
.then(x => x.map(encrSecret => ({
|
|
151
|
+
encrSecret,
|
|
152
|
+
encrPublicKey: getEncrPub(encrSecret),
|
|
153
|
+
})));
|
|
154
|
+
}
|
|
155
|
+
async function getSecretKeys(appId, secretStorage) {
|
|
156
|
+
return seq(await getSsKeys(appId, secretStorage), await getEncrKeys(appId, secretStorage)).map(([ss, encr]) => ({
|
|
157
|
+
...ss,
|
|
158
|
+
...encr,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
function createDeeplink(payload) {
|
|
162
|
+
return `polkadotapp://pair?handshake=${toHex(payload)}`;
|
|
163
|
+
}
|
|
164
|
+
function waitForStatements(transport, topic, abortSignal, callback) {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const unsubscribe = transport.subscribeStatements([topic], statements => {
|
|
167
|
+
if (abortSignal?.aborted) {
|
|
168
|
+
unsubscribe();
|
|
169
|
+
try {
|
|
170
|
+
abortSignal.throwIfAborted();
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
reject(e);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
callback(statements, value => {
|
|
178
|
+
unsubscribe();
|
|
179
|
+
resolve(value);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
unsubscribe();
|
|
184
|
+
reject(e);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../adapters/storage/types.js';
|
|
2
|
+
import type { Result } from '../helpers/result.js';
|
|
3
|
+
import type { SessionTopic } from '../types.js';
|
|
4
|
+
export type User = {
|
|
5
|
+
accountId: string;
|
|
6
|
+
sessionTopic: SessionTopic;
|
|
7
|
+
};
|
|
8
|
+
export declare const createUserManager: (appId: string, storage: StorageAdapter) => {
|
|
9
|
+
readSelectedUser(): Promise<Result<User | null>>;
|
|
10
|
+
readUser(accountId: string): Promise<Result<User | null>>;
|
|
11
|
+
createUser(user: User): Promise<Result<User>>;
|
|
12
|
+
removeUser(accountId: string): Promise<Result<void, Error>>;
|
|
13
|
+
readAccounts(): Promise<Result<string[], Error>>;
|
|
14
|
+
selectAccount(accountId: string): Promise<Result<void, Error>>;
|
|
15
|
+
};
|