@novasamatech/host-papp 0.5.0-8 → 0.5.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.
Files changed (152) hide show
  1. package/.papi/descriptors/dist/common-types.d.ts +8667 -0
  2. package/.papi/descriptors/dist/common.d.ts +1 -0
  3. package/.papi/descriptors/dist/descriptors-UUEW32EL.mjs +27 -0
  4. package/.papi/descriptors/dist/descriptors.d.ts +1 -0
  5. package/.papi/descriptors/dist/index.d.ts +10 -0
  6. package/.papi/descriptors/dist/index.js +237 -0
  7. package/.papi/descriptors/dist/index.mjs +148 -0
  8. package/.papi/descriptors/dist/metadataTypes-E4AQJDJR.mjs +6 -0
  9. package/.papi/descriptors/dist/metadataTypes.d.ts +2 -0
  10. package/.papi/descriptors/dist/people_lite.d.ts +7757 -0
  11. package/.papi/descriptors/dist/people_lite_metadata-EIVHV27X.mjs +6 -0
  12. package/.papi/descriptors/dist/people_lite_metadata.d.ts +2 -0
  13. package/.papi/descriptors/generated.json +1 -0
  14. package/.papi/descriptors/package.json +24 -0
  15. package/.papi/metadata/people_lite.scale +0 -0
  16. package/.papi/polkadot-api.json +15 -0
  17. package/dist/constants.d.ts +2 -2
  18. package/dist/constants.js +2 -2
  19. package/dist/{modules/crypto.d.ts → crypto.d.ts} +14 -12
  20. package/dist/crypto.js +87 -0
  21. package/dist/helpers/abortError.d.ts +0 -1
  22. package/dist/helpers/abortError.js +0 -3
  23. package/dist/{modules → helpers}/state.d.ts +1 -1
  24. package/dist/{modules → helpers}/state.js +5 -4
  25. package/dist/helpers/zipWith.d.ts +4 -0
  26. package/dist/helpers/zipWith.js +11 -0
  27. package/dist/identity/impl.d.ts +6 -0
  28. package/dist/identity/impl.js +68 -0
  29. package/dist/identity/rpcAdapter.d.ts +3 -0
  30. package/dist/identity/rpcAdapter.js +46 -0
  31. package/dist/identity/types.d.ts +21 -0
  32. package/dist/index.d.ts +7 -4
  33. package/dist/index.js +2 -7
  34. package/dist/papp.d.ts +24 -13
  35. package/dist/papp.js +19 -38
  36. package/dist/sso/auth/attestationService.d.ts +18 -0
  37. package/dist/sso/auth/attestationService.js +171 -0
  38. package/dist/sso/auth/impl.d.ts +79 -0
  39. package/dist/sso/auth/impl.js +186 -0
  40. package/dist/{components/auth/codec.d.ts → sso/auth/scale/handshake.d.ts} +3 -3
  41. package/dist/{components/auth/codec.js → sso/auth/scale/handshake.js} +3 -3
  42. package/dist/sso/auth/types.d.ts +28 -0
  43. package/dist/sso/sessionManager/impl.d.ts +22 -0
  44. package/dist/sso/sessionManager/impl.js +71 -0
  45. package/dist/sso/sessionManager/scale/hex.d.ts +1 -0
  46. package/dist/sso/sessionManager/scale/hex.js +3 -0
  47. package/dist/sso/sessionManager/scale/remoteMessage.d.ts +41 -0
  48. package/dist/sso/sessionManager/scale/remoteMessage.js +13 -0
  49. package/dist/sso/sessionManager/scale/signPayloadRequest.d.ts +20 -0
  50. package/dist/sso/sessionManager/scale/signPayloadRequest.js +20 -0
  51. package/dist/sso/sessionManager/scale/signPayloadResponse.d.ts +14 -0
  52. package/dist/sso/sessionManager/scale/signPayloadResponse.js +10 -0
  53. package/dist/{components/user → sso/sessionManager}/types.d.ts +1 -1
  54. package/dist/sso/sessionManager/userSession.d.ts +22 -0
  55. package/dist/sso/sessionManager/userSession.js +116 -0
  56. package/dist/sso/ssoSessionProver.d.ts +4 -0
  57. package/dist/sso/ssoSessionProver.js +35 -0
  58. package/dist/sso/userSecretRepository.d.ts +17 -0
  59. package/dist/sso/userSecretRepository.js +45 -0
  60. package/dist/sso/userSessionRepository.d.ts +18 -0
  61. package/dist/sso/userSessionRepository.js +26 -0
  62. package/package.json +13 -8
  63. package/dist/adapters/identity/rpc.d.ts +0 -6
  64. package/dist/adapters/identity/rpc.js +0 -101
  65. package/dist/adapters/identity/types.d.ts +0 -10
  66. package/dist/adapters/lazyClient/papi.d.ts +0 -3
  67. package/dist/adapters/lazyClient/papi.js +0 -17
  68. package/dist/adapters/lazyClient/types.d.ts +0 -5
  69. package/dist/adapters/statement/rpc.d.ts +0 -3
  70. package/dist/adapters/statement/rpc.js +0 -93
  71. package/dist/adapters/statement/types.d.ts +0 -9
  72. package/dist/adapters/storage/localStorage.d.ts +0 -2
  73. package/dist/adapters/storage/localStorage.js +0 -34
  74. package/dist/adapters/storage/memory.d.ts +0 -2
  75. package/dist/adapters/storage/memory.js +0 -22
  76. package/dist/adapters/storage/types.d.ts +0 -7
  77. package/dist/adapters/storage/types.js +0 -1
  78. package/dist/adapters/transport/rpc.d.ts +0 -3
  79. package/dist/adapters/transport/rpc.js +0 -51
  80. package/dist/adapters/transport/types.d.ts +0 -6
  81. package/dist/adapters/transport/types.js +0 -1
  82. package/dist/components/auth/codecs.d.ts +0 -9
  83. package/dist/components/auth/codecs.js +0 -10
  84. package/dist/components/auth/index.d.ts +0 -36
  85. package/dist/components/auth/index.js +0 -150
  86. package/dist/components/auth/types.d.ts +0 -15
  87. package/dist/components/auth/types.js +0 -1
  88. package/dist/components/session.d.ts +0 -34
  89. package/dist/components/session.js +0 -54
  90. package/dist/components/transport.d.ts +0 -27
  91. package/dist/components/transport.js +0 -57
  92. package/dist/components/user/codec.d.ts +0 -16
  93. package/dist/components/user/codec.js +0 -13
  94. package/dist/components/user/index.d.ts +0 -22
  95. package/dist/components/user/index.js +0 -58
  96. package/dist/components/user/ssoMessageStream.d.ts +0 -10
  97. package/dist/components/user/ssoMessageStream.js +0 -8
  98. package/dist/components/user/ssoSession.d.ts +0 -5
  99. package/dist/components/user/ssoSession.js +0 -5
  100. package/dist/components/user/storage.d.ts +0 -27
  101. package/dist/components/user/storage.js +0 -143
  102. package/dist/components/user/types.js +0 -1
  103. package/dist/components/user/userSessionStorage.d.ts +0 -20
  104. package/dist/components/user/userSessionStorage.js +0 -24
  105. package/dist/components/user.d.ts +0 -74
  106. package/dist/components/user.js +0 -188
  107. package/dist/helpers/result.d.ts +0 -12
  108. package/dist/helpers/result.js +0 -15
  109. package/dist/helpers/result.spec.d.ts +0 -1
  110. package/dist/helpers/result.spec.js +0 -23
  111. package/dist/helpers.d.ts +0 -1
  112. package/dist/helpers.js +0 -3
  113. package/dist/modules/accounts.d.ts +0 -1
  114. package/dist/modules/accounts.js +0 -2
  115. package/dist/modules/crypto.js +0 -84
  116. package/dist/modules/secretStorage.d.ts +0 -12
  117. package/dist/modules/secretStorage.js +0 -40
  118. package/dist/modules/session/helpers.d.ts +0 -5
  119. package/dist/modules/session/helpers.js +0 -29
  120. package/dist/modules/session/session.d.ts +0 -12
  121. package/dist/modules/session/session.js +0 -46
  122. package/dist/modules/session/types.d.ts +0 -12
  123. package/dist/modules/session/types.js +0 -1
  124. package/dist/modules/signIn.d.ts +0 -67
  125. package/dist/modules/signIn.js +0 -188
  126. package/dist/modules/statementStore.d.ts +0 -12
  127. package/dist/modules/statementStore.js +0 -20
  128. package/dist/modules/statementTopic.d.ts +0 -34
  129. package/dist/modules/statementTopic.js +0 -46
  130. package/dist/modules/storageView.d.ts +0 -25
  131. package/dist/modules/storageView.js +0 -51
  132. package/dist/modules/syncStorage.d.ts +0 -25
  133. package/dist/modules/syncStorage.js +0 -76
  134. package/dist/modules/transport/codec.d.ts +0 -24
  135. package/dist/modules/transport/codec.js +0 -36
  136. package/dist/modules/transport/crypto.d.ts +0 -2
  137. package/dist/modules/transport/crypto.js +0 -18
  138. package/dist/modules/transport/transport.d.ts +0 -27
  139. package/dist/modules/transport/transport.js +0 -56
  140. package/dist/modules/user.d.ts +0 -67
  141. package/dist/modules/user.js +0 -188
  142. package/dist/modules/userManager.d.ts +0 -15
  143. package/dist/modules/userManager.js +0 -105
  144. package/dist/modules/userStorage.d.ts +0 -19
  145. package/dist/modules/userStorage.js +0 -108
  146. package/dist/modules/userStore.d.ts +0 -15
  147. package/dist/modules/userStore.js +0 -105
  148. package/dist/structs.d.ts +0 -24
  149. package/dist/structs.js +0 -36
  150. /package/dist/{adapters/identity → identity}/types.js +0 -0
  151. /package/dist/{adapters/lazyClient → sso/auth}/types.js +0 -0
  152. /package/dist/{adapters/statement → sso/sessionManager}/types.js +0 -0
