Skip to content

Commit 64fe60e

Browse files
committed
Store id_token and fix up logout implementation
1 parent 82b4981 commit 64fe60e

File tree

6 files changed

+98
-23
lines changed

6 files changed

+98
-23
lines changed

src/domain/LogoutViewModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class LogoutViewModel extends ViewModel<SegmentType, Options> {
5252
this.emitChange("busy");
5353
try {
5454
const client = new Client(this.platform);
55-
await client.startLogout(this._sessionId);
55+
await client.startLogout(this._sessionId, this.urlRouter);
5656
this.navigation.push("session", true);
5757
} catch (err) {
5858
this._error = err;

src/domain/navigation/URLRouter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface IURLRouter<T> {
3333
openRoomActionUrl(roomId: string): string;
3434
createSSOCallbackURL(): string;
3535
createOIDCRedirectURL(): string;
36+
createOIDCPostLogoutRedirectURL(): string;
3637
absoluteAppUrl(): string;
3738
absoluteUrlForAsset(asset: string): string;
3839
normalizeUrl(): void;
@@ -159,6 +160,10 @@ export class URLRouter<T extends {session: string | boolean}> implements IURLRou
159160
return window.location.origin;
160161
}
161162

163+
createOIDCPostLogoutRedirectURL(): string {
164+
return window.location.origin;
165+
}
166+
162167
absoluteAppUrl(): string {
163168
return window.location.origin;
164169
}

src/matrix/Client.js

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ export class Client {
209209
sessionInfo.expiresIn = loginData.expires_in;
210210
}
211211

212+
if (loginData.id_token) {
213+
sessionInfo.idToken = loginData.id_token;
214+
}
215+
212216
if (loginData.oidc_issuer) {
213217
sessionInfo.oidcIssuer = loginData.oidc_issuer;
214218
sessionInfo.oidcClientId = loginData.oidc_client_id;
@@ -236,7 +240,7 @@ export class Client {
236240
});
237241
}
238242

239-
async _createSessionAfterAuth({deviceId, userId, accessToken, refreshToken, homeserver, expiresIn, oidcIssuer, oidcClientId, accountManagementUrl}, inspectAccountSetup, log) {
243+
async _createSessionAfterAuth({deviceId, userId, accessToken, refreshToken, homeserver, expiresIn, idToken, oidcIssuer, oidcClientId, accountManagementUrl}, inspectAccountSetup, log) {
240244
const id = this.createNewSessionId();
241245
const lastUsed = this._platform.clock.now();
242246
const sessionInfo = {
@@ -251,6 +255,7 @@ export class Client {
251255
oidcIssuer,
252256
oidcClientId,
253257
accountManagementUrl,
258+
idToken,
254259
};
255260
if (expiresIn) {
256261
sessionInfo.accessTokenExpiresAt = lastUsed + expiresIn * 1000;
@@ -497,34 +502,51 @@ export class Client {
497502
return !this._reconnector;
498503
}
499504

500-
startLogout(sessionId) {
505+
startLogout(sessionId, urlRouter) {
501506
return this._platform.logger.run("logout", async log => {
502507
this._sessionId = sessionId;
503508
log.set("id", this._sessionId);
504509
const sessionInfo = await this._platform.sessionInfoStorage.get(this._sessionId);
505510
if (!sessionInfo) {
506511
throw new Error(`Could not find session for id ${this._sessionId}`);
507512
}
513+
let endSessionRedirectEndpoint;
508514
try {
509-
const hsApi = new HomeServerApi({
510-
homeserver: sessionInfo.homeServer,
511-
accessToken: sessionInfo.accessToken,
512-
request: this._platform.request
513-
});
514-
await hsApi.logout({log}).response();
515-
const oidcApi = new OidcApi({
516-
issuer: sessionInfo.oidcIssuer,
517-
clientId: sessionInfo.oidcClientId,
518-
request: this._platform.request,
519-
encoding: this._platform.encoding,
520-
crypto: this._platform.crypto,
521-
});
522-
await oidcApi.revokeToken({ token: sessionInfo.accessToken, type: "access" });
523-
if (sessionInfo.refreshToken) {
524-
await oidcApi.revokeToken({ token: sessionInfo.refreshToken, type: "refresh" });
515+
if (sessionInfo.oidcClientId) {
516+
// OIDC logout
517+
const oidcApi = new OidcApi({
518+
issuer: sessionInfo.oidcIssuer,
519+
clientId: sessionInfo.oidcClientId,
520+
request: this._platform.request,
521+
encoding: this._platform.encoding,
522+
crypto: this._platform.crypto,
523+
urlRouter,
524+
});
525+
await oidcApi.revokeToken({ token: sessionInfo.accessToken, type: "access" });
526+
if (sessionInfo.refreshToken) {
527+
await oidcApi.revokeToken({ token: sessionInfo.refreshToken, type: "refresh" });
528+
}
529+
endSessionRedirectEndpoint = await oidcApi.endSessionEndpoint({
530+
idTokenHint: sessionInfo.idToken,
531+
logoutHint: sessionInfo.userId,
532+
})
533+
} else {
534+
// regular logout
535+
const hsApi = new HomeServerApi({
536+
homeserver: sessionInfo.homeServer,
537+
accessToken: sessionInfo.accessToken,
538+
request: this._platform.request
539+
});
540+
await hsApi.logout({log}).response();
525541
}
526-
} catch (err) {}
542+
} catch (err) {
543+
console.error(err);
544+
}
527545
await this.deleteSession(log);
546+
// OIDC might have given us a redirect URI to go to do tell the OP we are signing out
547+
if (endSessionRedirectEndpoint) {
548+
this._platform.openUrl(endSessionRedirectEndpoint);
549+
}
528550
});
529551
}
530552

src/matrix/login/OIDCLoginMethod.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class OIDCLoginMethod implements ILoginMethod {
5555
}
5656

5757
async login(hsApi: HomeServerApi, _deviceName: string, log: ILogItem): Promise<Record<string, any>> {
58-
const { access_token, refresh_token, expires_in } = await this._oidcApi.completeAuthorizationCodeGrant({
58+
const { access_token, refresh_token, expires_in, id_token } = await this._oidcApi.completeAuthorizationCodeGrant({
5959
code: this._code,
6060
codeVerifier: this._codeVerifier,
6161
redirectUri: this._redirectUri,
@@ -72,6 +72,6 @@ export class OIDCLoginMethod implements ILoginMethod {
7272
const oidc_issuer = this._oidcApi.issuer;
7373
const oidc_client_id = await this._oidcApi.clientId();
7474

75-
return { oidc_issuer, oidc_client_id, access_token, refresh_token, expires_in, user_id, device_id, oidc_account_management_url: this._accountManagementUrl };
75+
return { oidc_issuer, oidc_client_id, access_token, refresh_token, expires_in, id_token, user_id, device_id, oidc_account_management_url: this._accountManagementUrl };
7676
}
7777
}

src/matrix/net/OidcApi.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type BearerToken = {
3030
access_token: string,
3131
refresh_token?: string,
3232
expires_in?: number,
33+
id_token?: string,
3334
}
3435

3536
const isValidBearerToken = (t: any): t is BearerToken =>
@@ -48,6 +49,28 @@ type AuthorizationParams = {
4849
codeVerifier?: string,
4950
};
5051

52+
/**
53+
* @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
54+
*/
55+
type LogoutParams = {
56+
/**
57+
* Maps to the `id_token_hint` parameter.
58+
*/
59+
idTokenHint?: string,
60+
/**
61+
* Maps to the `state` parameter.
62+
*/
63+
state?: string,
64+
/**
65+
* Maps to the `post_logout_redirect_uri` parameter.
66+
*/
67+
redirectUri?: string,
68+
/**
69+
* Maps to the `logout_hint` parameter.
70+
*/
71+
logoutHint?: string,
72+
};
73+
5174
function assert(condition: any, message: string): asserts condition {
5275
if (!condition) {
5376
throw new Error(`Assertion failed: ${message}`);
@@ -97,6 +120,7 @@ export class OidcApi<N extends object = SegmentType> {
97120
redirect_uris: [this._urlRouter.createOIDCRedirectURL()],
98121
id_token_signed_response_alg: "RS256",
99122
token_endpoint_auth_method: "none",
123+
post_logout_redirect_uris: [this._urlRouter.createOIDCPostLogoutRedirectURL()],
100124
};
101125
}
102126

@@ -226,6 +250,30 @@ export class OidcApi<N extends object = SegmentType> {
226250
return metadata["revocation_endpoint"];
227251
}
228252

253+
async endSessionEndpoint({idTokenHint, logoutHint, redirectUri, state}: LogoutParams): Promise<string | undefined> {
254+
const metadata = await this.metadata();
255+
const endpoint = metadata["end_session_endpoint"];
256+
if (!endpoint) {
257+
return undefined;
258+
}
259+
if (!redirectUri) {
260+
redirectUri = this._urlRouter.createOIDCPostLogoutRedirectURL();
261+
}
262+
const url = new URL(endpoint);
263+
url.searchParams.append("client_id", await this.clientId());
264+
url.searchParams.append("post_logout_redirect_uri", redirectUri);
265+
if (idTokenHint) {
266+
url.searchParams.append("id_token_hint", idTokenHint);
267+
}
268+
if (logoutHint) {
269+
url.searchParams.append("logout_hint", logoutHint);
270+
}
271+
if (state) {
272+
url.searchParams.append("state", state);
273+
}
274+
return url.href;
275+
}
276+
229277
async isGuestAvailable(): Promise<boolean> {
230278
const metadata = await this.metadata();
231279
return metadata["scopes_supported"]?.includes("urn:matrix:org.matrix.msc2967.client:api:guest");
@@ -331,7 +379,6 @@ export class OidcApi<N extends object = SegmentType> {
331379
const req = this._requestFn(revocationEndpoint, {
332380
method: "POST",
333381
headers,
334-
format: "json",
335382
body,
336383
});
337384

src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface ISessionInfo {
2626
oidcIssuer?: string;
2727
accountManagementUrl?: string;
2828
lastUsed: number;
29+
idToken?: string;
2930
}
3031

3132
// todo: this should probably be in platform/types?

0 commit comments

Comments
 (0)