OIDC: use delegated auth account URL from OidcClientStore (#11723)

* 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

* utils to persist clientId and issuer after oidc authentication

* add dep oidc-client-ts

* persist issuer and clientId after successful oidc auth

* add OidcClientStore

* comments and tidy

* expose getters for stored refresh and access tokens in Lifecycle

* revoke tokens with oidc provider

* test logout action in MatrixChat

* comments

* prettier

* test OidcClientStore.revokeTokens

* put pickle key destruction back

* 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

* OidcClientStore.initClient use stored issuer when client well known unavailable

* test Lifecycle.logout

* update Lifecycle test replaceUsingCreds calls

* fix test

* add sdkContext to UserSettingsDialog

* use sdkContext and oidcClientStore in session manager

* use sdkContext and OidcClientStore in generalusersettingstab

* 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

* fix test mock, comment

* typo

* add sdkContext to SoftLogout, pass oidcClientStore to logout

* fullstops

* comments

* fussy comment formatting

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
Kerry 2023-10-16 12:03:25 +13:00 committed by GitHub
parent 84ca519b3f
commit d9d52fba8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 96 additions and 179 deletions

View file

@ -56,9 +56,8 @@ import SettingsSubsection, { SettingsSubsectionText } from "../../shared/Setting
import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading";
import Heading from "../../../typography/Heading";
import InlineSpinner from "../../../elements/InlineSpinner";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
import { ThirdPartyIdentifier } from "../../../../../AddThreepid";
import { getDelegatedAuthAccountUrl } from "../../../../../utils/oidc/getDelegatedAuthAccountUrl";
import { SDKContext } from "../../../../../contexts/SDKContext";
interface IProps {
closeSettingsFn: () => void;
@ -94,20 +93,22 @@ interface IState {
}
export default class GeneralUserSettingsTab extends React.Component<IProps, IState> {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
public static contextType = SDKContext;
public context!: React.ContextType<typeof SDKContext>;
private readonly dispatcherRef: string;
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
super(props);
this.context = context;
const cli = this.context.client!;
this.state = {
language: languageHandler.getCurrentLanguage(),
spellCheckEnabled: false,
spellCheckLanguages: [],
haveIdServer: Boolean(this.context.getIdentityServerUrl()),
haveIdServer: Boolean(cli.getIdentityServerUrl()),
idServerHasUnsignedTerms: false,
requiredPolicyInfo: {
// This object is passed along to a component for handling
@ -150,7 +151,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private onAction = (payload: ActionPayload): void => {
if (payload.action === "id_server_changed") {
this.setState({ haveIdServer: Boolean(this.context.getIdentityServerUrl()) });
this.setState({ haveIdServer: Boolean(this.context.client!.getIdentityServerUrl()) });
this.getThreepidState();
}
};
@ -164,7 +165,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
};
private async getCapabilities(): Promise<void> {
const cli = this.context;
const cli = this.context.client!;
const capabilities = await cli.getCapabilities(); // this is cached
const changePasswordCap = capabilities["m.change_password"];
@ -174,7 +175,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
// the enabled flag value.
const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false;
const externalAccountManagementUrl = getDelegatedAuthAccountUrl(cli.getClientWellKnown());
const externalAccountManagementUrl = this.context.oidcClientStore.accountManagementEndpoint;
// https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability
// We support as far back as v1.1 which doesn't have m.3pid_changes
// so the behaviour for when it is missing has to be assume true
@ -184,7 +185,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
}
private async getThreepidState(): Promise<void> {
const cli = this.context;
const cli = this.context.client!;
// Check to see if terms need accepting
this.checkTerms();
@ -195,7 +196,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
try {
threepids = await getThreepidsWithBindStatus(cli);
} catch (e) {
const idServerUrl = this.context.getIdentityServerUrl();
const idServerUrl = cli.getIdentityServerUrl();
logger.warn(
`Unable to reach identity server at ${idServerUrl} to check ` + `for 3PIDs bindings in Settings`,
);
@ -211,7 +212,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
private async checkTerms(): Promise<void> {
// By starting the terms flow we get the logic for checking which terms the user has signed
// for free. So we might as well use that for our own purposes.
const idServerUrl = this.context.getIdentityServerUrl();
const idServerUrl = this.context.client!.getIdentityServerUrl();
if (!this.state.haveIdServer || !idServerUrl) {
this.setState({ idServerHasUnsignedTerms: false });
return;
@ -221,7 +222,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
try {
const idAccessToken = await authClient.getAccessToken({ check: false });
await startTermsFlow(
this.context,
this.context.client!,
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
(policiesAndServices, agreedUrls, extraClassNames) => {
return new Promise((resolve, reject) => {