diff --git a/app/src/components/siteNav/SiteNav.tsx b/app/src/components/siteNav/SiteNav.tsx index 00fc215..4f52b4b 100644 --- a/app/src/components/siteNav/SiteNav.tsx +++ b/app/src/components/siteNav/SiteNav.tsx @@ -1,37 +1,27 @@ import { useEffect, useState } from 'react'; -import { ISiteNavProps } from './ISiteNavProps'; import { useNavigate } from 'react-router-dom'; import { useAppContext } from '../../hooks/appContext/UseAppContext'; import { - Avatar, - Button, - IconButton, - Menu, MenuItem, Navbar, PlaneIcon, SignOutIcon, - Spinner, Typography } from '@noahspan/noahspan-components'; import { useIsAuthenticated, useMsal } from '@azure/msal-react'; -import { InteractionStatus } from '@azure/msal-browser'; import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; import { AxiosInstance, AxiosResponse } from 'axios'; import { User } from '@microsoft/microsoft-graph-types'; -import { EventMessage, EventPayload, EventType } from '@azure/msal-browser'; - -type EventPayloadExtended = EventPayload & { accessToken: string }; +import { loginRequest } from '../../hooks/auth/authConfig'; const SiteNav: React.FC = () => { - const [loading, setLoading] = useState(false); const [userPhoto, setUserPhoto] = useState(); const httpClient: AxiosInstance = useHttpClient(); const appContext = useAppContext(); const isAuthenticated = useIsAuthenticated(); const { getAccessToken } = useAccessToken(); - const { inProgress, instance } = useMsal(); + const { instance } = useMsal(); const navigate = useNavigate(); const pages = [ { @@ -43,28 +33,23 @@ const SiteNav: React.FC = () => { url: '/pilots' } ]; - const handleSignIn = async () => { - // instance.handleRedirectPromise().then((response) => { - // if (response) { - // console.log(response) - // console.log('successful login', response); - // } else { - // instance.loginRedirect({ - // scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] - // }); - // } - // }).catch((error) => { - // console.log(error); - // }) - try { - await instance.loginPopup(); - } catch (err) { - console.log(err); - } - }; - const handleSignOut = () => { - instance.logoutRedirect(); + + const handleSignInRedirect = () => { + instance + .loginRedirect({ + ...loginRequest, + prompt: 'create', + }) + .catch((error) => console.log(error)); }; + + const handleSignOutRedirect = () => { + instance.logoutRedirect({ + postLogoutRedirectUri: '/' + }) + window.location.reload(); + } + const getUserProfile = async (accessToken: string): Promise => { try { const response: AxiosResponse = await httpClient.get(`api/userProfile`, { @@ -79,6 +64,7 @@ const SiteNav: React.FC = () => { throw new Error(); } }; + const getUserPhoto = async (accessToken: string): Promise => { try { const response: AxiosResponse = await httpClient.get(`api/userPhoto`, { @@ -103,7 +89,7 @@ const SiteNav: React.FC = () => { const Settings = () => { return ( - + Sign Out @@ -112,46 +98,9 @@ const SiteNav: React.FC = () => { ); }; - useEffect(() => { - const callback = instance.addEventCallback( - async (message: EventMessage) => { - if (message.eventType === EventType.LOGIN_SUCCESS) { - try { - setLoading(true); - - const eventPayload: EventPayloadExtended = - message.payload as EventPayloadExtended; - const userProfile: User = await getUserProfile( - eventPayload.accessToken - ); - const userPhoto = await getUserPhoto(eventPayload.accessToken); - - appContext.dispatch({ - type: 'SET_USER_PROFILE', - payload: userProfile - }); - } catch (error) { - console.log(error); - } finally { - setLoading(false); - } - } - } - ); - - return () => { - if (callback) { - instance.removeEventCallback(callback); - appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} }); - } - }; - }, []); - useEffect(() => { const setUserProfile = async () => { try { - setLoading(true); - const accessToken: string = await getAccessToken(); const userProfile = await getUserProfile(accessToken); const userPhoto = await getUserPhoto(accessToken); @@ -164,8 +113,6 @@ const SiteNav: React.FC = () => { }); } catch (error) { console.log(error); - } finally { - setLoading(false); } }; @@ -180,7 +127,7 @@ const SiteNav: React.FC = () => { return ( } pages={pages} diff --git a/app/src/hooks/auth/UseAuthProvider.tsx b/app/src/hooks/auth/UseAuthProvider.tsx new file mode 100644 index 0000000..ae954d7 --- /dev/null +++ b/app/src/hooks/auth/UseAuthProvider.tsx @@ -0,0 +1,7 @@ +import { AuthProvider } from "./authProvider" + +export const useAuthProvider = () => { + return { + AuthProvider + } +} \ No newline at end of file diff --git a/app/src/hooks/auth/authConfig.ts b/app/src/hooks/auth/authConfig.ts new file mode 100644 index 0000000..bf1ef48 --- /dev/null +++ b/app/src/hooks/auth/authConfig.ts @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { LogLevel } from '@azure/msal-browser'; + +/** + * Configuration object to be passed to MSAL instance on creation. + * For a full list of MSAL.js configuration parameters, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md + */ + +export const msalConfig = { + auth: { + clientId: import.meta.env.VITE_CLIENT_ID, // This is the ONLY mandatory field that you need to supply. + authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, // Replace the placeholder with your tenant subdomain + redirectUri: import.meta.env.VITE_REDIRECT_URL, // Points to window.location.origin. You must register this URI on Microsoft Entra admin center/App Registration. + postLogoutRedirectUri: '/', // Indicates the page to navigate after logout. + navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response. + }, + cache: { + cacheLocation: 'sessionStorage', // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs. + storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + }, + system: { + loggerOptions: { + loggerCallback: (level: any, message: any, containsPii: any) => { + if (containsPii) { + return; + } + switch (level) { + case LogLevel.Error: + console.error(message); + return; + case LogLevel.Info: + console.info(message); + return; + case LogLevel.Verbose: + console.debug(message); + return; + case LogLevel.Warning: + console.warn(message); + return; + default: + return; + } + }, + }, + }, +}; + +/** + * Scopes you add here will be prompted for user consent during sign-in. + * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request. + * For more information about OIDC scopes, visit: + * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes + */ +export const loginRequest = { + scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] +}; + +/** + * An optional silentRequest object can be used to achieve silent SSO + * between applications by providing a "login_hint" property. + */ +// export const silentRequest = { +// scopes: ["openid", "profile"], +// loginHint: "example@domain.net" +// }; \ No newline at end of file diff --git a/app/src/hooks/auth/authProvider.tsx b/app/src/hooks/auth/authProvider.tsx new file mode 100644 index 0000000..8e1676c --- /dev/null +++ b/app/src/hooks/auth/authProvider.tsx @@ -0,0 +1,32 @@ +import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser"; +import { ReactNode } from "react"; +import { msalConfig } from "./authConfig"; +import { MsalProvider } from "@azure/msal-react"; + +interface AuthProviderProps { + children: ReactNode +} + +export const AuthProvider = ({ children }: AuthProviderProps ) => { + const msalInstance = new PublicClientApplication(msalConfig); + + // Default to using the first account if no account is active on page load + if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) { + // Account selection logic is app dependent. Adjust as needed for different use cases. + msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]); + } + + // Listen for sign-in event and set active account + msalInstance.addEventCallback((event) => { + const authenticationResult = event.payload as AuthenticationResult; + const account = authenticationResult?.account; + + if (event.eventType === EventType.LOGIN_SUCCESS && account) { + msalInstance.setActiveAccount(account); + } + }); + + return + {children} + +} \ No newline at end of file diff --git a/app/src/main.tsx b/app/src/main.tsx index 6c5defd..b2d16c7 100644 --- a/app/src/main.tsx +++ b/app/src/main.tsx @@ -5,27 +5,21 @@ import AppContextProvider from './context/appContext/AppContextProvider.tsx'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; import '@noahspan/noahspan-components/noahspan-components.css'; -import { PublicClientApplication } from '@azure/msal-browser'; +import { EventMessage, EventPayload, EventType, LogLevel, PublicClientApplication } from '@azure/msal-browser'; import { MsalProvider } from '@azure/msal-react'; +import { useAuthProvider } from './hooks/auth/UseAuthProvider.tsx'; -const msalInstance: PublicClientApplication = new PublicClientApplication({ - auth: { - clientId: import.meta.env.VITE_CLIENT_ID, - authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, - redirectUri: import.meta.env.VITE_REDIRECT_URL - } -}); +const { AuthProvider } = useAuthProvider(); + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + + +); -msalInstance.initialize().then(() => { - ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - ); -}); diff --git a/infrastructure/config/main.tf b/infrastructure/config/main.tf index fd2b534..48f0305 100644 --- a/infrastructure/config/main.tf +++ b/infrastructure/config/main.tf @@ -8,55 +8,66 @@ locals { container_app_app_container_image = { dev = "noahspan/flying-app:v0.0.1" test = "noahspan/flying-app:v0.0.1" + prod = "noahspan/flying-app:v0.0.1" } container_app_app_container_name = { dev = "flying-app-dev" test = "flying-app-test" + prod = "flying-app-prod" } container_app_api_name = { dev = "flying-api-dev" test = "flying-api-test" + prod = "flying-api-prod" } container_app_api_container_image = { dev = "noahspan/flying-api:v0.0.1" test = "noahspan/flying-api:v0.0.1" + prod = "noahspan/flying-api:v0.0.1" } container_app_api_container_name = { dev = "flying-api-dev" test = "flying-api-test" + prod = "flying-api-prod" } container_app_api_dapr_app_id = { dev = "flyingapidev" test = "flyingapitest" + prod = "flyingpapiprod" } container_app_environment_name = { dev = "flying-dev" test = "flying-test" + prod = "flying-prod" } logbook_feature_flag_active = { dev = "true" test = "true" + prod = "true" } log_analytics_workspace_name = { dev = "flying-log-analytics-workspace-dev" test = "flying-log-analytics-workspace-test" + prod = "flying-log-analytics-workspace-prod" } pilots_feature_flag_active = { dev = "true" test = "true" + prod = "true" } storage_account_name = { dev = "noahspanflyingdev" test = "noahspanflyingtest" + prod = "noahspanflyingprod" } } \ No newline at end of file diff --git a/infrastructure/config/outputs.tf b/infrastructure/config/outputs.tf index fea3171..c7103b0 100644 --- a/infrastructure/config/outputs.tf +++ b/infrastructure/config/outputs.tf @@ -30,10 +30,6 @@ output "container_app_environment_name" { value = local.container_app_environment_name[var.environment] } -output "key_vault_name" { - value = local.key_vault_name[var.environment] -} - output "logbook_feature_flag_active" { value = local.logbook_feature_flag_active[var.environment] }