From 8ad07c49d576cb30c46f3faa0bb0a83ffd2aebea Mon Sep 17 00:00:00 2001
From: Matthias Mair <code@mjmair.com>
Date: Thu, 9 Jan 2025 00:52:14 +0100
Subject: [PATCH] re-implement registrations

---
 .../components/forms/AuthenticationForm.tsx   | 47 ++++++++++++++-----
 src/frontend/src/enums/ApiEndpoints.tsx       |  2 +-
 src/frontend/src/functions/auth.tsx           | 15 +++---
 3 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx
index 0335b76943..e8e432c50f 100644
--- a/src/frontend/src/components/forms/AuthenticationForm.tsx
+++ b/src/frontend/src/components/forms/AuthenticationForm.tsx
@@ -21,6 +21,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 import {
   doBasicLogin,
   doSimpleLogin,
+  ensureCsrf,
   followRedirect
 } from '../../functions/auth';
 import { showLoginNotification } from '../../functions/notifications';
@@ -196,7 +197,12 @@ export function AuthenticationForm() {
 
 export function RegistrationForm() {
   const registrationForm = useForm({
-    initialValues: { username: '', email: '', password1: '', password2: '' }
+    initialValues: {
+      username: '',
+      email: '',
+      password: '',
+      password2: '' as string | undefined
+    }
   });
   const navigate = useNavigate();
   const [auth_settings, registration_enabled, sso_registration] =
@@ -207,14 +213,26 @@ export function RegistrationForm() {
     ]);
   const [isRegistering, setIsRegistering] = useState<boolean>(false);
 
-  function handleRegistration() {
+  async function handleRegistration() {
+    // check if passwords match
+    if (
+      registrationForm.values.password !== registrationForm.values.password2
+    ) {
+      registrationForm.setFieldError('password2', t`Passwords do not match`);
+      return;
+    }
     setIsRegistering(true);
+
+    // remove password2 from the request
+    const { password2, ...vals } = registrationForm.values;
+    await ensureCsrf();
+
     api
-      .post(apiUrl(ApiEndpoints.user_register), registrationForm.values, {
+      .post(apiUrl(ApiEndpoints.user_register), vals, {
         headers: { Authorization: '' }
       })
       .then((ret) => {
-        if (ret?.status === 204 || ret?.status === 201) {
+        if (ret?.status === 200) {
           setIsRegistering(false);
           showLoginNotification({
             title: t`Registration successful`,
@@ -226,16 +244,23 @@ export function RegistrationForm() {
       .catch((err) => {
         if (err.response?.status === 400) {
           setIsRegistering(false);
-          for (const [key, value] of Object.entries(err.response.data)) {
-            registrationForm.setFieldError(key, value as string);
+
+          // collect all errors per field
+          const errors: { [key: string]: string[] } = {};
+          for (const val of err.response.data.errors) {
+            if (!errors[val.param]) {
+              errors[val.param] = [];
+            }
+            errors[val.param].push(val.message);
           }
-          let err_msg = '';
-          if (err.response?.data?.non_field_errors) {
-            err_msg = err.response.data.non_field_errors;
+
+          for (const key in errors) {
+            registrationForm.setFieldError(key, errors[key]);
           }
+
           showLoginNotification({
             title: t`Input error`,
-            message: t`Check your input and try again. ` + err_msg,
+            message: t`Check your input and try again. `,
             success: false
           });
         }
@@ -268,7 +293,7 @@ export function RegistrationForm() {
               label={t`Password`}
               aria-label='register-password'
               placeholder={t`Your password`}
-              {...registrationForm.getInputProps('password1')}
+              {...registrationForm.getInputProps('password')}
             />
             <PasswordInput
               required
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index c25e8d28db..1cedc5bde2 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -23,7 +23,7 @@ export enum ApiEndpoints {
   user_login = 'auth/v1/auth/login',
   user_login_mfa = 'auth/v1/auth/2fa/authenticate',
   user_logout = 'auth/v1/auth/session',
-  user_register = 'auth/registration/', // TODO change
+  user_register = 'auth/v1/auth/signup',
   user_mfa = 'auth/v1/account/authenticators',
   user_emails = 'auth/v1/account/email',
   login_provider_redirect = 'auth/v1/auth/provider/redirect',
diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx
index 09bdf49f60..56c24abf34 100644
--- a/src/frontend/src/functions/auth.tsx
+++ b/src/frontend/src/functions/auth.tsx
@@ -72,16 +72,10 @@ export const doBasicLogin = async (
   }
 
   clearCsrfCookie();
-  const cookie = getCsrfCookie();
+  await ensureCsrf();
 
   const login_url = apiUrl(ApiEndpoints.user_login);
 
-  if (cookie == undefined) {
-    await api.get(apiUrl(ApiEndpoints.user_token)).catch(() => {
-      // his is to be expected
-    });
-  }
-
   let loginDone = false;
   let success = false;
 
@@ -169,6 +163,13 @@ export const doSimpleLogin = async (email: string) => {
   return mail;
 };
 
+export async function ensureCsrf() {
+  const cookie = getCsrfCookie();
+  if (cookie == undefined) {
+    await api.get(apiUrl(ApiEndpoints.user_token)).catch(() => {});
+  }
+}
+
 export function handleReset(
   navigate: NavigateFunction,
   values: { email: string }