import { useBoolean } from "@fluentui/react-hooks"; import { client } from "Common/CosmosClient"; import { Keys, t } from "Localization"; import { updateUserContext, userContext } from "UserContext"; import * as React from "react"; import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg"; import ErrorImage from "../../../../images/error.svg"; import { AuthType } from "../../../AuthType"; import { HttpHeaders } from "../../../Common/Constants"; import { configContext } from "../../../ConfigContext"; import { AccessInputMetadata } from "../../../Contracts/DataModels"; import { parseConnectionString } from "../Helpers/ConnectionStringParser"; import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils"; import { extractAccountKeyFromConnectionString, isDirectConnectionStringLoginApi, validateDirectConnectionStringLogin, } from "../HostedUtils"; interface Props { connectionString: string; login: () => void; setEncryptedToken: (token: string) => void; setConnectionString: (connectionString: string) => void; setAuthType: (authType: AuthType) => void; setDirectLoginMetadata: (metadata: AccessInputMetadata) => void; } export const fetchEncryptedToken = async (connectionString: string): Promise => { const headers = new Headers(); headers.append(HttpHeaders.connectionString, connectionString); const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken"; const response = await fetch(url, { headers, method: "POST" }); if (!response.ok) { throw response; } const encryptedTokenResponse: string = await response.json(); return decodeURIComponent(encryptedTokenResponse); }; export const isAccountRestrictedForConnectionStringLogin = async (connectionString: string): Promise => { const headers = new Headers(); headers.append(HttpHeaders.connectionString, connectionString); const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/guest/accountrestrictions/checkconnectionstringlogin"; const response = await fetch(url, { headers, method: "POST" }); if (!response.ok) { throw response; } return (await response.text()).toLowerCase() === "true"; }; // Verifies the account key can actually authenticate against the account by making a lightweight read of // the database account through the Cosmos client. Returns undefined on success, or an error message when // the client cannot connect. export const validateDirectConnectionStringConnectivity = async ( connectionString: string, metadata: AccessInputMetadata, ): Promise => { const masterKey = extractAccountKeyFromConnectionString(connectionString); if (!metadata?.documentEndpoint || !masterKey) { return t(Keys.connectExplorer.errors.connectivityInvalid); } // The Cosmos client reads its connection settings from userContext, so write the master key and // endpoint there. Then we issue a lightweight authenticated read to confirm the // credentials work. updateUserContext({ authType: AuthType.ConnectionString, masterKey, endpoint: metadata.documentEndpoint, refreshCosmosClient: true, }); try { await client().getDatabaseAccount(); return undefined; } catch { return t(Keys.connectExplorer.errors.connectivityUnreachable); } }; export const ConnectExplorer: React.FunctionComponent = ({ setEncryptedToken, login, setAuthType, connectionString, setConnectionString, setDirectLoginMetadata, }: Props) => { const [isFormVisible, { setTrue: showForm }] = useBoolean(false); const [errorMessage, setErrorMessage] = React.useState(""); const enableConnectionStringLogin = !userContext.features.disableConnectionStringLogin; return (

Azure Cosmos DB

Welcome to Azure Cosmos DB

{isFormVisible && enableConnectionStringLogin ? (
{ event.preventDefault(); setErrorMessage(""); if (await isAccountRestrictedForConnectionStringLogin(connectionString)) { setErrorMessage( "This account has been blocked from connection-string login. Please go to cosmos.azure.com/aad for AAD based login.", ); return; } if (isResourceTokenConnectionString(connectionString)) { setAuthType(AuthType.ResourceToken); return; } const metadata = parseConnectionString(connectionString); if (metadata && isDirectConnectionStringLoginApi(metadata.apiKind)) { // SQL, Tables, and Gremlin sign data-plane requests client-side with the account key, so // we skip the Portal Backend proxy and use the metadata parsed from the connection string. // Validate the host and account client-side (mirrors the backend's ValidateHostAndAccount). const validationError = validateDirectConnectionStringLogin(connectionString, metadata); if (validationError) { setErrorMessage(validationError); return; } // Only open the view once we confirm the Cosmos client can actually connect. const connectivityError = await validateDirectConnectionStringConnectivity( connectionString, metadata, ); if (connectivityError) { setErrorMessage(connectivityError); return; } setDirectLoginMetadata(metadata); setAuthType(AuthType.ConnectionString); return; } try { const encryptedToken = await fetchEncryptedToken(connectionString); setEncryptedToken(encryptedToken); setAuthType(AuthType.ConnectionString); } catch (error) { setErrorMessage(t(Keys.connectExplorer.errors.connectFailed)); } }} >

Connect to your account with connection string

{ setConnectionString(event.target.value); }} /> {errorMessage.length > 0 && ( Error notification {errorMessage} )}

Sign In with Azure Account

) : (
{enableConnectionStringLogin && (

Connect to your account with connection string

)}
)}
); };