@@ -0,0 +1,171 @@
1
+ import { createAccountId } from '@novasamatech/statement-store';
2
+ import { mergeUint8 } from '@polkadot-api/utils';
3
+ import { blake2b256 } from '@polkadot-labs/hdkd-helpers';
4
+ import { customAlphabet } from 'nanoid';
5
+ import { errAsync, fromAsyncThrowable, fromPromise, okAsync } from 'neverthrow';
6
+ import { AccountId, Binary } from 'polkadot-api';
7
+ import { getPolkadotSigner } from 'polkadot-api/signer';
8
+ import { Bytes, Option, Tuple, str } from 'scale-ts';
9
+ import { member_from_entropy, sign } from 'verifiablejs/bundler';
10
+ import { deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js';
11
+ import { toError } from '../../helpers/utils.js';
12
+ const accountId = AccountId();
13
+ export function createAliceVerifier() {
14
+ return deriveSr25519Account('bottom drive obey lake curtain smoke basket hold race lonely fit walk', '//Alice');
15
+ }
16
+ export const createAttestationService = (lazyClient) => {
17
+ const service = {
18
+ claimUsername() {
19
+ const nameSuffixFactory = customAlphabet('abcdefghijklmnopqrstuvwxyz', 4);
20
+ return `guest${nameSuffixFactory()}.${createNumericSuffix(4)}`;
21
+ },
22
+ grantVerifierAllowance(verifier) {
23
+ const client = lazyClient.getClient();
24
+ const api = client.getUnsafeApi();
25
+ const verifierAddress = accountId.dec(verifier.publicKey);
26
+ if (!api.query.PeopleLite || !api.query.PeopleLite.AttestationAllowance) {
27
+ return errAsync(new Error('Query PeopleLite.AttestationAllowance not found.'));
28
+ }
29
+ const verifierAllowance = fromPromise(api.query.PeopleLite.AttestationAllowance.getValue(verifierAddress), toError);
30
+ const getAllowance = fromAsyncThrowable(async () => {
31
+ const increaseAllowanceCall = api.tx.PeopleLite.increase_attestation_allowance({
32
+ account: verifierAddress,
33
+ count: 10,
34
+ });
35
+ const sudoCall = api.tx.Sudo.sudo({
36
+ call: increaseAllowanceCall.decodedCall,
37
+ });
38
+ return sudoCall.signAndSubmit(createPeopleSigner(verifier)).then(() => undefined);
39
+ }, toError);
40
+ return verifierAllowance.andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance()));
41
+ },
42
+ getRingRfKey(candidate) {
43
+ const verifiableEntropy = blake2b256(candidate.entropy);
44
+ return member_from_entropy(verifiableEntropy);
45
+ },
46
+ getProofMessage(candidate, ringVrfKey) {
47
+ return mergeUint8([stringToBytes('pop:people-lite:register using'), candidate.publicKey, ringVrfKey]);
48
+ },
49
+ deriveAttestationParams(username, candidate, verifier) {
50
+ const verifiableEntropy = blake2b256(candidate.entropy);
51
+ const ringVrfKey = service.getRingRfKey(candidate);
52
+ const identifierKey = getEncrPub(blake2b256(candidate.secret));
53
+ const message = service.getProofMessage(candidate, ringVrfKey);
54
+ // Extract username without the `.` separator and any following digits
55
+ // For lite person usernames like "ceainnhgidpj.39642086", we only use "ceainnhgidpj"
56
+ const usernameWithoutDigits = username.split('.')[0] ?? username;
57
+ const candidateSignature = candidate.sign(message);
58
+ const proofOfOwnership = sign(verifiableEntropy, message);
59
+ const ResourceSignatureCodec = Tuple(
60
+ // candidate PublicKey (32 bytes)
61
+ Bytes(32),
62
+ // verifier AccountId (32 bytes)
63
+ Bytes(32),
64
+ // identifierKey
65
+ Bytes(65),
66
+ // username without digits
67
+ str,
68
+ // reserved_username
69
+ Option(Bytes()));
70
+ const resourcesSignatureData = ResourceSignatureCodec.enc([
71
+ candidate.publicKey,
72
+ createAccountId(verifier.publicKey),
73
+ identifierKey,
74
+ usernameWithoutDigits,
75
+ undefined,
76
+ ]);
77
+ const consumerRegistrationSignature = candidate.sign(resourcesSignatureData);
78
+ return okAsync({
79
+ candidateSignature: candidateSignature,
80
+ ringVrfKey,
81
+ proofOfOwnership,
82
+ identifierKey,
83
+ consumerRegistrationSignature,
84
+ });
85
+ },
86
+ registerLitePerson(username, candidate, verifier) {
87
+ const client = lazyClient.getClient();
88
+ const api = client.getUnsafeApi();
89
+ return service
90
+ .deriveAttestationParams(username, candidate, verifier)
91
+ .andThen(params => {
92
+ const attestCall = api.tx.PeopleLite.attest({
93
+ candidate: accountId.dec(candidate.publicKey),
94
+ candidate_signature: {
95
+ type: 'Sr25519',
96
+ value: Binary.fromBytes(params.candidateSignature),
97
+ },
98
+ ring_vrf_key: Binary.fromBytes(params.ringVrfKey),
99
+ proof_of_ownership: Binary.fromBytes(params.proofOfOwnership),
100
+ consumer_registration: {
101
+ signature: {
102
+ type: 'Sr25519',
103
+ value: Binary.fromBytes(params.consumerRegistrationSignature),
104
+ },
105
+ account: accountId.dec(candidate.publicKey),
106
+ identifier_key: Binary.fromBytes(params.identifierKey),
107
+ username: Binary.fromText(username),
108
+ reserved_username: undefined,
109
+ },
110
+ });
111
+ return fromPromise(new Promise((resolve, reject) => {
112
+ const subscription = attestCall.signSubmitAndWatch(createPeopleSigner(verifier)).subscribe({
113
+ next(event) {
114
+ if (event.type === 'finalized') {
115
+ // Check if transaction was successful
116
+ if (event.ok) {
117
+ subscription.unsubscribe();
118
+ resolve();
119
+ }
120
+ else {
121
+ // Extract error details
122
+ let errorMessage = 'Transaction failed';
123
+ if (event.dispatchError?.type === 'Module') {
124
+ const moduleError = event.dispatchError.value;
125
+ errorMessage = `${moduleError.type}.${moduleError.value?.type || 'Unknown'}`;
126
+ }
127
+ subscription.unsubscribe();
128
+ reject(errorMessage);
129
+ }
130
+ }
131
+ },
132
+ error: reject,
133
+ });
134
+ }), toError).map(() => undefined);
135
+ })
136
+ .andTee(() => console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`));
137
+ },
138
+ };
139
+ return service;
140
+ };
141
+ function createNumericSuffix(length) {
142
+ let suffix = '';
143
+ for (let i = 0; i < length; i++) {
144
+ suffix += (Math.random() * 9).toFixed();
145
+ }
146
+ return suffix;
147
+ }
148
+ function createPeopleSigner(verifier) {
149
+ const baseSigner = getPolkadotSigner(verifier.publicKey, 'Sr25519', verifier.sign);
150
+ return {
151
+ publicKey: baseSigner.publicKey,
152
+ signBytes: baseSigner.signBytes,
153
+ signTx: async (callData, signedExtensions, metadata, atBlockNumber, hasher) => {
154
+ // Add People chain custom signed extensions
155
+ const extensionsWithCustom = {
156
+ ...signedExtensions,
157
+ VerifyMultiSignature: {
158
+ identifier: 'VerifyMultiSignature',
159
+ value: new Uint8Array([1]), // 1u8 = Option::Some with empty data
160
+ additionalSigned: new Uint8Array([]), // Empty additional data
161
+ },
162
+ AsPerson: {
163
+ identifier: 'AsPerson',
164
+ value: new Uint8Array([0]), // 0u8 = Option::None
165
+ additionalSigned: new Uint8Array([]), // Empty additional data
166
+ },
167
+ };
168
+ return baseSigner.signTx(callData, extensionsWithCustom, metadata, atBlockNumber, hasher);
169
+ },
170
+ };
171
+ }
@@ -0,0 +1,79 @@
1
+ import type { LazyClient, StatementStoreAdapter } from '@novasamatech/statement-store';
2
+ import { ResultAsync } from 'neverthrow';
3
+ import type { UserSecretRepository } from '../userSecretRepository.js';
4
+ import type { StoredUserSession, UserSessionRepository } from '../userSessionRepository.js';
5
+ export type AuthComponent = ReturnType<typeof createAuth>;
6
+ type Params = {
7
+ metadata: string;
8
+ statementStore: StatementStoreAdapter;
9
+ ssoSessionRepository: UserSessionRepository;
10
+ userSecretRepository: UserSecretRepository;
11
+ lazyClient: LazyClient;
12
+ };
13
+ export declare function createAuth({ metadata, statementStore, ssoSessionRepository, userSecretRepository, lazyClient, }: Params): {
14
+ pairingStatus: {
15
+ read: () => {
16
+ step: "none";
17
+ } | {
18
+ step: "initial";
19
+ } | {
20
+ step: "attestation";
21
+ } | {
22
+ step: "pairing";
23
+ payload: string;
24
+ } | {
25
+ step: "pairingError";
26
+ message: string;
27
+ } | {
28
+ step: "finished";
29
+ session: StoredUserSession;
30
+ };
31
+ subscribe: (fn: (value: {
32
+ step: "none";
33
+ } | {
34
+ step: "initial";
35
+ } | {
36
+ step: "attestation";
37
+ } | {
38
+ step: "pairing";
39
+ payload: string;
40
+ } | {
41
+ step: "pairingError";
42
+ message: string;
43
+ } | {
44
+ step: "finished";
45
+ session: StoredUserSession;
46
+ }) => void) => () => void;
47
+ onFirstSubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
48
+ onLastUnsubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
49
+ };
50
+ attestationStatus: {
51
+ read: () => {
52
+ step: "none";
53
+ } | {
54
+ step: "attestation";
55
+ username: string;
56
+ } | {
57
+ step: "attestationError";
58
+ message: string;
59
+ } | {
60
+ step: "finished";
61
+ };
62
+ subscribe: (fn: (value: {
63
+ step: "none";
64
+ } | {
65
+ step: "attestation";
66
+ username: string;
67
+ } | {
68
+ step: "attestationError";
69
+ message: string;
70
+ } | {
71
+ step: "finished";
72
+ }) => void) => () => void;
73
+ onFirstSubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
74
+ onLastUnsubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
75
+ };
76
+ authenticate(): ResultAsync<StoredUserSession | null, Error>;
77
+ abortAuthentication(): void;
78
+ };
79
+ export {};
@@ -0,0 +1,186 @@
1
+ import { createAccountId, createEncryption, createLocalSessionAccount, createRemoteSessionAccount, khash, } from '@novasamatech/statement-store';
2
+ import { mergeUint8, toHex } from '@polkadot-api/utils';
3
+ import { generateMnemonic } from '@polkadot-labs/hdkd-helpers';
4
+ import { Result, ResultAsync, err, fromPromise, fromThrowable, ok } from 'neverthrow';
5
+ import { createEncrSecret, createSharedSecret, deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js';
6
+ import { AbortError } from '../../helpers/abortError.js';
7
+ import { createState, readonly } from '../../helpers/state.js';
8
+ import { toError } from '../../helpers/utils.js';
9
+ import { createStoredUserSession } from '../userSessionRepository.js';
10
+ import { createAliceVerifier, createAttestationService } from './attestationService.js';
11
+ import { HandshakeData, HandshakeResponsePayload, HandshakeResponseSensitiveData } from './scale/handshake.js';
12
+ export function createAuth({ metadata, statementStore, ssoSessionRepository, userSecretRepository, lazyClient, }) {
13
+ const attestationStatus = createState({ step: 'none' });
14
+ const pairingStatus = createState({ step: 'none' });
15
+ let authResult = null;
16
+ let abort = null;
17
+ function attestAccount(account, signal) {
18
+ const attestationService = createAttestationService(lazyClient);
19
+ const verifier = createAliceVerifier();
20
+ const username = attestationService.claimUsername();
21
+ attestationStatus.write({ step: 'attestation', username });
22
+ return attestationService
23
+ .grantVerifierAllowance(verifier)
24
+ .andThrough(() => processSignal(signal))
25
+ .andThen(() => attestationService.registerLitePerson(username, account, verifier))
26
+ .andThrough(() => processSignal(signal))
27
+ .andTee(() => {
28
+ attestationStatus.write({ step: 'finished' });
29
+ })
30
+ .orTee(e => {
31
+ if (!(e instanceof AbortError)) {
32
+ attestationStatus.write({ step: 'attestationError', message: e.message });
33
+ }
34
+ });
35
+ }
36
+ function handshake(account, signal) {
37
+ const localAccount = createLocalSessionAccount(createAccountId(account.publicKey));
38
+ pairingStatus.write({ step: 'initial' });
39
+ const encrKeys = createEncrKeys(account.entropy);
40
+ const handshakePayload = encrKeys.andThen(({ publicKey }) => createHandshakePayloadV1({
41
+ ssPublicKey: account.publicKey,
42
+ encrPublicKey: publicKey,
43
+ metadata,
44
+ }));
45
+ const handshakeTopic = encrKeys.andThen(({ publicKey }) => createHandshakeTopic(localAccount, publicKey));
46
+ const dataPrepared = Result.combine([handshakePayload, handshakeTopic, encrKeys]).andTee(([payload]) => pairingStatus.write({ step: 'pairing', payload: createDeeplink(payload) }));
47
+ return dataPrepared.asyncAndThen(([, handshakeTopic, encrKeys]) => {
48
+ const pappResponse = waitForStatements(callback => statementStore.subscribeStatements([handshakeTopic], callback), signal, (statements, resolve) => {
49
+ for (const statement of statements) {
50
+ if (!statement.data)
51
+ continue;
52
+ const session = retrieveSession({
53
+ localAccount,
54
+ encrSecret: encrKeys.secret,
55
+ payload: statement.data.asBytes(),
56
+ }).unwrapOr(null);
57
+ if (session) {
58
+ resolve(session);
59
+ break;
60
+ }
61
+ }
62
+ });
63
+ const secretesSaved = pappResponse.andThen(({ id }) => {
64
+ return userSecretRepository.write(id, {
65
+ ssSecret: account.secret,
66
+ encrSecret: encrKeys.secret,
67
+ entropy: account.entropy,
68
+ });
69
+ });
70
+ // secrets and sso session should be chained, or it can produce an incorrect state
71
+ const userCreated = secretesSaved.andThen(() => pappResponse.andThen(ssoSessionRepository.add));
72
+ const sessionReceived = ResultAsync.combine([userCreated, secretesSaved]).map(([session]) => session);
73
+ return sessionReceived
74
+ .andTee(session => {
75
+ pairingStatus.write(session ? { step: 'finished', session } : { step: 'none' });
76
+ })
77
+ .orTee(e => {
78
+ if (!(e instanceof AbortError)) {
79
+ pairingStatus.write({ step: 'pairingError', message: e.message });
80
+ }
81
+ });
82
+ });
83
+ }
84
+ const authModule = {
85
+ pairingStatus: readonly(pairingStatus),
86
+ attestationStatus: readonly(attestationStatus),
87
+ authenticate() {
88
+ if (authResult) {
89
+ return authResult;
90
+ }
91
+ abort = new AbortController();
92
+ const account = deriveSr25519Account(generateMnemonic(), '//wallet//sso');
93
+ authResult = ResultAsync.combine([handshake(account, abort.signal), attestAccount(account, abort.signal)])
94
+ .map(([session]) => session)
95
+ .orElse(e => (e instanceof AbortError ? ok(null) : err(e)))
96
+ .andTee(() => {
97
+ abort = null;
98
+ })
99
+ .orTee(() => {
100
+ authResult = null;
101
+ abort = null;
102
+ });
103
+ return authResult;
104
+ },
105
+ abortAuthentication() {
106
+ if (abort) {
107
+ abort.abort(new AbortError('Aborted by user.'));
108
+ abort = null;
109
+ }
110
+ authResult = null;
111
+ pairingStatus.reset();
112
+ attestationStatus.reset();
113
+ },
114
+ };
115
+ return authModule;
116
+ }
117
+ const createHandshakeTopic = fromThrowable((account, encrPublicKey) => khash(account.accountId, mergeUint8([encrPublicKey, stringToBytes('topic')])), toError);
118
+ const createHandshakePayloadV1 = fromThrowable(({ encrPublicKey, ssPublicKey, metadata, }) => HandshakeData.enc({
119
+ tag: 'v1',
120
+ value: [ssPublicKey, encrPublicKey, metadata],
121
+ }), toError);
122
+ function parseHandshakePayload(payload) {
123
+ const decoded = HandshakeResponsePayload.dec(payload);
124
+ switch (decoded.tag) {
125
+ case 'v1':
126
+ return {
127
+ encrypted: decoded.value[0],
128
+ tmpKey: decoded.value[1],
129
+ };
130
+ default:
131
+ throw new Error('Unsupported handshake payload version');
132
+ }
133
+ }
134
+ const createEncrKeys = fromThrowable((entropy) => {
135
+ const secret = createEncrSecret(entropy);
136
+ return {
137
+ secret,
138
+ publicKey: getEncrPub(secret),
139
+ };
140
+ }, toError);
141
+ function retrieveSession({ payload, encrSecret, localAccount, }) {
142
+ const { encrypted, tmpKey } = parseHandshakePayload(payload);
143
+ const symmetricKey = createSharedSecret(encrSecret, tmpKey);
144
+ return createEncryption(symmetricKey)
145
+ .decrypt(encrypted)
146
+ .map(decrypted => {
147
+ const [pappEncrPublicKey, pappAccountId] = HandshakeResponseSensitiveData.dec(decrypted);
148
+ const sharedSecret = createSharedSecret(encrSecret, pappEncrPublicKey);
149
+ const peerAccount = createRemoteSessionAccount(createAccountId(pappAccountId), sharedSecret);
150
+ return createStoredUserSession(localAccount, peerAccount);
151
+ });
152
+ }
153
+ function createDeeplink(payload) {
154
+ return `polkadotapp://pair?handshake=${toHex(payload)}`;
155
+ }
156
+ function waitForStatements(subscribe, signal, callback) {
157
+ return fromPromise(new Promise((resolve, reject) => {
158
+ const unsubscribe = subscribe(statements => {
159
+ const abortError = processSignal(signal).match(() => null, e => e);
160
+ if (abortError) {
161
+ unsubscribe();
162
+ reject(abortError);
163
+ return;
164
+ }
165
+ try {
166
+ callback(statements, value => {
167
+ unsubscribe();
168
+ resolve(value);
169
+ });
170
+ }
171
+ catch (e) {
172
+ unsubscribe();
173
+ reject(e);
174
+ }
175
+ });
176
+ }), toError);
177
+ }
178
+ function processSignal(signal) {
179
+ try {
180
+ signal.throwIfAborted();
181
+ return ok();
182
+ }
183
+ catch (e) {
184
+ return err(toError(e));
185
+ }
186
+ }
@@ -1,9 +1,9 @@
1
1
  export declare const HandshakeData: import("scale-ts").Codec<{
2
- tag: "V1";
3
- value: [import("../../modules/crypto.js").SsPublicKey, import("../../modules/crypto.js").EncrPublicKey, string];
2
+ tag: "v1";
3
+ value: [import("../../../crypto.js").SsPublicKey, import("../../../crypto.js").EncrPublicKey, string];
4
4
  }>;
5
5
  export declare const HandshakeResponsePayload: import("scale-ts").Codec<{
6
- tag: "V1";
6
+ tag: "v1";
7
7
  value: [Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>];
8
8
  }>;
9
9
  export declare const HandshakeResponseSensitiveData: import("scale-ts").Codec<[Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>]>;
@@ -1,10 +1,10 @@
1
1
  import { Bytes, Enum, Tuple, str } from 'scale-ts';
2
- import { EncrPubKey, SsPubKey } from '../../modules/crypto.js';
2
+ import { EncrPubKey, SsPubKey } from '../../../crypto.js';
3
3
  export const HandshakeData = Enum({
4
- V1: Tuple(SsPubKey, EncrPubKey, str),
4
+ v1: Tuple(SsPubKey, EncrPubKey, str),
5
5
  });
6
6
  export const HandshakeResponsePayload = Enum({
7
7
  // [encrypted, tmp_key]
8
- V1: Tuple(Bytes(), Bytes(65)),
8
+ v1: Tuple(Bytes(), Bytes(65)),
9
9
  });
10
10
  export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
@@ -0,0 +1,28 @@
1
+ import type { StoredUserSession } from '../userSessionRepository.js';
2
+ export type PairingStatus = {
3
+ step: 'none';
4
+ } | {
5
+ step: 'initial';
6
+ } | {
7
+ step: 'attestation';
8
+ } | {
9
+ step: 'pairing';
10
+ payload: string;
11
+ } | {
12
+ step: 'pairingError';
13
+ message: string;
14
+ } | {
15
+ step: 'finished';
16
+ session: StoredUserSession;
17
+ };
18
+ export type AttestationStatus = {
19
+ step: 'none';
20
+ } | {
21
+ step: 'attestation';
22
+ username: string;
23
+ } | {
24
+ step: 'attestationError';
25
+ message: string;
26
+ } | {
27
+ step: 'finished';
28
+ };
@@ -0,0 +1,22 @@
1
+ import type { StatementStoreAdapter } from '@novasamatech/statement-store';
2
+ import type { StorageAdapter } from '@novasamatech/storage-adapter';
3
+ import type { Callback } from '../../types.js';
4
+ import type { UserSecretRepository } from '../userSecretRepository.js';
5
+ import type { StoredUserSession, UserSessionRepository } from '../userSessionRepository.js';
6
+ import type { UserSession } from './userSession.js';
7
+ export type SsoSessionManager = ReturnType<typeof createSsoSessionManager>;
8
+ type Params = {
9
+ storage: StorageAdapter;
10
+ statementStore: StatementStoreAdapter;
11
+ ssoSessionRepository: UserSessionRepository;
12
+ userSecretRepository: UserSecretRepository;
13
+ };
14
+ export declare function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, statementStore, storage, }: Params): {
15
+ sessions: {
16
+ read: () => UserSession[];
17
+ subscribe: (callback: Callback<UserSession[]>) => () => void;
18
+ };
19
+ disconnect(userSession: StoredUserSession): import("neverthrow").ResultAsync<undefined, Error>;
20
+ dispose(): void;
21
+ };
22
+ export {};
@@ -0,0 +1,71 @@
1
+ import { createEncryption } from '@novasamatech/statement-store';
2
+ import { okAsync } from 'neverthrow';
3
+ import { createState } from '../../helpers/state.js';
4
+ import { createSsoStatementProver } from '../ssoSessionProver.js';
5
+ import { createUserSession } from './userSession.js';
6
+ export function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, statementStore, storage, }) {
7
+ const localSessions = createState({});
8
+ const disconnect = (session) => {
9
+ return ssoSessionRepository.filter(s => s.id !== session.id).map(() => undefined);
10
+ };
11
+ ssoSessionRepository.subscribe(userSessions => {
12
+ const activeSessions = localSessions.read();
13
+ const toRemove = new Set(Object.keys(activeSessions));
14
+ const toAdd = new Set();
15
+ for (const userSession of userSessions) {
16
+ if (userSession.id in activeSessions)
17
+ continue;
18
+ const session = createSession(userSession, statementStore, storage, userSecretRepository);
19
+ toRemove.delete(userSession.id);
20
+ toAdd.add(session);
21
+ session.subscribe(message => {
22
+ switch (message.data.tag) {
23
+ case 'v1': {
24
+ switch (message.data.value.tag) {
25
+ case 'Disconnected':
26
+ return disconnect(userSession).map(() => true);
27
+ }
28
+ }
29
+ }
30
+ return okAsync(false);
31
+ });
32
+ }
33
+ if (toRemove.size > 0) {
34
+ localSessions.write(prev => {
35
+ return Object.fromEntries(Object.entries(prev).filter(([id]) => !toRemove.has(id)));
36
+ });
37
+ }
38
+ if (toAdd.size > 0) {
39
+ localSessions.write(prev => ({
40
+ ...prev,
41
+ ...Object.fromEntries(Array.from(toAdd).map(s => [s.id, s])),
42
+ }));
43
+ }
44
+ });
45
+ return {
46
+ sessions: {
47
+ read: () => Object.values(localSessions.read()),
48
+ subscribe: (callback) => localSessions.subscribe(sessions => callback(Object.values(sessions))),
49
+ },
50
+ disconnect(userSession) {
51
+ const session = createSession(userSession, statementStore, storage, userSecretRepository);
52
+ return session.sendDisconnectMessage().andThen(() => disconnect(userSession));
53
+ },
54
+ dispose() {
55
+ for (const session of Object.values(localSessions.read())) {
56
+ session.dispose();
57
+ }
58
+ },
59
+ };
60
+ }
61
+ function createSession(userSession, statementStore, storage, userSecretRepository) {
62
+ const encryption = createEncryption(userSession.remoteAccount.publicKey);
63
+ const prover = createSsoStatementProver(userSession, userSecretRepository);
64
+ return createUserSession({
65
+ userSession,
66
+ statementStore,
67
+ encryption,
68
+ storage,
69
+ prover,
70
+ });
71
+ }
@@ -0,0 +1 @@
1
+ export declare const hexCodec: import("scale-ts").Codec<`0x${string}`>;
@@ -0,0 +1,3 @@
1
+ import { fromHex, toHex } from '@polkadot-api/utils';
2
+ import { Bytes, enhanceCodec } from 'scale-ts';
3
+ export const hexCodec = enhanceCodec(Bytes(), fromHex, v => toHex(v));
@@ -0,0 +1,41 @@
1
+ import type { CodecType } from 'scale-ts';
2
+ export type RemoteMessage = CodecType<typeof RemoteMessageCodec>;
3
+ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
4
+ messageId: string;
5
+ data: {
6
+ tag: "v1";
7
+ value: {
8
+ tag: "Disconnected";
9
+ value: undefined;
10
+ } | {
11
+ tag: "SignRequest";
12
+ value: {
13
+ address: string;
14
+ blockHash: `0x${string}`;
15
+ blockNumber: `0x${string}`;
16
+ era: `0x${string}`;
17
+ genesisHash: `0x${string}`;
18
+ method: `0x${string}`;
19
+ nonce: `0x${string}`;
20
+ specVersion: `0x${string}`;
21
+ tip: `0x${string}`;
22
+ transactionVersion: `0x${string}`;
23
+ signedExtensions: string[];
24
+ version: number;
25
+ assetId: `0x${string}` | undefined;
26
+ metadataHash: `0x${string}` | undefined;
27
+ mode: number | undefined;
28
+ withSignedTransaction: boolean | undefined;
29
+ };
30
+ } | {
31
+ tag: "SignResponse";
32
+ value: {
33
+ respondingTo: string;
34
+ payload: import("scale-ts").ResultPayload<{
35
+ signature: Uint8Array<ArrayBufferLike>;
36
+ signedTransaction: Uint8Array<ArrayBufferLike> | undefined;
37
+ }, string>;
38
+ };
39
+ };
40
+ };
41
+ }>;
@@ -0,0 +1,13 @@
1
+ import { Enum, Struct, _void, str } from 'scale-ts';
2
+ import { SignPayloadRequestCodec } from './signPayloadRequest.js';
3
+ import { SignPayloadResponseCodec } from './signPayloadResponse.js';
4
+ export const RemoteMessageCodec = Struct({
5
+ messageId: str,
6
+ data: Enum({
7
+ v1: Enum({
8
+ Disconnected: _void,
9
+ SignRequest: SignPayloadRequestCodec,
10
+ SignResponse: SignPayloadResponseCodec,
11
+ }),
12
+ }),
13
+ });
@@ -0,0 +1,20 @@
1
+ import type { CodecType } from 'scale-ts';
2
+ export type SignPayloadRequest = CodecType<typeof SignPayloadRequestCodec>;
3
+ export declare const SignPayloadRequestCodec: import("scale-ts").Codec<{
4
+ address: string;
5
+ blockHash: `0x${string}`;
6
+ blockNumber: `0x${string}`;
7
+ era: `0x${string}`;
8
+ genesisHash: `0x${string}`;
9
+ method: `0x${string}`;
10
+ nonce: `0x${string}`;
11
+ specVersion: `0x${string}`;
12
+ tip: `0x${string}`;
13
+ transactionVersion: `0x${string}`;
14
+ signedExtensions: string[];
15
+ version: number;
16
+ assetId: `0x${string}` | undefined;
17
+ metadataHash: `0x${string}` | undefined;
18
+ mode: number | undefined;
19
+ withSignedTransaction: boolean | undefined;
20
+ }>;