Skip to content

Commit

Permalink
Store id_token and fix up logout implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
hughns committed Feb 16, 2023
1 parent 82b4981 commit 64fe60e
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/domain/LogoutViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class LogoutViewModel extends ViewModel<SegmentType, Options> {
this.emitChange("busy");
try {
const client = new Client(this.platform);
await client.startLogout(this._sessionId);
await client.startLogout(this._sessionId, this.urlRouter);
this.navigation.push("session", true);
} catch (err) {
this._error = err;
Expand Down
5 changes: 5 additions & 0 deletions src/domain/navigation/URLRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface IURLRouter<T> {
openRoomActionUrl(roomId: string): string;
createSSOCallbackURL(): string;
createOIDCRedirectURL(): string;
createOIDCPostLogoutRedirectURL(): string;
absoluteAppUrl(): string;
absoluteUrlForAsset(asset: string): string;
normalizeUrl(): void;
Expand Down Expand Up @@ -159,6 +160,10 @@ export class URLRouter<T extends {session: string | boolean}> implements IURLRou
return window.location.origin;
}

createOIDCPostLogoutRedirectURL(): string {
return window.location.origin;
}

absoluteAppUrl(): string {
return window.location.origin;
}
Expand Down
60 changes: 41 additions & 19 deletions src/matrix/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ export class Client {
sessionInfo.expiresIn = loginData.expires_in;
}

if (loginData.id_token) {
sessionInfo.idToken = loginData.id_token;
}

if (loginData.oidc_issuer) {
sessionInfo.oidcIssuer = loginData.oidc_issuer;
sessionInfo.oidcClientId = loginData.oidc_client_id;
Expand Down Expand Up @@ -236,7 +240,7 @@ export class Client {
});
}

async _createSessionAfterAuth({deviceId, userId, accessToken, refreshToken, homeserver, expiresIn, oidcIssuer, oidcClientId, accountManagementUrl}, inspectAccountSetup, log) {
async _createSessionAfterAuth({deviceId, userId, accessToken, refreshToken, homeserver, expiresIn, idToken, oidcIssuer, oidcClientId, accountManagementUrl}, inspectAccountSetup, log) {
const id = this.createNewSessionId();
const lastUsed = this._platform.clock.now();
const sessionInfo = {
Expand All @@ -251,6 +255,7 @@ export class Client {
oidcIssuer,
oidcClientId,
accountManagementUrl,
idToken,
};
if (expiresIn) {
sessionInfo.accessTokenExpiresAt = lastUsed + expiresIn * 1000;
Expand Down Expand Up @@ -497,34 +502,51 @@ export class Client {
return !this._reconnector;
}

startLogout(sessionId) {
startLogout(sessionId, urlRouter) {
return this._platform.logger.run("logout", async log => {
this._sessionId = sessionId;
log.set("id", this._sessionId);
const sessionInfo = await this._platform.sessionInfoStorage.get(this._sessionId);
if (!sessionInfo) {
throw new Error(`Could not find session for id ${this._sessionId}`);
}
let endSessionRedirectEndpoint;
try {
const hsApi = new HomeServerApi({
homeserver: sessionInfo.homeServer,
accessToken: sessionInfo.accessToken,
request: this._platform.request
});
await hsApi.logout({log}).response();
const oidcApi = new OidcApi({
issuer: sessionInfo.oidcIssuer,
clientId: sessionInfo.oidcClientId,
request: this._platform.request,
encoding: this._platform.encoding,
crypto: this._platform.crypto,
});
await oidcApi.revokeToken({ token: sessionInfo.accessToken, type: "access" });
if (sessionInfo.refreshToken) {
await oidcApi.revokeToken({ token: sessionInfo.refreshToken, type: "refresh" });
if (sessionInfo.oidcClientId) {
// OIDC logout
const oidcApi = new OidcApi({
issuer: sessionInfo.oidcIssuer,
clientId: sessionInfo.oidcClientId,
request: this._platform.request,
encoding: this._platform.encoding,
crypto: this._platform.crypto,
urlRouter,
});
await oidcApi.revokeToken({ token: sessionInfo.accessToken, type: "access" });
if (sessionInfo.refreshToken) {
await oidcApi.revokeToken({ token: sessionInfo.refreshToken, type: "refresh" });
}
endSessionRedirectEndpoint = await oidcApi.endSessionEndpoint({
idTokenHint: sessionInfo.idToken,
logoutHint: sessionInfo.userId,
})
} else {
// regular logout
const hsApi = new HomeServerApi({
homeserver: sessionInfo.homeServer,
accessToken: sessionInfo.accessToken,
request: this._platform.request
});
await hsApi.logout({log}).response();
}
} catch (err) {}
} catch (err) {
console.error(err);
}
await this.deleteSession(log);
// OIDC might have given us a redirect URI to go to do tell the OP we are signing out
if (endSessionRedirectEndpoint) {
this._platform.openUrl(endSessionRedirectEndpoint);
}
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/matrix/login/OIDCLoginMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class OIDCLoginMethod implements ILoginMethod {
}

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

return { oidc_issuer, oidc_client_id, access_token, refresh_token, expires_in, user_id, device_id, oidc_account_management_url: this._accountManagementUrl };
return { oidc_issuer, oidc_client_id, access_token, refresh_token, expires_in, id_token, user_id, device_id, oidc_account_management_url: this._accountManagementUrl };
}
}
49 changes: 48 additions & 1 deletion src/matrix/net/OidcApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type BearerToken = {
access_token: string,
refresh_token?: string,
expires_in?: number,
id_token?: string,
}

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

/**
* @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
*/
type LogoutParams = {
/**
* Maps to the `id_token_hint` parameter.
*/
idTokenHint?: string,
/**
* Maps to the `state` parameter.
*/
state?: string,
/**
* Maps to the `post_logout_redirect_uri` parameter.
*/
redirectUri?: string,
/**
* Maps to the `logout_hint` parameter.
*/
logoutHint?: string,
};

function assert(condition: any, message: string): asserts condition {
if (!condition) {
throw new Error(`Assertion failed: ${message}`);
Expand Down Expand Up @@ -97,6 +120,7 @@ export class OidcApi<N extends object = SegmentType> {
redirect_uris: [this._urlRouter.createOIDCRedirectURL()],
id_token_signed_response_alg: "RS256",
token_endpoint_auth_method: "none",
post_logout_redirect_uris: [this._urlRouter.createOIDCPostLogoutRedirectURL()],
};
}

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

async endSessionEndpoint({idTokenHint, logoutHint, redirectUri, state}: LogoutParams): Promise<string | undefined> {
const metadata = await this.metadata();
const endpoint = metadata["end_session_endpoint"];
if (!endpoint) {
return undefined;
}
if (!redirectUri) {
redirectUri = this._urlRouter.createOIDCPostLogoutRedirectURL();
}
const url = new URL(endpoint);
url.searchParams.append("client_id", await this.clientId());
url.searchParams.append("post_logout_redirect_uri", redirectUri);
if (idTokenHint) {
url.searchParams.append("id_token_hint", idTokenHint);
}
if (logoutHint) {
url.searchParams.append("logout_hint", logoutHint);
}
if (state) {
url.searchParams.append("state", state);
}
return url.href;
}

async isGuestAvailable(): Promise<boolean> {
const metadata = await this.metadata();
return metadata["scopes_supported"]?.includes("urn:matrix:org.matrix.msc2967.client:api:guest");
Expand Down Expand Up @@ -331,7 +379,6 @@ export class OidcApi<N extends object = SegmentType> {
const req = this._requestFn(revocationEndpoint, {
method: "POST",
headers,
format: "json",
body,
});

Expand Down
1 change: 1 addition & 0 deletions src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface ISessionInfo {
oidcIssuer?: string;
accountManagementUrl?: string;
lastUsed: number;
idToken?: string;
}

// todo: this should probably be in platform/types?
Expand Down

0 comments on commit 64fe60e

Please sign in to comment.