Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

OIDC: persist refresh token #11249

Merged
merged 24 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2607997
test persistCredentials without a pickle key
Jul 11, 2023
3506c06
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 12, 2023
609f790
test setLoggedIn with pickle key
Jul 12, 2023
f3092c7
lint
Jul 12, 2023
fad7f33
type error
Jul 12, 2023
32d5fb0
extract token persisting code into function, persist refresh token
Jul 12, 2023
e6529f1
store has_refresh_token too
Jul 12, 2023
66d57e5
pass refreshToken from oidcAuthGrant into credentials
Jul 12, 2023
b33e347
rest restore session with pickle key
Jul 13, 2023
823ba2e
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 13, 2023
b7e0603
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 13, 2023
b8b0c86
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 13, 2023
f059642
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 16, 2023
9272110
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 17, 2023
d24fbd0
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 19, 2023
880c258
Merge branch 'kerry/25708/save-refresh-token' of https://github.com/m…
Jul 19, 2023
65c0734
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 19, 2023
70ddb4a
comments
Jul 20, 2023
56441dc
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 20, 2023
af481b2
prettier
Jul 20, 2023
e2d2d33
Update src/Lifecycle.ts
Sep 17, 2023
678815d
Merge branch 'develop' into kerry/25708/save-refresh-token
Sep 17, 2023
1f903c9
comments
Sep 18, 2023
777dbba
Merge branch 'develop' into kerry/25708/save-refresh-token
Sep 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,19 @@ import GenericToast from "./components/views/toasts/GenericToast";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";

/**
* Used as storage key
/*
* Keys used when storing the tokens in indexeddb or localstorage
*/
const ACCESS_TOKEN_STORAGE_KEY = "mx_access_token";
const REFRESH_TOKEN_STORAGE_KEY = "mx_refresh_token";
/**
/*
* Used as initialization vector during encryption in persistTokenInStorage
* And decryption in restoreFromLocalStorage
*/
const ACCESS_TOKEN_NAME = "access_token";
const REFRESH_TOKEN_NAME = "refresh_token";
/**
* Used in localstorage to store whether we expect a token in idb
const ACCESS_TOKEN_IV = "access_token";
const REFRESH_TOKEN_IV = "refresh_token";
/*
* Keys for localstorage items which indicate whether we expect a token in indexeddb.
*/
const HAS_ACCESS_TOKEN_STORAGE_KEY = "mx_has_access_token";
const HAS_REFRESH_TOKEN_STORAGE_KEY = "mx_has_refresh_token";
Expand Down Expand Up @@ -555,7 +555,7 @@ export async function getStoredSessionVars(): Promise<Partial<IStoredSession>> {

// The pickle key is a string of unspecified length and format. For AES, we
// need a 256-bit Uint8Array. So we HKDF the pickle key to generate the AES
// key. The AES key should be zeroed after it is used
// key. The AES key should be zeroed after it is used.
async function pickleKeyToAesKey(pickleKey: string): Promise<Uint8Array> {
const pickleKeyBuffer = new Uint8Array(pickleKey.length);
for (let i = 0; i < pickleKey.length; i++) {
Expand Down Expand Up @@ -624,7 +624,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
logger.log("Got pickle key");
if (typeof accessToken !== "string") {
const encrKey = await pickleKeyToAesKey(pickleKey);
decryptedAccessToken = await decryptAES(accessToken, encrKey, ACCESS_TOKEN_NAME);
decryptedAccessToken = await decryptAES(accessToken, encrKey, ACCESS_TOKEN_IV);
encrKey.fill(0);
}
} else {
Expand Down Expand Up @@ -869,17 +869,16 @@ class AbortLoginAndRebuildStorage extends Error {}
* Stores in idb, falling back to localStorage
*
* @param storageKey key used to store the token
* @param name eg "access_token" used as initialization vector during encryption
* only used when pickleKey is present to encrypt with
* @param initializationVector Initialization vector for encrypting the token. Only used when `pickleKey` is present
* @param token the token to store, when undefined any existing token at the storageKey is removed from storage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param token the token to store, when undefined any existing token at the storageKey is removed from storage
* @param token The token to store. When undefined, any existing token at the `storageKey` is removed from storage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could still use clarification

* @param pickleKey optional pickle key used to encrypt token
* @param hasTokenStorageKey used to store in localstorage whether we expect to have a token in idb, eg "mx_has_access_token"
* @param hasTokenStorageKey Localstorage key for an item which stores whether we expect to have a token in indexeddb, eg "mx_has_access_token".
*/
async function persistTokenInStorage(
storageKey: string,
name: string,
initializationVector: string,
token: string | undefined,
pickleKey: IMatrixClientCreds["pickleKey"],
pickleKey: string | undefined,
hasTokenStorageKey: string,
): Promise<void> {
// store whether we expect to find a token, to detect the case
Expand All @@ -898,7 +897,7 @@ async function persistTokenInStorage(
}
// try to encrypt the access token using the pickle key
const encrKey = await pickleKeyToAesKey(pickleKey);
encryptedToken = await encryptAES(token, encrKey, name);
encryptedToken = await encryptAES(token, encrKey, initializationVector);
encrKey.fill(0);
} catch (e) {
logger.warn("Could not encrypt access token", e);
Expand Down Expand Up @@ -941,14 +940,14 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void

await persistTokenInStorage(
ACCESS_TOKEN_STORAGE_KEY,
ACCESS_TOKEN_NAME,
ACCESS_TOKEN_IV,
credentials.accessToken,
credentials.pickleKey,
HAS_ACCESS_TOKEN_STORAGE_KEY,
);
await persistTokenInStorage(
REFRESH_TOKEN_STORAGE_KEY,
REFRESH_TOKEN_NAME,
REFRESH_TOKEN_IV,
credentials.refreshToken,
credentials.pickleKey,
HAS_REFRESH_TOKEN_STORAGE_KEY,
Expand Down
9 changes: 4 additions & 5 deletions test/components/structures/MatrixChat-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1063,11 +1063,10 @@ describe("<MatrixChat />", () => {

await flushPromises();

expect(localStorageSetSpy).toHaveBeenCalledWith("mx_hs_url", homeserverUrl);
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_user_id", userId);
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_has_access_token", "true");
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_has_refresh_token", "true");
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_device_id", deviceId);
expect(localStorage.getItem("mx_hs_url")).toEqual(homeserverUrl);
expect(localStorage.getItem("mx_user_id")).toEqual(userId);
expect(localStorage.getItem("mx_has_access_token")).toEqual("true");
expect(localStorage.getItem("mx_device_id")).toEqual(deviceId);
});

it("should store clientId and issuer in session storage", async () => {
Expand Down
Loading