Repository: Couchers-org/couchers (language: python)

Issue:
"Remember me" doesn't work (or too short?)
It seems like "Remember me" doesn't really work as expected. Even if I use it, next time I see login form again. Not sure, maybe there is some expiration time for cookies which is too short (I login to Couchers somewhere between weekly-monthly)

Please check it and make sure it actually works and remembers the user for some meaningful amount of time (I'd suggest one year)
Show both login and password at login form
It's very-very annoying when I cannot use my auto-fill in browser because "password" field appears only after one click to "Continue". Thus it causes few extra clicks every time.

![image](https://user-images.githubusercontent.com/918446/190371069-070d5c28-590c-44d4-82fc-8a0b94345ffd.png)


Please show both textboxes at the same time. If there is any kind of SSO support, then better *hide* password textbox when one enters SSO-related email.

In combination with non-working "Remember me" (#210) it creates a lot of inconvenience


Discussion / hints:
It doesn't look like the frontend is forwarding the value of the toggle to the API call. Fixing that should address this
interesting... in my browser it autocompletes automatically the password box, (like with outlook web, does the same aswell) which browser are you using? also, phone or desktop? :)
@bakeiro Desktop Chrome. It auto-completes after the second click to "Continue", but that's the extra click which is absolutely inconvenient. I really expect the form to work in the following way:
1. Open the page
2. Click on "email textbox"
3. Click on the login from the suggested auto-completion list
4. Click "Login"
5. Enjoy the website for logged in users

Now I need to click "Continue" before step 4, which is extra click.


The reason why it has a two step process is that we used to not have passwords. There are still some users who haven't set a password, and there is different behavior for them.

We're phasing that out however, and probably we could soon switch to something else, like having those users who've yet to set a password just have to reset it using a reset password page or similar.

https://github.com/Couchers-org/couchers/blob/c6469b681aca7c355533fab9586a0b659783568e/app/backend/src/couchers/servicers/auth.py#L348
Notes from duplicate #4116 :

> On top of this, there are a number of landing page improvements that should be addressed:
> 
>     The design for the guest landing page (aka sign up) vs. member landing page (aka log in) is different.
> 
>     Guest landing page says: The new alternative to couchsurfing. Member landing page says: Join and help us grow the new alternative to couchsurfing. The text should be the exact same (i.e. wording and position).
> 
>     The onboarding frame should remain the same size and structure between steps. (This will be mitigated anyways if email and password are integrated into one frame).
> 
>     Remove or change the red Report a bug button. Alternatively, it can be designed as a link in RED.
> 
>     It should not be a primary button. The only primary button should be the one inside the onboarding frame.
> 
>     Remove the sign-up button. For new users, the signup form is already accessible right away. This can create some confusion.

Fix patch:
diff --git a/app/web/features/auth/login/LoginForm.tsx b/app/web/features/auth/login/LoginForm.tsx
index 23b5fea3aa..9687c68d0a 100644
--- a/app/web/features/auth/login/LoginForm.tsx
+++ b/app/web/features/auth/login/LoginForm.tsx
@@ -10,7 +10,7 @@ import { useTranslation } from "i18n";
 import { AUTH, GLOBAL } from "i18n/namespaces";
 import { LoginRes } from "proto/auth_pb";
 import { useState } from "react";
-import { useForm } from "react-hook-form";
+import { Controller, useForm } from "react-hook-form";
 import { resetPasswordRoute } from "routes";
 import { service } from "service";
 import isGrpcError from "utils/isGrpcError";
@@ -46,10 +46,15 @@ export default function LoginForm() {
   const [loading, setLoading] = useState(false);
   const [loginWithLink, setLoginWithLink] = useState(true);
 
-  const { handleSubmit, register } = useForm<{ username: string }>();
+  const { handleSubmit, register, control } =
+    useForm<{ username: string; rememberDevice: boolean }>();
 
   const onSubmit = handleSubmit(
-    async (data: { username: string; password: string }) => {
+    async (data: {
+      username: string;
+      password: string;
+      rememberDevice: boolean;
+    }) => {
       setLoading(true);
       authActions.clearError();
       try {
@@ -67,8 +72,9 @@ export default function LoginForm() {
 
         if (!loginWithLink) {
           authActions.passwordLogin({
-            password: data.password,
             username: sanitizedUsername,
+            password: data.password,
+            rememberDevice: data.rememberDevice,
           });
         }
       } catch (e) {
@@ -127,10 +133,23 @@ export default function LoginForm() {
           </>
         )}
         <div className={classes.loginOptions}>
-          <FormControlLabel
-            className={classes.rememberSwitch}
-            control={<Switch size="small" />}
-            label={t("auth:login_page.form.remember_me")}
+          <Controller
+            control={control}
+            name="rememberDevice"
+            defaultValue={true}
+            render={({ onChange, value }) => (
+              <FormControlLabel
+                className={classes.rememberSwitch}
+                control={
+                  <Switch
+                    size="small"
+                    checked={value}
+                    onChange={(e, checked) => onChange(checked)}
+                  />
+                }
+                label={t("auth:login_page.form.remember_me")}
+              />
+            )}
           />
           {!loginWithLink && (
             <StyledLink href={resetPasswordRoute}>
diff --git a/app/web/features/auth/useAuthStore.ts b/app/web/features/auth/useAuthStore.ts
index 41e5fcbb16..844da06aa1 100644
--- a/app/web/features/auth/useAuthStore.ts
+++ b/app/web/features/auth/useAuthStore.ts
@@ -91,14 +91,20 @@ export default function useAuthStore() {
       async passwordLogin({
         username,
         password,
+        rememberDevice,
       }: {
         username: string;
         password: string;
+        rememberDevice: boolean;
       }) {
         setError(null);
         setLoading(true);
         try {
-          const auth = await service.user.passwordLogin(username, password);
+          const auth = await service.user.passwordLogin(
+            username,
+            password,
+            rememberDevice
+          );
           setUserId(auth.userId);
           Sentry.setUser({ id: auth.userId.toString() });
 
diff --git a/app/web/service/user.ts b/app/web/service/user.ts
index 369055059c..b24e72cb43 100644
--- a/app/web/service/user.ts
+++ b/app/web/service/user.ts
@@ -54,10 +54,15 @@ export type HostingPreferenceData = Omit<
 /**
  * Login user using password
  */
-export async function passwordLogin(username: string, password: string) {
+export async function passwordLogin(
+  username: string,
+  password: string,
+  rememberDevice: boolean
+) {
   const req = new AuthReq();
   req.setUser(username);
   req.setPassword(password);
+  req.setRememberDevice(rememberDevice);
 
   const res = await client.auth.authenticate(req);
   return res.toObject();


Test patch:
diff --git a/app/web/features/auth/useAuthStore.test.ts b/app/web/features/auth/useAuthStore.test.ts
index 0cc6967985..14984d1aea 100644
--- a/app/web/features/auth/useAuthStore.test.ts
+++ b/app/web/features/auth/useAuthStore.test.ts
@@ -105,6 +105,7 @@ describe("passwordLogin action", () => {
       result.current.authActions.passwordLogin({
         password: "pass",
         username: "user",
+        rememberDevice: true,
       })
     );
     expect(result.current.authState.authenticated).toBe(true);
@@ -120,6 +121,7 @@ describe("passwordLogin action", () => {
       result.current.authActions.passwordLogin({
         password: "pass",
         username: "user",
+        rememberDevice: true,
       })
     );
     expect(result.current.authState.authenticated).toBe(false);