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/pages/JamiRegistration.tsx b/client/src/pages/JamiRegistration.tsx
index ebfc5fd..2e8d23d 100644
--- a/client/src/pages/JamiRegistration.tsx
+++ b/client/src/pages/JamiRegistration.tsx
@@ -17,14 +17,23 @@
  */
 import { Box, Button, Stack, Typography, useMediaQuery } from '@mui/material';
 import { Theme, useTheme } from '@mui/material/styles';
-import { ChangeEvent, FormEvent, MouseEvent, useEffect, useState } from 'react';
+import { ChangeEvent, FormEvent, MouseEvent, ReactNode, useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import { Form } from 'react-router-dom';
+import { Form, useNavigate } from 'react-router-dom';
 
+import { AlertSnackbar } from '../components/AlertSnackbar';
 import { PasswordInput, UsernameInput } from '../components/Input';
 import ProcessingRequest from '../components/ProcessingRequest';
-import { checkPasswordStrength, isNameRegistered, StrengthValueCode } from '../utils/auth';
+import {
+  checkPasswordStrength,
+  isNameRegistered,
+  loginUser,
+  registerUser,
+  setAccessToken,
+  StrengthValueCode,
+} from '../utils/auth';
 import { inputWidth, jamiUsernamePattern } from '../utils/constants';
+import { InvalidPassword, UsernameNotFound } from '../utils/errors';
 
 const usernameTooltipTitle =
   'Choose a password hard to guess for others but easy to remember for you, ' +
@@ -44,14 +53,20 @@
 
 export default function JamiRegistration(props: JamiRegistrationProps) {
   const theme: Theme = useTheme();
+  const navigate = useNavigate();
   const { t } = useTranslation();
 
   const [isCreatingUser, setIsCreatingUser] = useState(false);
-  const [usernameValue, setUsernameValue] = useState('');
-  const [passwordValue, setPasswordValue] = useState('');
+
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+
   const [usernameStatus, setUsernameStatus] = useState<NameStatus>('default');
   const [passwordStatus, setPasswordStatus] = useState<PasswordStatus>('default');
 
+  const [errorAlertContent, setErrorAlertContent] = useState<ReactNode>(undefined);
+  const [successAlertContent, setSuccessAlertContent] = useState<ReactNode>(undefined);
+
   const usernameError = usernameStatus !== 'success' && usernameStatus !== 'default';
   const usernameSuccess = usernameStatus === 'success';
   const passwordError = passwordStatus !== 'strong' && passwordStatus !== 'default';
@@ -59,9 +74,9 @@
 
   useEffect(() => {
     // To prevent lookup if field is empty, in error state or lookup already done
-    if (usernameValue.length > 0 && usernameStatus === 'default') {
+    if (username.length > 0 && usernameStatus === 'default') {
       const validateUsername = async () => {
-        if (await isNameRegistered(usernameValue)) {
+        if (await isNameRegistered(username)) {
           setUsernameStatus('taken');
         } else {
           setUsernameStatus('success');
@@ -71,13 +86,36 @@
 
       return () => clearTimeout(timeout);
     }
-  }, [usernameValue, usernameStatus]);
+  }, [username, usernameStatus]);
+
+  const firstUserLogin = async () => {
+    try {
+      const accessToken = await loginUser(username, password);
+      setAccessToken(accessToken);
+      navigate('/settings', { replace: true });
+    } catch (err) {
+      setIsCreatingUser(false);
+      if (err instanceof UsernameNotFound) {
+        setErrorAlertContent(t('login_username_not_found'));
+      } else if (err instanceof InvalidPassword) {
+        setErrorAlertContent(t('login_invalid_password'));
+      } else {
+        throw err;
+      }
+    }
+  };
+
+  const createAccount = async () => {
+    await registerUser(username, password);
+    setSuccessAlertContent(t('registration_success'));
+    await firstUserLogin();
+  };
 
   const handleUsername = async (event: ChangeEvent<HTMLInputElement>) => {
-    const username: string = event.target.value;
-    setUsernameValue(username);
+    const usernameValue: string = event.target.value;
+    setUsername(usernameValue);
 
-    if (username.length > 0 && !jamiUsernamePattern.test(username)) {
+    if (usernameValue.length > 0 && !jamiUsernamePattern.test(usernameValue)) {
       setUsernameStatus('invalid');
     } else {
       setUsernameStatus('default');
@@ -85,11 +123,11 @@
   };
 
   const handlePassword = (event: ChangeEvent<HTMLInputElement>) => {
-    const password: string = event.target.value;
-    setPasswordValue(password);
+    const passwordValue: string = event.target.value;
+    setPassword(passwordValue);
 
-    if (password.length > 0) {
-      const checkResult = checkPasswordStrength(password);
+    if (passwordValue.length > 0) {
+      const checkResult = checkPasswordStrength(passwordValue);
       setPasswordStatus(checkResult.valueCode);
     } else {
       setPasswordStatus('default');
@@ -107,12 +145,9 @@
 
     if (canCreate) {
       setIsCreatingUser(true);
-      // TODO: Replace with registration logic (https://git.jami.net/savoirfairelinux/jami-web/-/issues/75).
-      await new Promise((resolve) => setTimeout(resolve, 2000));
-      console.log('Account created');
-      setIsCreatingUser(false);
+      createAccount();
     } else {
-      if (usernameError || usernameValue.length === 0) {
+      if (usernameError || username.length === 0) {
         setUsernameStatus('registration_failed');
       }
       if (!passwordSuccess) {
@@ -127,6 +162,18 @@
     <>
       <ProcessingRequest open={isCreatingUser} />
 
+      <AlertSnackbar
+        severity={'success'}
+        open={!!successAlertContent}
+        onClose={() => setSuccessAlertContent(undefined)}
+      >
+        {successAlertContent}
+      </AlertSnackbar>
+
+      <AlertSnackbar severity={'error'} open={!!errorAlertContent} onClose={() => setErrorAlertContent(undefined)}>
+        {errorAlertContent}
+      </AlertSnackbar>
+
       <Stack
         sx={{
           minHeight: `${isMobile ? 'auto' : '100%'}`,
@@ -144,7 +191,7 @@
         <Form method="post" id="register-form">
           <div>
             <UsernameInput
-              value={usernameValue}
+              value={username}
               onChange={handleUsername}
               error={usernameError}
               success={usernameSuccess}
@@ -155,7 +202,7 @@
           </div>
           <div>
             <PasswordInput
-              value={passwordValue}
+              value={password}
               onChange={handlePassword}
               error={passwordError}
               success={passwordSuccess}