|
| 1 | +import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' |
| 2 | + |
| 3 | +import { db } from 'src/lib/db' |
| 4 | + |
| 5 | +export const handler = async ( |
| 6 | + event, |
| 7 | + context |
| 8 | +) => { |
| 9 | + const forgotPasswordOptions = { |
| 10 | + // handler() is invoked after verifying that a user was found with the given |
| 11 | + // username. This is where you can send the user an email with a link to |
| 12 | + // reset their password. With the default dbAuth routes and field names, the |
| 13 | + // URL to reset the password will be: |
| 14 | + // |
| 15 | + // https://example.com/reset-password?resetToken=${user.resetToken} |
| 16 | + // |
| 17 | + // Whatever is returned from this function will be returned from |
| 18 | + // the `forgotPassword()` function that is destructured from `useAuth()` |
| 19 | + // You could use this return value to, for example, show the email |
| 20 | + // address in a toast message so the user will know it worked and where |
| 21 | + // to look for the email. |
| 22 | + handler: (user) => { |
| 23 | + return user |
| 24 | + }, |
| 25 | + |
| 26 | + // How long the resetToken is valid for, in seconds (default is 24 hours) |
| 27 | + expires: 60 * 60 * 24, |
| 28 | + |
| 29 | + errors: { |
| 30 | + // for security reasons you may want to be vague here rather than expose |
| 31 | + // the fact that the email address wasn't found (prevents fishing for |
| 32 | + // valid email addresses) |
| 33 | + usernameNotFound: 'Username not found', |
| 34 | + // if the user somehow gets around client validation |
| 35 | + usernameRequired: 'Username is required', |
| 36 | + }, |
| 37 | + } |
| 38 | + |
| 39 | + const loginOptions = { |
| 40 | + // handler() is called after finding the user that matches the |
| 41 | + // username/password provided at login, but before actually considering them |
| 42 | + // logged in. The `user` argument will be the user in the database that |
| 43 | + // matched the username/password. |
| 44 | + // |
| 45 | + // If you want to allow this user to log in simply return the user. |
| 46 | + // |
| 47 | + // If you want to prevent someone logging in for another reason (maybe they |
| 48 | + // didn't validate their email yet), throw an error and it will be returned |
| 49 | + // by the `logIn()` function from `useAuth()` in the form of: |
| 50 | + // `{ message: 'Error message' }` |
| 51 | + handler: (user) => { |
| 52 | + return user |
| 53 | + }, |
| 54 | + |
| 55 | + errors: { |
| 56 | + usernameOrPasswordMissing: 'Both username and password are required', |
| 57 | + usernameNotFound: 'Username ${username} not found', |
| 58 | + // For security reasons you may want to make this the same as the |
| 59 | + // usernameNotFound error so that a malicious user can't use the error |
| 60 | + // to narrow down if it's the username or password that's incorrect |
| 61 | + incorrectPassword: 'Incorrect password for ${username}', |
| 62 | + }, |
| 63 | + |
| 64 | + // How long a user will remain logged in, in seconds |
| 65 | + expires: 60 * 60 * 24 * 365 * 10, |
| 66 | + } |
| 67 | + |
| 68 | + const resetPasswordOptions = { |
| 69 | + // handler() is invoked after the password has been successfully updated in |
| 70 | + // the database. Returning anything truthy will automatically log the user |
| 71 | + // in. Return `false` otherwise, and in the Reset Password page redirect the |
| 72 | + // user to the login page. |
| 73 | + handler: (_user) => { |
| 74 | + return true |
| 75 | + }, |
| 76 | + |
| 77 | + // If `false` then the new password MUST be different from the current one |
| 78 | + allowReusedPassword: true, |
| 79 | + |
| 80 | + errors: { |
| 81 | + // the resetToken is valid, but expired |
| 82 | + resetTokenExpired: 'resetToken is expired', |
| 83 | + // no user was found with the given resetToken |
| 84 | + resetTokenInvalid: 'resetToken is invalid', |
| 85 | + // the resetToken was not present in the URL |
| 86 | + resetTokenRequired: 'resetToken is required', |
| 87 | + // new password is the same as the old password (apparently they did not forget it) |
| 88 | + reusedPassword: 'Must choose a new password', |
| 89 | + }, |
| 90 | + } |
| 91 | + |
| 92 | + const signupOptions = { |
| 93 | + // Whatever you want to happen to your data on new user signup. Redwood will |
| 94 | + // check for duplicate usernames before calling this handler. At a minimum |
| 95 | + // you need to save the `username`, `hashedPassword` and `salt` to your |
| 96 | + // user table. `userAttributes` contains any additional object members that |
| 97 | + // were included in the object given to the `signUp()` function you got |
| 98 | + // from `useAuth()`. |
| 99 | + // |
| 100 | + // If you want the user to be immediately logged in, return the user that |
| 101 | + // was created. |
| 102 | + // |
| 103 | + // If this handler throws an error, it will be returned by the `signUp()` |
| 104 | + // function in the form of: `{ error: 'Error message' }`. |
| 105 | + // |
| 106 | + // If this returns anything else, it will be returned by the |
| 107 | + // `signUp()` function in the form of: `{ message: 'String here' }`. |
| 108 | + handler: ({ username, hashedPassword, salt, userAttributes }) => { |
| 109 | + return db.user.create({ |
| 110 | + data: { |
| 111 | + email: username, |
| 112 | + hashedPassword: hashedPassword, |
| 113 | + salt: salt, |
| 114 | + fullName: userAttributes['full-name'], |
| 115 | + }, |
| 116 | + }) |
| 117 | + }, |
| 118 | + |
| 119 | + // Include any format checks for password here. Return `true` if the |
| 120 | + // password is valid, otherwise throw a `PasswordValidationError`. |
| 121 | + // Import the error along with `DbAuthHandler` from `@redwoodjs/api` above. |
| 122 | + passwordValidation: (_password) => { |
| 123 | + return true |
| 124 | + }, |
| 125 | + |
| 126 | + errors: { |
| 127 | + // `field` will be either "username" or "password" |
| 128 | + fieldMissing: '${field} is required', |
| 129 | + usernameTaken: 'Username `${username}` already in use', |
| 130 | + }, |
| 131 | + } |
| 132 | + |
| 133 | + const authHandler = new DbAuthHandler(event, context, { |
| 134 | + // Provide prisma db client |
| 135 | + db: db, |
| 136 | + |
| 137 | + // The name of the property you'd call on `db` to access your user table. |
| 138 | + // i.e. if your Prisma model is named `User` this value would be `user`, as in `db.user` |
| 139 | + authModelAccessor: 'user', |
| 140 | + |
| 141 | + // A map of what dbAuth calls a field to what your database calls it. |
| 142 | + // `id` is whatever column you use to uniquely identify a user (probably |
| 143 | + // something like `id` or `userId` or even `email`) |
| 144 | + authFields: { |
| 145 | + id: 'id', |
| 146 | + username: 'email', |
| 147 | + hashedPassword: 'hashedPassword', |
| 148 | + salt: 'salt', |
| 149 | + resetToken: 'resetToken', |
| 150 | + resetTokenExpiresAt: 'resetTokenExpiresAt', |
| 151 | + }, |
| 152 | + |
| 153 | + // Specifies attributes on the cookie that dbAuth sets in order to remember |
| 154 | + // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies |
| 155 | + cookie: { |
| 156 | + HttpOnly: true, |
| 157 | + Path: '/', |
| 158 | + SameSite: 'Strict', |
| 159 | + Secure: process.env.NODE_ENV !== 'development', |
| 160 | + |
| 161 | + // If you need to allow other domains (besides the api side) access to |
| 162 | + // the dbAuth session cookie: |
| 163 | + // Domain: 'example.com', |
| 164 | + }, |
| 165 | + |
| 166 | + forgotPassword: forgotPasswordOptions, |
| 167 | + login: loginOptions, |
| 168 | + resetPassword: resetPasswordOptions, |
| 169 | + signup: signupOptions, |
| 170 | + }) |
| 171 | + |
| 172 | + return await authHandler.invoke() |
| 173 | +} |
0 commit comments