@@ -9,17 +9,51 @@ import { eraseAndUpdateToLatestVersion } from 'src/shared/core/version';
99import { currentUserKey } from 'src/shared/getCurrentUser' ;
1010import type { PublicUser , User } from 'src/shared/types/User' ;
1111import { payloadId } from '@walletconnect/jsonrpc-utils' ;
12+ import { invariant } from 'src/shared/invariant' ;
1213import { Wallet } from '../Wallet/Wallet' ;
1314import { peakSavedWalletState } from '../Wallet/persistence' ;
1415import type { NotificationWindow } from '../NotificationWindow/NotificationWindow' ;
1516import { credentialsKey } from './storage-keys' ;
17+ import { isSessionCredentials } from './Credentials' ;
1618
1719const TEMPORARY_ID = 'temporary' ;
1820
1921async function sha256 ( { password, salt } : { password : string ; salt : string } ) {
2022 return await getSHA256HexDigest ( `${ salt } :${ password } ` ) ;
2123}
2224
25+ async function deriveUserKeys ( {
26+ user,
27+ credentials,
28+ } : {
29+ user : User ;
30+ credentials : { password : string } | { encryptionKey : string } ;
31+ } ) {
32+ let encryptionKey : string | null = null ;
33+ let seedPhraseEncryptionKey : string | null = null ;
34+ let seedPhraseEncryptionKey_deprecated : CryptoKey | null = null ;
35+ if ( 'password' in credentials ) {
36+ const { password } = credentials ;
37+ const [ key1 , key2 , key3 ] = await Promise . all ( [
38+ sha256 ( { salt : user . id , password } ) ,
39+ sha256 ( { salt : user . salt , password } ) ,
40+ createCryptoKey ( password , user . salt ) ,
41+ ] ) ;
42+ encryptionKey = key1 ;
43+ seedPhraseEncryptionKey = key2 ;
44+ seedPhraseEncryptionKey_deprecated = key3 ;
45+ } else {
46+ encryptionKey = credentials . encryptionKey ;
47+ }
48+
49+ return {
50+ id : user . id ,
51+ encryptionKey,
52+ seedPhraseEncryptionKey,
53+ seedPhraseEncryptionKey_deprecated,
54+ } ;
55+ }
56+
2357class EventEmitter < Events extends EventsMap > {
2458 private emitter = createNanoEvents < Events > ( ) ;
2559
@@ -77,17 +111,27 @@ export class Account extends EventEmitter<AccountEvents> {
77111 }
78112 }
79113
80- static async createUser ( password : string ) : Promise < User > {
114+ static validatePassword ( password : string ) {
81115 const validity = validate ( { password } ) ;
82116 if ( ! validity . valid ) {
83117 throw new Error ( validity . message ) ;
84118 }
119+ }
120+
121+ static async createUser ( password : string ) : Promise < User > {
122+ Account . validatePassword ( password ) ;
85123 const id = nanoid ( 36 ) ; // use longer id than default (21)
86124 const salt = createSalt ( ) ; // used to encrypt seed phrases
87125 const record = { id, salt /* passwordHash: hash */ } ;
88126 return record ;
89127 }
90128
129+ /** Updates salt */
130+ static async updateUser ( user : User ) : Promise < User > {
131+ const salt = createSalt ( ) ; // used to encrypt seed phrases
132+ return { id : user . id , salt } ;
133+ }
134+
91135 constructor ( {
92136 notificationWindow,
93137 } : {
@@ -100,6 +144,7 @@ export class Account extends EventEmitter<AccountEvents> {
100144 this . notificationWindow = notificationWindow ;
101145 this . wallet = new Wallet ( TEMPORARY_ID , null , this . notificationWindow ) ;
102146 this . on ( 'authenticated' , ( ) => {
147+ // TODO: Call Account.writeCurrentUser() here, too?
103148 if ( this . encryptionKey ) {
104149 Account . writeCredentials ( { encryptionKey : this . encryptionKey } ) ;
105150 }
@@ -152,39 +197,60 @@ export class Account extends EventEmitter<AccountEvents> {
152197 await this . setUser ( user , { password } , { isNewUser : false } ) ;
153198 }
154199
200+ async changePassword ( {
201+ currentPassword,
202+ newPassword,
203+ user : currentUser ,
204+ } : {
205+ user : User ;
206+ currentPassword : string ;
207+ newPassword : string ;
208+ } ) {
209+ Account . validatePassword ( newPassword ) ;
210+ await this . login ( currentUser , currentPassword ) ;
211+ invariant ( this . user , 'User must be set' ) ;
212+ const updatedUser = await Account . updateUser ( this . user ) ;
213+ const currentCredentials = await deriveUserKeys ( {
214+ user : currentUser ,
215+ credentials : { password : currentPassword } ,
216+ } ) ;
217+ const newCredentials = await deriveUserKeys ( {
218+ user : updatedUser ,
219+ credentials : { password : newPassword } ,
220+ } ) ;
221+ console . log ( { currentCredentials, newCredentials } ) ;
222+ if (
223+ ! isSessionCredentials ( currentCredentials ) ||
224+ ! isSessionCredentials ( newCredentials )
225+ ) {
226+ throw new Error ( 'Full credentials are expected' ) ;
227+ }
228+ await this . wallet . assignNewCredentials ( {
229+ id : payloadId ( ) ,
230+ params : { newCredentials, credentials : currentCredentials } ,
231+ } ) ;
232+ // Update local state only if the above call was successful
233+ this . user = updatedUser ;
234+ this . encryptionKey = newCredentials . encryptionKey ;
235+ await Account . writeCurrentUser ( this . user ) ;
236+ this . emit ( 'authenticated' ) ;
237+ }
238+
155239 async setUser (
156240 user : User ,
157- credentials : { password : string } | { encryptionKey : string } ,
241+ partialCredentials : { password : string } | { encryptionKey : string } ,
158242 { isNewUser = false } = { }
159243 ) {
160244 this . user = user ;
161245 this . isPendingNewUser = isNewUser ;
162- let seedPhraseEncryptionKey : string | null = null ;
163- let seedPhraseEncryptionKey_deprecated : CryptoKey | null = null ;
164- if ( 'password' in credentials ) {
165- const { password } = credentials ;
166- const [ key1 , key2 , key3 ] = await Promise . all ( [
167- sha256 ( { salt : user . id , password } ) ,
168- sha256 ( { salt : user . salt , password } ) ,
169- createCryptoKey ( password , user . salt ) ,
170- ] ) ;
171- this . encryptionKey = key1 ;
172- seedPhraseEncryptionKey = key2 ;
173- seedPhraseEncryptionKey_deprecated = key3 ;
174- } else {
175- this . encryptionKey = credentials . encryptionKey ;
176- }
246+ const credentials = await deriveUserKeys ( {
247+ user,
248+ credentials : partialCredentials ,
249+ } ) ;
250+ this . encryptionKey = credentials . encryptionKey ;
177251 await this . wallet . updateCredentials ( {
178252 id : payloadId ( ) ,
179- params : {
180- credentials : {
181- id : user . id ,
182- encryptionKey : this . encryptionKey ,
183- seedPhraseEncryptionKey,
184- seedPhraseEncryptionKey_deprecated,
185- } ,
186- isNewUser,
187- } ,
253+ params : { credentials, isNewUser } ,
188254 } ) ;
189255 if ( ! this . isPendingNewUser ) {
190256 this . emit ( 'authenticated' ) ;
@@ -272,16 +338,25 @@ export class AccountPublicRPC {
272338 return null ;
273339 }
274340
341+ async verifyUser ( user : PublicUser ) {
342+ const currentUser = await Account . readCurrentUser ( ) ;
343+ if ( ! currentUser || currentUser . id !== user . id ) {
344+ throw new Error ( `User ${ user . id } not found` ) ;
345+ }
346+ return currentUser ;
347+ }
348+
275349 async login ( {
276350 params : { user, password } ,
277351 } : PublicMethodParams < {
278352 user : PublicUser ;
279353 password : string ;
280354 } > ) : Promise < PublicUser | null > {
281- const currentUser = await Account . readCurrentUser ( ) ;
282- if ( ! currentUser || currentUser . id !== user . id ) {
283- throw new Error ( `User ${ user . id } not found` ) ;
284- }
355+ const currentUser = await this . verifyUser ( user ) ;
356+ // const currentUser = await Account.readCurrentUser();
357+ // if (!currentUser || currentUser.id !== user.id) {
358+ // throw new Error(`User ${user.id} not found`);
359+ // }
285360 const canAuthorize = await this . account . verifyPassword (
286361 currentUser ,
287362 password
@@ -294,6 +369,21 @@ export class AccountPublicRPC {
294369 }
295370 }
296371
372+ async changePassword ( {
373+ params : { user, currentPassword, newPassword } ,
374+ } : PublicMethodParams < {
375+ user : PublicUser ;
376+ currentPassword : string ;
377+ newPassword : string ;
378+ } > ) {
379+ const currentUser = await this . verifyUser ( user ) ;
380+ await this . account . changePassword ( {
381+ user : currentUser ,
382+ currentPassword,
383+ newPassword,
384+ } ) ;
385+ }
386+
297387 async hasActivePasswordSession ( ) {
298388 return this . account . hasActivePasswordSession ( ) ;
299389 }
0 commit comments