mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-12 01:46:08 +00:00
feat(invitations): guide invited users without an account through sign-up
Clicking an invitation link while signed out dropped the visitor on a bare login form, even though most invitees have no account yet and nothing on screen told them to create one. Signed-out visitors now get the invitation itself: who invited them, which workspace/project, which role, and which address it was sent to. The primary call to action follows whether an account already exists for that address — "Create your account" when it does not, "Sign in to accept" when it does. The sign-up path carries the invitation forward, so a new account lands back on the invitation and from there on the shared workspace/project instead of the onboarding wizard: - the register link passes invitationToken, the invited email and a callbackUrl - the register form locks the email to the invited address and shows what is being joined - the verification email round-trips the destination through a sanitized `next` parameter - login and verify-email keep the pending destination in their sign-in links Signing in with a different address than the one invited now explains the mismatch instead of silently redirecting to the dashboard. Callback sanitization moves to lib/safe-redirect.ts so login, register, verify-email and the verification route share one open-redirect guard.
This commit is contained in:
@@ -94,7 +94,11 @@ function createTransport() {
|
||||
return nodemailer.createTransport({ host, port, secure: port === 465, auth: { user, pass } });
|
||||
}
|
||||
|
||||
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
|
||||
export async function sendVerificationEmail(
|
||||
email: string,
|
||||
token: string,
|
||||
options?: { next?: string }
|
||||
): Promise<void> {
|
||||
const transporter = createTransport();
|
||||
if (!transporter) return;
|
||||
|
||||
@@ -109,7 +113,10 @@ export async function sendVerificationEmail(email: string, token: string): Promi
|
||||
return;
|
||||
}
|
||||
|
||||
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`;
|
||||
// `next` survives the round-trip so an invited user lands back on the invitation
|
||||
// (and from there on the shared project) instead of a generic login page.
|
||||
const nextParam = options?.next ? `&next=${encodeURIComponent(options.next)}` : '';
|
||||
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}${nextParam}`;
|
||||
const from = process.env.SMTP_FROM || process.env.EMAIL_FROM || 'OpenFrame <[email protected]>';
|
||||
|
||||
const html = brandedEmailTemplate(
|
||||
|
||||
Reference in New Issue
Block a user