mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 01:41:31 +00:00
Checkpoint
This commit is contained in:
@@ -241,7 +241,6 @@ src/Platform/Hosted/Authorization.ts
|
|||||||
src/Platform/Hosted/DataAccessUtility.ts
|
src/Platform/Hosted/DataAccessUtility.ts
|
||||||
src/Platform/Hosted/ExplorerFactory.ts
|
src/Platform/Hosted/ExplorerFactory.ts
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.ts
|
|
||||||
src/Platform/Hosted/HostedUtils.test.ts
|
src/Platform/Hosted/HostedUtils.test.ts
|
||||||
src/Platform/Hosted/HostedUtils.ts
|
src/Platform/Hosted/HostedUtils.ts
|
||||||
src/Platform/Hosted/Main.ts
|
src/Platform/Hosted/Main.ts
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { DefaultButton, IButtonStyles, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
import { DefaultButton, IButtonStyles, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||||
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
import { Dropdown, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
||||||
import { useSubscriptions } from "../../../hooks/useSubscriptions";
|
import { useSubscriptions } from "../../../hooks/useSubscriptions";
|
||||||
import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts";
|
import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts";
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import * as React from "react";
|
|||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import FeedbackIcon from "../images/Feedback.svg";
|
import FeedbackIcon from "../images/Feedback.svg";
|
||||||
import ConnectIcon from "../images/HostedConnectwhite.svg";
|
import ConnectIcon from "../images/HostedConnectwhite.svg";
|
||||||
import ChevronRight from "../images/chevron-right.svg";
|
|
||||||
import "../less/hostedexplorer.less";
|
import "../less/hostedexplorer.less";
|
||||||
import { CommandButtonComponent } from "./Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponent } from "./Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||||
@@ -22,12 +21,17 @@ import { useGraphPhoto } from "./hooks/useGraphPhoto";
|
|||||||
import "./Shared/appInsights";
|
import "./Shared/appInsights";
|
||||||
import { AccountSwitchComponent } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponent";
|
import { AccountSwitchComponent } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponent";
|
||||||
import { AuthContext, AuthProvider } from "./contexts/authContext";
|
import { AuthContext, AuthProvider } from "./contexts/authContext";
|
||||||
|
import { usePortalAccessToken } from "./hooks/usePortalAccessToken";
|
||||||
|
import { AuthType } from "./AuthType";
|
||||||
|
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const encryptedToken = params && params.get("key");
|
||||||
|
const encryptedTokenMetadata = usePortalAccessToken(encryptedToken);
|
||||||
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||||
const { isLoggedIn, login, account, logout } = React.useContext(AuthContext);
|
const { isLoggedIn, aadlogin: login, account, aadlogout: logout } = React.useContext(AuthContext);
|
||||||
const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false);
|
const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false);
|
||||||
const photo = useGraphPhoto();
|
const photo = useGraphPhoto();
|
||||||
|
|
||||||
@@ -121,11 +125,17 @@ const App: React.FunctionComponent = () => {
|
|||||||
Microsoft Azure
|
Microsoft Azure
|
||||||
</span>
|
</span>
|
||||||
<span className="accontSplitter" /> <span className="serviceTitle">Cosmos DB</span>
|
<span className="accontSplitter" /> <span className="serviceTitle">Cosmos DB</span>
|
||||||
{isLoggedIn && <img className="chevronRight" src={ChevronRight} alt="account separator" />}
|
{(isLoggedIn || encryptedTokenMetadata?.accountName) && (
|
||||||
|
<img className="chevronRight" src={ChevronRight} alt="account separator" />
|
||||||
|
)}
|
||||||
{isLoggedIn && (
|
{isLoggedIn && (
|
||||||
<span className="accountSwitchComponentContainer">
|
<span className="accountSwitchComponentContainer">
|
||||||
<AccountSwitchComponent />
|
<AccountSwitchComponent />
|
||||||
<span className="accountNameHeader">REPLACE ME - Connection string mode</span>;
|
</span>
|
||||||
|
)}
|
||||||
|
{!isLoggedIn && encryptedTokenMetadata?.accountName && (
|
||||||
|
<span className="accountSwitchComponentContainer">
|
||||||
|
<span className="accountNameHeader">{encryptedTokenMetadata?.accountName}</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -186,9 +196,27 @@ const App: React.FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{isLoggedIn ? (
|
{encryptedTokenMetadata && !isLoggedIn && (
|
||||||
<p>LOGGED IN!</p>
|
<iframe
|
||||||
) : (
|
id="explorerMenu"
|
||||||
|
name="explorer"
|
||||||
|
className="iframe"
|
||||||
|
title="explorer"
|
||||||
|
src={`explorer.html?v=1.0.1&platform=Hosted&authType=${AuthType.EncryptedToken}&key=${encodeURIComponent(
|
||||||
|
encryptedToken
|
||||||
|
)}&metadata=${JSON.stringify(encryptedTokenMetadata)}`}
|
||||||
|
></iframe>
|
||||||
|
)}
|
||||||
|
{!encryptedTokenMetadata && isLoggedIn && (
|
||||||
|
<iframe
|
||||||
|
id="explorerMenu"
|
||||||
|
name="explorer"
|
||||||
|
className="iframe"
|
||||||
|
title="explorer"
|
||||||
|
src={`explorer.html?v=1.0.1&platform=Hosted&authType=${AuthType.AAD}`}
|
||||||
|
></iframe>
|
||||||
|
)}
|
||||||
|
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||||
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
||||||
<div className="connectExplorerFormContainer">
|
<div className="connectExplorerFormContainer">
|
||||||
<div className="connectExplorer">
|
<div className="connectExplorer">
|
||||||
@@ -227,13 +255,6 @@ const App: React.FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* <iframe
|
|
||||||
id="explorerMenu"
|
|
||||||
name="explorer"
|
|
||||||
className="iframe"
|
|
||||||
title="explorer"
|
|
||||||
src="explorer.html?v=1.0.1&platform=Portal"
|
|
||||||
></iframe> */}
|
|
||||||
<div data-bind="react: firewallWarningComponentAdapter" />
|
<div data-bind="react: firewallWarningComponentAdapter" />
|
||||||
<div data-bind="react: dialogComponentAdapter" />
|
<div data-bind="react: dialogComponentAdapter" />
|
||||||
<Panel headerText="Select Directory" isOpen={isOpen} onDismiss={dismissPanel} closeButtonAriaLabel="Close">
|
<Panel headerText="Select Directory" isOpen={isOpen} onDismiss={dismissPanel} closeButtonAriaLabel="Close">
|
||||||
|
|||||||
36
src/Main.tsx
36
src/Main.tsx
@@ -84,42 +84,16 @@ window.authType = AuthType.AAD;
|
|||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeConfiguration().then(config => {
|
initializeConfiguration().then(config => {
|
||||||
|
let explorer: Explorer;
|
||||||
if (config.platform === Platform.Hosted) {
|
if (config.platform === Platform.Hosted) {
|
||||||
try {
|
explorer = Hosted.initializeExplorer();
|
||||||
Hosted.initializeExplorer().then(
|
|
||||||
(explorer: Explorer) => {
|
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
Hosted.configureTokenValidationDisplayPrompt(explorer);
|
|
||||||
},
|
|
||||||
(error: unknown) => {
|
|
||||||
try {
|
|
||||||
const uninitializedExplorer: Explorer = Hosted.getUninitializedExplorerForGuestAccess();
|
|
||||||
window.dataExplorer = uninitializedExplorer;
|
|
||||||
ko.applyBindings(uninitializedExplorer);
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
|
||||||
if (window.authType !== AuthType.AAD) {
|
|
||||||
uninitializedExplorer.isRefreshingExplorer(false);
|
|
||||||
uninitializedExplorer.displayConnectExplorerForm();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
} else if (config.platform === Platform.Emulator) {
|
} else if (config.platform === Platform.Emulator) {
|
||||||
window.authType = AuthType.MasterKey;
|
window.authType = AuthType.MasterKey;
|
||||||
const explorer = Emulator.initializeExplorer();
|
explorer = Emulator.initializeExplorer();
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
} else if (config.platform === Platform.Portal) {
|
} else if (config.platform === Platform.Portal) {
|
||||||
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.Open, {});
|
explorer = Portal.initializeExplorer();
|
||||||
const explorer = Portal.initializeExplorer();
|
|
||||||
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.IFrameReady, {});
|
|
||||||
applyExplorerBindings(explorer);
|
|
||||||
}
|
}
|
||||||
|
applyExplorerBindings(explorer);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { ConnectionStringParser } from "./ConnectionStringParser";
|
import { parseConnectionString } from "./ConnectionStringParser";
|
||||||
|
|
||||||
describe("ConnectionStringParser", () => {
|
describe("ConnectionStringParser", () => {
|
||||||
const mockAccountName: string = "Test";
|
const mockAccountName: string = "Test";
|
||||||
const mockMasterKey: string = "some-key";
|
const mockMasterKey: string = "some-key";
|
||||||
|
|
||||||
it("should parse a valid sql account connection string", () => {
|
it("should parse a valid sql account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};`
|
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid mongo account connection string", () => {
|
it("should parse a valid mongo account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.documents.azure.com:10255`
|
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.documents.azure.com:10255`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid compute mongo account connection string", () => {
|
it("should parse a valid compute mongo account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.mongo.cosmos.azure.com:10255`
|
`mongodb://${mockAccountName}:${mockMasterKey}@${mockAccountName}.mongo.cosmos.azure.com:10255`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid graph account connection string", () => {
|
it("should parse a valid graph account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`
|
`AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockMasterKey};ApiKind=Gremlin;`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid table account connection string", () => {
|
it("should parse a valid table account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`
|
`DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should parse a valid cassandra account connection string", () => {
|
it("should parse a valid cassandra account connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString(
|
||||||
`AccountEndpoint=${mockAccountName}.cassandra.cosmosdb.azure.com;AccountKey=${mockMasterKey};`
|
`AccountEndpoint=${mockAccountName}.cassandra.cosmosdb.azure.com;AccountKey=${mockMasterKey};`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -60,15 +60,13 @@ describe("ConnectionStringParser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to parse an invalid connection string", () => {
|
it("should fail to parse an invalid connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const metadata = parseConnectionString("some-rogue-connection-string");
|
||||||
"some-rogue-connection-string"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(metadata).toBe(undefined);
|
expect(metadata).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to parse an empty connection string", () => {
|
it("should fail to parse an empty connection string", () => {
|
||||||
const metadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString("");
|
const metadata = parseConnectionString("");
|
||||||
|
|
||||||
expect(metadata).toBe(undefined);
|
expect(metadata).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +1,36 @@
|
|||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import { AccessInputMetadata, ApiKind } from "../../../Contracts/DataModels";
|
||||||
|
|
||||||
export class ConnectionStringParser {
|
export function parseConnectionString(connectionString: string): AccessInputMetadata {
|
||||||
public static parseConnectionString(connectionString: string): DataModels.AccessInputMetadata {
|
if (connectionString) {
|
||||||
if (!!connectionString) {
|
|
||||||
try {
|
try {
|
||||||
const accessInput: DataModels.AccessInputMetadata = {} as DataModels.AccessInputMetadata;
|
const accessInput = {} as AccessInputMetadata;
|
||||||
const connectionStringParts = connectionString.split(";");
|
const connectionStringParts = connectionString.split(";");
|
||||||
|
|
||||||
connectionStringParts.forEach((connectionStringPart: string) => {
|
connectionStringParts.forEach((connectionStringPart: string) => {
|
||||||
if (RegExp(Constants.EndpointsRegex.sql).test(connectionStringPart)) {
|
if (RegExp(Constants.EndpointsRegex.sql).test(connectionStringPart)) {
|
||||||
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.sql)[1];
|
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.sql)[1];
|
||||||
accessInput.apiKind = DataModels.ApiKind.SQL;
|
accessInput.apiKind = ApiKind.SQL;
|
||||||
} else if (RegExp(Constants.EndpointsRegex.mongo).test(connectionStringPart)) {
|
} else if (RegExp(Constants.EndpointsRegex.mongo).test(connectionStringPart)) {
|
||||||
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongo);
|
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongo);
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
||||||
accessInput.apiKind = DataModels.ApiKind.MongoDB;
|
accessInput.apiKind = ApiKind.MongoDB;
|
||||||
} else if (RegExp(Constants.EndpointsRegex.mongoCompute).test(connectionStringPart)) {
|
} else if (RegExp(Constants.EndpointsRegex.mongoCompute).test(connectionStringPart)) {
|
||||||
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongoCompute);
|
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongoCompute);
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
||||||
accessInput.apiKind = DataModels.ApiKind.MongoDBCompute;
|
accessInput.apiKind = ApiKind.MongoDBCompute;
|
||||||
} else if (Constants.EndpointsRegex.cassandra.some(regex => RegExp(regex).test(connectionStringPart))) {
|
} else if (Constants.EndpointsRegex.cassandra.some(regex => RegExp(regex).test(connectionStringPart))) {
|
||||||
Constants.EndpointsRegex.cassandra.forEach(regex => {
|
Constants.EndpointsRegex.cassandra.forEach(regex => {
|
||||||
if (RegExp(regex).test(connectionStringPart)) {
|
if (RegExp(regex).test(connectionStringPart)) {
|
||||||
accessInput.accountName = connectionStringPart.match(regex)[1];
|
accessInput.accountName = connectionStringPart.match(regex)[1];
|
||||||
accessInput.apiKind = DataModels.ApiKind.Cassandra;
|
accessInput.apiKind = ApiKind.Cassandra;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (RegExp(Constants.EndpointsRegex.table).test(connectionStringPart)) {
|
} else if (RegExp(Constants.EndpointsRegex.table).test(connectionStringPart)) {
|
||||||
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.table)[1];
|
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.table)[1];
|
||||||
accessInput.apiKind = DataModels.ApiKind.Table;
|
accessInput.apiKind = ApiKind.Table;
|
||||||
} else if (connectionStringPart.indexOf("ApiKind=Gremlin") >= 0) {
|
} else if (connectionStringPart.indexOf("ApiKind=Gremlin") >= 0) {
|
||||||
accessInput.apiKind = DataModels.ApiKind.Graph;
|
accessInput.apiKind = ApiKind.Graph;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,4 +46,3 @@ export class ConnectionStringParser {
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -43,84 +43,40 @@ export default class Main {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static initializeExplorer(): Q.Promise<Explorer> {
|
public static initializeExplorer(): Explorer {
|
||||||
window.addEventListener("message", this._handleMessage.bind(this), false);
|
window.addEventListener("message", this._handleMessage.bind(this), false);
|
||||||
this._features = {};
|
this._features = {};
|
||||||
const params = new URLSearchParams(window.parent.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const deferred: Q.Deferred<Explorer> = Q.defer<Explorer>();
|
let authType: string = params && params.get("authType");
|
||||||
let authType: string = null;
|
|
||||||
|
|
||||||
// Encrypted token flow
|
|
||||||
if (!!params && params.has("key")) {
|
|
||||||
Main._encryptedToken = encodeURIComponent(params.get("key"));
|
|
||||||
SessionStorageUtility.setEntryString(StorageKey.EncryptedKeyToken, Main._encryptedToken);
|
|
||||||
authType = AuthType.EncryptedToken;
|
|
||||||
} else if (Main._hasCachedEncryptedKey()) {
|
|
||||||
Main._encryptedToken = SessionStorageUtility.getEntryString(StorageKey.EncryptedKeyToken);
|
|
||||||
authType = AuthType.EncryptedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aad flow
|
|
||||||
if (AuthHeadersUtil.isUserSignedIn()) {
|
|
||||||
authType = AuthType.AAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
this._features = Main.extractFeatures(params);
|
this._features = Main.extractFeatures(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypted token flow
|
||||||
|
if (params && params.has("key")) {
|
||||||
|
Main._encryptedToken = encodeURIComponent(params.get("key"));
|
||||||
|
Main._accessInputMetadata = JSON.parse(params.get("metadata"));
|
||||||
|
authType = AuthType.EncryptedToken;
|
||||||
|
}
|
||||||
|
|
||||||
(<any>window).authType = authType;
|
(<any>window).authType = authType;
|
||||||
if (!authType) {
|
if (!authType) {
|
||||||
return Q.reject("Sign in needed");
|
throw new Error("Sign in needed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const explorer: Explorer = this._instantiateExplorer();
|
const explorer: Explorer = this._instantiateExplorer();
|
||||||
if (authType === AuthType.EncryptedToken) {
|
if (authType === AuthType.EncryptedToken) {
|
||||||
sendMessage({
|
|
||||||
type: MessageTypes.UpdateAccountSwitch,
|
|
||||||
props: {
|
|
||||||
authType: AuthType.EncryptedToken,
|
|
||||||
displayText: "Loading..."
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
accessToken: Main._encryptedToken
|
accessToken: Main._encryptedToken
|
||||||
});
|
});
|
||||||
Main._getAccessInputMetadata(Main._encryptedToken).then(
|
|
||||||
() => {
|
|
||||||
const expiryTimestamp: number =
|
|
||||||
Main._accessInputMetadata && parseInt(Main._accessInputMetadata.expiryTimestamp);
|
|
||||||
if (authType === AuthType.EncryptedToken && (isNaN(expiryTimestamp) || expiryTimestamp <= 0)) {
|
|
||||||
return deferred.reject("Token expired");
|
|
||||||
}
|
|
||||||
|
|
||||||
Main._initDataExplorerFrameInputs(explorer);
|
Main._initDataExplorerFrameInputs(explorer);
|
||||||
deferred.resolve(explorer);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
console.error(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (authType === AuthType.AAD) {
|
} else if (authType === AuthType.AAD) {
|
||||||
sendMessage({
|
|
||||||
type: MessageTypes.GetAccessAadRequest
|
|
||||||
});
|
|
||||||
if (this._getAadAccessDeferred != null) {
|
|
||||||
// already request aad access, don't duplicate
|
|
||||||
return Q(null);
|
|
||||||
}
|
|
||||||
this._explorer = explorer;
|
this._explorer = explorer;
|
||||||
this._getAadAccessDeferred = Q.defer<Explorer>();
|
|
||||||
return this._getAadAccessDeferred.promise.finally(() => {
|
|
||||||
this._getAadAccessDeferred = null;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
Main._initDataExplorerFrameInputs(explorer);
|
Main._initDataExplorerFrameInputs(explorer);
|
||||||
deferred.resolve(explorer);
|
|
||||||
}
|
}
|
||||||
|
return explorer;
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static extractFeatures(params: URLSearchParams): { [key: string]: string } {
|
public static extractFeatures(params: URLSearchParams): { [key: string]: string } {
|
||||||
@@ -137,21 +93,6 @@ export default class Main {
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static configureTokenValidationDisplayPrompt(explorer: Explorer): void {
|
|
||||||
const authType: AuthType = (<any>window).authType;
|
|
||||||
if (
|
|
||||||
!explorer ||
|
|
||||||
!Main._encryptedToken ||
|
|
||||||
!Main._accessInputMetadata ||
|
|
||||||
Main._accessInputMetadata.expiryTimestamp == null ||
|
|
||||||
authType !== AuthType.EncryptedToken
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Main._showGuestAccessTokenRenewalPromptInMs(explorer, parseInt(Main._accessInputMetadata.expiryTimestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static parseResourceTokenConnectionString(connectionString: string): resourceTokenConnectionStringProperties {
|
public static parseResourceTokenConnectionString(connectionString: string): resourceTokenConnectionStringProperties {
|
||||||
let accountEndpoint: string;
|
let accountEndpoint: string;
|
||||||
let collectionId: string;
|
let collectionId: string;
|
||||||
@@ -234,7 +175,6 @@ export default class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const masterKey: string = Main._getMasterKeyFromConnectionString(connectionString);
|
const masterKey: string = Main._getMasterKeyFromConnectionString(connectionString);
|
||||||
Main.configureTokenValidationDisplayPrompt(explorer);
|
|
||||||
Main._setExplorerReady(explorer, masterKey);
|
Main._setExplorerReady(explorer, masterKey);
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
@@ -398,14 +338,6 @@ export default class Main {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _showGuestAccessTokenRenewalPromptInMs(explorer: Explorer, interval: number): void {
|
|
||||||
if (interval != null && !isNaN(interval)) {
|
|
||||||
setTimeout(() => {
|
|
||||||
explorer.displayGuestAccessTokenRenewalPrompt();
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _hasCachedEncryptedKey(): boolean {
|
private static _hasCachedEncryptedKey(): boolean {
|
||||||
return SessionStorageUtility.hasItem(StorageKey.EncryptedKeyToken);
|
return SessionStorageUtility.hasItem(StorageKey.EncryptedKeyToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const msal = new Msal.UserAgentApplication({
|
|||||||
auth: {
|
auth: {
|
||||||
authority: "https://login.microsoft.com/common",
|
authority: "https://login.microsoft.com/common",
|
||||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||||
redirectUri: "https://dataexplorer-dev.azurewebsites.net"
|
redirectUri: "https://dataexplorer-dev.azurewebsites.net" // TODO! This should only be set development
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,16 +18,16 @@ interface AuthContext {
|
|||||||
account?: Msal.Account;
|
account?: Msal.Account;
|
||||||
graphToken?: string;
|
graphToken?: string;
|
||||||
armToken?: string;
|
armToken?: string;
|
||||||
logout: () => unknown;
|
aadlogout: () => unknown;
|
||||||
login: () => unknown;
|
aadlogin: () => unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContext>({
|
export const AuthContext = createContext<AuthContext>({
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
login: () => {
|
aadlogin: () => {
|
||||||
throw Error(defaultError);
|
throw Error(defaultError);
|
||||||
},
|
},
|
||||||
logout: () => {
|
aadlogout: () => {
|
||||||
throw Error(defaultError);
|
throw Error(defaultError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -38,36 +38,27 @@ export const AuthProvider: React.FunctionComponent = ({ children }) => {
|
|||||||
const [graphToken, setGraphToken] = useState<string>();
|
const [graphToken, setGraphToken] = useState<string>();
|
||||||
const [armToken, setArmToken] = useState<string>();
|
const [armToken, setArmToken] = useState<string>();
|
||||||
|
|
||||||
const login = useCallback(() => {
|
const aadlogin = useCallback(async () => {
|
||||||
msal.loginPopup().then(response => {
|
const response = await msal.loginPopup();
|
||||||
setLoggedIn();
|
setLoggedIn();
|
||||||
setAccount(response.account);
|
setAccount(response.account);
|
||||||
msal
|
|
||||||
.acquireTokenSilent({ scopes: ["https://graph.windows.net//.default"] })
|
const [graphTokenResponse, armTokenResponse] = await Promise.all([
|
||||||
.then(resp => {
|
msal.acquireTokenSilent({ scopes: ["https://graph.windows.net//.default"] }),
|
||||||
setGraphToken(resp.accessToken);
|
msal.acquireTokenSilent({ scopes: ["https://management.azure.com//.default"] })
|
||||||
})
|
]);
|
||||||
.catch(e => {
|
|
||||||
console.error(e);
|
setGraphToken(graphTokenResponse.accessToken);
|
||||||
});
|
setArmToken(armTokenResponse.accessToken);
|
||||||
msal
|
|
||||||
.acquireTokenSilent({ scopes: ["https://management.azure.com//.default"] })
|
|
||||||
.then(resp => {
|
|
||||||
setArmToken(resp.accessToken);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const logout = useCallback(() => {
|
const aadlogout = useCallback(() => {
|
||||||
msal.logout();
|
msal.logout();
|
||||||
setLoggedOut();
|
setLoggedOut();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ isLoggedIn, account, login, logout, graphToken, armToken }}>
|
<AuthContext.Provider value={{ isLoggedIn, account, aadlogin, aadlogout, graphToken, armToken }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
36
src/hooks/usePortalAccessToken.tsx
Normal file
36
src/hooks/usePortalAccessToken.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ApiEndpoints } from "../Common/Constants";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
|
import { AccessInputMetadata } from "../Contracts/DataModels";
|
||||||
|
|
||||||
|
const url = `${configContext.BACKEND_ENDPOINT}${ApiEndpoints.guestRuntimeProxy}/accessinputmetadata?_=1609359229955`;
|
||||||
|
|
||||||
|
export async function fetchAccessData(portalToken: string): Promise<AccessInputMetadata> {
|
||||||
|
const headers = new Headers();
|
||||||
|
// Portal encrypted token API quirk: The token header must be URL encoded
|
||||||
|
headers.append("x-ms-encrypted-auth-token", encodeURIComponent(portalToken));
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: "GET",
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
fetch(url, options)
|
||||||
|
.then(response => response.json())
|
||||||
|
// Portal encrypted token API quirk: The response is double JSON encoded
|
||||||
|
.then(json => JSON.parse(json))
|
||||||
|
.catch(error => console.log(error))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePortalAccessToken(token: string): AccessInputMetadata {
|
||||||
|
const [state, setState] = useState<AccessInputMetadata>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (token) {
|
||||||
|
fetchAccessData(token).then(response => setState(response));
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user