Skip to content

Commit

Permalink
Merge branch 'no-auth-webapp' into feature-better-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
dehoward committed Aug 7, 2023
2 parents 85d40bf + 64f7b15 commit 408f580
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 128 deletions.
10 changes: 6 additions & 4 deletions webapp/.env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Required Variables
# If you add any new required variables, make sure you update the variables list in the checkEnv.ts file as well.
REACT_APP_BACKEND_URI=https://localhost:40443/
REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/common
REACT_APP_AAD_CLIENT_ID=

# Authorization scopes to access webapi when using Azure AD authorization.
# To enable authorization using Azure Active Directory, uncomment the following variables
# See paragraph "(Optional) Enable backend authorization via Azure AD" in README.md for details and setup
REACT_APP_AAD_API_SCOPE=
# REACT_APP_AUTH_TYPE=AzureAd
# REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/common
# REACT_APP_AAD_CLIENT_ID=
# Authorization scopes to access webapi when using Azure AD authorization.
# REACT_APP_AAD_API_SCOPE=

# To enable HTTPS, uncomment the following variables
# HTTPS="true"
Expand Down
145 changes: 81 additions & 64 deletions webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import BackendProbe from './components/views/BackendProbe';
import { ChatView } from './components/views/ChatView';
import Loading from './components/views/Loading';
import { Login } from './components/views/Login';
import { AuthHelper } from './libs/auth/AuthHelper';
import { useChat } from './libs/hooks';
import { AlertType } from './libs/models/AlertType';
import { useAppDispatch, useAppSelector } from './redux/app/hooks';
Expand Down Expand Up @@ -69,41 +70,37 @@ const App: FC = () => {
const chat = useChat();

useEffect(() => {
if (isAuthenticated) {
let isActiveUserInfoSet = activeUserInfo !== undefined;
if (!isActiveUserInfoSet) {
const account = instance.getActiveAccount();
if (!account) {
dispatch(addAlert({ type: AlertType.Error, message: 'Unable to get active logged in account.' }));
} else {
dispatch(
setActiveUserInfo({
id: account.homeAccountId,
email: account.username, // username in an AccountInfo object is the email address
username: account.name ?? account.username,
}),
);
}
isActiveUserInfoSet = true;
if (isAuthenticated && !activeUserInfo) {
const account = instance.getActiveAccount();
if (!account) {
dispatch(addAlert({ type: AlertType.Error, message: 'Unable to get active logged in account.' }));
} else {
dispatch(
setActiveUserInfo({
id: account.homeAccountId,
email: account.username, // username in an AccountInfo object is the email address
username: account.name ?? account.username,
}),
);
}
}

if (appState === AppState.LoadingChats) {
void Promise.all([
// Load all chats from memory
chat.loadChats().then((succeeded) => {
if (succeeded) {
setAppState(AppState.Chat);
}
}),
if ((isAuthenticated || !AuthHelper.IsAuthAAD) && appState === AppState.LoadingChats) {
void Promise.all([
// Load all chats from memory
chat.loadChats().then((succeeded) => {
if (succeeded) {
setAppState(AppState.Chat);
}
}),

// Load service options
chat.getServiceOptions().then((serviceOptions) => {
if (serviceOptions) {
dispatch(setServiceOptions(serviceOptions));
}
}),
]);
}
// Load service options
chat.getServiceOptions().then((serviceOptions) => {
if (serviceOptions) {
dispatch(setServiceOptions(serviceOptions));
}
}),
]);
}

// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -115,43 +112,63 @@ const App: FC = () => {
className="app-container"
theme={features[FeatureKeys.DarkMode].enabled ? semanticKernelDarkTheme : semanticKernelLightTheme}
>
<UnauthenticatedTemplate>
<div className={classes.container}>
<div className={classes.header}>
<Subtitle1 as="h1">Chat Copilot</Subtitle1>
</div>
{appState === AppState.SigningOut && <Loading text="Signing you out..." />}
{appState !== AppState.SigningOut && <Login />}
</div>
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<div className={classes.container}>
<div className={classes.header}>
<Subtitle1 as="h1">Chat Copilot</Subtitle1>
<div className={classes.cornerItems}>
<div data-testid="logOutMenuList" className={classes.cornerItems}>
<PluginGallery />
<UserSettingsMenu
setLoadingState={() => {
setAppState(AppState.SigningOut);
}}
/>
{AuthHelper.IsAuthAAD ? (
<>
<UnauthenticatedTemplate>
<div className={classes.container}>
<div className={classes.header}>
<Subtitle1 as="h1">Chat Copilot</Subtitle1>
</div>
{appState === AppState.SigningOut && <Loading text="Signing you out..." />}
{appState !== AppState.SigningOut && <Login />}
</div>
</div>
{appState === AppState.ProbeForBackend && (
<BackendProbe
uri={process.env.REACT_APP_BACKEND_URI as string}
onBackendFound={() => {
setAppState(AppState.LoadingChats);
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<Chat classes={classes} appState={appState} setAppState={setAppState} />
</AuthenticatedTemplate>
</>
) : (
<Chat classes={classes} appState={appState} setAppState={setAppState} />
)}
</FluentProvider>
);
};

const Chat = ({
classes,
appState,
setAppState,
}: {
classes: ReturnType<typeof useClasses>;
appState: AppState;
setAppState: (state: AppState) => void;
}) => {
return (
<div className={classes.container}>
<div className={classes.header}>
<Subtitle1 as="h1">Chat Copilot</Subtitle1>
<div className={classes.cornerItems}>
<div data-testid="logOutMenuList" className={classes.cornerItems}>
<PluginGallery />
<UserSettingsMenu
setLoadingState={() => {
setAppState(AppState.SigningOut);
}}
/>
)}
{appState === AppState.LoadingChats && <Loading text="Loading Chats..." />}
{appState === AppState.Chat && <ChatView />}
</div>
</div>
</AuthenticatedTemplate>
</FluentProvider>
</div>
{appState === AppState.ProbeForBackend && (
<BackendProbe
uri={process.env.REACT_APP_BACKEND_URI as string}
onBackendFound={() => {
setAppState(AppState.LoadingChats);
}}
/>
)}
{appState === AppState.LoadingChats && <Loading text="Loading Chats..." />}
{appState === AppState.Chat && <ChatView />}
</div>
);
};

Expand Down
4 changes: 0 additions & 4 deletions webapp/src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ export const Constants = {
},
msal: {
method: 'redirect', // 'redirect' | 'popup'
auth: {
clientId: process.env.REACT_APP_AAD_CLIENT_ID as string,
authority: process.env.REACT_APP_AAD_AUTHORITY as string,
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
Expand Down
13 changes: 12 additions & 1 deletion webapp/src/checkEnv.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AuthType } from './libs/auth/AuthHelper';

export const getMissingEnvVariables = () => {
// Should be aligned with variables defined in .env.example
const envVariables = ['REACT_APP_BACKEND_URI', 'REACT_APP_AAD_AUTHORITY', 'REACT_APP_AAD_CLIENT_ID'];
const envVariables = ['REACT_APP_BACKEND_URI'];

const missingVariables = [];

Expand All @@ -10,5 +12,14 @@ export const getMissingEnvVariables = () => {
}
}

if (process.env.REACT_APP_AUTH_TYPE === AuthType.AAD) {
const aadVariables = ['REACT_APP_AAD_AUTHORITY', 'REACT_APP_AAD_CLIENT_ID'];
for (const variable of aadVariables) {
if (!process.env[variable]) {
missingVariables.push(variable);
}
}
}

return missingVariables;
};
8 changes: 6 additions & 2 deletions webapp/src/components/chat/chat-history/ChatHistoryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Persona, Text, makeStyles, mergeClasses, shorthands } from '@fluentui/react-components';
import { ThumbDislike24Filled, ThumbLike16Filled } from '@fluentui/react-icons';
import React from 'react';
import { DefaultChatUser } from '../../../libs/auth/AuthHelper';
import { GetResponseOptions, useChat } from '../../../libs/hooks/useChat';
import { AuthorRoles, ChatMessageType, IChatMessage, UserFeedback } from '../../../libs/models/ChatMessage';
import { useAppSelector } from '../../../redux/app/hooks';
Expand Down Expand Up @@ -84,9 +85,12 @@ export const ChatHistoryItem: React.FC<ChatHistoryItemProps> = ({ message, getRe
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
const { activeUserInfo, features } = useAppSelector((state: RootState) => state.app);

const isMe = message.authorRole === AuthorRoles.User && message.userId === activeUserInfo?.id;
const isDefaultUser = message.userId === DefaultChatUser.id;
const isMe = isDefaultUser || (message.authorRole === AuthorRoles.User && message.userId === activeUserInfo?.id);
const isBot = message.authorRole === AuthorRoles.Bot;
const user = chat.getChatUserById(message.userName, selectedId, conversations[selectedId].users);
const user = isDefaultUser
? DefaultChatUser
: chat.getChatUserById(message.userName, selectedId, conversations[selectedId].users);
const fullName = user?.fullName ?? message.userName;

const avatar = isBot
Expand Down
106 changes: 66 additions & 40 deletions webapp/src/components/header/UserSettingsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FC, useCallback, useState } from 'react';
import { useMsal } from '@azure/msal-react';
import {
Avatar,
Button,
Menu,
MenuDivider,
MenuItem,
Expand All @@ -16,6 +17,7 @@ import {
shorthands,
tokens,
} from '@fluentui/react-components';
import { Settings24Regular } from '@fluentui/react-icons';
import { AuthHelper } from '../../libs/auth/AuthHelper';
import { useAppSelector } from '../../redux/app/hooks';
import { RootState, resetState } from '../../redux/app/store';
Expand Down Expand Up @@ -51,46 +53,70 @@ export const UserSettingsMenu: FC<IUserSettingsProps> = ({ setLoadingState }) =>

return (
<>
<Menu>
<MenuTrigger disableButtonEnhancement>
{
<Avatar
className={classes.root}
key={activeUserInfo?.username}
name={activeUserInfo?.username}
size={28}
badge={
!features[FeatureKeys.SimplifiedExperience].enabled
? { status: 'available' }
: undefined
}
/>
}
</MenuTrigger>
<MenuPopover>
<MenuList>
<Persona
className={classes.persona}
name={activeUserInfo?.username}
secondaryText={activeUserInfo?.email}
presence={
!features[FeatureKeys.SimplifiedExperience].enabled
? { status: 'available' }
: undefined
}
avatar={{ color: 'colorful' }}
/>
<MenuDivider />
<MenuItem data-testid="settingsMenuItem" onClick={() => { setOpenSettingsDialog(true); }}>
Settings
</MenuItem>
<MenuItem data-testid="logOutMenuButton" onClick={onLogout}>
Sign out
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
<SettingsDialog open={openSettingsDialog} closeDialog={() => { setOpenSettingsDialog(false); }} />
{AuthHelper.IsAuthAAD ? (
<Menu>
<MenuTrigger disableButtonEnhancement>
{
<Avatar
className={classes.root}
key={activeUserInfo?.username}
name={activeUserInfo?.username}
size={28}
badge={
!features[FeatureKeys.SimplifiedExperience].enabled
? { status: 'available' }
: undefined
}
/>
}
</MenuTrigger>
<MenuPopover>
<MenuList>
<Persona
className={classes.persona}
name={activeUserInfo?.username}
secondaryText={activeUserInfo?.email}
presence={
!features[FeatureKeys.SimplifiedExperience].enabled
? { status: 'available' }
: undefined
}
avatar={{ color: 'colorful' }}
/>
<MenuDivider />
<MenuItem
data-testid="settingsMenuItem"
onClick={() => {
setOpenSettingsDialog(true);
}}
>
Settings
</MenuItem>
<MenuItem data-testid="logOutMenuButton" onClick={onLogout}>
Sign out
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
) : (
<Button
data-testid="settingsButton"
style={{ color: 'white' }}
appearance="transparent"
icon={<Settings24Regular color="white" />}
onClick={() => {
setOpenSettingsDialog(true);
}}
>
Settings
</Button>
)}
<SettingsDialog
open={openSettingsDialog}
closeDialog={() => {
setOpenSettingsDialog(false);
}}
/>
</>
);
};
Loading

0 comments on commit 408f580

Please sign in to comment.