mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-21 11:52:23 +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": "Vielen Dank für Ihr Interesse an {name}. Um die Registrierung abzuschließen, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}. Mit freundlichen Grüßen, Ihr {name}-Team.",
|
|
"ConfirmationHTML": "<p>Hallo,</p><p>Vielen Dank für Ihr Interesse an {name}. Um die Registrierung abzuschließen, klicken Sie bitte <a href={link}>hier</a> oder fügen Sie den folgenden Link in die Adresszeile Ihres Browsers ein.</p><p>{link}</p><p>Mit freundlichen Grüßen,</p><p>Ihr {name}-Team.</p>",
|
|
"ConfirmationSubject": "Bestätigen Sie Ihre E-Mail-Adresse für die Registrierung bei {name}",
|
|
"RecoveryText": "Wir haben eine Anfrage zum Zurücksetzen des Passworts für Ihr Konto erhalten. Um Ihr Passwort zurückzusetzen, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}. Falls Sie kein Zurücksetzen des Passworts angefordert haben, ignorieren Sie bitte diese E-Mail.",
|
|
"RecoveryHTML": "<p>Wir haben eine Anfrage zum Zurücksetzen des Passworts für Ihr Konto erhalten. Um Ihr Passwort zurückzusetzen, klicken Sie bitte auf den folgenden Link: <a href={link}>Passwort zurücksetzen</a></p><p>Falls der Link zum Zurücksetzen des Passworts nicht funktioniert, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}</p><p>Falls Sie kein Zurücksetzen des Passworts angefordert haben, ignorieren Sie bitte diese E-Mail.</p>",
|
|
"RecoverySubject": "Passwort wiederherstellen",
|
|
"InviteText": "Sie wurden zu {ws} eingeladen. Um beizutreten, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}. Der Link ist {expHours} Stunden gültig.",
|
|
"InviteHTML": "<p>Sie wurden zu {ws} eingeladen. Um beizutreten, klicken Sie bitte auf den folgenden Link: <a href={link}>Beitreten</a></p><p>Falls der Einladungslink nicht funktioniert, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}</p><p>Der Link ist {expHours} Stunden gültig.</p>",
|
|
"InviteSubject": "Einladung zu {ws}",
|
|
"OtpText": "Bestätigen Sie Ihre E-Mail-Adresse für den Zugriff auf {app}!\n\nIhr Bestätigungscode steht unten - geben Sie ihn in dem Fenster ein, in dem Sie sich bei {app} anmelden.\n\n{code}\n\nWenn Sie diese E-Mail nicht angefordert haben, müssen Sie sich keine Sorgen machen — Sie können sie einfach ignorieren.",
|
|
"OtpHTML": "<h3>Bestätigen Sie Ihre E-Mail-Adresse für den Zugriff auf {app}!</h3><p>Ihr Bestätigungscode steht unten - geben Sie ihn in dem Fenster ein, in dem Sie sich bei {app} anmelden.<p/><br/><b style=\"font-size:36px\">{code}</b><br/><br/><p>Wenn Sie diese E-Mail nicht angefordert haben, müssen Sie sich keine Sorgen machen — Sie können sie einfach ignorieren.</p>",
|
|
"OtpSubject": "{app} Bestätigungscode: {code}",
|
|
"ResendInviteText": "Sie wurden erneut zu {ws} eingeladen. Bitte verwenden Sie den folgenden Link zum Beitreten: {link}. Der erneute Einladungslink ist {expHours} Stunden gültig.",
|
|
"ResendInviteHTML": "<p>Sie wurden erneut zu {ws} eingeladen. Um beizutreten, klicken Sie bitte auf den folgenden Link: <a href={link}>Beitreten</a></p><p>Falls der Einladungslink nicht funktioniert, fügen Sie bitte den folgenden Link in die Adresszeile Ihres Browsers ein: {link}</p><p>Der erneute Einladungslink ist {expHours} Stunden gültig.</p>",
|
|
"ResendInviteSubject": "Erneute Einladung zu {ws}",
|
|
"PasswordSetupText": "Passwort für Ihr Konto festlegen\n\nSie haben angefordert, die Anmeldung mit Passwort zu Ihrem Konto hinzuzufügen.\n\nLegen Sie Ihr Passwort hier fest:\n{link}\n\nNach der Einrichtung können Sie sich zusätzlich zu Ihrer bestehenden Anmeldemethode mit E-Mail und Passwort anmelden.\n\nFalls Sie dies nicht angefordert haben, können Sie diese E-Mail ignorieren.",
|
|
"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": "Passwort für Ihr Huly-Konto festlegen"
|
|
}
|
|
}
|