Checkpoint

This commit is contained in:
Steve Faulkner
2020-12-30 19:12:22 -06:00
parent 7116f25ce4
commit 585f75bc91
9 changed files with 156 additions and 208 deletions

View File

@@ -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

View File

@@ -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";

View File

@@ -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">

View File

@@ -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);
}); });
}, []); }, []);

View File

@@ -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);
}); });

View File

@@ -1,50 +1,48 @@
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 = {} as AccessInputMetadata;
const accessInput: DataModels.AccessInputMetadata = {} as DataModels.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;
}
});
if (Object.keys(accessInput).length === 0) {
return undefined;
} }
});
return accessInput; if (Object.keys(accessInput).length === 0) {
} catch (error) {
return undefined; return undefined;
} }
}
return undefined; return accessInput;
} catch (error) {
return undefined;
}
} }
return undefined;
} }

View File

@@ -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( Main._initDataExplorerFrameInputs(explorer);
() => {
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);
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);
} }

View File

@@ -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>
); );

View 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;
}