@@ -17,10 +17,7 @@ import {
1717 useRef ,
1818 useState ,
1919} from 'react' ;
20- import { SiweMessage } from 'siwe' ;
21- import type { Hex } from 'viem' ;
2220import { type Address , getAddress } from 'viem' ;
23- import { privateKeyToAccount } from 'viem/accounts' ;
2421
2522const 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 ( ) {
0 commit comments