wasp
wasp copied to clipboard
How best to add emails for use in Auth?
We currently lack the ability to do email verification and password reset for the EmailAndPassword
Auth method. This RFP-lite proposes a direction to solve this.
We could leverage Nodemailer for the underlying ability to send emails using different email providers (e.g., Sendgrid). The assumption when email is used is this would always be an external concern. Wasp would not attempt to spin up any local SMTP server when running locally, due to the complexity and the fact that most emails would not actually likely be delivered anyways due to spam policies.
We could also use nodemailer-express-handlebars to handle the email templating.
Users could enable email-related functionality in Wasp like so (using the Google AuthN PR updated syntax):
app todoApp {
title: "ToDo App",
auth: {
userEntity: User,
methods: {
emailAndPassword: {
emailValidation: {
emailTemplate: ValidateEmail,
linkExpiration: "10m" // some Simple Duration string
},
passwordReset: {
// just use all defaults, but can override any like above
}
}
},
onAuthFailedRedirectTo: "/login"
},
email: {
templatesDir: "@ext/email/templates",
configFn: import { config } from "@ext/email/config.js",
deliveryJob: Foo // optional, if they want to send via a job instead of async call
}
}
emailTemplate ValidateEmail {
from: "[email protected]",
subject: "Please validate your email",
textTemplateName: "validation.txt.handlebars", // assumed under `app.email.templatesDir`
htmlTempateName: "validation.html.handlebars"
}
export function config() {
return {
perform_deliveries: process.env.NODE_ENV === 'production', // optional, if false show in console instead
smtp_settings: { // required
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
...
}
}
}
<!-- Provided a context containing { user, magicLinkUrl } -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Please Validate Email</title>
</head>
<body>
<h2>Hello {{user.name}}!</h2>
<p>Please click <a href="{{{magicLinkUrl}}}">here</a> to validate your email. Thanks!</p>
</body>
</html>
Email Verification
Signup
When email verification is enabled and a user signs up, should we:
a) create a new user and mark them as email_verified=false
, or
b) not create a user yet, but once they click the magic link in their email they become a real user (or can sign up), or
c) some other more customizable approach?
Approach (a) feels like the most common, however, it always rubbed me slightly the wrong way. What if someone just starts spamming a site creating lots of accounts to be verified? There will be a lot of real (albeit unverified) users created. Should these be cleaned up periodically? Should we reset the password once they verify, so that someone doesn't accidentally click and the bad actor can then log in using the PW they used during signup (or maybe we force them to login to verify email)? Etc. However, it is also the most flexible, allowing the user potentially to control many aspects of what they can and cannot do until they are verified.
Flags
We will have an external model to handle the magic links for email verification and password reset, but how should we handle the email_verified
on the User
model flag? Should we:
a) require users to add that field to their User
model when they enable email verification (similar to what we do for email/password now), or
b) create a separate model that contains this?
The downside to a separate model is the dev will need a way still to get this info.
Password Reset
This one is pretty straightforward. We email a magic link to the user email, store that in a separate model, and when they click take them to a screen that allows password reset (within some time interval, say default to 10 min?).
Magic Link Prisma Model
model MagicLink {
id Int @id @default(autoincrement())
usage MagicLinkUsage
token String @default(uuid())
used Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
validUntil DateTime
createdAt DateTime @default(now()) /* the time we email them it */
updatedAt DateTime @updatedAt
}
enum MagicLinkUsage {
EMAIL_VALIDATION
PASSWORD_RESET
}
Open Questions
- How can we improve
emailTemplate
s?- Should we enable
emailTemplate
previews somehow?
- Should we enable
- Should we allow users to change the default signup/login behavior? For example, what if some devs want to require email to be verified before signup/login complete, but others want to allow login even if not verified? Or should those just have our default implementation and maybe they can override somehow? Or use callbacks at some key points instead?
- Should we allow for enabling CAPTCHAs or something when signing up?
Conversation recap: Do a bit more research on how other popular libs solve. Wait for sum types? In meantime can just print a warning in validation step. Figure out how this impacts social logins security.
Can we configure current model to username + password instead, leaving notion of "email" to require validation. Maybe we have to first reconsider what emailAndPassword
means for us, should it be usernameAndPassword
.
Can we separate authentication and verification? What if they want both?
What of we moved to usernameAndPassword
only, no need for this PR, and we just connect social in with an external auth field only used for social, and we autogenerate a username?
user: username | password
external auth: google | google_id | user_id external auth: facebook | facebook_id | user_id
This RFC may not be needed anytime soon if we move forward on this: https://github.com/wasp-lang/wasp/pull/696
Update: we renamed EmailAndPassword
to UsernameAndPassword
. This will give us time to add this new feature in, but not directly tied to auth itself. I will leave this open but we will likely not need to do anything here for a bit (and the issue itself may need updating after this rename).
https://github.com/wasp-lang/wasp/issues/870 -> opened this one, but turned out to be a duplicate.
Related to #891
Closed via https://github.com/wasp-lang/wasp/pull/1087