Skip to content

Merge Huly - #5

Merged
ArtyomSavchenko merged 4 commits into
developfrom
merge-huly
Mar 23, 2026
Merged

Merge Huly#5
ArtyomSavchenko merged 4 commits into
developfrom
merge-huly

Conversation

@ArtyomSavchenko

Copy link
Copy Markdown
Collaborator

No description provided.

ArtyomSavchenko and others added 4 commits March 20, 2026 17:18
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
* 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>
* Two-Factor Authentication (2FA)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

* Fix test

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

* Fix test

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

* Fix tests

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

* More test fixes

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

* Fix tests

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>

---------

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
@ArtyomSavchenko
ArtyomSavchenko merged commit a025906 into develop Mar 23, 2026
12 checks passed
@BykhovDenis
BykhovDenis deleted the merge-huly branch April 27, 2026 10:02
ArtyomSavchenko added a commit that referenced this pull request Jul 23, 2026
…error UX

- ImportFromWord now gated: CanImportDocument visibilityTester (Draft +
  owner/co-author, mirrors editor $isEditable) + query state=Draft, so import
  can't overwrite In-Review/Approved/Effective/Archived bodies (#1)
- remove dead DocContentProvider registry (providers.ts) (#2)
- apply normalizeMarkup to candidate + current in /document-import (#3)
- export.ts: one numbering instance per ordered list, so separate lists
  restart at 1 instead of sharing a counter (#4)
- /document-import deletes the transient uploaded blob after conversion (#5)
- export back to POST + Blob download (no bearer token in URL); revert global
  express.json to default (large body route was removed); surface apply
  failures via setPlatformStatus(unknownError)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tnsp7jnnjxrmt3jbyhhuml
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants