mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-09-19 09:02:41 +01:00
Remove connection string validation for connection string login
- Remove validateDirectConnectionStringLogin and its helpers (extractEndpointHostFromConnectionString, extractHostToken, directLoginAllowlistedEndpointZones) - Remove old extractMasterKeyfromConnectionString (Gremlin-specific), rename extractAccountKeyFromConnectionString to extractMasterKeyfromConnectionString - Change validateDirectConnectionStringConnectivity to throw on error instead of returning string|undefined - Simplify direct-login flow: submit connection string to CosmosClient as-is, no format or endpoint validation - Keep connectivity pre-check (throws if CosmosClient cannot reach account) - Remove 6 unused localization keys for validation errors - Update tests to match new behavior
This commit is contained in:
@@ -159,7 +159,7 @@ describe("HostedExplorer tryCosmosDB postMessage handler", () => {
|
||||
});
|
||||
|
||||
it("does not open the Data Explorer when the Cosmos client cannot connect", async () => {
|
||||
mockValidateDirectConnectionStringConnectivity.mockResolvedValue("Unable to connect to the account.");
|
||||
mockValidateDirectConnectionStringConnectivity.mockRejectedValue(new Error("Unable to connect to the account."));
|
||||
const { container } = render(<App />);
|
||||
|
||||
const validConnStr = `AccountEndpoint=https://${FAKE_ACCOUNT_NAME}.documents.azure.com:443/;AccountKey=${FAKE_KEY};`;
|
||||
|
||||
+4
-28
@@ -23,12 +23,7 @@ import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
||||
import "./Platform/Hosted/ConnectScreen.less";
|
||||
import { parseConnectionString } from "./Platform/Hosted/Helpers/ConnectionStringParser";
|
||||
import { isResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||
import {
|
||||
extractAccountKeyFromConnectionString,
|
||||
extractMasterKeyfromConnectionString,
|
||||
isDirectConnectionStringLoginApi,
|
||||
validateDirectConnectionStringLogin,
|
||||
} from "./Platform/Hosted/HostedUtils";
|
||||
import { extractMasterKeyfromConnectionString, isDirectConnectionStringLoginApi } from "./Platform/Hosted/HostedUtils";
|
||||
import "./Shared/appInsights";
|
||||
import { allowedHostedExplorerEndpoints } from "./Utils/EndpointUtils";
|
||||
import { useAADAuth } from "./hooks/useAADAuth";
|
||||
@@ -74,31 +69,14 @@ const App: React.FunctionComponent = () => {
|
||||
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 derived from the connection string directly.
|
||||
// Validate the host and account client-side (mirrors the backend's ValidateHostAndAccount).
|
||||
const validationError = validateDirectConnectionStringLogin(connStr, metadata);
|
||||
if (validationError) {
|
||||
logError(
|
||||
`Rejected connection string for direct login: ${validationError}`,
|
||||
"HostedExplorer/connectWithConnectionString",
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Only open the view once we confirm the Cosmos client can actually connect.
|
||||
validateDirectConnectionStringConnectivity(connStr, metadata)
|
||||
.then((connectivityError) => {
|
||||
if (connectivityError) {
|
||||
logError(
|
||||
`Rejected connection string for direct login: ${connectivityError}`,
|
||||
"HostedExplorer/connectWithConnectionString",
|
||||
);
|
||||
return;
|
||||
}
|
||||
.then(() => {
|
||||
setDirectLoginMetadata(metadata);
|
||||
setAuthType(AuthType.ConnectionString);
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(
|
||||
`Failed to validate connection string for direct login: ${error}`,
|
||||
`Failed to connect with connection string: ${error}`,
|
||||
"HostedExplorer/connectWithConnectionString",
|
||||
);
|
||||
});
|
||||
@@ -167,9 +145,7 @@ const App: React.FunctionComponent = () => {
|
||||
authType: AuthType.ConnectionString,
|
||||
encryptedToken,
|
||||
encryptedTokenMetadata: accountMetadata,
|
||||
masterKey: directLoginMetadata
|
||||
? extractAccountKeyFromConnectionString(connectionString)
|
||||
: extractMasterKeyfromConnectionString(connectionString),
|
||||
masterKey: extractMasterKeyfromConnectionString(connectionString),
|
||||
};
|
||||
} else if (authType === AuthType.ResourceToken) {
|
||||
frameWindow.hostedConfig = {
|
||||
|
||||
@@ -1177,15 +1177,8 @@
|
||||
},
|
||||
"connectExplorer": {
|
||||
"errors": {
|
||||
"connectionStringMissing": "Connection string is missing.",
|
||||
"accountNameMissing": "Account name is missing from the connection string.",
|
||||
"endpointHostMissing": "Endpoint host is missing from the connection string.",
|
||||
"endpointHostNotAllowed": "Endpoint host is not allowed.",
|
||||
"accountNameMismatch": "Account name does not match the endpoint host.",
|
||||
"accountKeyMissing": "Account key is missing from the connection string.",
|
||||
"connectivityInvalid": "Unable to connect to the account with the provided connection string.",
|
||||
"connectivityUnreachable": "Unable to connect to the account. Please verify the connection string is correct and that the account is reachable.",
|
||||
"connectFailed": "Failed to connect using the provided connection string. Please verify it is correct and try again."
|
||||
"connectFailed": "Failed to connect using the provided connection string. Please verify it is correct and try again.",
|
||||
"connectivityUnreachable": "Unable to connect to the account. Please verify the connection string is correct and that the account is reachable."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,7 @@ 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";
|
||||
import { extractMasterKeyfromConnectionString, isDirectConnectionStringLoginApi } from "../HostedUtils";
|
||||
|
||||
interface Props {
|
||||
connectionString: string;
|
||||
@@ -52,21 +48,17 @@ export const isAccountRestrictedForConnectionStringLogin = async (connectionStri
|
||||
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.
|
||||
// Verifies the connection string can actually authenticate against the account by making a lightweight
|
||||
// read of the database account through the Cosmos client. Throws on failure.
|
||||
export const validateDirectConnectionStringConnectivity = async (
|
||||
connectionString: string,
|
||||
metadata: AccessInputMetadata,
|
||||
): Promise<string | undefined> => {
|
||||
const masterKey = extractAccountKeyFromConnectionString(connectionString);
|
||||
): Promise<void> => {
|
||||
const masterKey = extractMasterKeyfromConnectionString(connectionString);
|
||||
if (!metadata?.documentEndpoint || !masterKey) {
|
||||
return t(Keys.connectExplorer.errors.connectivityInvalid);
|
||||
throw new Error(t(Keys.connectExplorer.errors.connectivityUnreachable));
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -76,9 +68,8 @@ export const validateDirectConnectionStringConnectivity = async (
|
||||
|
||||
try {
|
||||
await client().getDatabaseAccount();
|
||||
return undefined;
|
||||
} catch {
|
||||
return t(Keys.connectExplorer.errors.connectivityUnreachable);
|
||||
throw new Error(t(Keys.connectExplorer.errors.connectivityUnreachable));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -125,23 +116,13 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
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;
|
||||
try {
|
||||
await validateDirectConnectionStringConnectivity(connectionString, metadata);
|
||||
setDirectLoginMetadata(metadata);
|
||||
setAuthType(AuthType.ConnectionString);
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -150,7 +131,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
setEncryptedToken(encryptedToken);
|
||||
setAuthType(AuthType.ConnectionString);
|
||||
} catch (error) {
|
||||
setErrorMessage(t(Keys.connectExplorer.errors.connectFailed));
|
||||
setErrorMessage(t(Keys.connectExplorer.errors.connectivityUnreachable));
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { AccessInputMetadata, ApiKind } from "../../Contracts/DataModels";
|
||||
import {
|
||||
extractAccountKeyFromConnectionString,
|
||||
extractEndpointHostFromConnectionString,
|
||||
extractMasterKeyfromConnectionString,
|
||||
getDatabaseAccountPropertiesFromMetadata,
|
||||
isDirectConnectionStringLoginApi,
|
||||
validateDirectConnectionStringLogin,
|
||||
} from "./HostedUtils";
|
||||
|
||||
describe("getDatabaseAccountPropertiesFromMetadata", () => {
|
||||
@@ -37,13 +35,13 @@ describe("getDatabaseAccountPropertiesFromMetadata", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractAccountKeyFromConnectionString", () => {
|
||||
describe("extractMasterKeyfromConnectionString", () => {
|
||||
const mockAccountName = "Test";
|
||||
const mockKey = "abc123+/=someKey==";
|
||||
|
||||
it("extracts the account key from a SQL connection string", () => {
|
||||
expect(
|
||||
extractAccountKeyFromConnectionString(
|
||||
extractMasterKeyfromConnectionString(
|
||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockKey};`,
|
||||
),
|
||||
).toBe(mockKey);
|
||||
@@ -51,7 +49,7 @@ describe("extractAccountKeyFromConnectionString", () => {
|
||||
|
||||
it("extracts the account key from a Table connection string", () => {
|
||||
expect(
|
||||
extractAccountKeyFromConnectionString(
|
||||
extractMasterKeyfromConnectionString(
|
||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`,
|
||||
),
|
||||
).toBe(mockKey);
|
||||
@@ -59,7 +57,7 @@ describe("extractAccountKeyFromConnectionString", () => {
|
||||
|
||||
it("extracts the account key from a Gremlin connection string", () => {
|
||||
expect(
|
||||
extractAccountKeyFromConnectionString(
|
||||
extractMasterKeyfromConnectionString(
|
||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockKey};ApiKind=Gremlin;`,
|
||||
),
|
||||
).toBe(mockKey);
|
||||
@@ -67,12 +65,12 @@ describe("extractAccountKeyFromConnectionString", () => {
|
||||
|
||||
it("returns undefined when there is no account key", () => {
|
||||
expect(
|
||||
extractAccountKeyFromConnectionString(`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;`),
|
||||
extractMasterKeyfromConnectionString(`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;`),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for an empty connection string", () => {
|
||||
expect(extractAccountKeyFromConnectionString("")).toBeUndefined();
|
||||
expect(extractMasterKeyfromConnectionString("")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,82 +87,3 @@ describe("isDirectConnectionStringLoginApi", () => {
|
||||
expect(isDirectConnectionStringLoginApi(ApiKind.Cassandra)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractEndpointHostFromConnectionString", () => {
|
||||
it("extracts the host from a SQL/Gremlin AccountEndpoint", () => {
|
||||
expect(
|
||||
extractEndpointHostFromConnectionString(
|
||||
"AccountEndpoint=https://my-account.documents.azure.com:443/;AccountKey=key==;",
|
||||
),
|
||||
).toBe("my-account.documents.azure.com");
|
||||
});
|
||||
|
||||
it("extracts the host from a Table TableEndpoint", () => {
|
||||
expect(
|
||||
extractEndpointHostFromConnectionString(
|
||||
"DefaultEndpointsProtocol=https;AccountName=my-account;AccountKey=key==;TableEndpoint=https://my-account.table.cosmos.azure.com:443/;",
|
||||
),
|
||||
).toBe("my-account.table.cosmos.azure.com");
|
||||
});
|
||||
|
||||
it("returns undefined when no endpoint host is present", () => {
|
||||
expect(extractEndpointHostFromConnectionString("AccountName=my-account;AccountKey=key==;")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateDirectConnectionStringLogin", () => {
|
||||
const mockKey = "abc123+/=someKey==";
|
||||
const sqlMetadata = { accountName: "my-account", apiKind: ApiKind.SQL } as AccessInputMetadata;
|
||||
const tableMetadata = { accountName: "my-account", apiKind: ApiKind.Table } as AccessInputMetadata;
|
||||
const gremlinMetadata = { accountName: "my-account", apiKind: ApiKind.Graph } as AccessInputMetadata;
|
||||
|
||||
it("returns undefined for a valid SQL connection string", () => {
|
||||
const connectionString = `AccountEndpoint=https://my-account.documents.azure.com:443/;AccountKey=${mockKey};`;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, sqlMetadata)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for a valid Table connection string using the cosmos.azure.com zone", () => {
|
||||
const connectionString = `DefaultEndpointsProtocol=https;AccountName=my-account;AccountKey=${mockKey};TableEndpoint=https://my-account.table.cosmos.azure.com:443/;`;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, tableMetadata)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for a valid Gremlin connection string", () => {
|
||||
const connectionString = `AccountEndpoint=https://my-account.documents.azure.com:443/;AccountKey=${mockKey};ApiKind=Gremlin;`;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, gremlinMetadata)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects an empty connection string", () => {
|
||||
expect(validateDirectConnectionStringLogin("", {} as AccessInputMetadata)).toBe("Connection string is missing.");
|
||||
});
|
||||
|
||||
it("rejects when the account name is missing from metadata", () => {
|
||||
const connectionString = `AccountEndpoint=https://my-account.documents.azure.com:443/;AccountKey=${mockKey};`;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, {} as AccessInputMetadata)).toBe(
|
||||
"Account name is missing from the connection string.",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a host that is not in an allowlisted zone", () => {
|
||||
// Point the endpoint host at an untrusted zone while keeping a valid parsed account name.
|
||||
const tamperedConnectionString = `AccountEndpoint=https://my-account.evil.example.com:443/;AccountKey=${mockKey};`;
|
||||
expect(validateDirectConnectionStringLogin(tamperedConnectionString, sqlMetadata)).toBe(
|
||||
"Endpoint host is not allowed.",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects when the account name does not match the endpoint host label", () => {
|
||||
const connectionString = `AccountEndpoint=https://real-account.documents.azure.com:443/;AccountKey=${mockKey};`;
|
||||
const metadata = { accountName: "different-account", apiKind: ApiKind.SQL } as AccessInputMetadata;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, metadata)).toBe(
|
||||
"Account name does not match the endpoint host.",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects when the account key is missing", () => {
|
||||
const connectionString = "AccountEndpoint=https://my-account.documents.azure.com:443/;";
|
||||
const metadata = { accountName: "my-account", apiKind: ApiKind.SQL } as AccessInputMetadata;
|
||||
expect(validateDirectConnectionStringLogin(connectionString, metadata)).toBe(
|
||||
"Account key is missing from the connection string.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AccountKind, CapabilityNames } from "../../Common/Constants";
|
||||
import { AccessInputMetadata, ApiKind } from "../../Contracts/DataModels";
|
||||
import { Keys, t } from "../../Localization";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
@@ -41,15 +40,9 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC
|
||||
return AccountKind.GlobalDocumentDB;
|
||||
}
|
||||
|
||||
export function extractMasterKeyfromConnectionString(connectionString: string): string | undefined {
|
||||
// Only Gremlin uses the actual master key for connection to cosmos
|
||||
const matchedParts = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$");
|
||||
return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined;
|
||||
}
|
||||
|
||||
// Extracts the account key from any Cosmos connection string. The account key value cannot contain a
|
||||
// semicolon, so we capture everything up to the next connection-string delimiter.
|
||||
export function extractAccountKeyFromConnectionString(connectionString: string): string | undefined {
|
||||
export function extractMasterKeyfromConnectionString(connectionString: string): string | undefined {
|
||||
const matchedParts = connectionString?.match(/AccountKey=([^;]*)/);
|
||||
return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined;
|
||||
}
|
||||
@@ -59,101 +52,3 @@ export function extractAccountKeyFromConnectionString(connectionString: string):
|
||||
export function isDirectConnectionStringLoginApi(apiKind: ApiKind): boolean {
|
||||
return apiKind === ApiKind.SQL || apiKind === ApiKind.Table || apiKind === ApiKind.Graph;
|
||||
}
|
||||
|
||||
// DNS zones a SQL, Tables, or Gremlin connection-string endpoint host is allowed to belong to. Mirrors
|
||||
// the allowlist enforced by the Portal Backend's
|
||||
// ConnectionStringAccessProvider.ValidateHostAndAccount for the direct-login APIs. Both the newer
|
||||
// `table.cosmos.azure.com` and legacy `table.cosmosdb.azure.com` Tables zones are accepted.
|
||||
const directLoginAllowlistedEndpointZones = [
|
||||
"documents.azure.com",
|
||||
"table.cosmos.azure.com",
|
||||
"table.cosmosdb.azure.com",
|
||||
];
|
||||
|
||||
// Returns the substring of `value` starting at `startIndex` up to the first ':', '/', or '?'.
|
||||
// Mirrors ExtractHostToken in the Portal Backend.
|
||||
function extractHostToken(value: string, startIndex: number): string {
|
||||
let end = value.length;
|
||||
for (let i = startIndex; i < value.length; i++) {
|
||||
const c = value[i];
|
||||
if (c === ":" || c === "/" || c === "?") {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value.substring(startIndex, end);
|
||||
}
|
||||
|
||||
// Extracts the endpoint host from a connection string, mirroring ExtractEndpointHost in the Portal
|
||||
// Backend. Handles AccountEndpoint/TableEndpoint (URI or bare host) and HostName.
|
||||
export function extractEndpointHostFromConnectionString(connectionString: string): string | undefined {
|
||||
for (const part of connectionString.split(";")) {
|
||||
const trimmed = part.trim();
|
||||
|
||||
const equalsIndex = trimmed.indexOf("=");
|
||||
if (equalsIndex < 0 || equalsIndex === trimmed.length - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = trimmed.substring(0, equalsIndex).trim().toLowerCase();
|
||||
const value = trimmed.substring(equalsIndex + 1).trim();
|
||||
|
||||
if (key === "accountendpoint" || key === "tableendpoint") {
|
||||
try {
|
||||
return new URL(value).hostname;
|
||||
} catch {
|
||||
// Value may be a bare host without a scheme.
|
||||
return extractHostToken(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (key === "hostname") {
|
||||
return extractHostToken(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Client-side equivalent of the Portal Backend's ConnectionStringAccessProvider.ValidateHostAndAccount,
|
||||
// scoped to the direct-login APIs (SQL, Tables, Gremlin). Ensures the connection string parses to an
|
||||
// account name, exposes an endpoint host in an allowlisted DNS zone, that the account name matches the
|
||||
// first DNS label of that host, and that an account key is present. Returns an error message when
|
||||
// invalid, or undefined when the connection string is valid for direct login.
|
||||
export function validateDirectConnectionStringLogin(
|
||||
connectionString: string,
|
||||
metadata: AccessInputMetadata,
|
||||
): string | undefined {
|
||||
if (!connectionString) {
|
||||
return t(Keys.connectExplorer.errors.connectionStringMissing);
|
||||
}
|
||||
|
||||
if (!metadata || !metadata.accountName) {
|
||||
return t(Keys.connectExplorer.errors.accountNameMissing);
|
||||
}
|
||||
|
||||
const host = extractEndpointHostFromConnectionString(connectionString);
|
||||
if (!host) {
|
||||
return t(Keys.connectExplorer.errors.endpointHostMissing);
|
||||
}
|
||||
|
||||
// The host must belong to one of the allowlisted runtime endpoint zones.
|
||||
const isAllowlistedHost = directLoginAllowlistedEndpointZones.some((zone) =>
|
||||
host.toLowerCase().endsWith(`.${zone.toLowerCase()}`),
|
||||
);
|
||||
if (!isAllowlistedHost) {
|
||||
return t(Keys.connectExplorer.errors.endpointHostNotAllowed);
|
||||
}
|
||||
|
||||
// The account name must be the first DNS label of the host.
|
||||
if (host.split(".")[0].toLowerCase() !== metadata.accountName.toLowerCase()) {
|
||||
return t(Keys.connectExplorer.errors.accountNameMismatch);
|
||||
}
|
||||
|
||||
// Direct login signs requests with the account key, so it must be present in the connection string.
|
||||
if (!extractAccountKeyFromConnectionString(connectionString)) {
|
||||
return t(Keys.connectExplorer.errors.accountKeyMissing);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -478,7 +478,6 @@ function configureHostedWithConnectionString(config: ConnectionString): Explorer
|
||||
});
|
||||
} else {
|
||||
// Legacy encrypted-token proxy path (Mongo, Cassandra).
|
||||
// For legacy reasons lots of code expects a connection string login to look and act like an encrypted token login
|
||||
updateUserContext({
|
||||
authType: AuthType.EncryptedToken,
|
||||
accessToken: encodeURIComponent(config.encryptedToken),
|
||||
|
||||
Reference in New Issue
Block a user