diff --git a/docs/api-refs/auth-and-onboarding.mdx b/docs/api-refs/auth-and-onboarding.mdx index 544bec42..77846733 100644 --- a/docs/api-refs/auth-and-onboarding.mdx +++ b/docs/api-refs/auth-and-onboarding.mdx @@ -181,9 +181,8 @@ curl --location "$BASE_URL/merchant/members/invite" \ { "email": "teammate@example.com", "is_new_user": true, - "password": "TempPass#8821", "role": "member" } ``` -`password` is only present when the invite creates a brand-new user account — share it out of band so they can log in and change it. Inviting an email that's already a user omits `password` and just adds the membership. +No credentials are ever returned or emailed. When the invite creates a brand-new user (`is_new_user: true`), the invitee receives an email with a single-use **set-password link** (valid for 7 days) that opens the dashboard's reset-password page; the account is unusable until they set a password. If the invite email cannot be delivered, the request fails with `500` and nothing is created — just retry. Because the emailed link is the new account's only credential, inviting new users in a release deployment requires an active email client (`[email] active_email_client = "smtp"` or `"aws_ses"`); with `no_email_client` the request fails rather than creating an unreachable account. Inviting an email that's already a user simply adds the membership and sends a notification email. diff --git a/src/email/no_email.rs b/src/email/no_email.rs index 74d3adea..5f2afbd3 100644 --- a/src/email/no_email.rs +++ b/src/email/no_email.rs @@ -6,13 +6,18 @@ pub struct NoEmailClient; impl EmailClient for NoEmailClient { async fn send_email(&self, message: EmailMessage) -> error_stack::Result<(), EmailError> { // Extract the action URL from the body so developers can complete email - // verification or password reset manually. Avoid logging the full HTML body - // for other email types (e.g. invite emails) because it would expose - // temporary passwords. Reset URLs carry a live single-use credential, so they - // are only logged in debug builds — never from a release binary. + // verification, password reset, or an invite manually. Avoid logging the full + // HTML body for other email types. Reset and invite URLs carry a live single-use + // credential, so they are only logged in debug builds — never from a release + // binary. + // `starts_with` (not `contains`) for the release-logged verification branch: invite + // subjects embed an admin-chosen merchant name, so a substring match could let a + // crafted merchant name route a live set-password link into release logs. let subject_lower = message.subject.to_lowercase(); - let action_url = if subject_lower.contains("confirm your email") - || (cfg!(debug_assertions) && subject_lower.contains("reset your password")) + let action_url = if subject_lower.starts_with("confirm your email") + || (cfg!(debug_assertions) + && (subject_lower.contains("reset your password") + || subject_lower.contains("invited"))) { extract_href_from_cta(&message.html_body) } else { diff --git a/src/email/templates.rs b/src/email/templates.rs index 4f3b3b93..0afb98dd 100644 --- a/src/email/templates.rs +++ b/src/email/templates.rs @@ -299,19 +299,16 @@ impl PasswordResetTemplate { } } -pub struct InviteUserTemplate { +pub struct InviteSetPasswordTemplate { pub user_email: String, pub merchant_name: String, - pub temporary_password: String, - pub base_url: String, + pub set_password_url: String, } -impl InviteUserTemplate { +impl InviteSetPasswordTemplate { pub fn into_message(self) -> EmailMessage { let merchant = escape_html(&self.merchant_name); - let email = escape_html(&self.user_email); - let password = escape_html(&self.temporary_password); - let base_url = escape_html(&self.base_url); + let url = escape_html(&self.set_password_url); let html_body = format!( r#" @@ -322,7 +319,7 @@ impl InviteUserTemplate {
- +|
- {email} - |
-
|
- Temporary password -{password} - |
-
| - - Sign in to Decision Engine → + Set your password → |
+ Button not working? Copy and paste this link into your browser: +
++ {url} +
@@ -386,8 +377,8 @@ impl InviteUserTemplate {- For your security, please change your password after signing in. - If you weren’t expecting this invitation, contact your account administrator. + This link expires in 7 days and can be used only once. + If you weren’t expecting this invitation, you can safely ignore this email — the account cannot be used until a password is set.
Juspay Decision Engine · Automated security email — please do not reply. @@ -402,15 +393,13 @@ impl InviteUserTemplate {