|
2 | 2 | Copyright 2015, 2016 OpenMarket Ltd
|
3 | 3 | Copyright 2017 Vector Creations Ltd
|
4 | 4 | Copyright 2018 New Vector Ltd
|
5 |
| -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. |
| 5 | +Copyright 2019, 2020, 2023 The Matrix.org Foundation C.I.C. |
6 | 6 |
|
7 | 7 | Licensed under the Apache License, Version 2.0 (the "License");
|
8 | 8 | you may not use this file except in compliance with the License.
|
@@ -65,6 +65,7 @@ import AbstractLocalStorageSettingsHandler from "./settings/handlers/AbstractLoc
|
65 | 65 | import { OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPayload";
|
66 | 66 | import { SdkContextClass } from "./contexts/SDKContext";
|
67 | 67 | import { messageForLoginError } from "./utils/ErrorUtils";
|
| 68 | +import { completeOidcLogin } from "./utils/oidc/authorize"; |
68 | 69 |
|
69 | 70 | const HOMESERVER_URL_KEY = "mx_hs_url";
|
70 | 71 | const ID_SERVER_URL_KEY = "mx_is_url";
|
@@ -182,13 +183,102 @@ export async function getStoredSessionOwner(): Promise<[string, boolean] | [null
|
182 | 183 | }
|
183 | 184 |
|
184 | 185 | /**
|
| 186 | + * If query string includes OIDC authorization code flow parameters attempt to login using oidc flow |
| 187 | + * Else, we may be returning from SSO - attempt token login |
| 188 | + * |
185 | 189 | * @param {Object} queryParams string->string map of the
|
186 | 190 | * query-parameters extracted from the real query-string of the starting
|
187 | 191 | * URI.
|
188 | 192 | *
|
189 | 193 | * @param {string} defaultDeviceDisplayName
|
190 | 194 | * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again"
|
191 | 195 | *
|
| 196 | + * @returns {Promise} promise which resolves to true if we completed the delegated auth login |
| 197 | + * else false |
| 198 | + */ |
| 199 | +export async function attemptDelegatedAuthLogin( |
| 200 | + queryParams: QueryDict, |
| 201 | + defaultDeviceDisplayName?: string, |
| 202 | + fragmentAfterLogin?: string, |
| 203 | +): Promise<boolean> { |
| 204 | + if (queryParams.code && queryParams.state) { |
| 205 | + return attemptOidcNativeLogin(queryParams); |
| 206 | + } |
| 207 | + |
| 208 | + return attemptTokenLogin(queryParams, defaultDeviceDisplayName, fragmentAfterLogin); |
| 209 | +} |
| 210 | + |
| 211 | +/** |
| 212 | + * Attempt to login by completing OIDC authorization code flow |
| 213 | + * @param queryParams string->string map of the query-parameters extracted from the real query-string of the starting URI. |
| 214 | + * @returns Promise that resolves to true when login succceeded, else false |
| 215 | + */ |
| 216 | +async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean> { |
| 217 | + try { |
| 218 | + const { accessToken, homeserverUrl, identityServerUrl } = await completeOidcLogin(queryParams); |
| 219 | + |
| 220 | + const { |
| 221 | + user_id: userId, |
| 222 | + device_id: deviceId, |
| 223 | + is_guest: isGuest, |
| 224 | + } = await getUserIdFromAccessToken(accessToken, homeserverUrl, identityServerUrl); |
| 225 | + |
| 226 | + const credentials = { |
| 227 | + accessToken, |
| 228 | + homeserverUrl, |
| 229 | + identityServerUrl, |
| 230 | + deviceId, |
| 231 | + userId, |
| 232 | + isGuest, |
| 233 | + }; |
| 234 | + |
| 235 | + logger.debug("Logged in via OIDC native flow"); |
| 236 | + await onSuccessfulDelegatedAuthLogin(credentials); |
| 237 | + return true; |
| 238 | + } catch (error) { |
| 239 | + logger.error("Failed to login via OIDC", error); |
| 240 | + |
| 241 | + // TODO(kerrya) nice error messages https://github.com/vector-im/element-web/issues/25665 |
| 242 | + await onFailedDelegatedAuthLogin(_t("Something went wrong.")); |
| 243 | + return false; |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +/** |
| 248 | + * Gets information about the owner of a given access token. |
| 249 | + * @param accessToken |
| 250 | + * @param homeserverUrl |
| 251 | + * @param identityServerUrl |
| 252 | + * @returns Promise that resolves with whoami response |
| 253 | + * @throws when whoami request fails |
| 254 | + */ |
| 255 | +async function getUserIdFromAccessToken( |
| 256 | + accessToken: string, |
| 257 | + homeserverUrl: string, |
| 258 | + identityServerUrl?: string, |
| 259 | +): Promise<ReturnType<MatrixClient["whoami"]>> { |
| 260 | + try { |
| 261 | + const client = createClient({ |
| 262 | + baseUrl: homeserverUrl, |
| 263 | + accessToken: accessToken, |
| 264 | + idBaseUrl: identityServerUrl, |
| 265 | + }); |
| 266 | + |
| 267 | + return await client.whoami(); |
| 268 | + } catch (error) { |
| 269 | + logger.error("Failed to retrieve userId using accessToken", error); |
| 270 | + throw new Error("Failed to retrieve userId using accessToken"); |
| 271 | + } |
| 272 | +} |
| 273 | + |
| 274 | +/** |
| 275 | + * @param {QueryDict} queryParams string->string map of the |
| 276 | + * query-parameters extracted from the real query-string of the starting |
| 277 | + * URI. |
| 278 | + * |
| 279 | + * @param {string} defaultDeviceDisplayName |
| 280 | + * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again" |
| 281 | + * |
192 | 282 | * @returns {Promise} promise which resolves to true if we completed the token
|
193 | 283 | * login, else false
|
194 | 284 | */
|
|
0 commit comments