dbAuth - Fail to reset password (email)

I can’t seem to get dbAuth working. I get the following error when trying to reset password:

 Invalid `this.dbAccessor.update()` invocation in /workspace/trellis/node_modules/@redwoodjs/api/dist/functions/dbAuth/DbAuthHandler.js:188:36 185 const buffer = new Buffer(token); 
186 token = buffer.toString('base64').replace('=', '').substring(0, 16); 
// set token and expires time 187 → 188 user = await this.dbAccessor.update({ where: { id: 1 }, data: { resetToken: 'NTk1OTBmZWJhYjkx', ~~~~~~~~~~ resetTokenExpiresAt: new Date('2021-12-06T21:02:14.828Z') ~~~~~~~~~~~~~~~~~~~ } })
Unknown arg `resetToken` in data.resetToken for type UserUpdateInput. 
Did you mean `username`? 
Available args: type UserUpdateInput { username?: String | StringFieldUpdateOperationsInput 
email?: String | StringFieldUpdateOperationsInput name?: String | 
StringFieldUpdateOperationsInput mobile?: String | NullableStringFieldUpdateOperationsInput | Null 
landline?: String | NullableStringFieldUpdateOperationsInput | Null address?: String | 
StringFieldUpdateOperationsInput dob?: DateTime | DateTimeFieldUpdateOperationsInput | Null 
isAdmin?: Boolean | BoolFieldUpdateOperationsInput } Unknown arg `resetTokenExpiresAt` in 
data.resetTokenExpiresAt for type UserUpdateInput. Available args: type UserUpdateInput { 
username?: String | StringFieldUpdateOperationsInput email?: String | 
StringFieldUpdateOperationsInput name?: String | StringFieldUpdateOperationsInput mobile?: 
String | NullableStringFieldUpdateOperationsInput | Null landline?: String | 
NullableStringFieldUpdateOperationsInput | Null address?: String | 
StringFieldUpdateOperationsInput dob?: DateTime | DateTimeFieldUpdateOperationsInput | Null 
isAdmin?: Boolean | BoolFieldUpdateOperationsInput } 

And my prisma model for user is as follows:

model User {
  id             Int @id @default(autoincrement())
  username       String @unique
  email          String
  hashedPassword String @default("")
  salt           String @default("")
  resetToken     String?
  resetTokenExpiresAt DateTime?
  name           String
  mobile         String?
  landline       String?
  address        String
  dob            DateTime
  isAdmin        Boolean @default(false)
}

And my users.sdl.js file:

export const schema = gql`
  type User {
    id: Int!
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    resetToken: String
    resetTokenExpiresAt: DateTime
    name: String!
    mobile: String
    landline: String
    address: String!
    dob: DateTime!
    isAdmin: Boolean!
  }

  type Query {
    users: [User!]! @requireAuth
    user(id: Int!): User @requireAuth
  }

  input CreateUserInput {
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    resetToken: String
    resetTokenExpiresAt: DateTime
    name: String!
    mobile: String
    landline: String
    address: String!
    dob: DateTime!
    isAdmin: Boolean!
  }

  input UpdateUserInput {
    username: String
    email: String
    hashedPassword: String
    salt: String
    resetToken: String
    resetTokenExpiresAt: DateTime
    name: String
    mobile: String
    landline: String
    address: String
    dob: DateTime
    isAdmin: Boolean
  }

  type Mutation {
    createUser(input: CreateUserInput!): User! @requireAuth
    updateUser(id: Int!, input: UpdateUserInput!): User! @requireAuth
    deleteUser(id: Int!): User! @requireAuth
  }
`

The error message is quite confusing, I’m not sure what it means. Any help would be greatly appreciated!

Thank you :+1:

How is your authModelAccessor defined?

(at the bottom of api/src/functions/auth.[jt]s)

It almost looks like you’ve not migrated your additions to your Users table yet…

1 Like

Yes, I forgot to migrate the database :man_facepalming: . However, I ask to reset my password, and it says it has sent an email, but I don’t get one. Do I need to set up an email service?

Thanks again, Ed

@rob ^^^

1 Like

Yes you do — here is how I did it (logging and error checking removed to aid readability)

in api/src/functions/auth.ts

// https://www.npmjs.com/package/nodemailer
const nodemailer = require('nodemailer')
export const handler = async (event, context) => {
  const forgotPasswordOptions = {
    handler: async (user) => {
      try {
        let transporter = nodemailer.createTransport({
          host: process.env.SMTP_HOST,
          port: process.env.SMTP_PORT,
          secure: true,
          auth: {
            user: process.env.SMTP_USER,
            pass: process.env.SMTP_PASS,
          },
        })
        const resetLink = `${process.env.APP_URL}/reset-password?resetToken=${user.resetToken}`
        const message = {
          from: process.env.AUTH_EMAIL_FROM,
          to: user.email,
          subject: 'Reset Forgotten Password',
          html: `Here is a link reset your password.  It will expire after 4hrs. <a href="${resetLink}">Reset my Password</>`,
        }
        await transporter.sendMail(message)
      } catch (err) {
        logger.error(err)
      }

      return user
    },

2 Likes

Hello! So the “an email has been sent” is the generic message that starts with dbAuth and assumes that you will eventually fill out the Forgot Password logic to actually send an email (since that’s generally how it’s done). You can customize that message (maybe you want to send an SMS instead) or remove the Forgot Password flow completely if it doesn’t fit your needs.

If you look in api/src/functions/auth.js the comment above forgotPasswordOptions gives some background on when that handler() function is called and what it should do to provide the user a link to reset their password:

  const forgotPasswordOptions = {
    // handler() is invoked after verifying that a user was found with the given
    // username. This is where you can send the user an email with a link to
    // reset their password. With the default dbAuth routes and field names, the
    // URL to reset the password will be:
    //
    // https://example.com/reset-password?resetToken=${user.resetToken}
    //
    // Whatever is returned from this function will be returned from
    // the `forgotPassword()` function that is destructured from `useAuth()`
    // You could use this return value to, for example, show the email
    // address in a toast message so the user will know it worked and where
    // to look for the email.
    handler: (user) => {
      return user
    },

    // ...
  }

We’ll be updating the tutorial for v1.0 to use dbAuth and we’ll walk through filling out these handlers, including sending that Reset Password email!

1 Like

Thank you! Also thanks to @ajoslin103 for your suggestion! :+1:

1 Like