feat(auth): OIDC phase 4/5/6 — token refresh, logout revocation, account link

- initMatrix.ts: import the shared Session type; when a session has a refresh
  token + oidc metadata, wire a LotusOidcTokenRefresher via createClient's
  refreshToken + tokenRefreshFunction (reactive 401 refresh). Rust crypto is
  unaffected (still keyed on userId/deviceId).
- client/oidcTokenRefresher.ts: OidcTokenRefresher subclass that persists rotated
  tokens back to the fallback session.
- client/oidcLogout.ts + logoutClient: best-effort revoke access+refresh tokens at
  the issuer's revocation_endpoint on logout (tolerant of failure).
- settings/account/OidcManageAccount.tsx: MSC2965 "Manage account" deep-link,
  shown only when authMetadata is present (OIDC servers); mirrors OtherDevices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 16:12:13 -04:00
co-authored by Claude Opus 4.8
parent dd6b0bccb3
commit 67bd05fc96
5 changed files with 141 additions and 8 deletions
+42
View File
@@ -0,0 +1,42 @@
import { OidcTokenRefresher } from 'matrix-js-sdk';
import type { IdTokenClaims } from 'oidc-client-ts';
import { OidcSessionMeta, setFallbackSession } from '../app/state/sessions';
/**
* OidcTokenRefresher that persists rotated tokens back to the fallback session,
* so a page reload keeps the freshest access/refresh token. The matrix client
* calls this automatically (reactively on a 401) when a refresh token is set.
*/
export class LotusOidcTokenRefresher extends OidcTokenRefresher {
private readonly deviceIdRef: string;
private readonly userIdRef: string;
private readonly baseUrlRef: string;
private readonly oidcRef: OidcSessionMeta;
constructor(oidc: OidcSessionMeta, deviceId: string, userId: string, baseUrl: string) {
super(
oidc.issuer,
oidc.clientId,
oidc.redirectUri,
deviceId,
(oidc.idTokenClaims ?? {}) as unknown as IdTokenClaims,
);
this.deviceIdRef = deviceId;
this.userIdRef = userId;
this.baseUrlRef = baseUrl;
this.oidcRef = oidc;
}
protected async persistTokens(tokens: {
accessToken: string;
refreshToken?: string;
}): Promise<void> {
setFallbackSession(tokens.accessToken, this.deviceIdRef, this.userIdRef, this.baseUrlRef, {
refreshToken: tokens.refreshToken,
oidc: this.oidcRef,
});
}
}