Integrate new server authentication to client
Changes:
- Use server authentication REST API
- Log in automatically after registration
- Store token in localStorage
- Give feedback to user if registration or login fails
GitLab: #75
Change-Id: Ib90e5b911621567c6825af5e275920d703cdfe88
diff --git a/client/src/components/AccountPreferences.tsx b/client/src/components/AccountPreferences.tsx
index db3e62d..2eedc97 100644
--- a/client/src/components/AccountPreferences.tsx
+++ b/client/src/components/AccountPreferences.tsx
@@ -58,7 +58,6 @@
type AccountPreferencesProps = {
account: Account;
- onAccountChanged?: (account: Account) => void;
};
export default function AccountPreferences({ account }: AccountPreferencesProps) {
diff --git a/client/src/components/AlertSnackbar.tsx b/client/src/components/AlertSnackbar.tsx
new file mode 100644
index 0000000..c32eca8
--- /dev/null
+++ b/client/src/components/AlertSnackbar.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Alert, AlertColor, AlertProps, AlertTitle, Snackbar, SnackbarProps } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+type AlertSnackbarProps = AlertProps & {
+ severity: AlertColor;
+ open?: boolean;
+ snackBarProps?: Partial<SnackbarProps>;
+};
+
+export function AlertSnackbar({ severity, open, snackBarProps, children, ...alertProps }: AlertSnackbarProps) {
+ const { t } = useTranslation();
+
+ return (
+ <Snackbar
+ open={open}
+ {...snackBarProps}
+ anchorOrigin={{
+ vertical: 'top',
+ horizontal: 'center',
+ ...snackBarProps?.anchorOrigin,
+ }}
+ >
+ <Alert severity={severity} {...alertProps}>
+ <AlertTitle>{t(`severity_${severity}`)}</AlertTitle>
+ {children}
+ </Alert>
+ </Snackbar>
+ );
+}
diff --git a/client/src/components/ContactList.jsx b/client/src/components/ContactList.jsx
index 2aee98b..56f5f9c 100644
--- a/client/src/components/ContactList.jsx
+++ b/client/src/components/ContactList.jsx
@@ -37,7 +37,7 @@
};
export default function ContactList() {
- const { accountId, accountObject } = useAppSelector((state) => state.app);
+ const { accountId, account } = useAppSelector((state) => state.userInfo);
const dispatch = useAppDispatch();
const [contacts, setContacts] = useState([]);
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index 9ad47f6..316792b 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -31,7 +31,7 @@
search?: Conversation;
};
export default function ConversationList(props: ConversationListProps) {
- const { refresh } = useAppSelector((state) => state.app);
+ const { refresh } = useAppSelector((state) => state.userInfo);
useEffect(() => {
console.log('refresh list');
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 303fc67..f2a0c37 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -19,7 +19,7 @@
import { MouseEvent, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
-import authManager from '../AuthManager';
+import { setAccessToken } from '../utils/auth';
export default function Header() {
const navigate = useNavigate();
@@ -28,20 +28,23 @@
const handleClose = () => setAnchorEl(null);
const params = useParams();
- const goToAccountSelection = () => navigate(`/account`);
- const goToContacts = () => navigate(`/Contacts`);
+ const goToContacts = () => navigate(`/contacts`);
const goToAccountSettings = () => navigate(`/account/${params.accountId}/settings`);
+ const logout = () => {
+ setAccessToken('');
+ navigate('/', { replace: true });
+ };
+
return (
<Box>
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
Menu
</Button>
<Menu id="simple-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
- <MenuItem onClick={goToAccountSelection}>Change account</MenuItem>
<MenuItem onClick={goToContacts}>Contacts</MenuItem>
{params.accountId && <MenuItem onClick={goToAccountSettings}>Account settings</MenuItem>}
- <MenuItem onClick={() => authManager.disconnect()}>Log out</MenuItem>
+ <MenuItem onClick={logout}>Log out</MenuItem>
</Menu>
</Box>
);
diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx
index ae640cf..063b350 100644
--- a/client/src/components/Input.tsx
+++ b/client/src/components/Input.tsx
@@ -53,7 +53,13 @@
tooltipTitle: string;
};
-export const UsernameInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
+export const UsernameInput = ({
+ infoButtonProps,
+ onChange: _onChange,
+ success,
+ tooltipTitle,
+ ...props
+}: InputProps) => {
const [isSelected, setIsSelected] = useState(false);
const [input, setInput] = useState(props.defaultValue);
const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
@@ -73,13 +79,13 @@
let visibility = 'visible';
if (props.error) {
Icon = StyledRoundSaltireIconError;
- } else if (props.success) {
+ } else if (success) {
Icon = StyledCheckedIconSuccess;
} else if (!isSelected && !input) {
visibility = 'hidden'; // keep icon's space so text does not move
}
setStartAdornment(<Icon sx={{ visibility }} />);
- }, [props.error, props.success, isSelected, input]);
+ }, [props.error, success, isSelected, input]);
return (
<>
@@ -88,7 +94,7 @@
</RulesDialog>
<TextField
{...props}
- color={inputColor(props.error, props.success)}
+ color={inputColor(props.error, success)}
label={'Choose an identifier'}
variant="standard"
InputLabelProps={{ shrink: !!(isSelected || input) }}
@@ -106,7 +112,13 @@
);
};
-export const PasswordInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
+export const PasswordInput = ({
+ infoButtonProps,
+ onChange: _onChange,
+ success,
+ tooltipTitle,
+ ...props
+}: InputProps) => {
const [showPassword, setShowPassword] = useState(false);
const [isSelected, setIsSelected] = useState(false);
const [input, setInput] = useState(props.defaultValue);
@@ -131,13 +143,13 @@
let visibility = 'visible';
if (props.error) {
Icon = StyledRoundSaltireIconError;
- } else if (props.success) {
+ } else if (success) {
Icon = StyledCheckedIconSuccess;
} else if (!isSelected && !input) {
visibility = 'hidden'; // keep icon's space so text does not move
}
setStartAdornment(<Icon sx={{ visibility }} />);
- }, [props.error, props.success, isSelected, input]);
+ }, [props.error, success, isSelected, input]);
return (
<>
@@ -146,7 +158,7 @@
</RulesDialog>
<TextField
{...props}
- color={inputColor(props.error, props.success)}
+ color={inputColor(props.error, success)}
label="Password"
type={showPassword ? 'text' : 'password'}
variant="standard"