@@ -71,6 +71,23 @@ import GenericToast from "./components/views/toasts/GenericToast";
71
71
const HOMESERVER_URL_KEY = "mx_hs_url" ;
72
72
const ID_SERVER_URL_KEY = "mx_is_url" ;
73
73
74
+ /*
75
+ * Keys used when storing the tokens in indexeddb or localstorage
76
+ */
77
+ const ACCESS_TOKEN_STORAGE_KEY = "mx_access_token" ;
78
+ const REFRESH_TOKEN_STORAGE_KEY = "mx_refresh_token" ;
79
+ /*
80
+ * Used as initialization vector during encryption in persistTokenInStorage
81
+ * And decryption in restoreFromLocalStorage
82
+ */
83
+ const ACCESS_TOKEN_IV = "access_token" ;
84
+ const REFRESH_TOKEN_IV = "refresh_token" ;
85
+ /*
86
+ * Keys for localstorage items which indicate whether we expect a token in indexeddb.
87
+ */
88
+ const HAS_ACCESS_TOKEN_STORAGE_KEY = "mx_has_access_token" ;
89
+ const HAS_REFRESH_TOKEN_STORAGE_KEY = "mx_has_refresh_token" ;
90
+
74
91
dis . register ( ( payload ) => {
75
92
if ( payload . action === Action . TriggerLogout ) {
76
93
// noinspection JSIgnoredPromiseFromCall - we don't care if it fails
@@ -261,9 +278,8 @@ export async function attemptDelegatedAuthLogin(
261
278
*/
262
279
async function attemptOidcNativeLogin ( queryParams : QueryDict ) : Promise < boolean > {
263
280
try {
264
- const { accessToken, homeserverUrl, identityServerUrl, clientId, issuer } = await completeOidcLogin (
265
- queryParams ,
266
- ) ;
281
+ const { accessToken, refreshToken, homeserverUrl, identityServerUrl, clientId, issuer } =
282
+ await completeOidcLogin ( queryParams ) ;
267
283
268
284
const {
269
285
user_id : userId ,
@@ -273,6 +289,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
273
289
274
290
const credentials = {
275
291
accessToken,
292
+ refreshToken,
276
293
homeserverUrl,
277
294
identityServerUrl,
278
295
deviceId,
@@ -503,25 +520,25 @@ export async function getStoredSessionVars(): Promise<Partial<IStoredSession>> {
503
520
const isUrl = localStorage . getItem ( ID_SERVER_URL_KEY ) ?? undefined ;
504
521
let accessToken : string | undefined ;
505
522
try {
506
- accessToken = await StorageManager . idbLoad ( "account" , "mx_access_token" ) ;
523
+ accessToken = await StorageManager . idbLoad ( "account" , ACCESS_TOKEN_STORAGE_KEY ) ;
507
524
} catch ( e ) {
508
525
logger . error ( "StorageManager.idbLoad failed for account:mx_access_token" , e ) ;
509
526
}
510
527
if ( ! accessToken ) {
511
- accessToken = localStorage . getItem ( "mx_access_token" ) ?? undefined ;
528
+ accessToken = localStorage . getItem ( ACCESS_TOKEN_STORAGE_KEY ) ?? undefined ;
512
529
if ( accessToken ) {
513
530
try {
514
531
// try to migrate access token to IndexedDB if we can
515
- await StorageManager . idbSave ( "account" , "mx_access_token" , accessToken ) ;
516
- localStorage . removeItem ( "mx_access_token" ) ;
532
+ await StorageManager . idbSave ( "account" , ACCESS_TOKEN_STORAGE_KEY , accessToken ) ;
533
+ localStorage . removeItem ( ACCESS_TOKEN_STORAGE_KEY ) ;
517
534
} catch ( e ) {
518
535
logger . error ( "migration of access token to IndexedDB failed" , e ) ;
519
536
}
520
537
}
521
538
}
522
539
// if we pre-date storing "mx_has_access_token", but we retrieved an access
523
540
// token, then we should say we have an access token
524
- const hasAccessToken = localStorage . getItem ( "mx_has_access_token" ) === "true" || ! ! accessToken ;
541
+ const hasAccessToken = localStorage . getItem ( HAS_ACCESS_TOKEN_STORAGE_KEY ) === "true" || ! ! accessToken ;
525
542
const userId = localStorage . getItem ( "mx_user_id" ) ?? undefined ;
526
543
const deviceId = localStorage . getItem ( "mx_device_id" ) ?? undefined ;
527
544
@@ -607,7 +624,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
607
624
logger . log ( "Got pickle key" ) ;
608
625
if ( typeof accessToken !== "string" ) {
609
626
const encrKey = await pickleKeyToAesKey ( pickleKey ) ;
610
- decryptedAccessToken = await decryptAES ( accessToken , encrKey , "access_token" ) ;
627
+ decryptedAccessToken = await decryptAES ( accessToken , encrKey , ACCESS_TOKEN_IV ) ;
611
628
encrKey . fill ( 0 ) ;
612
629
}
613
630
} else {
@@ -846,28 +863,41 @@ async function showStorageEvictedDialog(): Promise<boolean> {
846
863
// `instanceof`. Babel 7 supports this natively in their class handling.
847
864
class AbortLoginAndRebuildStorage extends Error { }
848
865
849
- async function persistCredentials ( credentials : IMatrixClientCreds ) : Promise < void > {
850
- localStorage . setItem ( HOMESERVER_URL_KEY , credentials . homeserverUrl ) ;
851
- if ( credentials . identityServerUrl ) {
852
- localStorage . setItem ( ID_SERVER_URL_KEY , credentials . identityServerUrl ) ;
853
- }
854
- localStorage . setItem ( "mx_user_id" , credentials . userId ) ;
855
- localStorage . setItem ( "mx_is_guest" , JSON . stringify ( credentials . guest ) ) ;
856
-
857
- // store whether we expect to find an access token, to detect the case
866
+ /**
867
+ * Persist a token in storage
868
+ * When pickle key is present, will attempt to encrypt the token
869
+ * Stores in idb, falling back to localStorage
870
+ *
871
+ * @param storageKey key used to store the token
872
+ * @param initializationVector Initialization vector for encrypting the token. Only used when `pickleKey` is present
873
+ * @param token the token to store, when undefined any existing token at the storageKey is removed from storage
874
+ * @param pickleKey optional pickle key used to encrypt token
875
+ * @param hasTokenStorageKey Localstorage key for an item which stores whether we expect to have a token in indexeddb, eg "mx_has_access_token".
876
+ */
877
+ async function persistTokenInStorage (
878
+ storageKey : string ,
879
+ initializationVector : string ,
880
+ token : string | undefined ,
881
+ pickleKey : string | undefined ,
882
+ hasTokenStorageKey : string ,
883
+ ) : Promise < void > {
884
+ // store whether we expect to find a token, to detect the case
858
885
// where IndexedDB is blown away
859
- if ( credentials . accessToken ) {
860
- localStorage . setItem ( "mx_has_access_token" , "true" ) ;
886
+ if ( token ) {
887
+ localStorage . setItem ( hasTokenStorageKey , "true" ) ;
861
888
} else {
862
- localStorage . removeItem ( "mx_has_access_token" ) ;
889
+ localStorage . removeItem ( hasTokenStorageKey ) ;
863
890
}
864
891
865
- if ( credentials . pickleKey ) {
866
- let encryptedAccessToken : IEncryptedPayload | undefined ;
892
+ if ( pickleKey ) {
893
+ let encryptedToken : IEncryptedPayload | undefined ;
867
894
try {
895
+ if ( ! token ) {
896
+ throw new Error ( "No token: not attempting encryption" ) ;
897
+ }
868
898
// try to encrypt the access token using the pickle key
869
- const encrKey = await pickleKeyToAesKey ( credentials . pickleKey ) ;
870
- encryptedAccessToken = await encryptAES ( credentials . accessToken , encrKey , "access_token" ) ;
899
+ const encrKey = await pickleKeyToAesKey ( pickleKey ) ;
900
+ encryptedToken = await encryptAES ( token , encrKey , initializationVector ) ;
871
901
encrKey . fill ( 0 ) ;
872
902
} catch ( e ) {
873
903
logger . warn ( "Could not encrypt access token" , e ) ;
@@ -876,28 +906,56 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
876
906
// save either the encrypted access token, or the plain access
877
907
// token if we were unable to encrypt (e.g. if the browser doesn't
878
908
// have WebCrypto).
879
- await StorageManager . idbSave ( "account" , "mx_access_token" , encryptedAccessToken || credentials . accessToken ) ;
909
+ await StorageManager . idbSave ( "account" , storageKey , encryptedToken || token ) ;
880
910
} catch ( e ) {
881
911
// if we couldn't save to indexedDB, fall back to localStorage. We
882
912
// store the access token unencrypted since localStorage only saves
883
913
// strings.
884
- if ( ! ! credentials . accessToken ) {
885
- localStorage . setItem ( "mx_access_token" , credentials . accessToken ) ;
914
+ if ( ! ! token ) {
915
+ localStorage . setItem ( storageKey , token ) ;
886
916
} else {
887
- localStorage . removeItem ( "mx_access_token" ) ;
917
+ localStorage . removeItem ( storageKey ) ;
888
918
}
889
919
}
890
- localStorage . setItem ( "mx_has_pickle_key" , String ( true ) ) ;
891
920
} else {
892
921
try {
893
- await StorageManager . idbSave ( "account" , "mx_access_token" , credentials . accessToken ) ;
922
+ await StorageManager . idbSave ( "account" , storageKey , token ) ;
894
923
} catch ( e ) {
895
- if ( ! ! credentials . accessToken ) {
896
- localStorage . setItem ( "mx_access_token" , credentials . accessToken ) ;
924
+ if ( ! ! token ) {
925
+ localStorage . setItem ( storageKey , token ) ;
897
926
} else {
898
- localStorage . removeItem ( "mx_access_token" ) ;
927
+ localStorage . removeItem ( storageKey ) ;
899
928
}
900
929
}
930
+ }
931
+ }
932
+
933
+ async function persistCredentials ( credentials : IMatrixClientCreds ) : Promise < void > {
934
+ localStorage . setItem ( HOMESERVER_URL_KEY , credentials . homeserverUrl ) ;
935
+ if ( credentials . identityServerUrl ) {
936
+ localStorage . setItem ( ID_SERVER_URL_KEY , credentials . identityServerUrl ) ;
937
+ }
938
+ localStorage . setItem ( "mx_user_id" , credentials . userId ) ;
939
+ localStorage . setItem ( "mx_is_guest" , JSON . stringify ( credentials . guest ) ) ;
940
+
941
+ await persistTokenInStorage (
942
+ ACCESS_TOKEN_STORAGE_KEY ,
943
+ ACCESS_TOKEN_IV ,
944
+ credentials . accessToken ,
945
+ credentials . pickleKey ,
946
+ HAS_ACCESS_TOKEN_STORAGE_KEY ,
947
+ ) ;
948
+ await persistTokenInStorage (
949
+ REFRESH_TOKEN_STORAGE_KEY ,
950
+ REFRESH_TOKEN_IV ,
951
+ credentials . refreshToken ,
952
+ credentials . pickleKey ,
953
+ HAS_REFRESH_TOKEN_STORAGE_KEY ,
954
+ ) ;
955
+
956
+ if ( credentials . pickleKey ) {
957
+ localStorage . setItem ( "mx_has_pickle_key" , String ( true ) ) ;
958
+ } else {
901
959
if ( localStorage . getItem ( "mx_has_pickle_key" ) === "true" ) {
902
960
logger . error ( "Expected a pickle key, but none provided. Encryption may not work." ) ;
903
961
}
@@ -1090,7 +1148,7 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
1090
1148
AbstractLocalStorageSettingsHandler . clear ( ) ;
1091
1149
1092
1150
try {
1093
- await StorageManager . idbDelete ( "account" , "mx_access_token" ) ;
1151
+ await StorageManager . idbDelete ( "account" , ACCESS_TOKEN_STORAGE_KEY ) ;
1094
1152
} catch ( e ) {
1095
1153
logger . error ( "idbDelete failed for account:mx_access_token" , e ) ;
1096
1154
}
0 commit comments