Skip to content

Commit ea90224

Browse files
committed
wip
1 parent 86d9676 commit ea90224

File tree

3 files changed

+265
-224
lines changed

3 files changed

+265
-224
lines changed

packages/hypergraph-react/src/HypergraphAppContext.tsx

Lines changed: 1 addition & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ import {
1717
useRef,
1818
useState,
1919
} from 'react';
20-
import { SiweMessage } from 'siwe';
21-
import type { Hex } from 'viem';
2220
import { type Address, getAddress } from 'viem';
23-
import { privateKeyToAccount } from 'viem/accounts';
2421

2522
const decodeResponseMessage = Schema.decodeUnknownEither(Messages.ResponseMessage);
2623

@@ -127,228 +124,8 @@ export function HypergraphAppProvider({
127124
const sessionToken = useSelectorStore(store, (state) => state.context.sessionToken);
128125
const keys = useSelectorStore(store, (state) => state.context.keys);
129126

130-
function prepareSiweMessage(address: Address, nonce: string) {
131-
return new SiweMessage({
132-
domain: window.location.host,
133-
address,
134-
statement: 'Sign in to Hypergraph',
135-
uri: window.location.origin,
136-
version: '1',
137-
chainId,
138-
nonce,
139-
expirationTime: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
140-
}).prepareMessage();
141-
}
142-
143-
async function getSessionNonce(accountId: string) {
144-
const nonceReq = { accountId } as const satisfies Messages.RequestLoginNonce;
145-
const res = await fetch(new URL('/login/nonce', syncServerUri), {
146-
method: 'POST',
147-
headers: {
148-
'Content-Type': 'application/json',
149-
},
150-
body: JSON.stringify(nonceReq),
151-
});
152-
const decoded = Schema.decodeUnknownSync(Messages.ResponseLoginNonce)(await res.json());
153-
return decoded.sessionNonce;
154-
}
155-
156-
async function identityExists(accountId: string) {
157-
const res = await fetch(new URL(`/identity?accountId=${accountId}`, syncServerUri), {
158-
method: 'GET',
159-
});
160-
return res.status === 200;
161-
}
162-
163-
async function loginWithWallet(signer: Identity.Signer, accountId: Address, retryCount = 0) {
164-
const sessionToken = Identity.loadSyncServerSessionToken(storage, accountId);
165-
if (!sessionToken) {
166-
const sessionNonce = await getSessionNonce(accountId);
167-
// Use SIWE to login with the server and get a token
168-
const message = prepareSiweMessage(accountId, sessionNonce);
169-
const signature = await signer.signMessage(message);
170-
const loginReq = { accountId, message, signature } as const satisfies Messages.RequestLogin;
171-
const res = await fetch(new URL('/login', syncServerUri), {
172-
method: 'POST',
173-
headers: {
174-
'Content-Type': 'application/json',
175-
},
176-
body: JSON.stringify(loginReq),
177-
});
178-
const decoded = Schema.decodeUnknownSync(Messages.ResponseLogin)(await res.json());
179-
Identity.storeAccountId(storage, accountId);
180-
Identity.storeSyncServerSessionToken(storage, accountId, decoded.sessionToken);
181-
const keys = await restoreKeys(signer, accountId, decoded.sessionToken);
182-
return {
183-
accountId,
184-
sessionToken: decoded.sessionToken,
185-
keys,
186-
};
187-
}
188-
// use whoami to check if the session token is still valid
189-
const res = await fetch(new URL('/whoami', syncServerUri), {
190-
headers: {
191-
Authorization: `Bearer ${sessionToken}`,
192-
},
193-
});
194-
if (res.status !== 200 || (await res.text()) !== accountId) {
195-
console.warn('Session token is invalid, wiping state and retrying login with wallet');
196-
Identity.wipeSyncServerSessionToken(storage, accountId);
197-
if (retryCount > 3) {
198-
throw new Error('Could not login with wallet after several attempts');
199-
}
200-
return await loginWithWallet(signer, accountId, retryCount + 1);
201-
}
202-
const keys = await restoreKeys(signer, accountId, sessionToken);
203-
return {
204-
accountId,
205-
sessionToken,
206-
keys,
207-
};
208-
}
209-
210-
async function loginWithKeys(keys: Identity.IdentityKeys, accountId: Address, retryCount = 0) {
211-
const sessionToken = Identity.loadSyncServerSessionToken(storage, accountId);
212-
if (sessionToken) {
213-
// use whoami to check if the session token is still valid
214-
const res = await fetch(new URL('/whoami', syncServerUri), {
215-
headers: {
216-
Authorization: `Bearer ${sessionToken}`,
217-
},
218-
});
219-
if (res.status !== 200 || (await res.text()) !== accountId) {
220-
console.warn('Session token is invalid, wiping state and retrying login with keys');
221-
Identity.wipeSyncServerSessionToken(storage, accountId);
222-
if (retryCount > 3) {
223-
throw new Error('Could not login with keys after several attempts');
224-
}
225-
return await loginWithKeys(keys, accountId, retryCount + 1);
226-
}
227-
throw new Error('Could not login with keys');
228-
}
229-
230-
const account = privateKeyToAccount(keys.signaturePrivateKey as Hex);
231-
const sessionNonce = await getSessionNonce(account.address);
232-
const message = prepareSiweMessage(account.address, sessionNonce);
233-
const signature = await account.signMessage({ message });
234-
const req = {
235-
accountId,
236-
message,
237-
publicKey: keys.signaturePublicKey,
238-
signature,
239-
} as const satisfies Messages.RequestLoginWithSigningKey;
240-
const res = await fetch(new URL('/login/with-signing-key', syncServerUri), {
241-
method: 'POST',
242-
headers: {
243-
'Content-Type': 'application/json',
244-
},
245-
body: JSON.stringify(req),
246-
});
247-
if (res.status !== 200) {
248-
throw new Error('Error logging in with signing key');
249-
}
250-
const decoded = Schema.decodeUnknownSync(Messages.ResponseLogin)(await res.json());
251-
Identity.storeAccountId(storage, accountId);
252-
Identity.storeSyncServerSessionToken(storage, accountId, decoded.sessionToken);
253-
return {
254-
accountId,
255-
sessionToken: decoded.sessionToken,
256-
keys,
257-
};
258-
}
259-
260-
async function restoreKeys(signer: Identity.Signer, accountId: Address, sessionToken: string) {
261-
const keys = Identity.loadKeys(storage, accountId);
262-
if (keys) {
263-
return keys;
264-
}
265-
// Try to get the users identity from the sync server
266-
const res = await fetch(new URL('/identity/encrypted', syncServerUri), {
267-
headers: {
268-
Authorization: `Bearer ${sessionToken}`,
269-
},
270-
});
271-
if (res.status === 200) {
272-
console.log('Identity found');
273-
const decoded = Schema.decodeUnknownSync(Messages.ResponseIdentityEncrypted)(await res.json());
274-
const { keyBox } = decoded;
275-
const { ciphertext, nonce } = keyBox;
276-
const keys = await Identity.decryptIdentity(signer, accountId, ciphertext, nonce);
277-
Identity.storeKeys(storage, accountId, keys);
278-
return keys;
279-
}
280-
throw new Error(`Error fetching identity ${res.status}`);
281-
}
282-
283-
async function signup(signer: Identity.Signer, accountId: Address) {
284-
const keys = Identity.createIdentityKeys();
285-
const { ciphertext, nonce } = await Identity.encryptIdentity(signer, accountId, keys);
286-
const { accountProof, keyProof } = await Identity.proveIdentityOwnership(signer, accountId, keys);
287-
288-
const account = privateKeyToAccount(keys.signaturePrivateKey as Hex);
289-
const sessionNonce = await getSessionNonce(accountId);
290-
const message = prepareSiweMessage(account.address, sessionNonce);
291-
const signature = await account.signMessage({ message });
292-
const req = {
293-
keyBox: { accountId, ciphertext, nonce },
294-
accountProof,
295-
keyProof,
296-
message,
297-
signaturePublicKey: keys.signaturePublicKey,
298-
encryptionPublicKey: keys.encryptionPublicKey,
299-
signature,
300-
} as const satisfies Messages.RequestCreateIdentity;
301-
const res = await fetch(new URL('/identity', syncServerUri), {
302-
method: 'POST',
303-
headers: {
304-
'Content-Type': 'application/json',
305-
},
306-
body: JSON.stringify(req),
307-
});
308-
if (res.status !== 200) {
309-
// TODO: handle this better?
310-
throw new Error(`Error creating identity: ${res.status}`);
311-
}
312-
const decoded = Schema.decodeUnknownSync(Messages.ResponseCreateIdentity)(await res.json());
313-
Identity.storeAccountId(storage, accountId);
314-
Identity.storeSyncServerSessionToken(storage, accountId, decoded.sessionToken);
315-
Identity.storeKeys(storage, accountId, keys);
316-
317-
return {
318-
accountId,
319-
sessionToken: decoded.sessionToken,
320-
keys,
321-
};
322-
}
323-
324127
async function login(signer: Identity.Signer) {
325-
if (!signer) {
326-
return;
327-
}
328-
const address = await signer.getAddress();
329-
if (!address) {
330-
return;
331-
}
332-
const accountId = getAddress(address);
333-
const keys = Identity.loadKeys(storage, accountId);
334-
let authData: {
335-
accountId: Address;
336-
sessionToken: string;
337-
keys: Identity.IdentityKeys;
338-
};
339-
if (!keys && !(await identityExists(accountId))) {
340-
authData = await signup(signer, accountId);
341-
} else if (keys) {
342-
authData = await loginWithKeys(keys, accountId);
343-
} else {
344-
authData = await loginWithWallet(signer, accountId);
345-
}
346-
console.log('Identity initialized');
347-
store.send({
348-
...authData,
349-
type: 'setAuth',
350-
});
351-
store.send({ type: 'reset' });
128+
return Identity.login(signer, chainId, storage, syncServerUri);
352129
}
353130

354131
function logout() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './auth-storage.js';
22
export * from './create-identity-keys.js';
33
export * from './identity-encryption.js';
4+
export * from './login.js';
45
export * from './prove-ownership.js';
56
export * from './types.js';

0 commit comments

Comments
 (0)