mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-08-30 10:18:40 +01:00
Improve connection string login feedback
This commit is contained in:
@@ -80,6 +80,63 @@ it("hides the connection string link when feature.disableConnectionStringLogin i
|
||||
updateUserContext({ features: oldFeatures });
|
||||
});
|
||||
|
||||
it("rejects an unrecognized connection string before token exchange", async () => {
|
||||
render(
|
||||
<ConnectExplorer
|
||||
{...{
|
||||
login: jest.fn(),
|
||||
setEncryptedToken: jest.fn(),
|
||||
setAuthType: jest.fn(),
|
||||
connectionString: "not-a-valid-connection-string",
|
||||
setConnectionString: jest.fn(),
|
||||
setAccountMetadata: jest.fn(),
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText("Connect to your account with connection string"));
|
||||
fireEvent.click(screen.getByDisplayValue("Connect"));
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"We couldn't recognize this connection string. Verify that it is a valid Azure Cosmos DB connection string and try again.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(mockIsAccountRestricted).not.toHaveBeenCalled();
|
||||
expect(mockFetchEncryptedToken).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows that a connection is in progress", async () => {
|
||||
let finishRestrictionCheck: (restricted: boolean) => void = () => undefined;
|
||||
mockIsAccountRestricted.mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
finishRestrictionCheck = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<ConnectExplorer
|
||||
{...{
|
||||
login: jest.fn(),
|
||||
setEncryptedToken: jest.fn(),
|
||||
setAuthType: jest.fn(),
|
||||
connectionString: "AccountEndpoint=https://test.documents.azure.com:443/;AccountKey=some-key;",
|
||||
setConnectionString: jest.fn(),
|
||||
setAccountMetadata: jest.fn(),
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText("Connect to your account with connection string"));
|
||||
fireEvent.click(screen.getByDisplayValue("Connect"));
|
||||
|
||||
const connectButton = screen.getByDisplayValue("Connecting...");
|
||||
expect(connectButton).toBeDisabled();
|
||||
expect(connectButton.closest("form")).toHaveAttribute("aria-busy", "true");
|
||||
|
||||
finishRestrictionCheck(false);
|
||||
expect(await screen.findByDisplayValue("Connect")).toBeEnabled();
|
||||
});
|
||||
|
||||
it("shows the error when the Portal Backend rejects the connection string", async () => {
|
||||
// Mongo and Cassandra are the APIs that still exchange the connection string for an encrypted token.
|
||||
const mongoConnectionString = "mongodb://test:key@test.documents.azure.com:10255";
|
||||
|
||||
@@ -32,6 +32,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
const [isFormVisible, { setTrue: showForm }] = useBoolean(false);
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
const [isBlockedByFirewall, setIsBlockedByFirewall] = React.useState(false);
|
||||
const [isConnecting, setIsConnecting] = React.useState(false);
|
||||
const enableConnectionStringLogin = !userContext.features.disableConnectionStringLogin;
|
||||
|
||||
return (
|
||||
@@ -45,10 +46,30 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
{isFormVisible && enableConnectionStringLogin ? (
|
||||
<form
|
||||
id="connectWithConnectionString"
|
||||
aria-busy={isConnecting}
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault();
|
||||
if (isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage("");
|
||||
setIsBlockedByFirewall(false);
|
||||
setIsConnecting(true);
|
||||
|
||||
try {
|
||||
if (isResourceTokenConnectionString(connectionString)) {
|
||||
setAuthType(AuthType.ResourceToken);
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = parseConnectionString(connectionString);
|
||||
if (!metadata) {
|
||||
setErrorMessage(
|
||||
"We couldn't recognize this connection string. Verify that it is a valid Azure Cosmos DB connection string and try again.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (await isAccountRestrictedForConnectionStringLogin(connectionString)) {
|
||||
@@ -58,19 +79,11 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMessage(getErrorMessage(error));
|
||||
setErrorMessage(getErrorMessage(error as Error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isResourceTokenConnectionString(connectionString)) {
|
||||
setAuthType(AuthType.ResourceToken);
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = parseConnectionString(connectionString);
|
||||
if (metadata && isDirectConnectionStringLoginApi(metadata.apiKind)) {
|
||||
// SQL, Table, 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.
|
||||
if (isDirectConnectionStringLoginApi(metadata.apiKind)) {
|
||||
setAccountMetadata(metadata);
|
||||
setAuthType(AuthType.ConnectionString);
|
||||
return;
|
||||
@@ -94,6 +107,9 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
// to allowlist those services.
|
||||
setIsBlockedByFirewall((error as Response).status === HttpStatusCodes.Forbidden);
|
||||
}
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p className="connectExplorerContent connectStringText">Connect to your account with connection string</p>
|
||||
@@ -129,7 +145,12 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
</FluentProvider>
|
||||
)}
|
||||
<p className="connectExplorerContent">
|
||||
<input className="filterbtnstyle" type="submit" value="Connect" />
|
||||
<input
|
||||
className="filterbtnstyle"
|
||||
type="submit"
|
||||
value={isConnecting ? "Connecting..." : "Connect"}
|
||||
disabled={isConnecting}
|
||||
/>
|
||||
</p>
|
||||
<p className="switchConnectTypeText" onClick={login}>
|
||||
Sign In with Azure Account
|
||||
|
||||
Reference in New Issue
Block a user