mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +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
6.2 KiB
JSON
23 lines
6.2 KiB
JSON
{
|
||
"string": {
|
||
"ConfirmationText": "感谢您对 {name} 的兴趣。要完成注册过程,请将以下链接复制到您的浏览器的 URL 栏中 {link}。此致,{name} 团队。",
|
||
"ConfirmationHTML": "<p>您好,</p><p>感谢您对 {name} 的兴趣。要完成注册过程,请点击 <a href={link}>此链接</a> 或将以下链接复制到您的浏览器的 URL 栏中。</p><p>{link}</p><p>此致,</p><p>{name} 团队。</p>",
|
||
"ConfirmationSubject": "确认您的电子邮件地址以注册 {name}",
|
||
"RecoveryText": "我们收到了重置您账户密码的请求。要重置您的密码,请将以下链接粘贴到您的浏览器地址栏中:{link}。如果您没有请求密码重置,请忽略此邮件。",
|
||
"RecoveryHTML": "<p>我们收到了重置您账户密码的请求。要重置您的密码,请点击以下链接:<a href={link}>重置密码</a></p><p>如果上面的重置密码链接不起作用,请将以下链接粘贴到您的浏览器地址栏中:{link}</p><p>如果您没有请求密码重置,请忽略此邮件。</p>",
|
||
"RecoverySubject": "密码恢复",
|
||
"InviteText": "您被邀请加入 {ws}。要加入,请将以下链接粘贴到您的浏览器地址栏中:{link}。链接有效期为 {expHours} 小时。",
|
||
"InviteHTML": "<p>您被邀请加入 {ws}。要加入,请点击以下链接:<a href={link}>加入</a></p><p>如果上面的邀请链接不起作用,请将以下链接粘贴到您的浏览器地址栏中:{link}</p><p>链接有效期为 {expHours} 小时。</p>",
|
||
"InviteSubject": "邀请加入 {ws}",
|
||
"OtpText": "确认您的电子邮件地址以访问 {app}!\n\n您的确认码如下 - 请在您开始登录 {app} 的窗口中输入。\n\n{code}\n\n如果您没有请求此邮件,不用担心 — 您可以放心地忽略它。",
|
||
"OtpHTML": "<h3>确认您的电子邮件地址以访问 {app}!</h3><p>您的确认码如下 - 请在您开始登录 {app} 的窗口中输入。<p/><br/><b style=\"font-size:36px\">{code}</b><br/><br/><p>如果您没有请求此邮件,不用担心 — 您可以放心地忽略它。</p>",
|
||
"OtpSubject": "{app} 确认码:{code}",
|
||
"ResendInviteText": "您已被重新邀请加入 {ws}。请使用以下链接加入:{link}。重新邀请链接的有效期为 {expHours} 小时。",
|
||
"ResendInviteHTML": "<p>您已被重新邀请加入 {ws}。要加入,请点击以下链接:<a href={link}>加入</a></p><p>如果上方的邀请链接无效,请将以下链接粘贴到您的浏览器地址栏中:{link}</p><p>重新邀请链接的有效期为 {expHours} 小时。</p>",
|
||
"ResendInviteSubject": "重新邀请加入 {ws}",
|
||
"PasswordSetupText": "为您的账户设置密码\n\n您请求为账户添加密码登录方式。\n\n在此设置密码:\n{link}\n\n设置完成后,您可以使用电子邮件和密码登录,同时保留现有的登录方式。\n\n如果您没有发起此请求,可以放心地忽略这封邮件。",
|
||
"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": "为您的 Huly 账户设置密码"
|
||
}
|
||
}
|