Files
huly-platform/plugins/setting-assets/lang/en.json
T
Don KendallandGitHub 5a3d673e84 feat(account): revocable API tokens managed from account settings (#10624)
* 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>
2026-08-04 13:10:48 +07:00

287 lines
15 KiB
JSON

{
"string": {
"Setting": "Setting",
"Spaces": "Spaces",
"Integrations": "Integrations",
"Support": "Support",
"Privacy": "Privacy",
"Terms": "Terms",
"AccountSettings": "Account settings",
"Categories": "Categories",
"Delete": "Delete",
"ChangePassword": "Change password",
"Disconnect": "Disconnect",
"DisconnectAll": "Disconnect all",
"Saving": "Saving...",
"Saved": "Saved",
"Add": "Add",
"AddNew": "Add {type}",
"Proceed": "Proceed",
"NewEmail": "New email",
"SendConfirmation": "Send confirmation code",
"CodeSent": "Code has been sent. Please input it in the field below.",
"SendAgain": "Send again",
"SendAgainIn": "Can send again in",
"Value": "Value",
"Signout": "Sign out",
"Settings": "Settings",
"SelectWorkspace": "Select workspace",
"InviteWorkspace": "Invite to workspace",
"DeleteStatus": "Delete status",
"DeleteStatusConfirm": "Do you want to delete this status?",
"Reconnect": "Reconnect",
"IntegrationDisabled": " has been disabled",
"IntegrationDisabledSetting": "Integration has been disabled",
"IntegrationDisabledDescr": "Integration disabled",
"IntegrationWith": "Integration with ",
"ClassSetting": "Class setting",
"ClassSettingHint": "A set or category of things having some property or attribute in common from others by kind, type, or quality.",
"ClassProperties": "Class properties",
"Classes": "Classes",
"Attributes": "Attributes",
"DeleteAttribute": "Delete attribute",
"DeleteAttributeConfirm": "Do you want to delete this attribute?",
"DeleteAttributeExistConfirm": "Do you want to delete this attribute? Data will be lost",
"DeleteMixin": "Delete Mixin",
"DeleteMixinConfirm": "Do you want to delete this mixin?",
"DeleteMixinExistConfirm": "Do you want to delete this mixin? Data will not be available",
"Attribute": "Attribute",
"Custom": "Custom",
"Type": "Type",
"WithTime": "WithTime",
"DateMode": "Date mode",
"CreatingAttribute": "Creating an attribute",
"EditAttribute": "Edit attribute",
"CreateEnum": "Create enum",
"EditEnum": "Edit enum",
"Enums": "Enums",
"EnumsSettingHint": "A set or category of things having some property or attribute in common from others by kind, type, or quality.",
"EnumTitle": "Enum title",
"EnumsCount": "{count, plural, =1 {# option} other {# options}}",
"ProjectTypesCount": "{count, plural, =0 {No project types} =1 {# project type} other {# project types}}",
"Options": "Options",
"EnterOptionTitle": "Enter option title",
"NewEnumDialogClose": "Do you want to close this dialog?",
"NewEnumDialogCloseNote": "All changes will be lost",
"NewValue": "New value",
"Leave": "Leave workspace",
"LeaveDescr": "Are you sure you want to leave the workspace? This action cannot be undone.",
"Members": "Members",
"WorkspaceSettings": "Workspace settings",
"Select": "Select",
"AddOwner": "Add owner",
"ReadonlyGuest": "Readonly",
"Guest": "Guest",
"User": "User",
"Maintainer": "Maintainer",
"Owner": "Owner",
"OwnerFirstName": "Owner's First name",
"OwnerLastName": "Owner's Last name",
"Role": "Role",
"FailedToSave": "Failed to update password",
"ImportEnum": "Import enum values",
"ImportEnumCopy": "Copy enum values from clipboard",
"CreateMixin": "Create Mixin",
"OldNames": "Old values",
"NewClassName": "Type new class name or select from previous values...",
"ShowAttribute": "Show attribute",
"HideAttribute": "Hide attribute",
"Visibility": "Visibility",
"Hidden": "Hidden",
"Configure": "Configure",
"InviteSettings": "Invite settings",
"RoleCapabilitySettings": "Role permissions",
"DefaultInviteRoleForJoin": "Default role assigned after joining via invite link:",
"InviteLinkGeneratorRoles": "Select user roles who can generate invite links:",
"DefaultValue": "Default value",
"SelectAValue": "Select a value",
"DateOnly": "Date only",
"OnlyTime": "Only time",
"DateAndTime": "Date and time",
"Configuration": "Configuration",
"ConfigurationEnabled": "Enabled",
"ConfigurationDisabled": "Disabled",
"ConfigDisable": "Disable",
"ConfigEnable": "Enable",
"ConfigBeta": "Beta version",
"Properties": "Properties",
"TaskTypes": "Task types",
"Automations": "Automations",
"Collections": "Collections",
"ClassColon": "Class:",
"SpaceTypes": "Space types",
"NewSpaceType": "New space type",
"SpaceTypeTitle": "Space type title",
"General": "General",
"Description": "Description",
"CountSpaces": "{count, plural, =0 {No spaces} =1 {# space} other {# spaces}}",
"Roles": "Roles",
"RoleName": "Role name",
"Permissions": "Permissions",
"Assignees": "Assignees",
"DeleteRole": "Delete role",
"DeleteRoleConfirmation": "Are you sure you want to delete this role? All users with this role will lose their permissions.",
"DeleteWorkspace": "Delete workspace",
"DeleteWorkspaceConfirm": "Are you sure you want to delete this workspace? You and all other members will lose access to this workspace. All information from the workspace will be lost. This action cannot be undone. Do you want to proceed?",
"DeleteSpaceType": "Delete space type",
"DeleteSpaceTypeConfirm": "Are you sure you want to delete this space type?",
"WorkspaceName": "Workspace name",
"Workspace": "Workspace",
"OwnerOrMaintainerRequired": "You need to be a workspace Owner or Maintainer",
"LastOwnerLeaveTitle": "Cannot leave workspace",
"LastOwnerLeaveMessage": "You are the only owner of this workspace. To leave, first grant owner permissions to another member. If nobody needs this workspace anymore, consider deleting this workspace instead.",
"Backup": "Backup",
"BackupLast": "Last backup",
"BackupTotalSnapshots": "Total Snapshots",
"BackupTotalFiles": "Files",
"BackupSize": "Backup size",
"BackupLinkInfo": "The URL of a backup directory that can be downloaded recursively using tools like wget or curl.",
"BackupBearerTokenInfo": "A bearer token is required to access the backup.",
"BackupSnapshots": "Backup Snapshots",
"BackupFileDownload": "Download File",
"BackupFiles": "Backup Files",
"BackupNoBackup": "No backups are currently available.",
"BackupDownloadAll": "Download full backup",
"BackupPreparingDownload": "Preparing backup…",
"BackupDownloadAllInfo": "Download every backup file as a single .zip archive you can keep on your computer.",
"BackupCopyScript": "Copy download script",
"BackupCopyToken": "Copy token",
"BackupScriptInfo": "A shell script that downloads every backup file with curl. Save it and run it in a terminal; it prompts for your backup token, so no secrets are stored in the script.",
"BackupRestoreGuide": "Backup & restore guide",
"BackupRestoreGuideInfo": "Step-by-step instructions for downloading this backup and restoring it into another Huly instance.",
"NonBackupedBlobs": "Non Backed up blobs",
"Calendar": "Calendar",
"StartOfTheWeek": "Start of the week",
"SystemSetupString": "System Setup ({day})",
"DefaultString": "Default ({day})",
"AddAttribute": "Add attribute",
"WorkspaceNamePattern": "Name must be 40 characters or less, not empty, and cannot contain special characters (<, >, /)",
"Mailboxes": "Mailboxes",
"CreateMailbox": "Create mailbox",
"CreateMailboxPlaceholder": "my-cool-name",
"MailboxNoDomains": "Email domains are not configured",
"MailboxLimitReached": "Mailbox limit reached",
"MailboxErrorInvalidName": "Mailbox name is invalid",
"MailboxErrorDomainNotFound": "Domain not found",
"MailboxErrorNameRulesViolated": "Mialbox name must be {minLen} to {maxLen} characters long",
"MailboxErrorMailboxExists": "This mailbox name is already occupied",
"MailboxErrorMailboxCountLimit": "Mailbox count limit reached for your account",
"DeleteMailbox": "Delete mailbox",
"MailboxDeleteConfirmation": "Are you sure you want to delete this mailbox?",
"DisablePermissions": "Disable role-based access control",
"EnablePermissions": "Enable role-based access control",
"DisablePermissionsConfirmation": "Are you sure you want to disable role-based access control? All roles and permissions will be disabled.",
"EnablePermissionsConfirmation": "Are you sure you want to enable role-based access control? All roles and permissions will be enabled.",
"BetaWarning": "Modules labeled as beta are available for experimental purposes and may not be fully functional. We do not recommend relying on beta features for critical work at this time.",
"IntegrationFailed": "Failed to create integration",
"IntegrationError": "Please try again or contact support if the problem persists",
"EmailIsUsed": "Email address is already used by another account",
"Customize": "Customize",
"GuestAccess": "Anonymous guests",
"GuestAccessDescription": "Allows anonymous users to visit your workspace in read-only mode",
"GuestSignUpDescription": "Allows anonymous users to join your workspace as guests with limited editing rights",
"GuestChannelsDescription": "Channels where guests can write messages after joining",
"GuestChannelsArrayLabel": "Select channels",
"GuestSelectSpaces": "Select spaces",
"GuestAutoJoinAvailableSpaces": "Auto-join spaces",
"GuestAutoJoinAvailableSpacesHint": "Each application card has its own “Auto-join spaces” row: choose where workspace guests are added when they activate. Changes apply immediately.",
"GuestAnonymousVisibleSpaces": "Spaces visible to anonymous",
"GuestAnonymousVisibleSpacesHint": "Each application card has its own row: pick spaces where the read-only anonymous account is added as a member so visitors without an account can open them. Changes apply immediately.",
"ManageIdentities": "Manage identities",
"Release": "Release",
"ReleaseSocialId": "Release social ID",
"ReleaseSocialIdConfirm": "Are you sure you want to release this social ID: {socialId}? This will remove it from your account and you will no longer be able to use it to log in. Also, all associated integrations will be removed.",
"ReleasePrimarySocialId": "Release primary social ID",
"ReleasePrimarySocialIdConfirm": "Releasing your current primary social id will require the page to be reloaded. Are you sure you want to proceed?",
"Login": "Login",
"Primary": "Primary",
"MyIntegrations": "My integrations",
"AllIntegrations": "All",
"ConnectedIntegrations": "Integrated",
"AvailableIntegrations": "Available",
"Connect": "Connect",
"Integrate": "Integrate",
"FailedToLoadIntegrations": "Failed to load integrations",
"FailedToDisconnect": "Failed to disconnect integration",
"ServiceIsUnavailable": "Service is unavailable",
"Integrated": "Integrated",
"Connected": "Connected",
"Disconnected": "Disconnected",
"Available": "Available",
"NotConnectedIntegration": "The account {account} is not integrated with the workspace",
"IntegrationIsUnstable": "Integration service is experiencing issues. Some features may not work properly.",
"MinValue": "Minimum value",
"MaxValue": "Maximum value",
"IntegerOnly": "Integer numbers only",
"AccessControl": "Access control",
"DangerZone": "Danger zone",
"IdentifierExists": "Identifier already exists",
"Reset": "Reset",
"Restricted": "Restricted",
"RestrictedAttributeWarning": "Are you sure you want to restrict changing this attribute? This action will create permissions for this attribute and cannot be undone.",
"PasswordAgingRule": "Password aging rule",
"PasswordAgingRuleDescription": "Number of days after which users will be required to change their password.",
"OfficeSettings": "Office settings",
"OfficeDefaultSettings": "Default settings for meeting rooms",
"DefaultStartWithTranscription": "Enable transcription in new office rooms",
"DefaultStartWithRecording": "Enable recording in new office rooms",
"GuestPermissionsSettings": "Guests",
"GuestPermissionsApplicationPermissions": "Application permissions",
"GuestPermissionsApplicationPermissionsHint": "Choose which applications guests can use, then adjust permissions for each application below.",
"GuestPermissionsTabGuest": "Guest",
"GuestPermissionsTabAnonymousGuest": "Anonymous guest",
"GuestPermissionsAnonymousApplicationHint": "Application access for anonymous (read-only) guests. Which applications they see can also depend on deployment configuration.",
"ImportDocumentPermission": "Import documents",
"ImportDocumentDescription": "Grants users ability to import documents into the workspace",
"SelectUsers": "Select users",
"ShowInTitle": "Show in title",
"SpaceMembersOnly": "Space members only",
"Security": "Security",
"TwoFactorAuth": "Two-factor authentication",
"TwoFactorAuthDescription": "Two-factor authentication adds an extra layer of security to your account",
"EnableTwoFactorAuth": "Enable two-factor authentication",
"DisableTwoFactorAuth": "Disable two-factor authentication",
"TwoFactorAuthEnabled": "Two-factor authentication is enabled",
"TwoFactorAuthDisabled": "Two-factor authentication is disabled",
"ShowQRCode": "Show QR code",
"EnterVerificationCode": "Enter verification code",
"OverrideAttribute": "Override attribute",
"Required": "Required",
"ApiTokenStatusActive": "Active",
"ApiTokenStatusExpiring": "Expiring",
"ApiTokenStatusRevoked": "Revoked",
"ApiTokenStatusExpired": "Expired",
"ApiTokenExpiry7Days": "7 days",
"ApiTokenExpiry30Days": "30 days",
"ApiTokenExpiry90Days": "90 days",
"ApiTokenExpiry180Days": "180 days",
"ApiTokenExpiry365Days": "365 days",
"ApiTokenLoadError": "Failed to load API tokens",
"ApiTokenCreateError": "Failed to create token. Please try again.",
"ApiTokens": "API Tokens",
"CreateApiToken": "Create token",
"ApiTokenName": "Token name",
"ApiTokenExpiry": "Expiration",
"ApiTokenCreated": "Token created",
"ApiTokenRevoke": "Revoke token",
"ApiTokenRevokeConfirm": "Are you sure you want to revoke this token? It will no longer be usable for API access.",
"ApiTokenCopyWarning": "Copy this token now. You won't be able to see it again.",
"ApiTokenNoTokens": "No API tokens yet",
"ApiTokenWorkspace": "Workspace",
"Created": "Created",
"Expires": "Expires",
"TokenStatus": "Status",
"ApiUsageTitle": "Using the REST API",
"ApiUsageDescription": "Use your API token with the built-in REST API to query and modify workspace data. Pass the token as a Bearer token in the Authorization header.",
"ApiEndpointPing": "Health check",
"ApiEndpointFindAll": "Query documents by class",
"ApiEndpointFindAllPost": "Query with filters (JSON body)",
"ApiEndpointTx": "Create or update documents",
"ApiEndpointLoadModel": "Load the data model",
"ApiEndpointAccount": "Get account info",
"ApiBaseUrl": "Base URL",
"ApiWorkspaceId": "Your workspace ID (UUID) is included in the token. Pass it as :workspaceId in the URL.",
"ApiTokenRevokeError": "Failed to revoke token. Please try again."
}
}