Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Implementing password reset for non-admin users

default discord avatar
lzyac2 years ago
10

The documentation states that it uses the admin password reset flow even for regular users, but I would like to create a frontend page instead. Could I get some guidance conceptually on how to do this securely?



So far I am thinking of just creating a separate password reset page that consumes the token provided as a path parameter. It then sends a post request to

/api/[collection-slug]/reset-password

with the token and password attached in the request body. Will this be sufficient? If so, how will I go about generating and validating the tokens?



Is there a way to create separate password reset pages depending on the type of the user?

  • default discord avatar
    abdullahasad72532 years ago
    @1175782070828662844
  • default discord avatar
    lzyac2 years ago

    what?

  • default discord avatar
    lzyac2 years ago

    i see, I will have a look later today



    thank you



    Hi, I've tried to implement it myself and I get a Payload backend error as expectedd



    but I want to be able to catch that error somehow and display on the frontend





    this is what I get on the frontend



    [21:21:44] ERROR (payload): APIError: Token is either invalid or has expired.

    This is the error i get in the terminal that runs the nextjs server



    I was testing with invalid token of

    123

    (some random value)



    and I do expect this error, b



    but how do i get my frontend to catch it?



    const ResetForm: React.FC<ResetFormProps> = ({ resetToken }) => {
      const resetPassword = useAuth().resetPassword(resetToken)
      const [error, setError] = useState('')
      const [success, setSuccess] = useState(false)
    
      // hook for reset form
      const {
        register,
        handleSubmit,
        formState: { errors },
        watch,
      } = useForm<FormData>()
      const password = watch('password', '')
      return (
        <>
          <div className={classes.formTitle}>
            <h3>Reset Password</h3>
          </div>
          {!success && (
            <>
              <p>Enter a new password for your account</p>
    
              <form onSubmit={handleSubmit(resetPassword)} className={classes.form}>
                <Message error={error} className={classes.message} />
                <Input
                  name="password"
                  type="password"
                  label="Password"
                  required
                  register={register}
                  error={errors.password}
                />
                <Input
                  name="passwordConfirm"
                  type="password"
                  label="Confirm Password"
                  required
                  register={register}
                  validate={value => value === password || 'The passwords do not match'}
                  error={errors.passwordConfirm}
                />
                <Button
                  type="submit"
                  appearance="primary"
                  label="Reset Forgotten Password"
                  className={classes.submit}
                />
              </form>
            </>
          )}
    
          {success && (
            <>
              <h1>Password Reset Complete</h1>
              <p>Your password has been successfully reset.</p>
            </>
          )}
        </>
      )
    }


    here is the reset password component



    the

    useAuth

    hook exposes a

    resetPassword

    useCallback method that I can use (this calls the reset endpoint on the backend)



      // handler for reset form
      const handler: React.FormEventHandler<HTMLFormElement> = async e => {
        try {
          await handleSubmit(resetPassword)(e)
          setSuccess(true)
          setError('')
        } catch (err: unknown) {
          e.preventDefault()
          // `resetPassword` only throws errors of type `Error`
          const error = err as Error
          setError(error.message)
          console.log(error)
        }
      }


    Or should I try using something like this?

  • default discord avatar
    livog2 years ago
    @473221703418511360

    You would have to return some sort of error that the frontend. You can always see how I did it here:


    https://github.com/Livog/Payload.3.0.Starter/blob/main/src/components/ResetPasswordForm/actions.tsx

    I call signIn:


    await signIn('credentials', { email: user.email, password, redirect: false })



    after successful password reset.



    I have never used the useAuth hook.



    But instead of running signIn in my function you would run something like:


    const loginResult = await payload.login({
          collection: COLLECTION_SLUG_USER,
          data: {
            email: userEmailhere
            password
          }
        })
        cookies.set('payload-token', loginResult.token)


    and yes you should be trying to use something like that 🙂

  • default discord avatar
    lzyac2 years ago

    ah you're using the more modern approach



    i haven't learnt that way yet



    server actions and stuff

  • default discord avatar
    livog2 years ago

    You also got this if you want to use the rest version:


    const response = await fetch(`${process.env.AUTH_URL!}/api/${COLLECTION_SLUG_USER}/login`, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                  email: credentials?.email,
                  password: credentials?.password
                })
              })
              const data = await response.json()
  • default discord avatar
    lzyac2 years ago

    Yes I am using the rest version



    export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
      // other useCallback methods implemented above
    
        const resetPassword = (token: string) => useCallback<ResetPassword>(async args => {
        try {
          const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/reset-password`, {
            method: 'POST',
            credentials: 'include',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              password: args.password,
              token,
            }),
          })
    
          const { data, errors } = await res.json()
          if (errors) throw new Error(errors[0].message)
    
          if (res.ok) {
            if (errors) throw new Error(errors[0].message)
            setUser(data?.loginUser?.user)
            setStatus(data?.loginUser?.user ? 'loggedIn' : undefined)
          } else {
            throw new Error('Invalid login')
          }
        } catch (e) {
          console.log(e)
          throw new Error('An error occurred while attempting to login.')
        }
      }, [])
    
      return (
        <Context.Provider
          value={{
            user,
            setUser,
            login,
            logout,
            create,
            resetPassword,
            forgotPassword,
          }}
        >
          {children}
        </Context.Provider>
      )
    }
    
    export const useAuth: UseAuth = () => useContext(Context)


    This is the useAuth hook I'm talking about

  • default discord avatar
    livog2 years ago

    I see, yeah so then you will need to manage the errors yourself and catch it 🙂

  • default discord avatar
    lzyac2 years ago

    yep thank you for your help

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.