mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-23 21:02:24 +02:00
* feat(password): add email-confirmed password setup for SSO accounts SSO-only accounts (Google, GitHub, OIDC) now have a secure path to add a password credential without requiring direct session trust. **Problem:** Previously, password setup for SSO users either required an existing password (blocking SSO-only users entirely) or would have needed to trust the session token alone to create a persistent credential — a security gap where a compromised session could silently add a password. **Solution:** Email-confirmed flow that reuses the existing recovery infrastructure: 1. `checkHasPassword` RPC — authenticates via session token, returns whether the account has a password hash set (drives UI branching). 2. `requestPasswordSetup` RPC — authenticates via session token, looks up the account's verified email social ID, generates a recovery token (`restoreEmail` claim), and sends a "Password recovery" email via the existing mail service. No DB schema changes. 3. `PasswordRestore.svelte` (unchanged) handles the link click → calls the existing `restorePassword` RPC → password is set. **UI changes** (`Password.svelte`): - `hasPassword === false` → "Set a password" panel with description and "Send setup link" button - On success → "Check your email for a link to set your password." - On `SocialIdNotFound` → "No email address is linked to your account." with guidance to add one via Account Settings → Manage Identities - `hasPassword === true` → existing "Change password" form (unchanged) **Account client:** Added `checkHasPassword()` and `requestPasswordSetup()` methods to `AccountClientImpl`; both registered as platform resource functions (`login.function.CheckHasPassword` / `login.function.RequestPasswordSetup`). Signed-off-by: Don Kendall <dkendall@ledoweb.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * test(password): add unit tests for SSO password setup RPCs ssoPassword.test.ts — 12 tests covering: - checkHasPassword: returns true/false for hash+salt presence, false for partial state (hash-only or salt-only), error for missing account - changePassword: rejects empty old/new passwords, rejects wrong oldPassword (hash mismatch) - requestPasswordSetup: sends email when email social ID exists, returns SocialIdNotFound when no email is linked, handles mail service failures gracefully (logs error, does not rethrow) signupTokenGuard.test.ts — added edge-case for empty-string token to document current guard behaviour (token != null passes empty string through; noted as a future hardening opportunity). Signed-off-by: Don Kendall <dkendall@ledoweb.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * chore(dev): add dev-local webpack proxy for local Docker compose stack Adds a `dev-local` CLIENT_TYPE that proxies webpack dev server requests to a local Docker compose stack (nginx at localhost:8088), following the same pattern as the existing `dev-server`, `dev-huly`, etc. modes. Useful for developing frontend changes against a fully running local backend without needing `huly.local` DNS configuration. Signed-off-by: Don Kendall <dkendall@ledoweb.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * feat(email): redesign transactional emails with proper HTML and dedicated password setup template All account service email templates were bare <p> tags with no styling, branding, or call-to-action buttons. Replaced with production-quality HTML emails using email-safe table layout and inline CSS. Design: Huly wordmark on dark (#18181B) header, white card body, dark CTA button, subtle border, system font stack. Plain-text versions updated to match for clients that prefer text. Templates improved: - RecoveryHTML/Text — password reset flow - ConfirmationHTML/Text — email verification on signup - InviteHTML/Text — workspace invitation - ResendInviteHTML/Text — re-invitation - OtpHTML/Text — sign-in code with large monospace code display New dedicated template for SSO password setup (PasswordSetupHTML/Text/ Subject) so the setup email has copy distinct from forgot-password recovery. requestPasswordSetup now uses these instead of RecoveryHTML. Subject: "Set a password for your Huly account". Other language files updated with the new PasswordSetup* keys (English copy as fallback — translations can follow separately). Signed-off-by: Don Kendall <dkendall@ledoweb.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * fix(password): guard requestPasswordSetup against accounts with existing password Add server-side check that rejects requestPasswordSetup calls from accounts that already have a password hash+salt. The setup flow bypasses the old-password requirement in changePassword, so it must be restricted to SSO-only accounts. The UI already guards this branch but defence-in-depth requires the server to enforce it independently. Also adds JSDoc to requestPasswordSetup and extends unit test coverage: - TokenError path for checkHasPassword (invalid/expired token) - BadRequest guard for requestPasswordSetup on password-bearing accounts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * fix: add missing locale keys and fix eslint/formatting for CI - Add 5 missing SSO password translation keys to all non-en locale files (SetPassword, SSOPasswordDescription, SendSetupLink, SSOPasswordEmailSent, SSONoEmailLinked) to fix locale parity test - Replace non-null assertions with type casts in ssoPassword.test.ts to fix @typescript-eslint/no-non-null-assertion errors - Revert unrelated tracker/github cosmetic changes that triggered pre-existing eslint errors in those packages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> * fix: address review — remove dev/prod changes, translate PasswordSetup strings - Revert dev/prod/webpack.config.js and package.json (per BykhovDenis) - Translate PasswordSetupText and PasswordSetupSubject for all 10 locales (cs, de, es, fr, it, pt-br, pt, ru, tr, zh) - PasswordSetupHTML stays in English (reviewer approved) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com> --------- Signed-off-by: Don Kendall <dkendall@ledoweb.com> Signed-off-by: Don Kendall <kendall@donkendall.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
23 lines
7.1 KiB
JSON
23 lines
7.1 KiB
JSON
{
|
|
"string": {
|
|
"ConfirmationText": "Merci pour votre intérêt pour {name}. Pour compléter le processus d'inscription, veuillez copier le lien suivant dans la barre d'URL de votre navigateur {link}. Cordialement, l'équipe {name}.",
|
|
"ConfirmationHTML": "<p>Bonjour,</p><p>Merci pour votre intérêt pour {name}. Pour compléter le processus d'inscription, veuillez suivre <a href={link}>ce lien</a> ou copier le lien suivant dans la barre d'URL de votre navigateur.</p><p>{link}</p><p>Cordialement,</p><p>L'équipe {name}.</p>",
|
|
"ConfirmationSubject": "Confirmez votre adresse e-mail pour vous inscrire à {name}",
|
|
"RecoveryText": "Nous avons reçu une demande de réinitialisation du mot de passe pour votre compte. Pour réinitialiser votre mot de passe, veuillez coller le lien suivant dans la barre d'adresse de votre navigateur web : {link}. Si vous n'avez pas demandé de réinitialisation de mot de passe, ignorez cette lettre.",
|
|
"RecoveryHTML": "<p>Nous avons reçu une demande de réinitialisation du mot de passe pour votre compte. Pour réinitialiser votre mot de passe, veuillez cliquer sur le lien ci-dessous : <a href={link}>Réinitialiser le mot de passe</a></p><p>Si le lien ci-dessus ne fonctionne pas, collez le lien suivant dans la barre d'adresse de votre navigateur web : {link}</p><p>Si vous n'avez pas demandé de réinitialisation de mot de passe, ignorez cette lettre.</p>",
|
|
"RecoverySubject": "Réinitialisation du mot de passe",
|
|
"InviteText": "Vous avez été invité à {ws}. Pour rejoindre, veuillez coller le lien suivant dans la barre d'adresse de votre navigateur web : {link}. Le lien est valable pendant {expHours} heures.",
|
|
"InviteHTML": "<p>Vous avez été invité à {ws}. Pour rejoindre, veuillez cliquer sur le lien ci-dessous : <a href={link}>Rejoindre</a></p><p>Si le lien d'invitation ci-dessus ne fonctionne pas, collez le lien suivant dans la barre d'adresse de votre navigateur web : {link}</p><p>Le lien est valable pendant {expHours} heures.</p>",
|
|
"InviteSubject": "Invitation à {ws}",
|
|
"OtpText": "Confirmez votre adresse e-mail pour accéder à {app}!\n\nVotre code de confirmation est ci-dessous - entrez-le dans la fenêtre où vous avez commencé à vous connecter pour {app}.\n\n{code}\n\nSi vous n'avez pas demandé cet e-mail, ne vous inquiétez pas - vous pouvez l'ignorer en toute sécurité.",
|
|
"OtpHTML": "<h3>Confirmez votre adresse e-mail pour accéder à {app}!</h3><p>Votre code de confirmation est ci-dessous - entrez-le dans la fenêtre où vous avez commencé à vous connecter pour {app}.<p/><br/><b style=\"font-size:36px\">{code}</b><br/><br/><p>Si vous n'avez pas demandé cet e-mail, ne vous inquiétez pas - vous pouvez l'ignorer en toute sécurité.</p>",
|
|
"OtpSubject": "Code de confirmation {app} : {code}",
|
|
"ResendInviteText": "Vous avez été réinvité à {ws}. Veuillez utiliser le lien suivant pour rejoindre : {link}. Le lien de réinvitation est valide pendant {expHours} heures.",
|
|
"ResendInviteHTML": "<p>Vous avez été réinvité à {ws}. Pour rejoindre, cliquez sur le lien ci-dessous : <a href={link}>Rejoindre</a></p><p>Si le lien d'invitation ci-dessus ne fonctionne pas, collez le lien suivant dans la barre d'URL de votre navigateur : {link}</p><p>Le lien de réinvitation est valide pendant {expHours} heures.</p>",
|
|
"ResendInviteSubject": "Réinvitation à {ws}",
|
|
"PasswordSetupText": "Définir un mot de passe pour votre compte\n\nVous avez demandé à ajouter la connexion par mot de passe à votre compte.\n\nDéfinissez votre mot de passe ici :\n{link}\n\nUne fois défini, vous pourrez vous connecter avec votre adresse e-mail et votre mot de passe en plus de votre méthode de connexion existante.\n\nSi vous n'avez pas fait cette demande, vous pouvez ignorer cet e-mail en toute sécurité.",
|
|
"PasswordSetupHTML": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head><body style=\"margin:0;padding:0;background-color:#F2F2F7;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased\"><table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background-color:#F2F2F7;padding:40px 16px\"><tr><td align=\"center\"><table role=\"presentation\" width=\"520\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"max-width:520px;width:100%\"><tr><td style=\"background-color:#18181B;border-radius:12px 12px 0 0;padding:24px 40px\"><span style=\"display:inline-block;font-size:18px;font-weight:700;color:#FFFFFF;letter-spacing:-0.3px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">Huly</span></td></tr><tr><td style=\"background-color:#FFFFFF;padding:36px 40px;border:1px solid #E4E4E7;border-top:none;border-radius:0 0 12px 12px\"><h1 style=\"margin:0 0 12px 0;font-size:22px;font-weight:700;color:#18181B;letter-spacing:-0.4px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;line-height:1.3\">Set a password for your account</h1><p style=\"margin:0 0 28px 0;font-size:15px;line-height:1.65;color:#52525B;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">You requested to add password sign-in to your account. Click the button below to choose your password.</p><p style=\"margin:0 0 16px 0;font-size:13px;line-height:1.6;color:#71717A;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">Once set, you can sign in with email + password in addition to your existing sign-in method.</p><table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\"><tr><td style=\"background-color:#18181B;border-radius:8px\"><a href=\"{link}\" target=\"_blank\" style=\"display:inline-block;padding:13px 26px;font-size:14px;font-weight:600;color:#FFFFFF;text-decoration:none;letter-spacing:-0.1px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">Set password →</a></td></tr></table><p style=\"margin:28px 0 0 0;font-size:12px;line-height:1.6;color:#A1A1AA;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">Button not working? Copy and paste this link into your browser:<br><a href=\"{link}\" style=\"color:#71717A;word-break:break-all;text-decoration:underline;font-size:12px\">{link}</a></p><table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"margin-top:28px\"><tr><td style=\"border-top:1px solid #F4F4F5;padding-top:24px\"><p style=\"margin:0;font-size:12px;color:#A1A1AA;line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">If you didn't request this, you can safely ignore this email.</p></td></tr></table></td></tr><tr><td align=\"center\" style=\"padding:20px 0\"><p style=\"margin:0;font-size:12px;color:#9CA3AF;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif\">© Huly — All rights reserved</p></td></tr></table></td></tr></table></body></html>",
|
|
"PasswordSetupSubject": "Définir un mot de passe pour votre compte Huly"
|
|
}
|
|
}
|