mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-09 20:49:12 +00:00
Compare commits
4 Commits
revert-173
...
1249101
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f11e0b85bd | ||
|
|
533e9c887c | ||
|
|
76ad930930 | ||
|
|
932f211038 |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -20,8 +20,8 @@
|
|||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.organizeImports": true
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
/**********************************************************************************/
|
/**********************************************************************************/
|
||||||
|
|
||||||
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||||
|
@FabricToolbarIconColor: "brightness(0) saturate(100%) invert(50%) sepia(17%) saturate(1459%) hue-rotate(81deg) brightness(99%) contrast(94%)";
|
||||||
|
|
||||||
@FabricBoxBorderRadius: 8px;
|
@FabricBoxBorderRadius: 8px;
|
||||||
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||||
|
|||||||
@@ -78,8 +78,8 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"patch-package": "8.0.0",
|
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "4.6.2",
|
||||||
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
"q": "1.5.1",
|
"q": "1.5.1",
|
||||||
@@ -238,4 +238,4 @@
|
|||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"endOfLine": "auto"
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
.throughputInputSpacing > :not(:last-child) {
|
.throughputInputSpacing > :not(:last-child) {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
}
|
}
|
||||||
.capacitycalculator-link:focus{
|
.capacitycalculator-link:focus {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,8 +224,10 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
Estimate your required RU/s with{" "}
|
Estimate your required RU/s with{" "}
|
||||||
<Link
|
<Link
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
className="capacitycalculator-link"
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
aria-label="capacity calculator of azure cosmos db"
|
aria-label="capacity calculator of azure cosmos db"
|
||||||
|
style={{ outline: "none" }}
|
||||||
>
|
>
|
||||||
capacity calculator
|
capacity calculator
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -733,12 +733,24 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
aria-label="capacity calculator of azure cosmos db"
|
aria-label="capacity calculator of azure cosmos db"
|
||||||
|
className="capacitycalculator-link"
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"outline": "none",
|
||||||
|
}
|
||||||
|
}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<LinkBase
|
<LinkBase
|
||||||
aria-label="capacity calculator of azure cosmos db"
|
aria-label="capacity calculator of azure cosmos db"
|
||||||
|
className="capacitycalculator-link"
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"outline": "none",
|
||||||
|
}
|
||||||
|
}
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
theme={
|
theme={
|
||||||
@@ -1017,9 +1029,14 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
aria-label="capacity calculator of azure cosmos db"
|
aria-label="capacity calculator of azure cosmos db"
|
||||||
className="ms-Link root-117"
|
className="ms-Link capacitycalculator-link root-117"
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"outline": "none",
|
||||||
|
}
|
||||||
|
}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
capacity calculator
|
capacity calculator
|
||||||
|
|||||||
@@ -296,6 +296,14 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async openCESCVAFeedbackBlade(): Promise<void> {
|
||||||
|
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
|
||||||
|
Logger.logInfo(
|
||||||
|
`CES CVA Feedback logging current date when survey is shown ${Date.now().toString()}`,
|
||||||
|
"Explorer/openCESCVAFeedbackBlade",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async refreshDatabaseForResourceToken(): Promise<void> {
|
public async refreshDatabaseForResourceToken(): Promise<void> {
|
||||||
const databaseId = userContext.parsedResourceToken?.databaseId;
|
const databaseId = userContext.parsedResourceToken?.databaseId;
|
||||||
const collectionId = userContext.parsedResourceToken?.collectionId;
|
const collectionId = userContext.parsedResourceToken?.collectionId;
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
const feedbackButtonOptions: CommandButtonComponentProps = {
|
const feedbackButtonOptions: CommandButtonComponentProps = {
|
||||||
iconSrc: FeedbackIcon,
|
iconSrc: FeedbackIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => container.provideFeedbackEmail(),
|
onCommandClick: () => container.openCESCVAFeedbackBlade(),
|
||||||
commandButtonLabel: undefined,
|
commandButtonLabel: undefined,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
tooltipText: label,
|
tooltipText: label,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
return StyleConstants.GrayScale;
|
return StyleConstants.GrayScale;
|
||||||
}
|
}
|
||||||
return configContext.platform == Platform.Fabric ? StyleConstants.NoColor : undefined;
|
return configContext.platform == Platform.Fabric ? StyleConstants.FabricToolbarIconColor : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
return btns
|
return btns
|
||||||
@@ -96,7 +96,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
},
|
},
|
||||||
width: 16,
|
width: 16,
|
||||||
},
|
},
|
||||||
label: { fontSize: StyleConstants.mediumFontSize },
|
label: {
|
||||||
|
fontSize:
|
||||||
|
configContext.platform == Platform.Fabric
|
||||||
|
? StyleConstants.DefaultFontSize
|
||||||
|
: StyleConstants.mediumFontSize,
|
||||||
|
},
|
||||||
rootHovered: { backgroundColor: hoverColor },
|
rootHovered: { backgroundColor: hoverColor },
|
||||||
rootPressed: { backgroundColor: hoverColor },
|
rootPressed: { backgroundColor: hoverColor },
|
||||||
splitButtonMenuButtonExpanded: {
|
splitButtonMenuButtonExpanded: {
|
||||||
@@ -133,7 +138,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
// TODO Figure out how to do it the proper way with subComponentStyles.
|
// TODO Figure out how to do it the proper way with subComponentStyles.
|
||||||
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
||||||
selectors: {
|
selectors: {
|
||||||
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
|
".ms-ContextualMenu-itemText": {
|
||||||
|
fontSize:
|
||||||
|
configContext.platform == Platform.Fabric
|
||||||
|
? StyleConstants.DefaultFontSize
|
||||||
|
: StyleConstants.mediumFontSize,
|
||||||
|
},
|
||||||
".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor },
|
".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor },
|
||||||
".ms-ContextualMenu-icon": { width: 16, height: 16 },
|
".ms-ContextualMenu-icon": { width: 16, height: 16 },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Link, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
|
import { Link, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
|
||||||
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { configContext, updateConfigContext } from "ConfigContext";
|
import { Platform, configContext, updateConfigContext } from "ConfigContext";
|
||||||
import { IpRule } from "Contracts/DataModels";
|
import { IpRule } from "Contracts/DataModels";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||||
@@ -35,7 +35,7 @@ interface TabsProps {
|
|||||||
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
||||||
const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs();
|
const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs();
|
||||||
const [showRUThresholdMessageBar, setShowRUThresholdMessageBar] = useState<boolean>(
|
const [showRUThresholdMessageBar, setShowRUThresholdMessageBar] = useState<boolean>(
|
||||||
userContext.apiType === "SQL" && !hasRUThresholdBeenConfigured(),
|
userContext.apiType === "SQL" && configContext.platform !== Platform.Fabric && !hasRUThresholdBeenConfigured(),
|
||||||
);
|
);
|
||||||
const [
|
const [
|
||||||
showMongoAndCassandraProxiesNetworkSettingsWarningState,
|
showMongoAndCassandraProxiesNetworkSettingsWarningState,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { initializeIcons } from "@fluentui/react";
|
import { initializeIcons } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
|
import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import ChevronRight from "../images/chevron-right.svg";
|
import ChevronRight from "../images/chevron-right.svg";
|
||||||
@@ -32,7 +33,8 @@ const App: React.FunctionComponent = () => {
|
|||||||
// For showing/hiding panel
|
// For showing/hiding panel
|
||||||
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
|
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } =
|
||||||
|
useAADAuth();
|
||||||
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
||||||
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
||||||
const [connectionString, setConnectionString] = React.useState<string>();
|
const [connectionString, setConnectionString] = React.useState<string>();
|
||||||
@@ -136,7 +138,10 @@ const App: React.FunctionComponent = () => {
|
|||||||
{!isLoggedIn && !encryptedTokenMetadata && (
|
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||||
<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />
|
<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />
|
||||||
)}
|
)}
|
||||||
{isLoggedIn && <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />}
|
{isLoggedIn && authFailure && <AadAuthorizationFailure {...{ authFailure }} />}
|
||||||
|
{isLoggedIn && !authFailure && (
|
||||||
|
<DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
52
src/Platform/Hosted/AadAuthorizationFailure.less
Normal file
52
src/Platform/Hosted/AadAuthorizationFailure.less
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
.aadAuthFailureContainer {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.aadAuthFailureContainer .aadAuthFailureFormContainer {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -ms-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.aadAuthFailureContainer .aadAuthFailure {
|
||||||
|
text-align: center;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -ms-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
.aadAuthFailureContainer .aadAuthFailure .authFailureTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #d12d2d;
|
||||||
|
margin: 16px 8px 8px 8px;
|
||||||
|
}
|
||||||
|
.aadAuthFailureContainer .aadAuthFailure .authFailureMessage {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #393939;
|
||||||
|
margin: 16px 16px 16px 16px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.aadAuthFailureContainer .aadAuthFailure .authFailureLink {
|
||||||
|
margin: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #0058ad;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aadAuthFailureContainer .aadAuthFailure .aadAuthFailureContent {
|
||||||
|
margin: 8px;
|
||||||
|
color: #393939;
|
||||||
|
}
|
||||||
29
src/Platform/Hosted/Components/AadAuthorizationFailure.tsx
Normal file
29
src/Platform/Hosted/Components/AadAuthorizationFailure.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { AadAuthFailure } from "hooks/useAADAuth";
|
||||||
|
import * as React from "react";
|
||||||
|
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
||||||
|
import "../AadAuthorizationFailure.less";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
authFailure: AadAuthFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AadAuthorizationFailure: React.FunctionComponent<Props> = ({ authFailure }: Props) => {
|
||||||
|
return (
|
||||||
|
<div id="aadAuthFailure" className="aadAuthFailureContainer" style={{ display: "flex" }}>
|
||||||
|
<div className="aadAuthFailureFormContainer">
|
||||||
|
<div className="aadAuthFailure">
|
||||||
|
<p className="aadAuthFailureContent">
|
||||||
|
<img src={ConnectImage} alt="Azure Cosmos DB" />
|
||||||
|
</p>
|
||||||
|
<p className="authFailureTitle">Authorization Failure</p>
|
||||||
|
<p className="authFailureMessage">{authFailure.failureMessage}</p>
|
||||||
|
{authFailure.failureLinkTitle && (
|
||||||
|
<p className="authFailureLink" onClick={authFailure.failureLinkAction}>
|
||||||
|
{authFailure.failureLinkTitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import * as msal from "@azure/msal-browser";
|
import * as msal from "@azure/msal-browser";
|
||||||
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
|
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
|
||||||
@@ -43,8 +45,8 @@ export function decryptJWTToken(token: string) {
|
|||||||
return JSON.parse(tokenPayload);
|
return JSON.parse(tokenPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMsalInstance() {
|
export async function getMsalInstance() {
|
||||||
const config: msal.Configuration = {
|
const msalConfig: msal.Configuration = {
|
||||||
cache: {
|
cache: {
|
||||||
cacheLocation: "localStorage",
|
cacheLocation: "localStorage",
|
||||||
},
|
},
|
||||||
@@ -55,8 +57,46 @@ export function getMsalInstance() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
config.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
|
msalConfig.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
|
||||||
}
|
}
|
||||||
const msalInstance = new msal.PublicClientApplication(config);
|
|
||||||
|
const msalInstance = new msal.PublicClientApplication(msalConfig);
|
||||||
return msalInstance;
|
return msalInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function acquireTokenWithMsal(msalInstance: msal.IPublicClientApplication, request: msal.SilentRequest) {
|
||||||
|
const tokenRequest = {
|
||||||
|
account: msalInstance.getActiveAccount() || null,
|
||||||
|
...request,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// attempt silent acquisition first
|
||||||
|
return (await msalInstance.acquireTokenSilent(tokenRequest)).accessToken;
|
||||||
|
} catch (silentError) {
|
||||||
|
if (silentError instanceof msal.InteractionRequiredAuthError) {
|
||||||
|
try {
|
||||||
|
// The error indicates that we need to acquire the token interactively.
|
||||||
|
// This will display a pop-up to re-establish authorization. If user does not
|
||||||
|
// have pop-ups enabled in their browser, this will fail.
|
||||||
|
return (await msalInstance.acquireTokenPopup(tokenRequest)).accessToken;
|
||||||
|
} catch (interactiveError) {
|
||||||
|
traceFailure(Action.SignInAad, {
|
||||||
|
request: JSON.stringify(tokenRequest),
|
||||||
|
acquireTokenType: "interactive",
|
||||||
|
errorMessage: JSON.stringify(interactiveError),
|
||||||
|
});
|
||||||
|
|
||||||
|
throw interactiveError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traceFailure(Action.SignInAad, {
|
||||||
|
request: JSON.stringify(tokenRequest),
|
||||||
|
acquireTokenType: "silent",
|
||||||
|
errorMessage: JSON.stringify(silentError),
|
||||||
|
});
|
||||||
|
|
||||||
|
throw silentError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import * as msal from "@azure/msal-browser";
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { getMsalInstance } from "../Utils/AuthorizationUtils";
|
import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
|
||||||
|
|
||||||
const msalInstance = getMsalInstance();
|
const msalInstance = await getMsalInstance();
|
||||||
|
|
||||||
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
||||||
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
||||||
@@ -18,6 +18,13 @@ interface ReturnType {
|
|||||||
tenantId: string;
|
tenantId: string;
|
||||||
account: msal.AccountInfo;
|
account: msal.AccountInfo;
|
||||||
switchTenant: (tenantId: string) => void;
|
switchTenant: (tenantId: string) => void;
|
||||||
|
authFailure: AadAuthFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AadAuthFailure {
|
||||||
|
failureMessage: string;
|
||||||
|
failureLinkTitle?: string;
|
||||||
|
failureLinkAction?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAADAuth(): ReturnType {
|
export function useAADAuth(): ReturnType {
|
||||||
@@ -28,6 +35,7 @@ export function useAADAuth(): ReturnType {
|
|||||||
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
|
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
|
||||||
const [graphToken, setGraphToken] = React.useState<string>();
|
const [graphToken, setGraphToken] = React.useState<string>();
|
||||||
const [armToken, setArmToken] = React.useState<string>();
|
const [armToken, setArmToken] = React.useState<string>();
|
||||||
|
const [authFailure, setAuthFailure] = React.useState<AadAuthFailure>(undefined);
|
||||||
|
|
||||||
msalInstance.setActiveAccount(account);
|
msalInstance.setActiveAccount(account);
|
||||||
const login = React.useCallback(async () => {
|
const login = React.useCallback(async () => {
|
||||||
@@ -61,24 +69,60 @@ export function useAADAuth(): ReturnType {
|
|||||||
[account, tenantId],
|
[account, tenantId],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
const acquireTokens = React.useCallback(async () => {
|
||||||
if (account && tenantId) {
|
if (!(account && tenantId)) {
|
||||||
Promise.all([
|
return;
|
||||||
msalInstance.acquireTokenSilent({
|
}
|
||||||
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
|
|
||||||
scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
|
try {
|
||||||
}),
|
const armToken = await acquireTokenWithMsal(msalInstance, {
|
||||||
msalInstance.acquireTokenSilent({
|
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
|
||||||
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
|
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
|
||||||
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
|
|
||||||
}),
|
|
||||||
]).then(([graphTokenResponse, armTokenResponse]) => {
|
|
||||||
setGraphToken(graphTokenResponse.accessToken);
|
|
||||||
setArmToken(armTokenResponse.accessToken);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setArmToken(armToken);
|
||||||
|
setAuthFailure(null);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof msal.AuthError && error.errorCode === msal.BrowserAuthErrorMessage.popUpWindowError.code) {
|
||||||
|
// This error can occur when acquireTokenWithMsal() has attempted to acquire token interactively
|
||||||
|
// and user has popups disabled in browser. This fails as the popup is not the result of a explicit user
|
||||||
|
// action. In this case, we display the failure and a link to repeat the operation. Clicking on the
|
||||||
|
// link is a user action so it will work even if popups have been disabled.
|
||||||
|
// See: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/76#issuecomment-324787539
|
||||||
|
setAuthFailure({
|
||||||
|
failureMessage:
|
||||||
|
"We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease click below to retry authorization without requiring popups being enabled.",
|
||||||
|
failureLinkTitle: "Retry Authorization",
|
||||||
|
failureLinkAction: acquireTokens,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const errorJson = JSON.stringify(error);
|
||||||
|
setAuthFailure({
|
||||||
|
failureMessage: `We were unable to establish authorization for this account, due to the following error: \n${errorJson}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const graphToken = await acquireTokenWithMsal(msalInstance, {
|
||||||
|
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
|
||||||
|
scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
|
||||||
|
});
|
||||||
|
|
||||||
|
setGraphToken(graphToken);
|
||||||
|
} catch (error) {
|
||||||
|
// Graph token is used only for retrieving user photo at the moment, so
|
||||||
|
// it's not critical if this fails.
|
||||||
|
console.warn("Error acquiring graph token: " + error);
|
||||||
}
|
}
|
||||||
}, [account, tenantId]);
|
}, [account, tenantId]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (account && tenantId && !authFailure) {
|
||||||
|
acquireTokens();
|
||||||
|
}
|
||||||
|
}, [account, tenantId, acquireTokens, authFailure]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -88,5 +132,6 @@ export function useAADAuth(): ReturnType {
|
|||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
switchTenant,
|
switchTenant,
|
||||||
|
authFailure,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdap
|
|||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -35,7 +36,7 @@ import {
|
|||||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
||||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||||
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
|
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
|
||||||
import { getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils";
|
import { acquireTokenWithMsal, getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils";
|
||||||
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
|
||||||
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
|
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
|
||||||
@@ -243,16 +244,19 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
let keys: DatabaseAccountListKeysResult = {};
|
let keys: DatabaseAccountListKeysResult = {};
|
||||||
if (account.properties?.documentEndpoint) {
|
if (account.properties?.documentEndpoint) {
|
||||||
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
|
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
|
||||||
const msalInstance = getMsalInstance();
|
const msalInstance = await getMsalInstance();
|
||||||
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
||||||
msalInstance.setActiveAccount(cachedAccount);
|
msalInstance.setActiveAccount(cachedAccount);
|
||||||
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
||||||
const aadTokenResponse = await msalInstance.acquireTokenSilent({
|
try {
|
||||||
forceRefresh: true,
|
aadToken = await acquireTokenWithMsal(msalInstance, {
|
||||||
scopes: [hrefEndpoint],
|
forceRefresh: true,
|
||||||
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
|
scopes: [hrefEndpoint],
|
||||||
});
|
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
|
||||||
aadToken = aadTokenResponse.accessToken;
|
});
|
||||||
|
} catch (authError) {
|
||||||
|
logConsoleError("Failed to acquire authorization token: " + authError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!account.properties.disableLocalAuth) {
|
if (!account.properties.disableLocalAuth) {
|
||||||
|
|||||||
Reference in New Issue
Block a user