New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add public registration #22125
base: main
Are you sure you want to change the base?
Add public registration #22125
Conversation
🦋 Changeset detectedLatest commit: d69c13d The changes in this PR will be included in the next version bump. This PR includes changesets to release 21 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
email, | ||
password, | ||
role: publicRegistrationRole, | ||
status: hasEmailValidation ? 'draft' : 'active', // TODO: Do we want to have a dedicated "unverified" status? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about renaming the invited
-status to a more generic pending
-status so that we can use the same status for invited and registered-but-not-yet-verified users but simply using the migration file to rename the status could lead to breaking existing logic in users projects for example somebody might have Flows that expect the users to be invited
Another approach could be to just replace the Invited
UI label to Pending
and keep the underlying invited
string the same to stay backwards compatible.
Mhh. Could also just add a new status to be both precise and backwards compatible. Like so:
If we add the new status we can give it a more concrete name too like Unverified
for example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have to take a closer look, but I think a dedicated (hidden) field for the email verification status could make sense. This could also be used, for example, if the user is already active and changes the email address, which would have to be verified again (but the user status should stay "active").
app/src/routes/register/register.vue
Outdated
|
||
<register-form v-else :provider="provider" @was-successful="wasSuccessful = $event" /> | ||
|
||
<sso-links v-if="!authenticated && wasSuccessful == false" :providers="auth.providers" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, do we want to show sso links on the register page? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, as those fall under the action of "login", right? So a better fit for the login page
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as those fall under the action of "login", right
Not necessarily if you have public registration enabled for SSO it doubles as both login and registration 🤔
I wouldnt be against having the SSO buttons on the registration page if configured as such but feels like someone with more UX insight may have more opinions there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, removing them now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily if you have public registration enabled for SSO it doubles as both login and registration 🤔
Yeah, but you actually login to the provider with an existing account there (at least in most cases)
api/src/controllers/users.ts
Outdated
const registerSchema = Joi.object<{ email: string; password: string }>({ | ||
email: Joi.string().email().required(), | ||
password: Joi.string().required(), | ||
}); | ||
|
||
router.post( | ||
'/register', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice if we would add a way to specify more user fields because I can see people using this service to build their own login pages with more fields 🤔
Allowing other user fields would need to be sanitized though we cant let them override role for example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean like require a phone number or similar? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes for example a number or even simpler first & last name. Avatar could also be useful, stuff like that. It'd be nice to also allow populating user-provided fields for when users have manually extended the directus_users
table so it'd have to be dynamic but I'm a little hesitant there because of security concerns. Mhh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting ❤️ Well, actually it would "just" be another (public/shared) form where we could use existing validation logic, etc. 🤔 Of course would need some proxying of the corresponding fields.
@@ -5,6 +5,7 @@ export type ServerInfoOutput = { | |||
project: { | |||
project_name: string; | |||
default_language: string; | |||
is_public_registration_enabled: boolean; // TODO Get opinion from @Brainslug |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be sufficient IMO, the type already indicates that it's a toggle state:
is_public_registration_enabled: boolean; // TODO Get opinion from @Brainslug | |
public_registration: boolean; |
E.g. comparable to bool states in Collection metadata:
directus/packages/system-data/src/types.ts
Lines 26 to 33 in d10385d
hidden: boolean; | |
singleton: boolean; | |
icon: string | null; | |
color: string | null; | |
translations: CollectionTranslations[] | null; | |
display_template: string | null; | |
preview_url: string | null; | |
versioning: boolean; |
email, | ||
password, | ||
role: publicRegistrationRole, | ||
status: hasEmailValidation ? 'draft' : 'active', // TODO: Do we want to have a dedicated "unverified" status? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have to take a closer look, but I think a dedicated (hidden) field for the email verification status could make sense. This could also be used, for example, if the user is already active and changes the email address, which would have to be verified again (but the user status should stay "active").
api/src/controllers/users.ts
Outdated
const registerSchema = Joi.object<{ email: string; password: string }>({ | ||
email: Joi.string().email().required(), | ||
password: Joi.string().required(), | ||
}); | ||
|
||
router.post( | ||
'/register', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean like require a phone number or similar? 🤔
public_registration: Public registration | ||
is_public_registration_enabled: Public registration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should probably both be title case
public_registration: Public registration | |
is_public_registration_enabled: Public registration | |
public_registration: Public Registration | |
is_public_registration_enabled: Public Registration |
is_public_registration_enabled: Public registration | ||
public_registration_role: Role | ||
public_registration_email_filter: Email Filter | ||
is_public_registration_email_validation_enabled: Send verification email |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to public_registration_send_verification_email
? Also, title case.
public_registration: Public registration | ||
is_public_registration_enabled: Public registration | ||
public_registration_role: Role | ||
public_registration_email_filter: Email Filter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably make it a bit clearer that it is a filter for the email address and does not have anything to do with the "Send verification email".
public_registration_email_filter: Email Filter | |
public_registration_email_filter: Email Address Filter |
display_options: | ||
template: '{{ name }}' | ||
|
||
- field: is_public_registration_email_validation_enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd swap the "Send Verification Email" and "Email Filter" field order, since the email filter is related more closely to the user, vs the verification email toggle.
Please note that before you can sign in, you may need to verify your email address by clicking on the verification link sent to you. | ||
dont_have_an_account: Don't have an account? | ||
already_have_an_account: Already have an account? | ||
sign_up_now: Sign up now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Title Case :)
password.value === null || | ||
passwordsMatch.value === false | ||
) { | ||
error.value = 'INVALID_PAYLOAD'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use ErrorCode.InvalidPayload
here?
await this.createOne(partialUser); | ||
} catch (error: unknown) { | ||
// To avoid giving attackers infos about registered emails we dont fail for violated unique constraints | ||
if (isDirectusError(error) && error.code === 'RECORD_NOT_UNIQUE') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be simplified to isDirectusError(error, ErrorCode.RecordNotUnique)
if (hasEmailValidation) { | ||
const mailService = new MailService(serviceOptions); | ||
const payload = { email: input.email, scope: 'pending-registration' }; | ||
const token = jwt.sign(payload, env['SECRET'] as string, { expiresIn: '7d', issuer: 'directus' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the expiresIn
here and in the user invite be moved to a common constant?
Scope
What's changed:
Screencast.from.2024-04-08.20-49-44.webm
Screencast.from.2024-04-08.20-57-14.webm
Potential Risks / Drawbacks
Review Notes / Questions
Fixes #21981