You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-07-30 02:21:17 +03:00
OIDC: refresh tokens (#11699)
* test persistCredentials without a pickle key * test setLoggedIn with pickle key * lint * type error * extract token persisting code into function, persist refresh token * store has_refresh_token too * pass refreshToken from oidcAuthGrant into credentials * rest restore session with pickle key * retreive stored refresh token and add to credentials * extract token decryption into function * remove TODO * very messy poc * comments * prettier * comment pedantry * working refresh without persistence * extract token persistence functions to utils * add sugar * implement TokenRefresher class with persistence * tidying * persist idTokenClaims * persist idTokenClaims * tests * remove unused cde * create token refresher during doSetLoggedIn * tidying * also tidying * update Lifecycle test replaceUsingCreds calls * tidy * test tokenrefresher creation in login flow * test token refresher * Update src/utils/oidc/TokenRefresher.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * use literal value for m.authentication Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * improve comments --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
@ -19,6 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import * as MatrixJs from "matrix-js-sdk/src/matrix";
|
||||
import { setCrypto } from "matrix-js-sdk/src/crypto/crypto";
|
||||
import * as MatrixCryptoAes from "matrix-js-sdk/src/crypto/aes";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import StorageEvictedDialog from "../src/components/views/dialogs/StorageEvictedDialog";
|
||||
import { restoreFromLocalStorage, setLoggedIn } from "../src/Lifecycle";
|
||||
@ -27,6 +28,8 @@ import Modal from "../src/Modal";
|
||||
import * as StorageManager from "../src/utils/StorageManager";
|
||||
import { getMockClientWithEventEmitter, mockPlatformPeg } from "./test-utils";
|
||||
import ToastStore from "../src/stores/ToastStore";
|
||||
import { makeDelegatedAuthConfig } from "./test-utils/oidc";
|
||||
import { persistOidcAuthenticatedSettings } from "../src/utils/oidc/persistOidcSettings";
|
||||
|
||||
const webCrypto = new Crypto();
|
||||
|
||||
@ -233,6 +236,7 @@ describe("Lifecycle", () => {
|
||||
userId,
|
||||
guest: true,
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith("mx_is_guest", "true");
|
||||
});
|
||||
@ -264,16 +268,19 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await restoreFromLocalStorage()).toEqual(true);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: undefined,
|
||||
});
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: undefined,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove fresh login flag from session storage", async () => {
|
||||
@ -312,18 +319,21 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await restoreFromLocalStorage()).toEqual(true);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
accessToken,
|
||||
// refreshToken included in credentials
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: undefined,
|
||||
});
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
accessToken,
|
||||
// refreshToken included in credentials
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: undefined,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -373,17 +383,20 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await restoreFromLocalStorage()).toEqual(true);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
// decrypted accessToken
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
});
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
// decrypted accessToken
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
describe("with a refresh token", () => {
|
||||
@ -412,18 +425,21 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await restoreFromLocalStorage()).toEqual(true);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
accessToken,
|
||||
// refreshToken included in credentials
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
});
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
accessToken,
|
||||
// refreshToken included in credentials
|
||||
refreshToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: false,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -529,16 +545,19 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await setLoggedIn(credentials)).toEqual(mockClient);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: null,
|
||||
});
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: null,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -628,17 +647,133 @@ describe("Lifecycle", () => {
|
||||
it("should create new matrix client with credentials", async () => {
|
||||
expect(await setLoggedIn(credentials)).toEqual(mockClient);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith({
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
{
|
||||
userId,
|
||||
accessToken,
|
||||
homeserverUrl,
|
||||
identityServerUrl,
|
||||
deviceId,
|
||||
freshLogin: true,
|
||||
guest: false,
|
||||
pickleKey: expect.any(String),
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when authenticated via OIDC native flow", () => {
|
||||
const clientId = "test-client-id";
|
||||
const issuer = "https://auth.com/";
|
||||
|
||||
const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
|
||||
const idTokenClaims = {
|
||||
aud: "123",
|
||||
iss: issuer,
|
||||
sub: "123",
|
||||
exp: 123,
|
||||
iat: 456,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.get(
|
||||
`${delegatedAuthConfig.issuer}.well-known/openid-configuration`,
|
||||
delegatedAuthConfig.metadata,
|
||||
);
|
||||
fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
keys: [],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// mock oidc config for oidc client initialisation
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"m.authentication": {
|
||||
issuer: issuer,
|
||||
},
|
||||
});
|
||||
initSessionStorageMock();
|
||||
// set values in session storage as they would be after a successful oidc authentication
|
||||
persistOidcAuthenticatedSettings(clientId, issuer, idTokenClaims);
|
||||
});
|
||||
|
||||
it("should not try to create a token refresher without a refresh token", async () => {
|
||||
await setLoggedIn(credentials);
|
||||
|
||||
// didn't try to initialise token refresher
|
||||
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||
});
|
||||
|
||||
it("should not try to create a token refresher without a deviceId", async () => {
|
||||
await setLoggedIn({
|
||||
...credentials,
|
||||
refreshToken,
|
||||
deviceId: undefined,
|
||||
});
|
||||
|
||||
// didn't try to initialise token refresher
|
||||
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||
});
|
||||
|
||||
it("should not try to create a token refresher without an issuer in session storage", async () => {
|
||||
persistOidcAuthenticatedSettings(
|
||||
clientId,
|
||||
// @ts-ignore set undefined issuer
|
||||
undefined,
|
||||
idTokenClaims,
|
||||
);
|
||||
await setLoggedIn({
|
||||
...credentials,
|
||||
refreshToken,
|
||||
});
|
||||
|
||||
// didn't try to initialise token refresher
|
||||
expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
|
||||
});
|
||||
|
||||
it("should create a client with a tokenRefreshFunction", async () => {
|
||||
expect(
|
||||
await setLoggedIn({
|
||||
...credentials,
|
||||
refreshToken,
|
||||
}),
|
||||
).toEqual(mockClient);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
}),
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("should create a client when creating token refresher fails", async () => {
|
||||
// set invalid value in session storage for a malformed oidc authentication
|
||||
persistOidcAuthenticatedSettings(null as any, issuer, idTokenClaims);
|
||||
|
||||
// succeeded
|
||||
expect(
|
||||
await setLoggedIn({
|
||||
...credentials,
|
||||
refreshToken,
|
||||
}),
|
||||
).toEqual(mockClient);
|
||||
|
||||
expect(MatrixClientPeg.replaceUsingCreds).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
}),
|
||||
// no token refresh function
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user