@novasamatech/host-papp 0.5.0-8 → 0.5.0-9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/identity/rpc.d.ts +3 -3
- 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/user/index.d.ts +3 -3
- package/dist/components/user/userSessionStorage.d.ts +3 -3
- package/dist/components/user/userSessionStorage.js +1 -1
- package/dist/crypto.d.ts +23 -0
- package/dist/crypto.js +51 -0
- package/dist/helpers/state.d.ts +16 -0
- package/dist/helpers/state.js +51 -0
- 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 +38 -0
- package/dist/identity/types.d.ts +22 -0
- package/dist/identity/types.js +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -7
- package/dist/modules/crypto.d.ts +4 -8
- package/dist/modules/crypto.js +10 -43
- package/dist/modules/secretStorage.d.ts +12 -9
- package/dist/modules/secretStorage.js +36 -32
- package/dist/modules/session/helpers.d.ts +1 -1
- package/dist/modules/session/helpers.js +2 -2
- package/dist/modules/session/session.js +18 -14
- package/dist/modules/statementStore.d.ts +2 -2
- package/dist/modules/statementStore.js +4 -2
- package/dist/modules/storageView.d.ts +6 -6
- package/dist/modules/transport/crypto.js +5 -3
- package/dist/modules/transport/transport.d.ts +17 -2
- package/dist/modules/transport/transport.js +15 -5
- package/dist/papp.d.ts +24 -13
- package/dist/papp.js +18 -37
- package/dist/sso/auth/impl.d.ts +48 -0
- package/dist/sso/auth/impl.js +148 -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 +15 -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/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/remoteMessage.d.ts +23 -0
- package/dist/sso/sessionManager/scale/remoteMessage.js +14 -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 +24 -0
- package/dist/sso/sessionManager/userSession.js +75 -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 +16 -0
- package/dist/sso/userSecretRepository.js +44 -0
- package/dist/sso/userSessionRepository.d.ts +18 -0
- package/dist/sso/userSessionRepository.js +27 -0
- package/package.json +5 -3
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createAccountId, createEncryption, createLocalSessionAccount, createRemoteSessionAccount, khash, } from '@novasamatech/statement-store';
|
|
2
|
+
import { mergeUint8, toHex } from '@polkadot-api/utils';
|
|
3
|
+
import { Result, ResultAsync, err, fromPromise, fromThrowable, ok } from 'neverthrow';
|
|
4
|
+
import { createEncrSecret, createSharedSecret, createSsHardDerivation, createSsSecret, getEncrPub, getSsPub, stringToBytes, } from '../../crypto.js';
|
|
5
|
+
import { AbortError } from '../../helpers/abortError.js';
|
|
6
|
+
import { createState, readonly } from '../../helpers/state.js';
|
|
7
|
+
import { toError } from '../../helpers/utils.js';
|
|
8
|
+
import { createStoredUserSession } from '../userSessionRepository.js';
|
|
9
|
+
import { HandshakeData, HandshakeResponsePayload, HandshakeResponseSensitiveData } from './scale/handshake.js';
|
|
10
|
+
export function createAuth({ metadata, statementStore, ssoSessionRepository, userSecretRepository }) {
|
|
11
|
+
const authStatus = createState({ step: 'none' });
|
|
12
|
+
let authResults = null;
|
|
13
|
+
let abort = null;
|
|
14
|
+
function handshake(signal) {
|
|
15
|
+
return getSecretKeys()
|
|
16
|
+
.andTee(() => {
|
|
17
|
+
authStatus.write({ step: 'initial' });
|
|
18
|
+
})
|
|
19
|
+
.asyncAndThen(({ encrSecret, encrPublicKey, ssSecret, ssPublicKey }) => {
|
|
20
|
+
const localAccount = createLocalSessionAccount(createAccountId(ssPublicKey));
|
|
21
|
+
const handshakePayload = createHandshakePayloadV1({ ssPublicKey, encrPublicKey, metadata }).andTee(payload => authStatus.write({ step: 'pairing', payload: createDeeplink(payload) }));
|
|
22
|
+
const pappResponse = handshakePayload
|
|
23
|
+
.andThen(() => createHandshakeTopic(localAccount, encrPublicKey))
|
|
24
|
+
.asyncAndThen(topic => waitForStatements(callback => statementStore.subscribeStatements([topic], callback), signal, (statements, resolve) => {
|
|
25
|
+
for (const statement of statements) {
|
|
26
|
+
if (!statement.data)
|
|
27
|
+
continue;
|
|
28
|
+
const session = retrieveSession({
|
|
29
|
+
localAccount,
|
|
30
|
+
encrSecret,
|
|
31
|
+
payload: statement.data.asBytes(),
|
|
32
|
+
}).unwrapOr(null);
|
|
33
|
+
if (session) {
|
|
34
|
+
resolve(session);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
const secretesSaved = pappResponse.andThen(({ id }) => userSecretRepository.write(id, { ssSecret, encrSecret }));
|
|
40
|
+
const userCreated = secretesSaved.andThen(() => pappResponse.andThen(ssoSessionRepository.add));
|
|
41
|
+
const result = ResultAsync.combine([userCreated, secretesSaved]).map(([session]) => session);
|
|
42
|
+
return result
|
|
43
|
+
.orElse(e => (AbortError.isAbortError(e) ? ok(null) : err(toError(e))))
|
|
44
|
+
.andTee(session => {
|
|
45
|
+
authStatus.write(session ? { step: 'finished', session } : { step: 'none' });
|
|
46
|
+
})
|
|
47
|
+
.orTee(e => {
|
|
48
|
+
authStatus.write({ step: 'error', message: e.message });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const authModule = {
|
|
53
|
+
status: readonly(authStatus),
|
|
54
|
+
authenticate() {
|
|
55
|
+
if (authResults) {
|
|
56
|
+
return authResults;
|
|
57
|
+
}
|
|
58
|
+
abort = new AbortController();
|
|
59
|
+
authResults = handshake(abort.signal);
|
|
60
|
+
return authResults;
|
|
61
|
+
},
|
|
62
|
+
abortAuthentication() {
|
|
63
|
+
if (abort) {
|
|
64
|
+
authResults = null;
|
|
65
|
+
authStatus.reset();
|
|
66
|
+
abort.abort(new AbortError('Aborted by user.'));
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return authModule;
|
|
71
|
+
}
|
|
72
|
+
const createHandshakeTopic = fromThrowable((account, encrPublicKey) => khash(account.accountId, mergeUint8([encrPublicKey, stringToBytes('topic')])), toError);
|
|
73
|
+
const createHandshakePayloadV1 = fromThrowable(({ encrPublicKey, ssPublicKey, metadata, }) => HandshakeData.enc({
|
|
74
|
+
tag: 'v1',
|
|
75
|
+
value: [ssPublicKey, encrPublicKey, metadata],
|
|
76
|
+
}), toError);
|
|
77
|
+
function parseHandshakePayload(payload) {
|
|
78
|
+
const decoded = HandshakeResponsePayload.dec(payload);
|
|
79
|
+
switch (decoded.tag) {
|
|
80
|
+
case 'v1':
|
|
81
|
+
return {
|
|
82
|
+
encrypted: decoded.value[0],
|
|
83
|
+
tmpKey: decoded.value[1],
|
|
84
|
+
};
|
|
85
|
+
default:
|
|
86
|
+
throw new Error('Unsupported handshake payload version');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function retrieveSession({ payload, encrSecret, localAccount, }) {
|
|
90
|
+
const { encrypted, tmpKey } = parseHandshakePayload(payload);
|
|
91
|
+
const symmetricKey = createSharedSecret(encrSecret, tmpKey);
|
|
92
|
+
return createEncryption(symmetricKey)
|
|
93
|
+
.decrypt(encrypted)
|
|
94
|
+
.map(decrypted => {
|
|
95
|
+
const [pappEncrPublicKey, pappAccountId] = HandshakeResponseSensitiveData.dec(decrypted);
|
|
96
|
+
const sharedSecret = createSharedSecret(encrSecret, pappEncrPublicKey);
|
|
97
|
+
const peerAccount = createRemoteSessionAccount(createAccountId(pappAccountId), sharedSecret);
|
|
98
|
+
return createStoredUserSession(localAccount, peerAccount);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const getSsKeys = fromThrowable(() => {
|
|
102
|
+
const ssSecret = createSsHardDerivation(createSsSecret(), '//wallet//sso');
|
|
103
|
+
return {
|
|
104
|
+
ssSecret: ssSecret,
|
|
105
|
+
ssPublicKey: getSsPub(ssSecret),
|
|
106
|
+
};
|
|
107
|
+
}, toError);
|
|
108
|
+
const getEncrKeys = fromThrowable(() => {
|
|
109
|
+
const encrSecret = createEncrSecret();
|
|
110
|
+
return {
|
|
111
|
+
encrSecret,
|
|
112
|
+
encrPublicKey: getEncrPub(encrSecret),
|
|
113
|
+
};
|
|
114
|
+
}, toError);
|
|
115
|
+
function getSecretKeys() {
|
|
116
|
+
return Result.combine([getSsKeys(), getEncrKeys()]).map(([ss, encr]) => ({
|
|
117
|
+
...ss,
|
|
118
|
+
...encr,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
function createDeeplink(payload) {
|
|
122
|
+
return `polkadotapp://pair?handshake=${toHex(payload)}`;
|
|
123
|
+
}
|
|
124
|
+
function waitForStatements(subscribe, abortSignal, callback) {
|
|
125
|
+
return fromPromise(new Promise((resolve, reject) => {
|
|
126
|
+
const unsubscribe = subscribe(statements => {
|
|
127
|
+
if (abortSignal?.aborted) {
|
|
128
|
+
unsubscribe();
|
|
129
|
+
try {
|
|
130
|
+
abortSignal.throwIfAborted();
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
reject(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
callback(statements, value => {
|
|
138
|
+
unsubscribe();
|
|
139
|
+
resolve(value);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
unsubscribe();
|
|
144
|
+
reject(e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}), toError);
|
|
148
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const HandshakeData: import("scale-ts").Codec<{
|
|
2
|
+
tag: "v1";
|
|
3
|
+
value: [import("../../../crypto.js").SsPublicKey, import("../../../crypto.js").EncrPublicKey, string];
|
|
4
|
+
}>;
|
|
5
|
+
export declare const HandshakeResponsePayload: import("scale-ts").Codec<{
|
|
6
|
+
tag: "v1";
|
|
7
|
+
value: [Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>];
|
|
8
|
+
}>;
|
|
9
|
+
export declare const HandshakeResponseSensitiveData: import("scale-ts").Codec<[Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Bytes, Enum, Tuple, str } from 'scale-ts';
|
|
2
|
+
import { EncrPubKey, SsPubKey } from '../../../crypto.js';
|
|
3
|
+
export const HandshakeData = Enum({
|
|
4
|
+
v1: Tuple(SsPubKey, EncrPubKey, str),
|
|
5
|
+
});
|
|
6
|
+
export const HandshakeResponsePayload = Enum({
|
|
7
|
+
// [encrypted, tmp_key]
|
|
8
|
+
v1: Tuple(Bytes(), Bytes(65)),
|
|
9
|
+
});
|
|
10
|
+
export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StoredUserSession } from '../userSessionRepository.js';
|
|
2
|
+
export type AuthentificationStatus = {
|
|
3
|
+
step: 'none';
|
|
4
|
+
} | {
|
|
5
|
+
step: 'initial';
|
|
6
|
+
} | {
|
|
7
|
+
step: 'pairing';
|
|
8
|
+
payload: string;
|
|
9
|
+
} | {
|
|
10
|
+
step: 'error';
|
|
11
|
+
message: string;
|
|
12
|
+
} | {
|
|
13
|
+
step: 'finished';
|
|
14
|
+
session: StoredUserSession;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Transport } from '@novasamatech/statement-store';
|
|
2
|
+
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
3
|
+
import type { UserSession, ssoSessionStorage } from './ssoSessionStorage.js';
|
|
4
|
+
export type SsoSessionManager = ReturnType<typeof createSsoSessionManager>;
|
|
5
|
+
type Params = {
|
|
6
|
+
transport: Transport;
|
|
7
|
+
storage: StorageAdapter;
|
|
8
|
+
ssoSessionStorage: ssoSessionStorage;
|
|
9
|
+
};
|
|
10
|
+
export declare function createSsoSessionManager({ ssoSessionStorage, transport }: Params): {
|
|
11
|
+
sessions: {
|
|
12
|
+
add(value: UserSession): import("neverthrow").ResultAsync<UserSession, Error>;
|
|
13
|
+
filter(fn: (value: UserSession) => boolean): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
14
|
+
mutate(fn: (value: UserSession[]) => UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
15
|
+
read(): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
16
|
+
write(value: UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error> | import("neverthrow").ResultAsync<null, Error>;
|
|
17
|
+
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
18
|
+
subscribe(fn: (value: UserSession[]) => void): VoidFunction;
|
|
19
|
+
};
|
|
20
|
+
disconnect: (session: UserSession) => import("neverthrow").ResultAsync<undefined, Error>;
|
|
21
|
+
destroy(): void;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { okAsync } from 'neverthrow';
|
|
2
|
+
import { createSSOSession } from './ssoSession.js';
|
|
3
|
+
export function createSsoSessionManager({ ssoSessionStorage, transport }) {
|
|
4
|
+
let unsubStatements = null;
|
|
5
|
+
const disconnect = (session) => {
|
|
6
|
+
return ssoSessionStorage.mutate(sessions => sessions.filter(s => s.id !== session.id)).map(() => undefined);
|
|
7
|
+
};
|
|
8
|
+
const unsubSessions = ssoSessionStorage.subscribe(userSessions => {
|
|
9
|
+
if (unsubStatements) {
|
|
10
|
+
unsubStatements();
|
|
11
|
+
unsubStatements = null;
|
|
12
|
+
}
|
|
13
|
+
const ssoSessions = [];
|
|
14
|
+
for (const userSession of userSessions) {
|
|
15
|
+
const session = createSSOSession({
|
|
16
|
+
localAccount: userSession.local,
|
|
17
|
+
remoteAccount: userSession.remote,
|
|
18
|
+
transport,
|
|
19
|
+
});
|
|
20
|
+
session.subscribe(message => {
|
|
21
|
+
switch (message.data.tag) {
|
|
22
|
+
case 'v1': {
|
|
23
|
+
switch (message.data.value.tag) {
|
|
24
|
+
case 'Disconnected':
|
|
25
|
+
return disconnect(userSession).map(() => true);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return okAsync(false);
|
|
30
|
+
});
|
|
31
|
+
ssoSessions.push(session);
|
|
32
|
+
}
|
|
33
|
+
unsubStatements = () => {
|
|
34
|
+
for (const session of ssoSessions) {
|
|
35
|
+
session.dispose();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
sessions: ssoSessionStorage,
|
|
41
|
+
disconnect,
|
|
42
|
+
destroy() {
|
|
43
|
+
unsubSessions();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// function createDisconnectMessage(ssSecret: SsSecret, topic: Uint8Array) {
|
|
48
|
+
// const statement = createStatement(ssSecret, {
|
|
49
|
+
// priority: 0,
|
|
50
|
+
// channel: createRequestChannel(topic),
|
|
51
|
+
// topics: [topic],
|
|
52
|
+
// data: SSOMessage.enc({
|
|
53
|
+
// tag: 'Disconnected',
|
|
54
|
+
// value: undefined,
|
|
55
|
+
// }),
|
|
56
|
+
// });
|
|
57
|
+
// }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Enum, Struct, _void, str } from 'scale-ts';
|
|
2
|
+
export const HostRemoteMessageCodec = Struct({
|
|
3
|
+
messageId: str,
|
|
4
|
+
data: Enum({
|
|
5
|
+
v1: Enum({
|
|
6
|
+
Disconnected: _void,
|
|
7
|
+
// TODO implement
|
|
8
|
+
// SigningRequest: Bytes(),
|
|
9
|
+
// TODO implement
|
|
10
|
+
// SigningResponse: Bytes(),
|
|
11
|
+
}),
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../../adapters/storage/types.js';
|
|
2
|
+
import type { Transport } from '../../modules/transport/transport.js';
|
|
3
|
+
import type { UserSession, UserSessionStorage } from './userSessionStorage.js';
|
|
4
|
+
export type SessionManager = ReturnType<typeof createSessionManager>;
|
|
5
|
+
type Params = {
|
|
6
|
+
transport: Transport;
|
|
7
|
+
storage: StorageAdapter;
|
|
8
|
+
userSessionStorage: UserSessionStorage;
|
|
9
|
+
};
|
|
10
|
+
export declare function createSessionManager({ userSessionStorage, storage, transport }: Params): {
|
|
11
|
+
sessions: {
|
|
12
|
+
add(value: UserSession): import("neverthrow").ResultAsync<UserSession, Error>;
|
|
13
|
+
filter(fn: (value: UserSession) => boolean): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
14
|
+
mutate(fn: (value: UserSession[]) => UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
15
|
+
read(): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
16
|
+
write(value: UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error> | import("neverthrow").ResultAsync<null, Error>;
|
|
17
|
+
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
18
|
+
subscribe(fn: (value: UserSession[]) => void): VoidFunction;
|
|
19
|
+
};
|
|
20
|
+
disconnect: (session: UserSession) => import("neverthrow").ResultAsync<undefined, Error>;
|
|
21
|
+
destroy(): void;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { okAsync } from 'neverthrow';
|
|
2
|
+
import { createSSOSession } from './ssoSession.js';
|
|
3
|
+
export function createSessionManager({ userSessionStorage, storage, transport }) {
|
|
4
|
+
let unsubStatements = null;
|
|
5
|
+
const disconnect = (session) => {
|
|
6
|
+
return userSessionStorage.mutate(sessions => sessions.filter(s => s.id !== session.id)).map(() => undefined);
|
|
7
|
+
};
|
|
8
|
+
const unsubSessions = userSessionStorage.subscribe(userSessions => {
|
|
9
|
+
if (unsubStatements) {
|
|
10
|
+
unsubStatements();
|
|
11
|
+
unsubStatements = null;
|
|
12
|
+
}
|
|
13
|
+
const ssoSessions = [];
|
|
14
|
+
for (const userSession of userSessions) {
|
|
15
|
+
const session = createSSOSession({
|
|
16
|
+
ownAccount: userSession.host,
|
|
17
|
+
peerAccount: userSession.peer,
|
|
18
|
+
storage,
|
|
19
|
+
transport,
|
|
20
|
+
});
|
|
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
|
+
ssoSessions.push(session);
|
|
33
|
+
}
|
|
34
|
+
unsubStatements = () => {
|
|
35
|
+
for (const session of ssoSessions) {
|
|
36
|
+
session.dispose();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
sessions: userSessionStorage,
|
|
42
|
+
disconnect,
|
|
43
|
+
destroy() {
|
|
44
|
+
unsubSessions();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// function createDisconnectMessage(ssSecret: SsSecret, topic: Uint8Array) {
|
|
49
|
+
// const statement = createStatement(ssSecret, {
|
|
50
|
+
// priority: 0,
|
|
51
|
+
// channel: createRequestChannel(topic),
|
|
52
|
+
// topics: [topic],
|
|
53
|
+
// data: SSOMessage.enc({
|
|
54
|
+
// tag: 'Disconnected',
|
|
55
|
+
// value: undefined,
|
|
56
|
+
// }),
|
|
57
|
+
// });
|
|
58
|
+
// }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LocalSessionAccount, RemoteSessionAccount, Session, Transport } from '@novasamatech/statement-store';
|
|
2
|
+
import { HostRemoteMessageCodec } from './scale/remoteMessage.js';
|
|
3
|
+
export type SsoSession = Session<typeof HostRemoteMessageCodec>;
|
|
4
|
+
export declare function createSSOSession({ localAccount, remoteAccount, transport, }: {
|
|
5
|
+
localAccount: LocalSessionAccount;
|
|
6
|
+
remoteAccount: RemoteSessionAccount;
|
|
7
|
+
transport: Transport;
|
|
8
|
+
}): SsoSession;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createSession } from '@novasamatech/statement-store';
|
|
2
|
+
import { HostRemoteMessageCodec } from './scale/remoteMessage.js';
|
|
3
|
+
export function createSSOSession({ localAccount, remoteAccount, transport, }) {
|
|
4
|
+
return createSession({ localAccount, remoteAccount, transport, codec: HostRemoteMessageCodec });
|
|
5
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LocalSessionAccount, RemoteSessionAccount } from '@novasamatech/statement-store';
|
|
2
|
+
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
3
|
+
export type ssoSessionStorage = ReturnType<typeof createSsoSessionStorage>;
|
|
4
|
+
export type UserSession = {
|
|
5
|
+
id: string;
|
|
6
|
+
local: LocalSessionAccount;
|
|
7
|
+
remote: RemoteSessionAccount;
|
|
8
|
+
};
|
|
9
|
+
type Params = {
|
|
10
|
+
storage: StorageAdapter;
|
|
11
|
+
};
|
|
12
|
+
export declare const createSsoSessionStorage: ({ storage }: Params) => {
|
|
13
|
+
add(value: UserSession): import("neverthrow").ResultAsync<UserSession, Error>;
|
|
14
|
+
filter(fn: (value: UserSession) => boolean): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
15
|
+
mutate(fn: (value: UserSession[]) => UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
16
|
+
read(): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
17
|
+
write(value: UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error> | import("neverthrow").ResultAsync<null, Error>;
|
|
18
|
+
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
19
|
+
subscribe(fn: (value: UserSession[]) => void): VoidFunction;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LocalSessionAccountCodec, RemoteSessionAccountCodec } from '@novasamatech/statement-store';
|
|
2
|
+
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
3
|
+
import { fromHex, toHex } from '@polkadot-api/utils';
|
|
4
|
+
import { Struct, Vector, str } from 'scale-ts';
|
|
5
|
+
const userSessionCodec = Struct({
|
|
6
|
+
id: str,
|
|
7
|
+
local: LocalSessionAccountCodec,
|
|
8
|
+
remote: RemoteSessionAccountCodec,
|
|
9
|
+
});
|
|
10
|
+
const userSessionsCodec = Vector(userSessionCodec);
|
|
11
|
+
export const createSsoSessionStorage = ({ storage }) => {
|
|
12
|
+
return fieldListView({
|
|
13
|
+
storage,
|
|
14
|
+
key: 'Sessions',
|
|
15
|
+
autosync: true,
|
|
16
|
+
initial: [],
|
|
17
|
+
from: x => userSessionsCodec.dec(fromHex(x)),
|
|
18
|
+
to: x => toHex(userSessionsCodec.enc(x)),
|
|
19
|
+
});
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LocalSessionAccount, RemoteSessionAccount } from '@novasamatech/statement-store';
|
|
2
|
+
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
3
|
+
export type UserSessionStorage = ReturnType<typeof createUserSessionStorage>;
|
|
4
|
+
export type UserSession = {
|
|
5
|
+
id: string;
|
|
6
|
+
local: LocalSessionAccount;
|
|
7
|
+
remote: RemoteSessionAccount;
|
|
8
|
+
};
|
|
9
|
+
type Params = {
|
|
10
|
+
storage: StorageAdapter;
|
|
11
|
+
};
|
|
12
|
+
export declare const createUserSessionStorage: ({ storage }: Params) => {
|
|
13
|
+
add(value: UserSession): import("neverthrow").ResultAsync<UserSession, Error>;
|
|
14
|
+
filter(fn: (value: UserSession) => boolean): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
15
|
+
mutate(fn: (value: UserSession[]) => UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
16
|
+
read(): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
17
|
+
write(value: UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error> | import("neverthrow").ResultAsync<null, Error>;
|
|
18
|
+
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
19
|
+
subscribe(fn: (value: UserSession[]) => void): VoidFunction;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LocalSessionAccountCodec, RemoteSessionAccountCodec } from '@novasamatech/statement-store';
|
|
2
|
+
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
3
|
+
import { fromHex, toHex } from '@polkadot-api/utils';
|
|
4
|
+
import { Struct, Vector, str } from 'scale-ts';
|
|
5
|
+
const userSessionCodec = Struct({
|
|
6
|
+
id: str,
|
|
7
|
+
local: LocalSessionAccountCodec,
|
|
8
|
+
remote: RemoteSessionAccountCodec,
|
|
9
|
+
});
|
|
10
|
+
const userSessionsCodec = Vector(userSessionCodec);
|
|
11
|
+
export const createUserSessionStorage = ({ storage }) => {
|
|
12
|
+
return fieldListView({
|
|
13
|
+
storage,
|
|
14
|
+
key: 'Sessions',
|
|
15
|
+
autosync: true,
|
|
16
|
+
initial: [],
|
|
17
|
+
from: x => userSessionsCodec.dec(fromHex(x)),
|
|
18
|
+
to: x => toHex(userSessionsCodec.enc(x)),
|
|
19
|
+
});
|
|
20
|
+
};
|
|
@@ -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,22 @@
|
|
|
1
|
+
import type { LocalSessionAccount, RemoteSessionAccount } from '@novasamatech/statement-store';
|
|
2
|
+
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
3
|
+
export type SsoSessionRepository = ReturnType<typeof createSsoSessionRepository>;
|
|
4
|
+
export type UserSession = {
|
|
5
|
+
id: string;
|
|
6
|
+
local: LocalSessionAccount;
|
|
7
|
+
remote: RemoteSessionAccount;
|
|
8
|
+
};
|
|
9
|
+
export declare function createUserSession(localAccount: LocalSessionAccount, remoteAccount: RemoteSessionAccount): UserSession;
|
|
10
|
+
type Params = {
|
|
11
|
+
storage: StorageAdapter;
|
|
12
|
+
};
|
|
13
|
+
export declare const createSsoSessionRepository: ({ storage }: Params) => {
|
|
14
|
+
add(value: UserSession): import("neverthrow").ResultAsync<UserSession, Error>;
|
|
15
|
+
filter(fn: (value: UserSession) => boolean): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
16
|
+
mutate(fn: (value: UserSession[]) => UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
17
|
+
read(): import("neverthrow").ResultAsync<UserSession[], Error>;
|
|
18
|
+
write(value: UserSession[]): import("neverthrow").ResultAsync<UserSession[], Error> | import("neverthrow").ResultAsync<null, Error>;
|
|
19
|
+
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
20
|
+
subscribe(fn: (value: UserSession[]) => void): VoidFunction;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LocalSessionAccountCodec, RemoteSessionAccountCodec } from '@novasamatech/statement-store';
|
|
2
|
+
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
3
|
+
import { fromHex, toHex } from '@polkadot-api/utils';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
import { Struct, Vector, str } from 'scale-ts';
|
|
6
|
+
const userSessionCodec = Struct({
|
|
7
|
+
id: str,
|
|
8
|
+
local: LocalSessionAccountCodec,
|
|
9
|
+
remote: RemoteSessionAccountCodec,
|
|
10
|
+
});
|
|
11
|
+
export function createUserSession(localAccount, remoteAccount) {
|
|
12
|
+
return {
|
|
13
|
+
id: nanoid(12),
|
|
14
|
+
local: localAccount,
|
|
15
|
+
remote: remoteAccount,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const userSessionsCodec = Vector(userSessionCodec);
|
|
19
|
+
export const createSsoSessionRepository = ({ storage }) => {
|
|
20
|
+
return fieldListView({
|
|
21
|
+
storage,
|
|
22
|
+
key: 'SsoSessions',
|
|
23
|
+
initial: [],
|
|
24
|
+
from: x => userSessionsCodec.dec(fromHex(x)),
|
|
25
|
+
to: x => toHex(userSessionsCodec.enc(x)),
|
|
26
|
+
});
|
|
27
|
+
};
|