* feat(tracker): field-prefixed search, inline filter chips and zero-hit empty state
Adds SearchInputAdvanced (field:value prefixes routed to Elasticsearch query_string), match highlighting, inline filter chips with overflow popover, a reusable zero-hit empty state in view-resources, and search-scope/highlight view options for List and Kanban.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* refactor(tracker-search): address review follow-ups on search/filter rework
Shared, non-storage-specific field list, test-folder conventions, locale
coverage, single regex source and an owner-token gate for the result count.
- core: add shared `fullTextSearchFields` constant next to
FullTextSearchContext as the single source of truth for the full-text
fields exposed to `field:value` targeting. The client encoder derives
ES_NATIVE_FIELDS from it and the elastic adapter derives KNOWN_FIELD_RE
from it, replacing the two "KEEP IN SYNC" copies. Per-field boost weights
stay a local adapter detail.
- tests: move the five co-located tests into each package's existing test
folder convention (ui `__test__`, view-resources `__tests__`) and fix the
relative imports.
- i18n: translate the new tracker search/filter strings in the remaining
locales (zh, ja, ko, cs, es, fr, it, pt, pt-br, tr), reusing each file's
existing terminology; ICU placeholders left unchanged.
- encoder: hoist the reserved-character class into one constant and build two
RegExp instances from it (non-global for `.test()`, global for `.replace()`)
to avoid the shared-lastIndex trap.
- view-resources: guard `resultIssueCountStore` writes with an owner-token
gate so a superseded viewlet can no longer clobber the active viewlet's
count; List/KanbanView claim and release, IssuesView resets through the
current owner. Assumes a single active IssuesView surface.
- view-resources: make result-count reporting opt-in via a new
`reportResultCount` prop on List (default true). Embedded, non-primary List
instances (sub-issues / related issues in the issue edit panel, routed
through SubIssueList) pass false and never claim the owner token, so opening
and closing an issue can no longer strand the primary Issues viewlet with a
dead token — the zero-hit SearchEmptyState card renders again afterwards. Add
a regression test covering the opted-out embedded consumer.
- elastic: escape every regex metacharacter (not just `.`) when building
KNOWN_FIELD_RE from `fullTextSearchFields`, so a future field name carrying
another metacharacter cannot silently corrupt the alternation. Behaviour for
the current fields is unchanged.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* refactor(tracker-search): centralize regex escaping and lock down result-count store
- Share escapeRegExp from @hcengineering/core so the client encoder and the server elastic adapter escape the fulltext field list identically, not just dots on the client.
- Export resultIssueCountStore as a read-only Readable; the owner-token gate functions (setResultCount / resetResultCount / releaseResultCountOwner) are now the only write path.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
---------
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Co-authored-by: Michael Uray <michaeluray@users.noreply.github.com>
Co-authored-by: Artyom Savchenko <armisav@gmail.com>
* feat: API token management in workspace settings
Add UI and backend support for creating, listing, and revoking
API tokens scoped to workspaces. Includes owner-level workspace
token visibility, OpenAPI documentation, Mongo/Postgres persistence,
and i18n translations.
Signed-off-by: Don Kendall <kendall@donkendall.com>
* feat: enforce API token revocation at transactor level
Embed apiTokenId in JWT extra field and add a per-token revocation
cache (60s TTL) in the transactor REST handler. Revoked tokens are
now rejected within ~60 seconds instead of remaining valid until
JWT expiry.
Adds checkApiTokenRevoked account service method for the transactor
to query individual token revocation status.
Signed-off-by: Don Kendall <kendall@donkendall.com>
* feat: implement Phase 1 API token scopes (read/write/delete)
Add coarse-grained scope enforcement for API tokens. Tokens can now
be created with scopes ['read:*'], ['read:*','write:*'], or
['read:*','write:*','delete:*']. Existing tokens without scopes
retain full access (backward compatible).
- DB: v26 migration adds scopes TEXT[] column to api_tokens
- Types: add scopes field to ApiToken and ApiTokenInfo
- Operations: createApiToken accepts/validates/persists scopes,
embeds in JWT via extra.scopes
- Enforcement: withSession checks scopes against method; tx handler
additionally requires delete:* for TxRemoveDoc
- Client: createApiToken signature accepts optional scopes param
- UI: scope preset dropdown in create popup (default: Read Only),
permissions column in token list with i18n labels
- Also fixes 3 pre-existing TS2322/TS2345 errors in operations.ts
Signed-off-by: Don Kendall <kendall@donkendall.com>
* test: add unit tests for API token scope enforcement
- scopes.test.ts: 8 tests for hasScope() and getRequiredScope() logic
- apiTokenScopes.test.ts: 7 tests for createApiToken scope validation
(valid scopes, multiple scopes, no scopes backward compat, invalid
format rejection, empty array rejection, domain-scope rejection)
and listApiTokens scopes inclusion
- Export hasScope/getRequiredScope from rpc.ts for testability
Signed-off-by: Don Kendall <kendall@donkendall.com>
* fix: address review feedback — role restriction, locale parity, formatting
- Restrict API token creation/revocation to AccountRole.User or higher
(guests cannot use API tokens), per reviewer suggestion
- Add 5 missing translation keys (ApiTokenPermissions, ApiTokenScopePreset,
ApiTokenScopeReadOnly, ApiTokenScopeReadWrite, ApiTokenScopeFullAccess)
to all non-en locale files to fix locale parity CI test
- Fix prettier formatting in apiTokenScopes.test.ts
- Rename local `extra` to `tokenExtra` in createApiToken to avoid
shadowing the decoded token's `extra` field
Signed-off-by: Don Kendall <kendall@donkendall.com>
* fix: address aonnikov architectural review feedback
- rpc.ts: use system service token for checkApiTokenRevoked so the
revocation check is not coupled to the user's potentially-revoked
bearer token; systemAccountUuid + service:'server' ensures account
service always accepts the call
- ApiDocsSection.svelte: derive transactor base URL from
login.metadata.LoginEndpoint (set on auth) instead of
window.location.origin, which is not necessarily the transactor host
- ApiTokenCreatePopup.svelte: replace manual translate() calls and
themeStore language watch with DropdownLabelsIntl + DropdownIntlItem[],
which handle i18n automatically; error state is now IntlString
- General.svelte: remove legacy GenerateApiToken button, handler, and
ApiTokenPopup import in favour of the new ApiTokens settings panel
Signed-off-by: Don Kendall <kendall@donkendall.com>
* fix: formatting in ApiTokenPopup, apiTokenScopes test, and operations
Signed-off-by: Don Kendall <kendall@donkendall.com>
* fix: apply rushx fmt to pass CI formatting check
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* fix: restore (s as any) cast in server_http.ts removed by ESLint autofix
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* refactor(token): centralize API token revocation/expiry in verifyToken
Address @aonnikov's review: the bespoke checkApiTokenRevoked RPC and the
ad-hoc revocation cache in the transactor are replaced by a reusable
verifyToken in server-token that checks signature, expiry, and (for
revokable API tokens) revocation via a pluggable checker.
- server-token: add verifyToken + isTokenExpired + setApiTokenRevocationChecker
(the 'method to verify' metadata the plugin needs, without depending on the
account client). Revocation cache (60s TTL) now lives here, reusable by any
service (transactor, blob access, etc.).
- account: the account is now authoritative — wrap() rejects revoked/expired
API tokens, so any account method (selectWorkspace/getWorkspaceInfo/...)
naturally 401s. Removed the redundant checkApiTokenRevoked method.
- account-client: drop checkApiTokenRevoked.
- transactor: withSession uses verifyToken; the registered checker reuses the
existing getLoginInfoByToken instead of a dedicated boolean RPC. Scopes are
parsed once and threaded through (no re-decode in the tx handler).
- tests: verifyToken/isTokenExpired unit coverage.
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* fix(setting): derive REST API base from account-provided transactor endpoint
Address @aonnikov: the REST API host must come from the transactor endpoint
returned by the account service (the login endpoint, a ws(s):// URL), not be
constructed from window.location. Convert ws->http and append /api/v1, matching
the existing ServerManagerGeneral pattern.
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* i18n(setting): translate API token strings across all locales
Address @ArtyomSavchenko: the new API token UI strings were left in English
in non-en locales. Provide translations for ru/de/es/fr/it/pt/pt-br/zh/ja/cs/tr.
The en/ru locale-parity test passes (the prior failure was an en/ru key
mismatch, resolved by the develop merge).
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* style: wrap long lines to satisfy prettier (account utils import, ApiDocsSection)
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* fix(api-token): drop unenforceable scopes, harden the token lifecycle
The scopes were only ever consulted in the transactor's REST handler, so
they promised a boundary the platform did not keep:
- the WebSocket transport decodes the token and never looks at scopes, so
a read-only token could open a socket and issue any transaction;
- even on the REST path, delete:* only matched a top-level TxRemoveDoc and
was bypassed by nesting the removal in a TxApplyIf;
- nothing stopped a scoped token from calling createApiToken and minting
an unscoped one.
Narrowing a token's rights has to happen in the pipeline, where it covers
every transport, and that is a larger design than this feature. Until then
a token carries its account's rights and says so, rather than displaying a
restriction that does not hold. Scopes are removed end to end.
What is kept is made to work:
- API tokens can no longer create, list or revoke API tokens. Otherwise a
leaked token renews itself and revokes the tokens meant to stop it.
- Revocation fails closed. The account is the only authority on it, so an
unreachable account now rejects the token instead of admitting a revoked
one to whoever can keep the account busy. Verdicts stay trusted for the
cache TTL so brief outages do not cut off healthy tokens.
- The revocation cache is bounded and re-checks negative verdicts.
- Revoking your own token no longer requires a role in its workspace, so
leaving a workspace cannot strand a credential you can never revoke.
- The per-account limit counts only usable tokens; revoked and expired
ones are kept for the audit trail and used to lock out anyone rotating.
- Rejected REST tokens are logged with a reason, since expired, revoked
and unverifiable are one opaque 401 from outside.
The unused listWorkspaceApiTokens/revokeWorkspaceApiToken pair is removed;
it had no caller and no test.
Tests cover the role restriction, the API-token guard, ownership on
revoke, the limit accounting and fail-closed revocation. Removing any of
those checks fails them.
Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* fix(setting): correct the API token settings page
- Moves the page from workspace settings to account settings. Tokens
belong to the account: the page already lists them across every
workspace, and creating one only needs the User role the account
service checks, not Owner as the category required.
- Surfaces failures. A failed revoke was logged to the console and the
row re-rendered unchanged; loading workspaces could reject unhandled
and leave an empty dropdown with no explanation.
- Outside a secure context there is no clipboard API, so the OK button
did nothing and the dialog had no way out but Cancel. It now closes,
leaving the token selectable.
- Replaces hardcoded English labels, adds aria-expanded on the docs
disclosure and labels on the copy targets.
- Fills in the Korean and Polish translations, which were the only two
locales missing these keys, and drops the API access strings orphaned
when this PR replaced the old Generate API token button.
Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* chore: drop docs/openapi.yaml from the API token PR
Unrelated to this feature and wrong for this repo: it documents a
/_tokens minting service and a tools/mint-token CLI that do not exist
here, uses reverse-proxy prefixes from a self-hosted deployment rather
than the transactor's /api/v1 routes, and states that revocation is a
future enhancement, which is what this PR implements. Nothing references
it. REST API docs belong in their own change, generated against the
routes that exist.
Claude-Session: https://claude.ai/code/session_01ANdoXbdn5k2hZy734EwKe7
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
---------
Signed-off-by: Don Kendall <kendall@donkendall.com>
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
* feat: implement reference versioning functionality across various components and plugins
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
* fix: simplify provider function calls and label retrieval in MentionPopup component
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
* refactor: improve readability of version selection and reference handling in MentionVersionPopup and reference.ts
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
* feat: add CardReferenceObjectProvider and integrate into card model and resources
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
---------
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
Report slow client findAll measurements as warnings instead of errors so the browser console reflects that these entries are performance diagnostics, not failed queries.
Preserve the existing threshold, payload, and timing details for performance triage.
Signed-off-by: Ignat Remizov <ignat@ignatremizov.com>
2026-05-26 14:27:45 +07:00
Artyom SavchenkoGitHubCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Do not set migration state in case of mode mismatch and add duration
Signed-off-by: Artem Savchenko <armisav@gmail.com>
* Bump model version
Signed-off-by: Artem Savchenko <armisav@gmail.com>
---------
Signed-off-by: Artem Savchenko <armisav@gmail.com>
When grouping by assignee with a secondary grouping (e.g. by Status),
the "Not Specified" category passed `undefined` as the query value.
Since `undefined` is stripped during JSON serialization, the server
received no filter and returned all documents — causing assigned
issues to appear under "Not Specified".
Use `null` instead so the filter survives serialization and correctly
matches only documents where the field is unset.
Fixes#10606
Signed-off-by: Don Kendall <kendall@donkendall.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.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>