2019-05-03 04:57:49 +00:00
/ *
2021-04-06 11:26:50 +00:00
Copyright 2019 - 2021 The Matrix . org Foundation C . I . C .
2019-05-03 04:57:49 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2021-04-06 11:26:50 +00:00
import React , { ReactNode } from "react" ;
2023-08-15 15:00:17 +00:00
import {
AutoDiscovery ,
ClientConfig ,
OidcClientConfig ,
M_AUTHENTICATION ,
IClientWellKnown ,
} from "matrix-js-sdk/src/matrix" ;
2021-10-22 22:23:32 +00:00
import { logger } from "matrix-js-sdk/src/logger" ;
2023-08-22 15:32:05 +00:00
import { _t , TranslationKey , UserFriendlyError } from "../languageHandler" ;
2019-08-07 10:15:56 +00:00
import SdkConfig from "../SdkConfig" ;
2023-07-10 00:57:16 +00:00
import { ValidatedServerConfig } from "./ValidatedServerConfig" ;
2019-05-03 04:57:49 +00:00
2021-04-06 11:26:50 +00:00
const LIVELINESS_DISCOVERY_ERRORS : string [ ] = [
2019-06-05 05:41:59 +00:00
AutoDiscovery . ERROR_INVALID_HOMESERVER ,
AutoDiscovery . ERROR_INVALID_IDENTITY_SERVER ,
] ;
2021-04-06 11:26:50 +00:00
export interface IAuthComponentState {
serverIsAlive : boolean ;
serverErrorIsFatal : boolean ;
serverDeadError? : ReactNode ;
}
2019-05-03 04:57:49 +00:00
export default class AutoDiscoveryUtils {
2019-06-05 05:41:59 +00:00
/ * *
* Checks if a given error or error message is considered an error
* relating to the liveliness of the server . Must be an error returned
* from this AutoDiscoveryUtils class .
2021-04-06 11:26:50 +00:00
* @param { string | Error } error The error to check
2019-06-05 05:41:59 +00:00
* @returns { boolean } True if the error is a liveliness error .
* /
2023-07-07 13:46:12 +00:00
public static isLivelinessError ( error : unknown ) : boolean {
2019-06-05 05:41:59 +00:00
if ( ! error ) return false ;
2023-07-07 13:46:12 +00:00
return ! ! LIVELINESS_DISCOVERY_ERRORS . find ( ( e ) = > ( error instanceof Error ? e === error.message : e === error ) ) ;
2019-06-05 05:41:59 +00:00
}
/ * *
* Gets the common state for auth components ( login , registration , forgot
* password ) for a given validation error .
* @param { Error } err The error encountered .
2019-06-11 01:28:32 +00:00
* @param { string } pageName The page for which the error should be customized to . See
* implementation for known values .
* @returns { * } The state for the component , given the error .
2019-06-05 05:41:59 +00:00
* /
2023-07-07 13:46:12 +00:00
public static authComponentStateForError ( err : unknown , pageName = "login" ) : IAuthComponentState {
2019-11-01 10:44:30 +00:00
if ( ! err ) {
return {
serverIsAlive : true ,
serverErrorIsFatal : false ,
serverDeadError : null ,
} ;
}
2019-06-05 17:32:02 +00:00
let title = _t ( "Cannot reach homeserver" ) ;
2021-04-06 11:26:50 +00:00
let body : ReactNode = _t ( "Ensure you have a stable internet connection, or get in touch with the server admin" ) ;
2019-06-05 17:32:02 +00:00
if ( ! AutoDiscoveryUtils . isLivelinessError ( err ) ) {
2020-07-10 18:07:11 +00:00
const brand = SdkConfig . get ( ) . brand ;
title = _t ( "Your %(brand)s is misconfigured" , { brand } ) ;
2019-06-05 17:32:02 +00:00
body = _t (
2020-07-10 18:07:11 +00:00
"Ask your %(brand)s admin to check <a>your config</a> for incorrect or duplicate entries." ,
{
brand ,
} ,
{
2019-06-05 17:32:02 +00:00
a : ( sub ) = > {
return (
< a
2020-08-03 15:02:26 +00:00
href = "https://github.com/vector-im/element-web/blob/master/docs/config.md"
2019-06-05 17:32:02 +00:00
target = "_blank"
2020-02-23 22:14:29 +00:00
rel = "noreferrer noopener"
2021-07-19 21:43:11 +00:00
>
{ sub }
< / a >
) ;
2019-06-05 18:15:37 +00:00
} ,
2019-06-05 17:32:02 +00:00
} ,
) ;
2019-06-05 05:41:59 +00:00
}
2019-06-05 17:32:02 +00:00
2019-06-11 01:28:32 +00:00
let isFatalError = true ;
2023-07-07 13:46:12 +00:00
const errorMessage = err instanceof Error ? err.message : err ;
2019-06-11 01:28:32 +00:00
if ( errorMessage === AutoDiscovery . ERROR_INVALID_IDENTITY_SERVER ) {
isFatalError = false ;
title = _t ( "Cannot reach identity server" ) ;
// It's annoying having a ladder for the third word in the same sentence, but our translations
// don't make this easy to avoid.
if ( pageName === "register" ) {
body = _t (
2023-08-22 15:32:05 +00:00
"You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin." ,
2019-06-11 01:28:32 +00:00
) ;
} else if ( pageName === "reset_password" ) {
body = _t (
2023-08-22 15:32:05 +00:00
"You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin." ,
2019-06-11 01:28:32 +00:00
) ;
} else {
body = _t (
2023-08-22 15:32:05 +00:00
"You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin." ,
2019-06-11 01:28:32 +00:00
) ;
}
}
2019-06-05 17:32:02 +00:00
return {
serverIsAlive : false ,
2019-06-11 01:28:32 +00:00
serverErrorIsFatal : isFatalError ,
2019-06-05 17:32:02 +00:00
serverDeadError : (
< div >
2021-07-19 21:43:11 +00:00
< strong > { title } < / strong >
< div > { body } < / div >
2019-06-05 17:32:02 +00:00
< / div >
) ,
} ;
2019-06-05 05:41:59 +00:00
}
/ * *
* Validates a server configuration , using a pair of URLs as input .
* @param { string } homeserverUrl The homeserver URL .
* @param { string } identityUrl The identity server URL .
* @param { boolean } syntaxOnly If true , errors relating to liveliness of the servers will
* not be raised .
* @returns { Promise < ValidatedServerConfig > } Resolves to the validated configuration .
* /
2022-12-16 12:29:59 +00:00
public static async validateServerConfigWithStaticUrls (
2021-04-06 11:26:50 +00:00
homeserverUrl : string ,
identityUrl? : string ,
syntaxOnly = false ,
) : Promise < ValidatedServerConfig > {
2019-05-03 04:57:49 +00:00
if ( ! homeserverUrl ) {
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "No homeserver URL provided" ) ;
2019-05-03 04:57:49 +00:00
}
2023-02-13 11:39:16 +00:00
const wellknownConfig : IClientWellKnown = {
2019-05-03 04:57:49 +00:00
"m.homeserver" : {
base_url : homeserverUrl ,
} ,
} ;
2019-08-07 10:15:56 +00:00
if ( identityUrl ) {
wellknownConfig [ "m.identity_server" ] = {
base_url : identityUrl ,
} ;
}
2019-05-03 04:57:49 +00:00
const result = await AutoDiscovery . fromDiscoveryConfig ( wellknownConfig ) ;
const url = new URL ( homeserverUrl ) ;
const serverName = url . hostname ;
2020-12-08 10:58:16 +00:00
return AutoDiscoveryUtils . buildValidatedConfigFromDiscovery ( serverName , result , syntaxOnly , true ) ;
2019-05-03 04:57:49 +00:00
}
2019-06-05 05:41:59 +00:00
/ * *
* Validates a server configuration , using a homeserver domain name as input .
* @param { string } serverName The homeserver domain name ( eg : "matrix.org" ) to validate .
* @returns { Promise < ValidatedServerConfig > } Resolves to the validated configuration .
* /
2022-12-16 12:29:59 +00:00
public static async validateServerName ( serverName : string ) : Promise < ValidatedServerConfig > {
2019-05-03 04:57:49 +00:00
const result = await AutoDiscovery . findClientConfig ( serverName ) ;
return AutoDiscoveryUtils . buildValidatedConfigFromDiscovery ( serverName , result ) ;
}
2019-06-05 05:41:59 +00:00
/ * *
* Validates a server configuration , using a pre - calculated AutoDiscovery result as
* input .
* @param { string } serverName The domain name the AutoDiscovery result is for .
* @param { * } discoveryResult The AutoDiscovery result .
2020-12-08 10:58:16 +00:00
* @param { boolean } syntaxOnly If true , errors relating to liveliness of the servers will not be raised .
2020-12-09 11:14:06 +00:00
* @param { boolean } isSynthetic If true , then the discoveryResult was synthesised locally .
2019-06-05 05:41:59 +00:00
* @returns { Promise < ValidatedServerConfig > } Resolves to the validated configuration .
* /
2022-12-16 12:29:59 +00:00
public static buildValidatedConfigFromDiscovery (
2023-04-24 07:29:01 +00:00
serverName? : string ,
discoveryResult? : ClientConfig ,
2020-12-09 11:14:06 +00:00
syntaxOnly = false ,
isSynthetic = false ,
) : ValidatedServerConfig {
2023-04-24 07:29:01 +00:00
if ( ! discoveryResult ? . [ "m.homeserver" ] ) {
2019-05-03 04:57:49 +00:00
// This shouldn't happen without major misconfiguration, so we'll log a bit of information
2023-04-03 08:26:55 +00:00
// in the log so we can find this bit of code but otherwise tell the user "it broke".
2021-10-15 14:30:53 +00:00
logger . error ( "Ended up in a state of not knowing which homeserver to connect to." ) ;
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Unexpected error resolving homeserver configuration" ) ;
2019-05-03 04:57:49 +00:00
}
const hsResult = discoveryResult [ "m.homeserver" ] ;
2019-06-07 19:14:43 +00:00
const isResult = discoveryResult [ "m.identity_server" ] ;
2022-03-18 16:12:36 +00:00
const defaultConfig = SdkConfig . get ( "validated_server_config" ) ;
2019-08-07 10:15:56 +00:00
2019-06-07 19:14:43 +00:00
// Validate the identity server first because an invalid identity server causes
2019-11-01 10:44:30 +00:00
// an invalid homeserver, which may not be picked up correctly.
2019-05-03 04:57:49 +00:00
2019-08-07 10:15:56 +00:00
// Note: In the cases where we rely on the default IS from the config (namely
2019-06-06 18:18:41 +00:00
// lack of identity server provided by the discovery method), we intentionally do not
2019-08-07 10:15:56 +00:00
// validate it. This has already been validated and this helps some off-the-grid usage
2020-08-03 15:02:26 +00:00
// of Element.
2019-08-07 10:15:56 +00:00
let preferredIdentityUrl = defaultConfig && defaultConfig [ "isUrl" ] ;
2019-05-03 04:57:49 +00:00
if ( isResult && isResult . state === AutoDiscovery . SUCCESS ) {
2023-04-03 08:26:55 +00:00
preferredIdentityUrl = isResult [ "base_url" ] ? ? undefined ;
2019-05-03 04:57:49 +00:00
} else if ( isResult && isResult . state !== AutoDiscovery . PROMPT ) {
2021-10-15 14:30:53 +00:00
logger . error ( "Error determining preferred identity server URL:" , isResult ) ;
2019-11-01 10:44:30 +00:00
if ( isResult . state === AutoDiscovery . FAIL_ERROR ) {
2023-02-13 11:39:16 +00:00
if ( AutoDiscovery . ALL_ERRORS . indexOf ( isResult . error as string ) !== - 1 ) {
2023-08-22 15:32:05 +00:00
// XXX: We mark these with _td at the top of Login.tsx - we should come up with a better solution
throw new UserFriendlyError ( String ( isResult . error ) as TranslationKey ) ;
2019-06-05 05:41:59 +00:00
}
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Unexpected error resolving identity server configuration" ) ;
2019-06-05 05:41:59 +00:00
} // else the error is not related to syntax - continue anyways.
2019-06-07 19:14:43 +00:00
2019-11-01 10:44:30 +00:00
// rewrite homeserver error since we don't care about problems
hsResult . error = AutoDiscovery . ERROR_INVALID_IDENTITY_SERVER ;
2019-06-07 19:14:43 +00:00
2019-11-01 10:44:30 +00:00
// Also use the user's supplied identity server if provided
if ( isResult [ "base_url" ] ) preferredIdentityUrl = isResult [ "base_url" ] ;
2019-06-07 19:14:43 +00:00
}
if ( hsResult . state !== AutoDiscovery . SUCCESS ) {
2021-10-15 14:30:53 +00:00
logger . error ( "Error processing homeserver config:" , hsResult ) ;
2019-06-07 19:14:43 +00:00
if ( ! syntaxOnly || ! AutoDiscoveryUtils . isLivelinessError ( hsResult . error ) ) {
2023-02-13 11:39:16 +00:00
if ( AutoDiscovery . ALL_ERRORS . indexOf ( hsResult . error as string ) !== - 1 ) {
2023-08-22 15:32:05 +00:00
// XXX: We mark these with _td at the top of Login.tsx - we should come up with a better solution
throw new UserFriendlyError ( String ( hsResult . error ) as TranslationKey ) ;
2019-06-07 19:14:43 +00:00
}
2023-08-14 08:25:13 +00:00
if ( hsResult . error === AutoDiscovery . ERROR_HOMESERVER_TOO_OLD ) {
throw new UserFriendlyError (
"Your homeserver is too old and does not support the minimum API version required. Please contact your server owner, or upgrade your server." ,
) ;
}
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Unexpected error resolving homeserver configuration" ) ;
2019-06-07 19:14:43 +00:00
} // else the error is not related to syntax - continue anyways.
2019-05-03 04:57:49 +00:00
}
const preferredHomeserverUrl = hsResult [ "base_url" ] ;
2023-04-03 08:26:55 +00:00
if ( ! preferredHomeserverUrl ) {
logger . error ( "No homeserver URL configured" ) ;
throw new UserFriendlyError ( "Unexpected error resolving homeserver configuration" ) ;
}
2023-04-24 07:29:01 +00:00
let preferredHomeserverName = serverName ? ? hsResult [ "server_name" ] ;
2019-05-03 04:57:49 +00:00
const url = new URL ( preferredHomeserverUrl ) ;
if ( ! preferredHomeserverName ) preferredHomeserverName = url . hostname ;
// It should have been set by now, so check it
if ( ! preferredHomeserverName ) {
2021-10-15 14:30:53 +00:00
logger . error ( "Failed to parse homeserver name from homeserver URL" ) ;
2023-03-31 07:30:43 +00:00
throw new UserFriendlyError ( "Unexpected error resolving homeserver configuration" ) ;
2019-05-03 04:57:49 +00:00
}
2023-07-10 00:57:16 +00:00
let delegatedAuthentication : OidcClientConfig | undefined ;
2023-06-13 01:43:25 +00:00
if ( discoveryResult [ M_AUTHENTICATION . stable ! ] ? . state === AutoDiscovery . SUCCESS ) {
2023-07-10 00:57:16 +00:00
const {
authorizationEndpoint ,
registrationEndpoint ,
tokenEndpoint ,
account ,
issuer ,
metadata ,
signingKeys ,
} = discoveryResult [ M_AUTHENTICATION . stable ! ] as OidcClientConfig ;
2023-06-22 10:15:44 +00:00
delegatedAuthentication = Object . freeze ( {
2023-06-13 01:43:25 +00:00
authorizationEndpoint ,
registrationEndpoint ,
tokenEndpoint ,
account ,
issuer ,
2023-07-10 00:57:16 +00:00
metadata ,
signingKeys ,
2023-06-22 10:15:44 +00:00
} ) ;
2023-06-13 01:43:25 +00:00
}
2023-04-03 08:26:55 +00:00
return {
2019-05-03 04:57:49 +00:00
hsUrl : preferredHomeserverUrl ,
hsName : preferredHomeserverName ,
hsNameIsDifferent : url.hostname !== preferredHomeserverName ,
isUrl : preferredIdentityUrl ,
2019-05-14 19:06:56 +00:00
isDefault : false ,
2019-11-01 10:44:30 +00:00
warning : hsResult.error ,
2020-12-09 11:14:06 +00:00
isNameResolvable : ! isSynthetic ,
2023-06-13 01:43:25 +00:00
delegatedAuthentication ,
2023-04-03 08:26:55 +00:00
} as ValidatedServerConfig ;
2019-05-03 04:57:49 +00:00
}
}