Harden the last old-version-anchored seds in releases/version.sh, the
same fragile pattern that froze the Docker ARG VERSION and the wekan.fi
install page:
- snapcraft.yaml bundle download (RELEASE-CRITICAL, same class as the
Docker bug: the snap downloads wekan-<v>-<arch>.zip, so a stale value
ships the wrong bundle under the right name). Anchor on 'wekan-<v>-'
and 'releases/download/v<v>/' and assert; was stuck at 9.57.
- sandstorm appVersion: global self-healing rewrite; dropped the
redundant $OLD_NO_DOTS-anchored fixup.
- Offline.md doc links: self-healing with a soft warning (cosmetic, so
it must not fail the release); was stuck at 9.57.
Also corrected snapcraft.yaml and Offline.md from their stale 9.57 to
the current 9.62 (package.json is now v9.62.0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
release-website.sh (and version.sh's local-flow copy) updated the
install page version with a sed anchored on >v$OLD</span>. Once the
published page's value did not match the $OLD passed for a release, the
substitution silently no-op'd, so the page stuck at an old version
(v9.53) while every later release passed a newer $OLD that no longer
matched. Same fragile-old-version-anchor failure mode as the Dockerfile
ARG VERSION bug.
Anchor on the stable <span class="version-number"> instead and
re-normalize whatever version is there to v$NEW, with an assert so a
miss fails loudly instead of silently. Self-healing from any stale value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Admin Panel reads the WeKan version from the bundled package.json,
which comes verbatim from the wekan-<version>-<arch>.zip the Dockerfile
unzips into /build. The release-all.yml docker job never passed
--build-arg VERSION, so every image built against the Dockerfile's
hardcoded ARG VERSION default, which was stuck at 9.57 because
releases/version.sh rewrote it with a sed anchored on the old version
number (silently no-op when old_version did not match). Images tagged
v9.59/v9.60/v9.61 thus shipped the v9.57 bundle and reported 9.57.
- docker job now passes --build-arg VERSION=${VERSION}
- version.sh anchors the Dockerfile ARG VERSION rewrite on the
^ARG VERSION= prefix and asserts the result
- new pre-build guard fails fast if the release bundle's app
package.json version does not match the release tag
- corrected the stale Dockerfile default to 9.60
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The win64 attach step failed with "failed to run git: fatal: not a git
repository" — gh release upload tries to detect the target repo from a git
remote, but build-win64 checks the repo out into src/ (workspace root is not a
repo) and build-mac-arm64 / build-extra-arches do not check it out at all.
Set GH_REPO=${{ github.repository }} on all three attach steps so gh targets
wekan/wekan directly without git remote detection. The snap job is unaffected
(it does a full checkout into the workspace root).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add upcoming section documenting the win64/mac-arm64 post-release move and
the armv7l removal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The build-extra-arches armv7l matrix entry failed with "no matching manifest
for linux/arm/v7" pulling node:24-slim. Root cause: Node.js 24 publishes no
armv7l (32-bit ARM) binaries at all — neither the official dist nor unofficial
builds — so there is no node:24 arm/v7 image to rebuild native modules against,
and no Node 24 runtime to run such a bundle on an armv7 device.
Remove the armv7l matrix entry; the extra-arch bundles are now s390x + ppc64le.
armv7 was already excluded from the Docker image and snap, so those are
unaffected. Comments document why armv7l is not built.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build-win64 and build-mac-arm64 were prerequisites of the `release` job, so a
slow or flaky Windows/macOS runner blocked creation of the GitHub Release (and
thus Docker and snap, which depend on it).
Move them alongside build-extra-arches: they now depend on `release` instead,
and each attaches its own wekan-<version>-<platform>.zip to the already-created
Release via `gh release upload --clobber`. The core release now waits only on
the amd64 + arm64 bundles. No build steps changed; only the dependency wiring,
upload mechanism, and section grouping/comments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Release All Platforms bump job regenerates public/api/wekan.yml via
openapi/generate_openapi.py and renders it with @redocly/cli, whose strict
YAML parser rejects duplicate mapping keys. The 3-level nested SimpleSchema
in models/attachmentStorageSettings.js (storageConfig.filesystem.enabled,
storageConfig.gridfs.enabled, ...) exposed two generator bugs:
- The sub-schema name was derived from only the first dotted path segment,
collapsing filesystem.* and gridfs.* leaf keys (enabled/read/write) into a
single mapping.
- The linear emitter reopened the parent schema header once per interleaved
nested object.
Build sub-schema names from all leading path segments, and stable-sort dotted
properties so each sub-schema's fields stay contiguous and its header is
emitted once. Output is unchanged for existing 1- and 2-level schemas;
@redocly/cli now renders the spec successfully.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
package-lock.json (lockfileVersion 3) stores the WeKan root version twice:
the top-level "version" and the nested packages."".version. The old
0,/regex/ sed replaced only the first one, so packages."".version stayed
stale. The manual flow (release.sh) accidentally masked this because
rebuild-release.sh later runs `meteor npm install`, which re-syncs the lock
before commit; but the remote flow (release-all.yml bump job) commits
version.sh's output directly with no intervening npm install, pushing a
package-lock.json whose packages."".version is stale.
Anchor the replacement on the 'v'-prefixed WeKan version ("version": "v...")
and make it a global replace, so both root fields update. The 'v' prefix is
unique to the WeKan version (dependency versions are plain semver like
"5.2.0"), so this never touches any dependency version number.
Node.js (24.x) and MongoDB (7.x) bumps are left as-is: the pinned major is
intentional and the [0-9]+.[0-9]+ minor/patch already bumps any older
24.x/7.x to the newest 24.x/7.x without rewriting other version numbers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opening All Boards / Templates no longer auto-creates an empty Template
Container board (the boardList autorun that called ensureTemplatesBoard on
view open is removed). The container is now created only on demand via the
"Add Template Container" button.
To make a user-created container actually usable, createBoardWithInitialSwimlanes
now wires the user's profile template pointers (templatesBoardId and the
card/list/board template swimlane ids, keyed by a new per-swimlane `role`) when
type is 'template-container'. Without this the container looked right but stayed
inert, because swimlane.isCardTemplatesSwimlane()/... compare against those
pointers to decide that an added card/list/swimlane/board becomes a template.
Deleting a Template Container (boardRemover) now clears those profile pointers
for any user referencing it, so no save/insert-from-template path is left
pointing at a dead board and a fresh container can be created.
Adds Playwright e2e coverage: opening /templates does not auto-create a
container, the create flow wires the profile pointers, and delete clears them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the bugs the new Playwright specs (30/31/32/33) surfaced in the recently
added All Boards / Templates redesign and per-org/team/domain board sharing:
- server/imports.js: import the new server method modules (ldapGroupSync,
sharedTemplateTargets, propagateOrgTeamMembers). This app uses an explicit
server mainModule, so files not imported here are never loaded and their
Meteor methods are not registered ("method not found").
- server/ldapGroupSync.js (setUserOrgsTeamsFromLdap): call check() on ALL
arguments FIRST, before the admin guard. When the guard threw before the
checks ran, audit-argument-checks raised "Did not check() all arguments" as an
unhandled rejection, which SyncedCron's global unhandledRejection handler
turned into a full app crash. Also read the caller with an explicit
{_id}-selector + isAdmin projection.
- server/models/org.js, server/models/team.js: the per-org/team feature-toggle
methods (setOrg/Team SharedTemplates, PropagateMembersToBoards,
SyncMembersFromAuth, setAll*Feature) now check() arguments unconditionally and
authorize via a callerIsAdmin(this.userId) helper instead of
Meteor.user()/getCurrentUser(), which can return null inside an async method
after an await.
- server/publications/boards.js (publishComposite 'boards'): publish
template-container boards and domain-shared boards too (was hardcoded
type:'board'), so the Templates sub-view and domain-based sharing actually
show their boards.
- client/components/boards/boardsList.js: the /templates and /remaining routes
now apply the same membership filtering as the home route (previously only
'home' did, so those views were empty).
- tests/playwright/specs/33-board-domains.e2e.js: assert the domain-shared board
via the Remaining sub-view; the default All Boards view is Starred, which only
lists starred boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running the Playwright browser suites (and, in "Run ALL tests", also Mocha +
import + Node E2E) all at once against a single dev server uses too much RAM and
swap on lower-memory machines and can crash them. Run the test jobs one at a
time instead:
- rebuild-wekan.sh: "Run ALL tests" (launch_job) now runs each job to completion
sequentially instead of backgrounding them; "Test Playwright ALL browsers" runs
Chromium, Firefox and WebKit one after another rather than as three concurrent
background processes.
- rebuild-wekan.bat: "Run ALL tests" runs each job with `call` one at a time
(no more `start` windows + .done polling); the all-browsers options use
Playwright `--workers=1` so the three browser projects run one at a time.
Menu labels and messages updated to say "sequentially".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds admin-only REST endpoints modelled on the existing /api/settings ones
(WebApp.handlers + Authentication.checkUserId gate + @operation JSDoc that
feeds the OpenAPI generator): GET /api/admin/domains; GET/PUT
/api/admin/attachment-settings (cloud secrets stay masked, update reuses the
updateAttachmentStorageSettings method path); and GET + per-id/all feature
toggles under /api/admin/orgs and /api/admin/teams. api.py gains matching
admindomains/attachmentsettings/editattachmentsetting/adminorgs/adminteams
and the org/team feature commands. Human REST-API.md docs left to the docs agent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add REST endpoints for board domain sharing and global-admin settings, document
exporting large boards without base64 attachments, and document template
drag-to-share drop-target gating plus Propagate Members To Boards behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For each org/team with orgPropagateMembersToBoards / teamPropagateMembersToBoards
enabled, add that org/team's member users as members of the regular boards that
list it. Add-only (existing members untouched) and template boards are skipped.
New method propagateOrgTeamMembersToBoards, triggered at the end of LDAP sync().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Templates view drop targets now come from a new non-admin getMyShareableGroups
server method, which returns only the user's orgs/teams that have orgSharedTemplates /
teamSharedTemplates set (plus email domains), instead of every org/team the user is in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The snap build broke because the Node.js download used the floating
nodejs.org/dist/latest-v24.x/ path with a pinned filename, which 404s once
upstream advances past that release. snapcraft.yaml/Dockerfile now use the
explicit version path (fixed in f74467837), so version.sh no longer needs to
rewrite the latest-v24.x pattern; removing it ensures the bump script can never
re-introduce the bad floating path. The WeKan version itself is already bumped
correctly in snapcraft.yaml by version.sh (version field, download URL and zip
name) and guarded by release-all.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the All Boards / Templates view, the left menu now shows the user's
Organizations, Teams and Domains as drop targets (gated by the admin's Shared
Templates scopes). Dragging a Template Board onto one shares the board with that
org/team/domain via setBoardOrgs / setBoardTeams / setBoardDomains — add-only,
preserving existing shares and adding no individual members. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds GET /api/settings and PUT /api/settings, restricted to the global admin
(Authentication.checkUserId), to read and update the Admin Panel global settings
(registration, product name, logos, custom head/manifest, accessibility and
support pages, etc.). A whitelist of fields is exposed; mailServer/SMTP
credentials are deliberately never returned or writable over REST. Documented
with @operation JSDoc (OpenAPI) and api.py examples (getsettings, editsettings).
Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds GET/POST/DELETE /api/boards/:boardId/domains REST handlers in
server/models/boards.js with @operation JSDoc (so OpenAPI is generated),
mirroring the board-member endpoints' auth and error handling, plus
boarddomains/addboarddomain/removeboarddomain examples in api.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers setBoardDomains normalisation/validation/de-dup, the admin-only
getDomainsWithUserCounts counts (and non-admin rejection), and that a board
shared via a user's email domain shows up in that user's All Boards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document LDAP background-sync user disabling and Organizations/Teams group sync,
OAuth2/OIDC auto-registration and admin groups, NOTIFY_ON_ASSIGN, and the Admin
Panel People Domains tab plus per-org/team toggles and domain board sharing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generic Create Board popups no longer show an "Add template board" checkbox.
Template boards are created only via the dedicated "Add Template Board" tile on
the All Boards / Templates view (createTemplateContainerPopup), which now signals
template creation through a Session flag instead of pre-checking a hidden
checkbox. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On template boards (template-board / template-container), the board members
"People" tab now shows only the original creator and hides the add-individual-
member button, so these shared boards are shared only with groups (organizations,
teams, domains) — not individual users. Adds a Board.isAnyTemplateBoard() helper.
Regular boards are unchanged. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the existing add-organization-to-board feature for free-form email
domains. Adds a board-admin-gated setBoardDomains method (plus add/remove
single-domain helpers) with trim/lowercase validation, a Domains tab in the
members sidebar listing board.domains with add/remove popups, and the i18n keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a board.domains field (mirroring board.orgs/board.teams) so a board can be
shared with an email-address domain: every user whose primary email is in an
active domain on the board gets access. Adds a User.emailDomains() helper and
wires the new dimension into the board visibility selector, the board
publications and the All Boards membership query. UI to add domains to a board
and the groups-only restriction follow separately. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the LDAP/OIDC group sync (setUserOrgsTeamsFromLdap) creates or assigns an
Organization or Team, it now sets that org/team's orgSyncMembersFromAuth /
teamSyncMembersFromAuth flag to true, so the "Sync Members From Auth Provider"
column in Admin Panel > People reflects which orgs/teams are auth-managed.
Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a Domains tab to Admin Panel > People that lists every email-address domain
across all users with the count of users in each (each user counted once by
their primary email's domain, sorted by count). Backed by a new admin-only
getDomainsWithUserCounts method. This is the per-domain piece of the Template
Boards sharing feature. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
helpers/mongo-runner.js discarded the driver result for insertOne/insertMany, so
db.insertOne(...) returned null and callers could not read insertedId. Capture
and return the result (empty insertMany yields insertedCount 0). Lets specs use
the returned id instead of pre-generating _ids.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spec 32 exercises setOrg*/setTeam* per-record toggles (passed a Mongo
selector {_id}) plus the bulk setAllOrgsFeature/setAllTeamsFeature, and
asserts non-admin calls are silent no-ops that leave flags unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers ensureTemplatesBoard lazy creation/idempotency, the /templates and
/remaining All Boards sub-views filtering by board type, and the member-menu
Templates link navigation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-contained Node + assert test for oauth2AdminStatusFromGroups in
packages/wekan-oidc/loginHandler.js, covering unset/empty env, matching and
non-matching groups, comma+space parsing, object groups, and non-array input.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /templates and /remaining routes (plus the home / Starred view) now address
the All Boards left-menu sub-views via the boardListMenu Session value.
- boardList honors the route: selectedMenu initializes from boardListMenu and an
autorun switches the view live for the three recognized menus, without
clobbering a workspace selection.
- The Member Settings -> Templates link now opens the All Boards / Templates page
(pathFor allboards-templates) instead of opening/creating a specific board;
unused templatesBoardId/Slug helpers removed from userHeader.js.
- New users no longer get an auto-created Templates board at signup. The
templates-container board (Templates + Card/List/Board Templates swimlanes) is
now created lazily via the new ensureTemplatesBoard Meteor method, called on
first use of the Templates view, auth-agnostic for any login method.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When OAUTH2_ADMIN_GROUPS (comma/whitespace separated group names) is set, an
OAuth2/OIDC user whose groups claim intersects the list becomes a Wekan admin;
if set but no intersection, they are not made admin. Group-name extraction
handles both string and displayName object forms. When the var is empty/unset
(the default), isAdmin is left untouched so existing behavior is unchanged. This
mirrors the LDAP_SYNC_ADMIN_GROUPS concept for OAuth2/OIDC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The user's group_member_format value (e.g. a member DN or cn) was interpolated
into the LDAP group search filter unescaped, so a value containing "(" or ")"
produced "illegal unescaped char: (" and broke sign-in when LDAP_SYNC_ADMIN_STATUS
(or any group sync) was enabled. Adds RFC 4515 escaping (escapeLdapFilterValue)
for ( ) * \ and NUL, applied to the member-format value in getUserGroups and
isUserInGroup. Strictly safer (also prevents filter injection). Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renames the "Sync Members From LDAP" column to "Sync Members From Auth Provider"
so the setting and feature apply to any authentication provider (LDAP,
OAuth2/OIDC, SAML, etc.), not just LDAP. Renames the field/method/i18n key from
*SyncMembersFromLdap / sync-members-from-ldap to *SyncMembersFromAuth /
sync-members-from-auth across schema, publications, admin methods and the admin
UI, and generalizes the translation string and code comments. The other two
columns (Shared Templates, Propagate Members To Boards) were already
auth-agnostic. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds three per-record boolean settings to Organizations and Teams, each shown as
its own checkbox column in Admin Panel > People > Organizations / Teams, disabled
by default:
- Shared Templates (orgSharedTemplates / teamSharedTemplates)
- Propagate Members To Boards (orgPropagateMembersToBoards / teamPropagateMembersToBoards)
- Sync Members From LDAP (orgSyncMembersFromLdap / teamSyncMembersFromLdap)
Each column header has select-all / unselect-all (bulk set across all orgs/teams),
and the existing per-tab search box filters the rows. Schema fields, admin-only
toggle methods (per-record and bulk), and the publication projections are added;
all default off. This is the settings surface; the behaviours these flags gate
are wired separately. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS is enabled, the background
sync now also re-enables (loginDisabled=false) an LDAP user that is present in
the directory again, in addition to disabling users that disappeared — so LDAP
is the authoritative source of active status (matching the external
docs/Login/ldap-sync/ldap-sync.py behaviour). This recovers from a removal or a
transient outage, and overrides a manual disable of an LDAP user. Still off by
default. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setUserOrgsTeamsFromLdap may now be called by an admin in addition to the
server-side LDAP sync (admins already manage orgs/teams in the Admin Panel);
other client calls are still rejected. Adds a Playwright e2e spec that verifies a
group is created as a Team/Organization (active), added to the user, add-only
(existing memberships preserved), idempotent, and that a non-admin call is
rejected. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the org/team sync into a shared syncUserGroupsToOrgsTeams helper and
calls it from the login handler (both existing-user and new-user branches) in
addition to the background sync, so a user's LDAP groups become Wekan
Organizations/Teams on login as well. Still gated by LDAP_SYNC_ORGANIZATIONS /
LDAP_SYNC_TEAMS (default off). Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New optional, default-off settings sync a user's LDAP groups into Wekan
Organizations and/or Teams during background sync:
- LDAP_SYNC_ORGANIZATIONS (default false) — create/assign Organizations.
- LDAP_SYNC_TEAMS (default false) — create/assign Teams.
- LDAP_SYNC_ORGANIZATIONS_GROUPS / LDAP_SYNC_TEAMS_GROUPS — optional
comma-separated allowlists restricting which of the user's groups become
orgs/teams (empty = all of the user's groups).
The user's groups are read with the existing LDAP_GROUP_FILTER_* machinery
(getUserGroups now also runs when org/team sync is enabled). The create/assign
runs in a new server-only app method (setUserOrgsTeamsFromLdap) that uses the
real Org/Team models, finds an org/team by display name, creates it (active) if
missing, and adds it to the user's membership. The assignment is add-only, so
existing memberships — including manual ones — are never removed. Off by
default, so existing deployments are unaffected. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New optional LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS (default false):
when enabled, the background sync disables (loginDisabled=true) LDAP-sourced
Wekan users that are no longer found in the LDAP directory. Off by default, so
existing deployments are unaffected; only LDAP users are considered, and
already-disabled accounts are left untouched (no auto re-enable, so manual
disables are respected). Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LDAP_SYNC_ADMIN_STATUS previously only updated a user's admin status at login,
so an admin-group change in LDAP was never reflected for existing users who did
not log in. The background sync now applies the same admin-status logic as the
login handler to each existing LDAP user it syncs. Gated by the existing
LDAP_SYNC_ADMIN_STATUS flag (default off) and only runs when background sync of
existing users is enabled, so default behaviour is unchanged. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Big boards could not be exported as JSON because every attachment is base64-
encoded inline into one object that is then JSON.stringify-ed, which overflows
V8's maximum string length (and loads every attachment buffer into memory at
once). Adds an optional ?attachments=false query parameter to
GET /api/boards/:boardId/export that exports the board structure and attachment
metadata while skipping the base64 file data entirely, so large boards export
successfully. A matching "Export / JSON (without attachments)" entry is added to
the board export menu. The default export is unchanged (attachments still
included). Includes a REST regression test. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a user is added as a card member (joinMember) or assignee (joinAssignee),
that user is now added to the notification watchers so the assignment reaches
the assignee themselves, instead of only board watchers — or, with
BIGEVENTS_PATTERN, every active board member. The performer of the action is
still skipped, so self-assignment does not self-notify. Optional: opt out with
NOTIFY_ON_ASSIGN=false; on by default. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reframe the Template Boards entry as improvements to the existing card/board
templates feature (All Boards page changes), and link issue #5850.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Optional and off by default: when OAUTH2_AUTO_REGISTRATION=false, a first-time
OAuth2/OIDC login that does not already match a Wekan account (by verified email,
the same secure key the existing account-merge logic uses) is rejected instead of
silently creating a new account. Lets an instance allow only already-provisioned
users (e.g. synced from LDAP) to sign in via OAuth2. Default 'true' keeps the
previous behaviour, so existing deployments are unaffected. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describes the planned feature: a dedicated "Add Template Board" button at
All Boards / Templates (replacing the "Add template board" checkbox on the
"+ Add board" button); Template Boards containing Board/Swimlane/List/Card
templates that members can add to their boards; drag-to-share personal Template
Boards onto Organization / Team / Domain boards when enabled in Admin Panel;
Domain meaning the email-address domain; and a new Admin Panel / People /
Domains page listing domains and per-domain user counts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Playwright e2e tests for recent features: the "Mark as complete" Show at
Minicard option (allowsDueCompleteOnMinicard — hidden by default, shown when
enabled) and the Admin Panel avatar-upload block (#4740 isAvatarUploadBlocked /
limitSettings.avatarsUploadBlocked — disabled by default).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds limitSettings.avatarsUploadBlocked (default false — avatars enabled)
to Admin Panel > Attachments > Transfer limits. When enabled, the avatar
upload option is hidden in the user avatar popup and new avatar uploads are
rejected server-side (server/permissions/avatars.js), mirroring the existing
attachmentsUploadBlocked. Useful when avatars are synced from an external
source. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Release All Platforms" workflow now also builds the WeKan server bundle
for every other architecture Node.js 24.x publishes binaries for — s390x,
ppc64le and armv7l — by rebuilding the bundle's native modules under QEMU
emulation, and attaches each `wekan-<version>-<arch>.zip` to the GitHub Release
as an extra download. These extra-architecture bundles are not part of the
Docker image or snap (run a separate MongoDB on those platforms), and are built
independently so a slow or failing emulated build never blocks the main
amd64/arm64/win64/mac release, Docker or snap.
Thanks to xet7.
The Launchpad snap build produced no `.snap` (only build logs), but
`snapcraft remote-build` exited 0 so the workflow reported success; the upload
step then globbed `wekan_<version>_*.snap`, matched nothing, and passed the
literal pattern to `snapcraft upload` ("is not a valid file", exit 64).
Hardened the workflow to verify a `.snap` was actually produced (instead of
trusting the exit code alone), print the Launchpad build logs when it was not,
and use `nullglob` + existence checks in the upload/attach steps so a no-match
fails clearly instead of cryptically. Also made `releases/version.sh` set
`snapcraft.yaml`'s `version:` robustly — matching `^version:` rather than a
hard-coded line number and tolerating any quoting, then verifying the change
applied — so the snap can no longer be built with a stale version that the
upload glob cannot match, and the snap job fails fast if the tagged version
does not match the release.
Thanks to xet7.
toggle](https://github.com/wekan/wekan/issues/6385). The "Mark as complete"
row in Board Settings → Card Settings now has its own "Show at Minicard"
checkbox (new board setting `allowsDueCompleteOnMinicard`), unchecked by
default — so the complete toggle is no longer shown on the current board's
minicards unless it is explicitly enabled.
Thanks to xet7.
Every Playwright end-to-end test (all 183, across Chromium, Firefox and WebKit) failed at
the `waitForMeteor` step with `TimeoutError: page.waitForFunction`, because
`typeof Meteor` was `undefined` in the browser `window` scope. Under the Meteor 3.5 +
rspack build, bare `Meteor` references in app code are rewritten by rspack's ProvidePlugin
into per-module imports, so `Meteor` is no longer placed on `window` the way the classic
Meteor linker did. The tests call `Meteor.loginWithToken` / `Meteor.userId` /
`Meteor.subscribe` etc. via `page.evaluate` (which runs in `window` scope), so they could
never proceed past login. Fixed by re-exposing `window.Meteor = Meteor` early in
`client/00-startup.js`, restoring the long-standing classic-Meteor behaviour where `Meteor`
is a global (also handy in the browser console).
Thanks to xet7.
incomplete multi-character HTML sanitization in card dependency
import allowed HTML/script element injection
(CWE-79 Cross-site Scripting, CWE-80 Improper Neutralization of Script-Related HTML
Tags, CWE-116 Improper Encoding or Escaping of Output; GitHub CodeQL code scanning
alert #421, rule `js/incomplete-multi-character-sanitization`, severity High).
`stripHtml()` in `client/lib/importDependencies.js` removed HTML tags with a single
pass of `/<[^>]*>/g`. That is an incomplete multi-character sanitization in two ways:
removing one match can splice surrounding text into a new match (so the replacement
must be looped to a fixed point), and a dangling, unclosed tag that has no closing
`>` (for example a trailing `<script` or `<svg/onload=...`) is never matched by the
regex at all and survives untouched — leaving `<script` in the output, exactly as the
scanner warned. A crafted card-dependency ("Red Strings") import file (the
WeKan/generic JSON or Miro item titles and connector captions that pass through
`stripHtml`) could therefore smuggle an HTML/script fragment past the sanitizer.
Fixed by looping the tag-stripping replacement to a fixed point and then removing any
remaining stray `<`/`>` characters, so neither a complete nor a partial tag can
remain.
Thanks to GitHub CodeQL, xet7 and Claude !
the top of the page when a card was opened as a popup (for example from the
Board Table view Edit link or from search results). The card details content is
always position:fixed, so it renders as its own framed box and escapes the
generic popup wrapper, leaving that wrapper (with its "Card Details" title
header, border and background) visible as an empty box. The wrapper is now
collapsed and made invisible so only the card itself shows. The card's own close
button now closes the popup (in addition to clicking outside or pressing Escape).
Thanks to xet7.
Members, Labels, Received, Start, Due and End. The Received, Start, Due and End
dates are formatted and styled the same way as on the opened card details and the
Gantt view (reusing the cardReceivedDate / cardStartDate / cardDueDate / cardEndDate
badge templates), and can be sorted by clicking their column headers. Clicking a
date opens the date-select popup so it can be changed, and an add (+) button is
shown for empty dates — both only for board roles that have permission to change
the card (the same `canModifyCard` check used on card details). A Received, Start,
Due or End column is hidden when both its "Show at Card" and "Show at Minicard"
Card Settings are unchecked for the board. The Labels cell now word-wraps so long
label names no longer overflow across the other columns. Each row also has a
leftmost "Edit" link (pencil icon + text) styled like the one on the Admin Panel /
People page; clicking it opens the Card Details popup on top of the Board Table
view.
Thanks to xet7 !
Labels cells, so emoji shortcodes and markdown (for example `:thumbsup:`) display
rendered instead of as literal text, and word-wrap the Card, List and Swimlane
cells so long text no longer overflows across the other columns.
Thanks to xet7.
The `tableView.jade`, `tableView.js` and `tableView.css` files of the new Board
Table view were never imported in `client/features/boards.js`, so the `tableView`
template was not bundled and selecting the Table view threw a Tracker recompute
exception. Added the missing imports.
Thanks to xet7.
The board view menu (Swimlanes, Lists, Calendar, Gantt) gains a new "Table" entry
below Gantt. It shows every card of the current board in a table — Card, List,
Swimlane, Members, Labels and Due Date — using the same styling as the My Cards
table view (the `.my-cards-board-table` CSS classes). Whereas My Cards' table spans
all of the user's cards across boards, this is the per-board equivalent showing all
of the current board's cards. The view is stored like the others as the user's
`board-view-table` boardView. It also has a search box and previous/next pagination
styled like the Admin Panel / People page, and Excel-like column sorting: click the
Card, List, Swimlane or Due Date header to sort by it, click again to reverse, with
an arrow showing the active column and direction. Search, sort and pagination run
client-side over the board's already-loaded cards.
Thanks to xet7 and Claude.
Dragging empty space to scroll already worked on the board Swimlanes view but not on
these pages. The dragscroll library scrolls whichever element carries the `dragscroll`
class, and that element must be the actual scroll container. On All Boards, My Cards,
Login and Register the page scroll container is `<body>` (the previous `dragscroll`
class on `ul.board-list` only scrolled in mobile view), so a small shared helper
(`client/lib/pageDragscroll.js`) now toggles the `dragscroll` class on `<body>` while
those templates are mounted and calls `dragscroll.reset()`; the library has a dedicated
`el == document.body` branch for whole-page scrolling. In the board Lists view the
`.board-canvas` only received `dragscroll` when the board had swimlanes, so vertical
drag-scroll failed on swimlane-less boards; the canvas now always carries the class.
One-finger touch scrolling (client/lib/dragscrollTouch.js) covers these too.
Thanks to xet7 and Claude.
Tall authentication forms (many login methods, legal notice, language selector, etc.)
could overflow the viewport without a reliable way to scroll to all fields. The auth
pages render directly into `<body>`, which already has `overflow-y: auto`, so the
vertical scrollbar now appears whenever the form is taller than the viewport, with a
real draggable thumb, placed on the right for LTR and on the left for RTL (since
`<body>` inherits `direction` from the html `dir` attribute). Two `<br>` are added
below the dialog so the bottom of the form scrolls fully into view. An earlier attempt
forced `overflow-y: scroll`, which left a non-draggable empty scrollbar track in
Chromium and no scrollbar at all in Firefox when the form fit the viewport; using the
default `overflow-y: auto` shows the scrollbar only when there is something to scroll.
Thanks to xet7 and Claude.
When changing the width of a list by dragging the resize handle between lists, the list's vertical scrollbar
(used to scroll up and down through the cards in a list) disappeared. Cause: dragging
the resize handle sets an inline `--list-width` property on the list, which made the
resize CSS rules force `display: block` on the list permanently. That collapsed the
flex-column layout so `.list-body` was no longer height-bounded and its
`overflow-y: auto` scrollbar never appeared. Fixed by keeping the flex column layout
(`display: flex; flex-direction: column`) instead of `display: block` during and after
resize; the explicit inline width still pins the list width.
Thanks to xet7 and Claude !
Fixes#6386
`rebuild-wekan.sh` menu option 9 ("Run ALL tests") previously errored out with
"Port 3000 is already in use" when a dev server was already running, forcing the
user to stop it manually. It now detects and stops the existing Meteor dev server
before starting its own.
Details:
- When port 3000 is busy, option 9 finds the existing Meteor dev server
(`pgrep -f 'meteor run --port 3000'`) and sends it a normal `kill`, which also
tears down the node child it spawned.
- It then polls the port for up to 30s, escalating to `SIGKILL` at the 15s mark
and falling back to `lsof -ti tcp:3000` (or `fuser -k 3000/tcp`) to catch
anything still holding the port whose command line does not match.
- Only if the port is still occupied after all that does it print an error and
abort; otherwise it reports "Port 3000 is now free" and starts its own server.
Thanks to xet7 and Claude.
the move, copy, copy-many and convert-checklist-item card dialogs could leave a
card in its original list instead of moving it.
Details:
- Root cause: the dialog's "Done" handler read the target board/swimlane/list by
scraping the DOM `<select>` elements. The reactive `boards()` helper can
transiently return `[]` while a `board` subscription re-resolves, leaving the
board `<select>` momentarily option-less, so `selectedIndex` was `-1` and the
scraped `boardId` was `undefined`. `card.move(undefined, …)` then failed
server-side with `ValidationError: Not permitted. Untrusted code may only
updateAsync documents by ID [403]`, and the card silently stayed put. This was
a gap in the earlier dialog fix, which bound only the swimlane and list options
to the live selection but left the board `<select>` on the (empty)
last-confirmed option.
- Fix: the Done handler now reads `boardId`/`swimlaneId`/`listId` from the
dialog's live reactive selection (`selectedBoardId`/`selectedSwimlaneId`/
`selectedListId`), which stays correct across re-renders, and the board
`<option selected>` attribute is bound to the live selection in all four
dialogs for UI consistency.
- Test hardening: three Playwright/Node E2E tests that flaked under the
all-parallel run (move-list-right, add-to-top/add-to-bottom, and the Node E2E
second-session list-order check) now wait for the board subscription to
populate before acting, instead of reading once after a fixed delay.
Thanks to xet7 and Claude !
the parallel "Run ALL tests" flow launched Mocha (in its own .meteor/local-test build)
before the :3000 dev server, so two full Meteor builds competed for CPU/disk
and the server took a long time to become ready — shown as a long line of
dots during the readiness wait. Mocha and the import regression do not need
the server, so they are now launched only after the server build is underway;
the server builds alone and boots fast again, while they still run in
parallel with the E2E and browser jobs. Applied to both
rebuild-wekan.sh and rebuild-wekan.bat.
Thanks to xet7 and Claude.
rebuild-wekan.sh/.bat run all test jobs in parallel against one dev server
(menu option 9) plus a new "all browsers in parallel" option (16), with
per-job logs and a PASS/FAIL summary. Mocha runs in its own .meteor/local-test
build dir; .gitignore ignores the new build dirs.
Fix three load-induced flaky failures surfaced by the parallel run:
- 03-cards-operations "move does not create duplicate cards": snapshot the
card titles only after the move settles (wait for the card in the target
list / gone from the source list) instead of mid-flight.
- 03-cards-operations "add-to-bottom places the card last": poll until the
reactive re-sort places the new card last, since submitNewCard only waits
for the card to exist.
- helpers/auth.js openBoard: 5 attempts x 20s so the slowest browser (WebKit)
survives contention from the 3-browser parallel run against one server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a board setting allowsDueComplete (defaultValue false) that controls whether
the "Mark as complete" toggle (.js-toggle-due-complete) is shown on cards. The
toggle in cardDetails is now gated behind currentBoard.allowsDueComplete, and a
new checkbox row in the board Card Settings popup lets each board enable it. It
is hidden by default as requested; existing boards (no field) keep it hidden
until enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The password form (.at-pwd-form) is hidden by default via CSS and only revealed
by JS once Meteor.call('isPasswordLoginEnabled') returns truthy. If that call
errored/returned late, or the accounts-templates form had not yet rendered its
fields when onRendered ran, $('.at-pwd-form').show() matched nothing and the
login page stayed without username/password fields (reported after a Snap
9.34 -> 9.46 upgrade on Ubuntu).
Now treat password login as enabled unless it is explicitly disabled
(result === false), and retry showing the form until the element exists (the
same wait-for-element pattern already used for the OAuth button), so a slow or
failed method call no longer locks users out.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getDefaultSwimline()/getDefaultSwimlineAsync() inserted a default swimlane as a
side effect whenever none was found. On the client these getters run inside
reactive render contexts, so for a board whose swimlanes are not yet
loaded/subscribed (e.g. the default subtasks board reached by viewing a subtask
as a standalone card via "All boards"), getSwimlanes() was transiently empty and
every re-render inserted another empty swimlane — producing thousands (2008 in
the report) and freezing the browser.
Only auto-create the default swimlane on the server (guarded with
Meteor.isServer); the server already creates it at board creation and self-heals
here. On the client the getters now return an existing swimlane or undefined
without inserting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the Windows batch script with the same interactive menu as
rebuild-wekan.sh so building, running and testing WeKan works on Windows
amd64/arm64 too: dev-server options, "Run ALL tests" (import regression, Mocha,
Node E2E and Playwright Chromium/Firefox/WebKit with a pass/fail summary), the
individual test options and the floating-promises guard. WebKit runs natively on
Windows, so no Docker is needed there. CRLF line endings per .gitattributes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Detect OS/arch (Linux amd64/arm64, macOS arm64). On Linux arm64, where the
bundled Playwright WebKit cannot launch natively, run the WebKit specs via the
official Playwright Docker image (mounting the repo, sharing the host network so
it reaches WeKan and Mongo); Linux amd64 and macOS arm64 keep running WebKit
natively. Override with WEKAN_WEBKIT_DOCKER=1/0. The "Run ALL tests" option now
runs Chromium, Firefox and WebKit (was Chromium only), each recorded separately
in the summary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Before running, probe each browser project and skip any that cannot launch
(e.g. the bundled WebKit needs old system libraries — libicu 74, libxml2 v2,
libevent 2.1 — that newer Linux arm64 distros like Ubuntu 26.04 no longer ship).
This removes false WebKit failures locally while still running every browser on
CI, where a genuinely broken browser fails loudly instead of silently shrinking
coverage. Override with WEKAN_PLAYWRIGHT_PROBE=1 (force probe) or =0 (disable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bind the swimlane and list <select> "selected" option in the move, copy,
copy-many and convert-checklist-item card dialogs to the live selection
(selectedSwimlaneId/selectedListId) instead of the last-confirmed dialog option,
so a Blaze reactive re-render (e.g. when board subscription data arrives) can no
longer silently revert the user's in-progress swimlane/list choice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Admin Panel / People / Shared templates scope checkboxes are live view
filters (no Save button by design), but selectedScopes started empty on every
load, so a checked option was lost on reload — looking like it "wasn't saved".
Persist the selection in localStorage (restore on create, save on toggle), so it
sticks across reloads without needing a Save button. Added an e2e test asserting
a checked scope survives a reload. Verified: spec 26 passes 6/6 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Translates the 26 newly-added untranslated keys (popup titles incl. dependency/
shared-templates/admin/error popups, the bookmarks popup, and the global search
'My cards / All cards' view toggle) using the existing Finnish terminology
(Taulu, Lista, Uimarata, Tiimi, Organisaatio, Tuo/Vie, Käsittelijä, Tarrat,
profiilikuva, riippuvuus). The remaining identical-to-English entries are
intentionally untranslated proper nouns / abbreviations / format placeholders.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builds the view toggle that the previously-dead globalSearchViewChange handler
hinted at (it had been an incomplete copy of the due-cards toggle, with no
button/popup/state). Now a real feature:
- globalSearchHeaderBar gets a view-change button (My cards / All cards) and a
globalSearchViewChangePopup, mirroring the due-cards pattern.
- Utils.globalSearchView()/setGlobalSearchView(): reactive (Session) + persisted
(localStorage), default "all". Reactive so the search re-runs immediately on
toggle (no page reload, unlike due cards).
- globalSearch re-runs reactively on the view + query; when "My cards", the
current user's username is injected as a `user:` predicate, reusing the
existing server selector ($or members/assignees) — no new server filtering.
- i18n (en) for the new strings + popup title.
Build-verified (meteor run compiles and starts). Logic reuses the already-tested
`user:` operator; not yet covered by an e2e spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bookmarks: Popup.open('bookmarksPopup') resolved to a non-existent
'bookmarksPopupPopup' template (open() appends "Popup"). Fixed both calls to
Popup.open('bookmarks') so the bookmarksPopup template + bookmarksPopup-title
("Starred boards") apply.
- Removed two dead handlers that opened non-existent templates with no
triggering button: 'click .js-import' -> boardImportBoard (boardHeader.js x2,
sidebar.js) and 'click .js-due-cards-view-change' -> globalSearchViewChange
(globalSearch.js; the globalSearchHeaderBar template has no such button).
- Set the error popup titles to "List width error" / "Swimlane height error".
- docs/Features/RedStrings: added ASCII-art step-by-step for drawing a line by
dragging the connect handle, and for changing a line's relation type/color/icon.
Verified: spec 27 passes 9/9 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds <popupName>Popup-title keys (en) for popups that had no title (WeKan shows
no header when the key is missing): the dependency icon picker, the on-board
dependency line editor, the dependency import popup, plus pre-existing untitled
popups (add/remove board org/team, change avatar, board backgrounds, card
assignee/member/stickers, invite people, list sort, list-width/swimlane-height
errors, restore archived card/list, rules import/export).
Skipped 3 Popup.open calls with no matching/clean template (boardImportBoard,
globalSearchViewChange — no template; bookmarksPopup — name mismatch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The overlay's recompute() ran this.find('.js-dependency-overlay') from deferred
requestAnimationFrame / setTimeout callbacks that could fire after the overlay
template was destroyed (toggling Show dependencies off, or a board re-render),
throwing "Can't select in removed DomRange". Guard recompute()/scheduleRecompute
with a destroyed flag (and the live view check), and clear the init setTimeout on
destroy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add merged Dependabot PRs azure/setup-helm #6378 and dompurify #6379 to the Upcoming updates section, with links to the pull requests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revise Red Strings drag-to-connect to the handle (no-mode) approach with Part 1/2 links, and add merged PR #6377 (docker/setup-buildx-action) to the updates section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the modal "Connect" toggle with an always-available, non-modal UX so
cards stay clickable:
- Each minicard shows a small connect handle (right edge, on hover) when the
dependency overlay is on and the user can edit the board. Drag it onto another
card to create a dependency (dashed guide line follows the cursor).
- Connection lines are individually clickable (pointer-events:stroke) to open the
edit/delete popup, while the overlay SVG stays click-through.
- Removed the board-header Connect toggle, the Session connect-mode state and the
is-connecting overlay capture; drag is initiated from the handle (capture-phase
mousedown so it beats the card drag-sort).
Updates the e2e test to drag the handle, docs to describe the no-mode UX, and
i18n (drag-to-connect, en/fi). Verified: spec 27 passes 9/9 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describe and link the Red Strings drag-to-connect mode + Miro/generic import (06bc92c).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a piplanning.io / Kendis / Miro-style drag-to-connect UX:
- Board header "Connect" toggle (members with edit access) enters a transient
connect mode (Session-based) and turns the overlay on.
- In connect mode the SVG overlay captures pointer events: drag from one card
to another to create a dependency (a dashed guide line follows the cursor),
and click a connection line to open a popup to change its relation
type/color/icon or delete it.
- Out of connect mode the overlay stays non-interactive (cards clickable).
Import from other tools (best-effort): the Dependencies (JSON/SVG) importer now
also maps Miro REST API data (items + connectors -> lines, resolved to titles;
"block"/"fix" captions -> relation type). Kendis/piplanning.io have no public
file format — documented the generic { "lines": [...] } interchange they (and
GitHub/GitLab) can be converted to.
Also: shared DEPENDENCY_ICON_CHOICES in models/metadata/dependencies.js (used by
card detail + the new line editor), i18n (en/fi) for connect/done, CSS for
connect mode + clickable lines + dashed temp line, docs updated, and an e2e test
for drag-to-connect. Verified: spec 27 passes 9/9 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Link the Red Strings test-suite commits as Part 1/2/3 on the 'Covered by tests' clause, including the previously-unreferenced openapi test hardening (415284748).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Link and describe the Red Strings client-403 fix (1db64f4) and icon-picker fix (e8a8007) in the Upcoming Red Strings entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The icon picker popup read its own data context (the dependency row, which has
no setDependencyProps) instead of the source card, so picking an icon did
nothing. Capture the source card when the picker opens and use it in the popup.
Adds an e2e test that edits an existing dependency's relation type, color and
icon from the card detail and asserts each persists — this is the path that
previously threw the client 403 (selector/positional-$ update) and the icon
no-op, now both fixed. Verified locally: spec 27 passes 8/8 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
addDependency (updating an existing link), setDependencyProps and
removeDependency used updateAsync with a selector + positional `$` operator.
On the client that is rejected by collection2 ("Untrusted code may only
updateAsync documents by ID" / 403), so changing a dependency's relation type,
color or icon (or removing it) threw a ValidationError.
Rewrite all three to read-modify-write the cardDependencies array and $set it
by _id only, which is permitted from untrusted client code. The server REST
endpoints keep the positional update (server code is trusted).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the merged form-data (#6375) and launch-editor (#6376) Dependabot updates to the Upcoming updates section, with links to the pull requests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add commit links to the CI Playwright/mongosh and Finnish-translation entries in the Upcoming section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- playwright.yml: install mongosh in the Playwright e2e jobs and the Puppeteer
regression job. The e2e DB helpers (tests/playwright/helpers/db.js and
tests/e2e/list-regressions.js) shell out to `mongosh`, which is not
preinstalled on the runner — the regression job was failing with
"spawnSync mongosh ENOENT".
- 27-red-strings spec: remove the rspack dev-server error-overlay iframe before
clicking the board-header dependency toggle, so the click is not swallowed by
the dev overlay (the button does not exist in production builds).
- CHANGELOG: note the Playwright Firefox/WebKit + mongosh CI fixes and the
Finnish translation update in the Upcoming section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The e2e matrix ran `playwright test --project=firefox`, but playwright.config.js
only defines the firefox/webkit projects when WEKAN_PLAYWRIGHT_ALL=1, which the
workflow never set — so the Firefox job failed with "Project(s) firefox not
found. Available projects: chromium".
- playwright.yml: set WEKAN_PLAYWRIGHT_ALL=1 on the test run step so --project
can select firefox/webkit, and add webkit to the matrix (Playwright's bundled
WebKit runs headless on the Linux runner; the old "requires macOS" note was
wrong). Each job still installs only its own browser via --with-deps.
- package.json: set WEKAN_PLAYWRIGHT_ALL=1 in test:playwright:all so the same
command works locally (it had the same latent failure).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move the Shared Templates (Admin view) content into docs/Features/Templates.md
as a section and delete the separate SharedTemplates/ page; repoint the
Features index, docs/README.md and the ChangeLog to the in-page anchor.
- Templates.md: relabel the working features as "Implemented and working"
(per-user Card/List/Board templates + the admin Shared Templates view), and
correct the roadmap list which still listed Shared templates (#2209) as a
pending funded item although it is now implemented.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/Features/SharedTemplates/SharedTemplates.md: documents the admin
"Shared templates" view (Admin Panel → People), grouping by Org/Team/Domain,
the adminSharedTemplates method and the e2e coverage. Linked from the Features
index, docs/README.md and Templates.md; ChangeLog entry links to it.
- CHANGELOG: reordered the Red Strings "Upcoming" entry so the feature
description, REST API, import/export and preservation/tests read in order.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server/lib/tests/cards.dependencies.move.tests.js: stub-based unit tests of
the card.move() helper — a cross-board move clears the moved card's
cardDependencies and issues a multi-update $pull to remove inbound references
on the old board; a same-board move leaves dependencies untouched.
- server/lib/tests/jiraCreator.dependencies.tests.js: JiraCreator.createDependencies
maps Jira issuelinks to typed dependencies (outward/inward "Blocks" ->
blocks/is-blocked-by, others -> related-to), normalizes color/icon, and skips
self-links, unknown targets and link-less issues.
- Registered both in server/lib/tests/index.js; CHANGELOG note updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turns the flagged manual checks into automated tests and covers the newest
dependency ("Red Strings") features that lacked tests.
Mocha (server/lib/tests, registered in index.js):
- dependencies.metadata.tests.js: normalizeDependency (legacy string upgrade,
defaults, invalid type), direction meta (blocks/fixes forward,
is-blocked-by/is-fixed-by reversed, related-to undirected), inverse map.
- dependencies.openapi.tests.js: every WebApp.handlers dependency route has
@operation/@tag Dependencies/@summary, no undocumented route, unique ids
(guards "OpenAPI picks up new routes" without running the python generator).
Mocha (client/lib/tests):
- filter.dependencies.tests.js: Filter.cardDependencies toggles and builds a
cardDependencies.type mongo selector.
Playwright e2e:
- 28-dependencies-rest.e2e.js: REST CRUD with type/color/icon, board-level
list, update-in-place, validation (bad type, self-link), auth — exercises the
cardDependencies SimpleSchema via real $push and positional ($) updates on the
live server (the schema manual-check).
- 27-red-strings.e2e.js: added minicard badge, copyCard same-board preservation
(type/color), and importBoardDependencies card matching by number and title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greatly expands the card dependency "Red Strings" feature (#3392).
Data model
- cardDependencies entries are now objects { cardId, type, color, icon }
(models/metadata/dependencies.js defines the 5 relation types, defaults and a
normalizer that upgrades legacy bare-string ids on read).
Relations + visualization
- Relation types: related-to, blocks, is-blocked-by, fixes, is-fixed-by. The
type sets the SVG arrow direction (related-to is undirected); per-line color
and per-color arrowhead markers; arrows drawn prerequisite-right -> dependent-left.
- Minicard badge (icon + color + count) for cards with dependencies.
- Board Filter sidebar: filter cards by dependency relation type.
Card detail UI
- Each dependency row has a relation-type dropdown, color picker, icon picker
and remove; the add popup picks type + color then searches a card by title.
REST API + tooling
- server/models/dependencies.js: list/add/edit/remove endpoints (tag
Dependencies) with OpenAPI annotations + board-level list endpoint.
- api.py: list/add/edit/remove dependency commands.
Import / export
- Board Settings -> Export: Dependencies as JSON and as round-trippable SVG
(client/lib/exportDependencies.js).
- All Boards -> New -> Import -> Dependencies (JSON/SVG) into a chosen board,
matching cards by id/number/title (importBoardDependencies method +
client/lib/importDependencies.js).
- jiraCreator maps Jira issuelinks best-effort to dependency relations.
Preservation
- type/color/icon preserved through card/board copy and WeKan import/migrate;
cross-board card move drops now-dangling dependencies.
Docs/tests/i18n
- docs/Features/RedStrings/RedStrings.md (+ Features index and docs/README links).
- i18n keys; CSS for the rows/badge/icon picker; expanded e2e spec 27.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Card-to-card dependencies (#3392 "Red Strings") were stored on cards and the
showDependencies toggle on the board, but were lost on board copy and on WeKan
export/import/migrate, so copied/imported boards drew no dependency lines.
- models/cards.js copy(): a single-card copy keeps only dependencies whose
target card exists on the destination board (same-board copies keep links,
cross-board copies drop dangling ones); when part of a whole-board/swimlane
copy it records old->new card ids in a shared cardIdMap for the caller.
- models/swimlanes.js, models/lists.js copy(): thread the cardIdMap through to
card.copy().
- models/boards.js copy(): collect the cardIdMap and remap every copied card's
cardDependencies to the new card ids (dropping uncopied targets).
- models/wekanCreator.js: carry over the board showDependencies flag, and add a
createCardDependencies() pass that remaps imported cardDependencies to the new
card ids (used by JSON import, board clone and POST /api/boards/import).
- tests: e2e spec 27-red-strings (overlay drawn, header toggle persists to the
board, add/remove dependency from card detail persists) + db helpers
(findCardIdByTitle, setCardDependencies, setBoardShowDependencies).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an admin-only "Shared templates" tab to Admin Panel -> People that
lists users' shareable template boards, grouped by Organization, Team or
email Domain. The three scope checkboxes are live view filters (default
unchecked); only users whose Templates board is non-empty are listed.
- server: adminSharedTemplates() method enumerates each user's linked
template boards (cardType-linkedBoard cards in their Board Templates
swimlane) and returns them with the user's orgs/teams/email domains.
- client: templatesGeneral template + grouping logic and i18n strings.
- tests: e2e spec 26-shared-templates and db seed helpers
(setUserGroups, seedTemplatesBoard) with template-board cleanup.
Fixes#3313.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the board-color hardening: when importing a board, only set a card or
swimlane color if it is a recognized CARD_COLORS / SWIMLANE_COLORS value, so an
out-of-range color from a foreign or old export cannot fail collection2 validation
and abort the import. (Lists do not import a color.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The POST /api/boards and DELETE /api/boards/:boardId handlers caught errors and
returned { code: 200, data: error }, masking failures (e.g. an unauthorized delete)
as success. Return the real status code (error.statusCode/code or 500) and a JSON
error body instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The export endpoints passed a bare number to sendJsonResult (e.g. sendJsonResult(
res, 403)), but sendJsonResult treats its second arg as an options object, so a
number produced HTTP 200 with an empty body instead of the intended 4xx. Pass
{ code, data } so 404/400/403/auth failures return the correct status and a JSON
error body.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The teardown timeout stopped the computation and deleted the cached value without
re-checking whether a dependent re-attached during the timeout window, which could
delete a value still in use and surface as a transient undefined (a contributor to
the board-not-found flicker). Re-check hasDependents() in the timeout callback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In the member menu popup the 'Change Avatar' label was indented one level too
deep, making it a child of the <i class="fa fa-picture-o"> icon element instead
of a sibling like every other menu entry. It therefore inherited the FontAwesome
icon font styling and rendered differently (uppercase-looking, off style). Dedent
the label so it sits directly under the <a>, matching the other items.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visualize card-to-card dependencies as red connection lines drawn on top of the
board (SAFe PI-planning program board). Cards gain a 'cardDependencies' array
(target card ids on the same board), edited from a new Dependencies section in
the card detail (pick/remove other board cards). A board header toggle
(showDependencies board setting) renders an SVG overlay over the board canvas
that draws a red, arrow-headed curve from each card to each of its dependencies,
positioned from the live minicard rects and recomputed on board data changes,
canvas scroll and window resize. The overlay is pointer-events:none so cards stay
interactive. Fixes#3392.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
releases/rebuild-docs.sh runs openapi/generate_openapi.py, which crashed with
"AttributeError: 'NoneType' object has no attribute 'rstrip'" because a route
path argument that is a template literal (e.g. WebApp.handlers.get(
`/api/boards/:boardId/export/${format}`, ...) in models/export.js) has no
Literal .value. Resolve template-literal paths (turning ${identifier} into a
:identifier path parameter) and skip any route whose path cannot be resolved
statically instead of crashing the whole release. The export/:format route now
documents as /api/boards/{board}/export/{format}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
markdown-it renders ~~text~~ to <s>...</s>, but the DOMPurify ALLOWED_TAGS list
did not include s/del/strike, so the sanitizer stripped the tag (keeping the bare
text), losing the strikethrough. Add s, del and strike to both DOMPurify configs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Board/card 'button' rules now denormalise their button type/label onto the
schema-backed rules document (via a board-admin-gated rules.createRule method)
and the header/card read them from there, because the schemaless triggers
collection's documents do not reach the client over the board subscription;
the boardRules publication and the rules-list editor read from it too. Also
warn (instead of silently creating an empty board) when a pasted WeKan export
has no swimlanes/lists/cards (a broken older export).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive the header-login source IP from the real TCP socket peer instead of the
spoofable X-Forwarded-For header, fail closed when the trusted-IP allowlist is
unset, and add opt-in multi-proxy support via HEADER_LOGIN_TRUSTED_PROXIES
(honor XFF only from a trusted proxy peer, taking the right-most untrusted hop).
Propagate the new env vars across docker-compose, start-wekan.sh/.bat, Dockerfile,
.devcontainer, snap config and the virtualbox script; add docs and regression tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
that checks the page language, skip link, landmark roles, visible focus, dialog
roles, accessible names on icon controls, and the absence of duplicate element ids.
Thanks to xet7 and Claude !
- Fixed 14 broken relative links in `docs/README.md` (verified with a link check).
- Split the monolithic `docs/Features/Features.md` into focused topic pages under
`docs/Features/` subdirectories: Boards, Lists, Cards, Members, Keyboard
Shortcuts, Admin Panel, WIP Limits, Cleanup and Stats; `Features.md` is now an
index linking to them, and `docs/README.md` links the new pages.
- Added documentation for previously-undocumented features: Right-to-Left (RTL)
UI, Stickers, Card Locations, Board Background Images, Attachments and File
Storage, the card "complete" checkbox, and a new Accessibility feature page
(covering both the Admin Panel / Settings / Accessibility info page and the
built-in accessibility features above).
- Updated outdated information based on the CHANGELOG: the Meteor/Node versions
(now Meteor 3.x / Node.js 24.x) and the obsolete feature wishlist (Custom
Fields, Subtasks, Swimlanes, Gantt, WIP limits, voting and templates are now
implemented).
Thanks to xet7 and Claude !
- Added a visible keyboard focus indicator (`:focus-visible` outline) for links,
buttons and form controls. The global CSS reset previously stripped all focus
outlines, leaving keyboard users with no indication of focus (WCAG 2.4.7).
- Added a "Skip to main content" link that lets keyboard and screen-reader users
bypass the header chrome (WCAG 2.4.1 Bypass Blocks).
- Added landmark roles so assistive technology can navigate page regions:
`role="main"` on the content area, `role="navigation"` on the header quick-access
bar, and `role="search"` on the global search form (WCAG 1.3.1).
- Marked modals and popups as `role="dialog"` with accessible names, set
`aria-modal="true"` on modals, and moved keyboard focus into a modal when it
opens and back to the triggering element when it closes (WCAG 2.4.3 Focus Order).
- Added accessible names (`aria-label`) to icon-only controls that previously had
none: modal/popup close and back buttons, the announcement close button, the
sidebar close/back buttons and the global search input and clear button
(WCAG 4.1.2 Name, Role, Value).
- Marked decorative Font Awesome icons inside labelled controls as
`aria-hidden="true"` so they are not announced redundantly.
- Added a screen-reader-only `.sr-only` helper class.
Thanks to xet7 and Claude !
which produced invalid HTML and broke assistive-technology navigation (WCAG 4.1.1).
- Fixed the My Cards table markup: header cells (`<th>`) are now wrapped in a `<tr>`
with `scope="col"`, and a caption was added, so the table is announced correctly.
- Added missing `alt` text to the user avatar image (the surrounding link already
carries the accessible name).
Thanks to xet7 and Claude !
failures. `snapcraft remote-build` intermittently dies with a transient TLS
drop (`SSLEOFError: EOF occurred in violation of protocol`, exit 70) while
talking to Launchpad after pushing the repo — unrelated to the build itself —
which failed the whole release. The step now retries the remote build up to
three times before giving up.
Thanks to xet7 and Claude.
(Arabic and its variants, Persian/Farsi, Hebrew, Uyghur, Uzbek-Arabic and
Yiddish — the languages flagged `rtl: true` in `imports/i18n/languages.js`).
Previously only some pages flipped. The root `<html>` element now gets a
reactive `dir="rtl"`/`dir="ltr"` attribute that follows the chosen language, so
the whole UI mirrors at once. To make this work on every page rather than a
handful, all directional component CSS was converted to CSS logical properties
(`margin-left`→`margin-inline-start`, `left:`→`inset-inline-start:`,
`text-align: left`→`text-align: start`, `float: left`→`float: inline-start`,
and the mirror-image right/end variants) across 48 stylesheets, which flip
automatically with `dir`. Horizontal-centering rules using
`left: 50%` + `transform: translate(-50%, …)` were intentionally kept physical,
since those already center correctly in both directions. The calendar view also
renders RTL. Thanks to xet7 and Claude.
- Tests for the RTL UI. A fast, server-less `tests/rtl.test.js` (run with
`node tests/rtl.test.js`) checks that exactly the expected languages are
flagged `rtl: true`, that the direction helper maps each language to the right
`dir` value, that the root `<html>` and the client keep `dir` in sync, and —
as a regression guard — that component CSS keeps using logical properties
(no physical `margin-left`/`float: left`/bare `left:` offsets sneak back in).
A Playwright browser spec `tests/playwright/specs/18-rtl-layout.e2e.js` drives
the app in both English (LTR) and Arabic (RTL) and asserts, on the boards list,
board view, card details, my-cards / due-cards / global-search, the admin
settings page and the login page, that the direction is correct, the
translated text is visible, and leading-edge content (the boards menu, the
first board list) sits on the correct side. Thanks to xet7 and Claude.
- Fixed the "Meteor unit tests" CI job hanging until its 45-minute timeout. The
workflow set `TEST_WATCH: '0'`, but meteortesting:mocha computes
`testWatch: TEST_WATCH || …` and the string `'0'` is truthy in JavaScript, so
it turned on watch mode and the process never exited. Removing the env var lets
`meteor test --once` finish and exit with the correct status.
Thanks to xet7 and Claude !
Fixes#6136,
fixes#3376,
fixes#884
providers, grouped by region — USA: Google Maps, Bing Maps, Apple Maps, Waze;
Europe: OpenStreetMap, HERE WeGo, Yandex Maps, Mapy.cz, 2GIS; Asia: Baidu Maps,
Amap (Gaode) — plus generic `?q=`/`?ll=` links: paste the link, press "Detect",
and the latitude, longitude and (when present) the place name/address are
filled in automatically. Detection handles each provider's real-world URL
shapes, including percent-encoded commas (e.g. Waze/Yandex `ll=...%2C...`) and
alternate forms (HERE `share.here.com/l/`, Yandex `pt=`, 2GIS `m=`), and the
coordinate order (several non-US providers use lon,lat). For the Chinese
providers a datum conversion is applied — Baidu uses BD-09 and Amap uses
GCJ-02, both offset from WGS-84 — so pins land in the right place on the way in
and out. The popup also has an "Open map links at" setting (OpenStreetMap by
default) saved to the user profile, with the same region-grouped provider list,
controlling which map service the location "Open in map" links use.
Thanks to xet7 and Claude.
and fix:
- Fix incomplete URL substring sanitization.
Thanks to xet7 and Claude.
Thanks to above GitHub users for their contributions and translators for their translations.
- Attachments now import. Trello attachment URLs require OAuth to download, so
WeKan could not fetch them. The Trello import page now has two separate file
fields: a **.json** field for a single Trello board, and a
**.zip** field for a package produced by the
[Trello Card Attachments Downloader](https://github.com/wekan/trello-attachments-downloader).
The .zip may contain one *or more* board .json files together with each
board's attachments in a board-name subfolder. The .zip is uploaded over HTTP
and processed entirely on the server (the attachment bytes never travel over
the realtime WebSocket, which previously overflowed the connection and made
the page flicker): WeKan detects every board .json, matches each board to its
attachment subfolder by name, streams each file straight into the Default
file storage, imports all boards, then opens All Boards. The upload is guarded
against abuse — it rejects oversized uploads, zip bombs (too many files or too
much uncompressed data, per-file size caps) and unsafe entry paths (absolute
paths or `..` traversal), and never overwrites existing files (stored under
generated ids). The importer also reads attachments from `card.attachments`
(newer Trello exports), not only from the action log, and uses the correct
Meteor-Files insert API (the previous calls were no-ops on Meteor 3). Single
board imports (the .json file or the pasted JSON, after the map-members step)
now run through the same server-side HTTP path rather than the realtime
`importBoard` method, so a heavy import can no longer drop the WebSocket and
get retried in an endless loop (the page flickering with "Invalid frame
header" / repeated "logged out" errors). When importing only a single board
.json and you have saved Trello API key and token, WeKan uses them on the
server to download that board's attachments, background image, member
avatars and card stickers from Trello (a .json export often omits stickers;
avatars only fill in mapped users who have none — an existing avatar is never
overwritten); each download is best-effort and a failure never aborts the
import. After a single-board import the new board's data is subscribed and
loaded before opening it, so it shows its swimlanes, lists and cards
immediately instead of needing a browser page reload (the HTTP import, unlike
the old realtime method, does not push the written documents to the client by
itself).
- Import Trello-only card features: stickers (shown as Font Awesome icons on
minicards and the card detail), card cover (color or attachment), card
location (name, address, coordinates with an OpenStreetMap link), and the
due-date "complete" checkbox shown on the minicard. Trello's two named
sticker packs are renamed and highlighted (the icon keeps its normal colour)
so they stay distinct: the "taco" pack becomes "mascot" with an underlined
icon, and the "pete" pack becomes "computer" with a ring around the icon,
e.g. "pete-ghost" imports as "computer ghost" with a Snapchat-ghost icon.
Several named stickers that used to share an icon now get their own Font
Awesome 4.7 icon (mascot active => heartbeat, pixel => qrcode, proto =>
flask, embarrassed => meh-o, clean => bath, computer shipped => truck).
- Live Trello API import: paste your Trello API key and token on the Trello
import page to list all your Trello workspaces and boards, then import all
or selected boards together with their attachments (downloaded
server-side). Imported boards are placed under a personal workspace named
after their Trello workspace, created by name if it does not exist, under an
optional parent workspace you choose. The API client respects Trello's rate
limits: it spaces out requests, honours `Retry-After` on HTTP 429, retries
transient 5xx and network errors with capped exponential backoff, and
reports a clear error on an invalid key/token (401). After a successful API
import you are taken to the All Boards page where the new boards appear.
- The live API import runs as a background job on the server: you can navigate
away and come back to the Trello import page to watch progress, see
per-board results, and read the full error log. Errors are shown in a
selectable text box with a one-click "Copy to clipboard" button (and you can
still select and copy just part of the text) so they are easy to share when
fixing issues. If the import stops on a fatal API error (invalid token, or
rate limit still failing after retries) you can fix the cause and Resume
from where it left off, and a server restart leaves the job paused for
resuming rather than lost. You can also Cancel the import, or Cancel and
delete the boards already imported, to start the whole process over cleanly.
- You can optionally save your Trello API key and token with the Save and
Delete buttons on the import page. When saved, they are stored only on the
server, never sent back to the browser, and are reused automatically when
you list workspaces or import (so you do not have to paste them again); a
"saved" indicator is shown, Save can overwrite them with new values, and
Delete clears them from the database. If you do not save them, they are kept
only in server memory for the running import and re-supplied when you resume.
- Larger Trello JSON exports can be selected as a file instead of pasted.
- Keep imported usernames for later mapping. Imported members that match an
existing WeKan user (by username, or by a previously recorded imported
username) are mapped automatically, and the imported username is remembered
on that user so future imports map too. Members with no matching WeKan user
are no longer lost: their usernames are kept on the board, so an admin can
map them to real users later in Admin Panel / People (Imported Usernames),
without having to map everyone up front at import time.
Thanks to xet7 and Claude.
- Board background images can now be stored in WeKan. A new `backgrounds` file
storage directory is created alongside `attachments` and `avatars` (using the
current default storage backend), and Board menu → Board backgrounds lets a
board admin upload background images, set one as the active board background,
download, and delete them. Board export now includes the board's background
images and re-imports them, and a Trello board's background image is
downloaded and stored on import (so it keeps working even if the original
Trello URL later changes). Thanks to xet7 and Claude.
- Stickers can be added to and removed from cards directly in WeKan, chosen from
a set of Font Awesome icons similar to Trello's stickers (previously stickers
only arrived via Trello import). The picker also includes every "mascot"
(underlined) and "computer" (ringed) highlighted sticker that a Trello import
can produce — generated from the same icon mapping the importer uses — so any
imported sticker can also be added by hand. Stickers show on the minicard and
in the card detail. Thanks to xet7 and Claude.
- The Trello-style "complete" checkbox (mark a card complete/incomplete,
independent of the due date) is shown as an animated green checkbox to the
left of the card title, both on the minicard and in the opened card, with
"Mark as complete" / "Mark as incomplete" tooltips. It uses the same animated
checkbox style as Admin Panel / Settings / Announcements and is vertically
centered with the title text, and the two stay in sync. The minicard and the
opened-card checkboxes use the same checked style (the board theme's colour, or
green when no theme is set), so they always look identical. Subtask checkboxes
and the card-detail custom-field checkbox now use the same animated checkbox as
checklist items (they previously used static square icons), so every checkbox
on a card animates consistently. Thanks to xet7 and Claude.
- Cards can now have multiple locations, similar to multiple members. The card
detail shows a Location section (after Labels and Stickers) listing each
location with its name, address and an OpenStreetMap link, with an "Add
location" button to add more and a button to edit or remove each one. A single
location imported from Trello keeps working and is shown in the same list. The
Add/Edit location popup can also detect a location from any map link (Google
Maps, OpenStreetMap, Bing Maps, Apple Maps, or generic `?q=`/`?ll=` links):
paste the link, press "Detect", and the latitude, longitude and (when present)
the place name/address are filled in automatically. The popup also has an
"Open map links at" setting (OpenStreetMap by default, or Google/Bing/Apple
Maps) saved to the user profile, controlling which map service the location
"Open in map" links use. Thanks to xet7 and Claude.
- The opened card now docks to the top of the window, overlaying the global and
board header bars, instead of opening downward from the clicked minicard, and
it can be dragged all the way to the top without its top hiding behind those
bars. (At 100% zoom the board wrapper no longer sets a `transform: scale(1)`,
which had made it the containing block for the fixed card and trapped it below
the headers.) Thanks to xet7 and Claude.
Fixes#5727,
fixes#2457,
fixes#5818,
fixes#755
Fix import/clone of inconsistent board JSON so the newest WeKan can import
board exports from any newer or older WeKan version. A syntactically valid
export can still be internally inconsistent (a board member whose user
account was deleted is no longer in the export's `users`, cards pointing at a
missing `listId`, orphaned checklists, or a missing `lists` array). Such
exports previously failed import as "error-json-malformed" or silently
dropped data (cards with an undefined list never rendered). The importer now
skips dangling user references in the member mapper (client
`wekanMembersMapper`, server `wekanmapper` used by board cloning, and
`WekanCreator`), falls back to the first imported list — creating one default
list when the export has none — for cards with a dangling `listId`, and skips
orphaned checklists whose card is missing. The importer remains
version-agnostic: it never reads `_format`, normalizes old `id` vs `_id`
fields, and ignores the source `boardId`.
Added `tests/wekanCreator.inconsistent.test.js` covering these cases.
Thanks to titver968, xet7 and Claude !
Fixes#6371
(CWE-284, CWE-639). WeKan boards are membership-scoped, but the DDP collection write
policies for Cards, Lists and Swimlanes (`server/permissions/cards.js`,
`server/permissions/lists.js`, `server/permissions/swimlanes.js`) authorized an update by
checking only the CURRENT (pre-update) `boardId` of the document — i.e. the attacker's own
source board — and never validated the NEW `boardId` supplied in the update modifier.
Because every logged-in user can create a board where they are admin, an attacker could take
a document they own and, in a single `/cards/update`, `/lists/update` or `/swimlanes/update`
DDP call, `$set` its `boardId` (plus `swimlaneId`/`listId`) to a victim's private board: the
allow rule saw the attacker's own source board, approved the write, and the document was
relocated into a board the attacker is not a member of and cannot even read. This let an
unprivileged user inject arbitrary cards/lists/swimlanes (attacker-controlled titles,
descriptions, assignees, etc.) into any private board by id, defeating board-level access
control. The REST API for the same operation
(`PUT /api/boards/:boardId/lists/:listId/cards/:cardId` with `newBoardId`) was not affected
because it correctly calls `Authentication.checkBoardWriteAccess(req.userId, newBoardId)` on
the destination board; only the DDP allow/deny layer was vulnerable. Fixed by adding a
`denyCrossBoardMove` helper in `server/lib/utils.js` and a `Cards.deny`/`Lists.deny`/
`Swimlanes.deny` `update` rule on each collection that rejects any update whose modifier
`$set`s a `boardId` on which the caller does not have write access, so a cross-board move is
only allowed into a destination board where the user is an active write-capable member.
Affected Wekan v9.35 and earlier.
Thanks to 0xzap, xet7 and Claude.
`POST /api/createtoken/:userId`](https://github.com/wekan/wekan/blob/main/CHANGELOG.md)
(CWE-863, CWE-287). `Authentication.checkUserId` in `server/authentication.js` is an
`async` function, so its 401 (undefined `userId`) and 403 (not an admin) throws become
rejected promises rather than synchronous exceptions. The REST handlers in
`server/models/users.js` and `server/models/boards.js` called it without `await` inside a
plain synchronous `try/catch`, which cannot catch a rejected promise, so the failed check
never stopped execution. `POST /api/createtoken/:userId` then went on to mint and return a
usable login token for any user ID in the URL — including an admin — with no credentials at
all (unauthenticated account takeover). The same detached-rejection bypass also affected
`GET /api/users`, `GET /api/users/:userId`, `PUT /api/users/:userId`, `POST /api/users/`,
`DELETE /api/users/:userId`, `POST /api/deletetoken`, `GET /api/boards`,
`GET /api/boards_count`, `DELETE /api/boards/:boardId`, `GET /api/users/:userId/boards` and
`POST /api/boards/:boardId/copy`. Fixed by awaiting every async `Authentication` check (and
making the two non-`async` handlers `async`) so a failed check rejects before any privileged
code runs. The same un-awaited pattern in the board/card/Excel/PDF export handlers
(`models/export.js`, `models/exportExcel.js`, `models/exportExcelCard.js`, `models/exportPDF.js`,
which were backstopped by `exporter.canExport()`) and in the checklist-create handler
(`server/models/checklists.js`) was given the same await pass. Affected Wekan v9.35 and earlier.
Thanks to Zion Boggan, xet7 and Claude.
- **Admin Panel / Attachments: the MongoDB GridFS page notes how to make legacy
files visible.** A translatable line under the "MongoDB GridFS Storage" title
reminds admins: "To have attachments and avatars visible, move them from
CollectionFS to any other Storage." Thanks to Claude.
- **Admin Panel / Attachments: the Google Cloud Storage page now documents the
required bucket permission.** A note under the "GCS Storage" title explains that
the service account needs the **Storage Object Admin** role
(`roles/storage.objectAdmin`) on the bucket (Cloud Console → Cloud Storage →
Buckets → your bucket → Permissions → Grant access → add the service account →
role "Storage Object Admin"), and that the bucket must exist in the same
project — otherwise "Test connection" fails with `storage.objects.list denied`.
Thanks to Claude.
- **Admin Panel / Attachments: a "Save" button next to the Enabled / Read toggles
at the top of each cloud storage.** S3, Azure and Google Cloud Storage now have
a Save button directly below the "[ ] Enabled [ ] Read" row, so those toggles
can be saved without scrolling to the bottom of the (now quite long) storage
page. It saves the same way as the existing bottom Save button — the server
merges that provider's config and preserves secrets left blank — and the bottom
Save button is kept. (Filesystem and GridFS have only a Read toggle, which
already saves the instant it is toggled, so they need no button.) Thanks to Claude.
Fixes:
- **Admin Panel / Attachments: "Run MongoDB compact" now actually compacts the
database.** MongoDB refuses `compact` on an active replica-set primary unless
`force: true` is given, so every collection failed with "will not run compact
on an active replica set primary … use force:true to force" — and because the
usual Meteor setup is a single-node replica set (only a primary, no
secondaries), nothing was compacted at all and no disk space was reclaimed. The
primary is now compacted with `force: true` (secondaries are still compacted
without it, so the primary stays available while they run), so the GridFS
collections are actually rewritten and freed space is returned to the
filesystem. Thanks to Claude.
- **Admin Panel / Attachments: clearer cloud "Test connection" errors, and cloud
config inputs are trimmed.** Testing an Azure/GCS/S3 connection used to report a
single generic `Incomplete configuration or adapter not installed` even when the
real problem was specific — e.g. Azure rejecting the config with `Invalid URL`
(a stray space/newline in the account name, or a bad endpoint / connection
string). `testCloudConnection` now reports the actual cause: adapter not
installed, required fields missing, the adapter's own configuration error (such
as `Invalid URL`), or the real `listFiles` error (auth failure, container not
found, …). It also pre-validates the config and gives an actionable message
before the adapter turns it into a cryptic error — e.g. for Azure it explains
that the **Storage account name** must be just the account name (3–24 lowercase
letters/numbers, e.g. `wekanstorage`, not a full `https://…` URL and with no
spaces), or that the **Connection string** is malformed; and when Azure still
returns `Invalid URL` the message appends which field to check. In addition, all
cloud-storage text fields are now trimmed when read from the form, so
leading/trailing whitespace from copy-paste no longer produces an invalid URL.
Thanks to xet7 and Claude.
- **Admin Panel / Attachments: "Calculate file counts" on every storage backend,
shown right below the "Read" toggle.** Azure Blob Storage and Google Cloud
Storage now have a "Calculate file counts" button (new `getAzureStorageStats` /
`getGcsStorageStats` methods) that reports how many attachments and avatars are
stored on that backend, matching what Filesystem, MongoDB GridFS and S3 already
offer. The count is read from the stored file metadata (fast, no cloud API
calls). The S3 button was also moved up to sit directly under its "Read"
checkbox, so all backends are consistent. Thanks to Claude.
- **Admin Panel / Attachments: bilingual, self-documenting cloud-storage fields
(S3, Azure and Google Cloud Storage).** Each cloud-storage setting now guides
the admin from top to bottom with both English and translated text, so it is
easy to follow along while clicking through the provider's Cloud Console:
- the field **label** uses the provider's own console wording in English
(e.g. *Access key ID*, *Secret access key*, *Storage account key*,
*Service account key (JSON)*), with the translated label shown below it;
- a literal **example** of what the value should look like
(e.g. `eu-west-1`, an example bucket name / endpoint / connection string),
which is intentionally not translated;
- a short **description** of the value, in English and then translated;
- the **Cloud Console menu path** showing exactly where to find or create the
value (e.g. *AWS Console → IAM → Users → your user → Security credentials →
Access keys → Create access key*; *Azure Portal → Storage accounts → your
account → Access keys → key1*; *Google Cloud Console → IAM & Admin →
Service accounts → … → Keys → Add key → Create new key → JSON*), again in
English and then translated.
The provider field names, examples and menu paths are kept in English so they
match what the Cloud Console actually shows, while the descriptions and menu
paths are also translatable; section titles, the enable/read/force-path-style
checkboxes and the Test/Save buttons remain fully translated. Thanks to Claude.
- **Admin Panel / Attachments / Move Attachment: "Repair file locations" button
and a persistent "last move" message.** The new **Repair file locations** button
scans all attachments and avatars and finds any whose recorded storage
(`versions.<v>.storage` / `path` / `meta.gridFsFileId`) no longer matches where
the binary actually is — left inconsistent by an interrupted or failed move —
and fixes the database to point at the real location: it detects the binary in
GridFS (by the `metadata.fileId` stamped on upload, recovering files whose
GridFS id reference was lost) or on the filesystem (using the storage
strategy's own thorough path resolution, so the repair agrees with what a real
download/move would find even when `versions.<v>.path` is stale), then corrects
`storage`, `path` and `meta.gridFsFileId` accordingly. Cloud-stored files are
left untouched, and files found nowhere are reported as "Not found". The scan is
streamed (memory-safe) and shows a per-scope searched / repaired / not-found
summary. Separately, after a move finishes the page now keeps showing
the last move operation — source → destination (scope) and the date/time as
`YYYY-MM-DD HH:MM:SS` — so it is clear what was last done. Thanks to Claude.
New fixes:
- **Fixed moving attachments to S3 (and other cloud storage) failing and
crashing the server.** Uploading to S3 failed two ways, each of which crashed
the whole server because the rejection was unhandled and SyncedCron treats
those as fatal. The `@tweedegolf/sab-adapter-amazon-s3` adapter uploaded with a
`PutObjectCommand` whose `Body` was a live stream: (1) with no `ContentLength`
the AWS SDK fell back to a chunked signed upload that needs a decoded content
length, which was undefined for a stream of unknown size — `Invalid value
"undefined" for header "x-amz-decoded-content-length"`; and (2) if the socket
dropped mid-upload (`socket hang up`), the SDK's body-stream promise rejected
unhandled. Fixed by uploading the file as a complete in-memory **buffer**
(`addFileFromBuffer`) instead of a live stream: a buffer has a known length (no
content-length error) and no socket-bound stream to re-reject. The cloud upload
promise now never rejects — any failure is captured and exposed via
`waitUntilStored()`, which `moveToStorage` checks inside a try/catch so a failed
upload leaves the source file intact and is logged cleanly. (Files are moved one
at a time, so peak memory is one file.) Thanks to Claude.
- **Fixed `TypeError: Cannot read properties of undefined (reading 'on')` when
moving attachments between Meteor-Files/GridFS and Filesystem.** If a file's
source binary was missing at its recorded location (e.g. a file left
half-moved by an earlier interrupted run — storage says "gridfs" but the
GridFS id reference is gone, or a filesystem path that no longer exists),
`getReadStream()`/`getWriteStream()` returned `undefined` and `moveToStorage`
(and `copyFile`) called `.on()` on it, throwing. Both now detect a missing
read/write stream, log a clear message (which file, version, from→to storage,
and why), **skip that file and leave the source intact** so no data is lost,
and continue with the rest. Use the new "Repair file locations" button to fix
the underlying inconsistent records. Thanks to Claude.
- **Fixed moving attachments/avatars from CollectionFS to Meteor-Files crashing
the server, hanging the Admin Panel move at "File 1 / N", and leaving broken
avatars.** Three problems in Admin Panel / Attachments / Move Attachment:
- **Server crash on the first file** (`uncaughtException: TypeError: Cannot read
properties of undefined (reading '_id')` at
`AttachmentStoreStrategyGridFs.writeStreamFinished`). The current mongodb
driver's GridFS `GridFSBucketWriteStream` `'finish'` event no longer passes
the stored file document, so `finishedData._id` threw and killed the process
(which is also why the move appeared stuck at "File 1 / 4" — the background
job died mid-file and its persisted status was frozen). The GridFS strategy
now reads the uploaded file id from the write stream itself (`gridFSFile._id`
/ `id`), the `'finish'` handler is wrapped so it can never crash the process,
and a startup reconciliation clears a stale "running" move status left by a
crashed run so the UI un-sticks and a new move can be started.
- **Broken avatar after migrating avatars.** The bulk move only repointed card
cover references (`cards.coverId`); a user's `profile.avatarUrl` still pointed
at the deleted legacy `/cfs/files/avatars/<oldId>` URL, so the avatar rendered
broken. The move now repoints `profile.avatarUrl` to the migrated avatar
(`remapReferences` handles avatars), and migrated files stamp
`meta.migratedFromId` so references can be repaired to the **exact** new file.
The startup repair in `server/models/users.js` (previously a no-op: it
string-replaced the URL prefix while keeping the now-deleted id and never
saved the document) now points each affected user's avatar at their migrated
Meteor-Files avatar — matched precisely by `meta.migratedFromId`, otherwise
strictly by `userId` (newest migrated avatar first), so a user can only ever
be given their own avatar, never another user's.
- **`ObjectID` deprecation warning** from `models/lib/grid/createObjectId.js`
(`MongoInternals.NpmModule.ObjectID` → `ObjectId`).
Thanks to Claude and xet7 !
**Fixed Dropdown custom field options not being addable / saving as empty.**
Creating a "Dropdown" custom field showed the "List Options" box, but pressing
Enter (or typing and saving) never added any option, and on the card the only
selectable value was `(none)`. The custom-fields sidebar component was migrated
from `BlazeComponent` to a plain `Template`, but the template still iterates the
options with `{{#each dropdownItems.get}}` — under BlazeComponent that resolved
to the instance's `dropdownItems` ReactiveVar, whereas a plain Template does not
expose instance variables to the template, so the list rendered nothing. Because
the options were never rendered as inputs, `getDropdownItems()` then overwrote
the ReactiveVar with the empty DOM on save and every entered value was dropped.
Fixed by re-adding the missing `dropdownItems` helper (returning the ReactiveVar),
matching the pattern the other migrated templates already use. Thanks to Claude.
**Fixed `Exception in global helper _` when opening the Create Custom Field
popup (and any translation containing a literal `%`).** i18n is configured with
a global sprintf post-processor (`postProcess: ["sprintf"]`), so every
translation is run through `i18next-sprintf-postprocessor`. The help text
`custom-field-stringtemplate-format` (`"Format (use %{value} as placeholder)"`)
contains a literal `%{value}` that sprintf cannot parse, so `vsprintf` threw and
crashed the global Blaze `_` translation helper — breaking that popup and any
string (in any language, including user translation overrides) that contains a
stray `%`. `TAPi18n.__` now retries without the sprintf post-processor when it
throws, returning the raw string (so `%{value}` is shown literally) instead of
crashing.
Thanks to rouceto1 and Claude.
Fixes#6357
visible until logout/login.** The `board` publication batched checklists,
checklist items, comments and attachments into board-level cursors filtered by
`cardId: { $in: cardIds }`, where `cardIds` was a one-time snapshot. In
`reywood:publish-composite` a child cursor only re-runs when its parent (the
board) document changes, so the snapshot never refreshed when a card was added
— a checklist on a card created after subscribing matched no published card and
only appeared on the next subscribe (logout/login). This was a regression from
the "Optimized board loading" change, which had replaced the original reactive
per-card child cursors with these batched snapshots. Fixed by **denormalizing a
`boardId` field onto `Checklists` and `ChecklistItems`** so they can be
published with a single board-level cursor filtered by `boardId` — one cursor
per collection (keeping the load optimization) that still reacts to checklists
on newly added cards, because a new checklist is created already carrying the
board's id. `boardId` is set on insert (server `before.insert` hooks, plus
explicitly in the Trello/WeKan board importers, which use `direct.insert` and
bypass hooks), re-derived when a checklist/item or its card moves to another
card (`before.update` on `cardId`) or the card moves to another board
(`Cards.after.update` cascade), and backfilled for existing data by an
idempotent startup migration. New `{ boardId: 1 }` indexes were added on both
collections. Comments and attachments instead remain reactive as per-card
children of the cards cursor. Assigned-only board members still only receive
checklists for cards assigned to them (the board-level cursor falls back to the
assigned cards' ids for those roles).
Thanks to ahlgrimma and Claude !
Fixes#6361
**Fixed deleting archived lists (or many cards) crashing the server with
`SyncedCron: Fatal error encountered (unhandledRejection): TypeError: Cannot
read properties of undefined (reading 'boardId')` at
`server/models/checklistItems.js`.** Deleting a list cascades into removing its
cards, and each card's checklists, checklist items, comments and attachments
(`cardRemover` in `models/cards.js`). `Cards.before.remove` called `cardRemover`
**without `await`** (and was not `async`), so the card document was deleted
first and the cascade then ran with the parent card already gone; the
`ChecklistItems.before.remove` / `Checklists.before.remove` hooks dereferenced
the now-undefined card (`card.boardId`) and threw, and because the promise was
unhandled, SyncedCron caught the rejection and tore down all running cron jobs.
Fixed by making `Cards.before.remove` `async` and awaiting `cardRemover` (so
sub-items are removed while the card still exists), and the REST card-delete
handler now runs `cardRemover` before removing the card. As defense in depth,
every card-activity hook and helper that looked up a card and used its
`boardId` / `listId` / `swimlaneId` now skips (with a warning) when the parent
card — or, for checklist-completion activities, the parent checklist — is
missing, instead of throwing: `before.remove` on checklist items and
checklists, `Checklists.after.insert`, the `Cards.before.update` timing
activity, and the shared `itemCreation` / `publishCheckActivity` /
`publishChekListCompleted` / `publishChekListUncompleted` / `commentCreation`
helpers (matching the guards already present in
`server/models/cardComments.js`).
Thanks to titver968 and Claude !
Fixes#6366
Several issues in the bulk move:
- The GridFS source matcher required `meta.gridFsFileId` to be *absent*, but
Meteor-Files always sets it, so selecting "MongoDB Meteor-Files" matched zero
files and "nothing happened". The matcher now recognizes real GridFS files
(`versions.*.storage === 'gridfs'` or a `meta.gridFsFileId` reference), and a
"nothing to move" message is shown when a source is empty instead of silently
doing nothing.
- Moving files crashed the whole server with
`FilesCollection#findOne() not available in server` — `ReactiveCache.getAttachment`
used a synchronous `findOne()`; it now uses `findOneAsync()`. The background
job is also hardened so a single failing file is skipped instead of crashing
the server via an unhandled rejection.
- The attachment **copy** API now honours the admin API upload limits (it
previously skipped them).
- **Fixed the "MongoDB Meteor-Files" file-count statistic** in Admin Panel /
Attachments, which counted *every* attachment metadata document (so files on
the Filesystem were wrongly reported as being in GridFS). It now counts only
attachments actually stored in GridFS, consistent with the move tool. Also
renamed the mislabeled "Mongo-Files" column to "Meteor-Files". Thanks to Claude.
- **Unified attachment/avatar storage migration: move any → any.** Admin Panel /
Attachments / Move Attachment can now move **Attachments, Avatars, or both**,
from **any source to any destination** across Filesystem, Meteor-Files GridFS,
Cloud (S3/Azure/GCS) and legacy CollectionFS GridFS. The default source is
"All Read-enabled storages" (every backend whose Read flag is enabled and whose
settings work), so everything can be consolidated into one destination in a
single run. All metadata is preserved (board / swimlane / list / card / user /
uploaded date / name / type / size), attachment cover references
(`cards.coverId`) are remapped when an id changes, and the legacy source is
deleted only after the new copy is verified. Thanks to Claude.
- **Legacy CollectionFS GridFS is now a first-class storage backend** (read,
migrate-from, and export-to) for both attachments and avatars, via the new
`models/lib/collectionFsStore.js` (binary keyed by `copies.<coll>.key` in the
`cfs_gridfs.<coll>` bucket, metadata in `cfs.<coll>.filerecord`). Thanks to Claude.
- **The storage strategy layer is now collection-aware.** `moveToStorage` and the
GridFS/Cloud strategies previously hard-coded the `Attachments` collection, so
moving avatars to GridFS/cloud would have updated the wrong collection. The
factory now carries its collection (`Attachments` or `Avatars`), so **avatars
can be stored in Meteor-Files GridFS and cloud**, not only on the filesystem.
Attachment behavior is unchanged. Thanks to Claude.
- **Read legacy CollectionFS attachments and avatars in place** (without
migrating). The backward-compatibility layer
(`models/lib/attachmentBackwardCompatibility.js`) was broken — it looked up the
GridFS binary by the filerecord `_id` and by filename instead of by
`ObjectId(copies.<coll>.key)`, so legacy files were never found. It is fixed and
generalized for attachments and avatars. Legacy attachments now appear in the
card attachment gallery (new `legacyBoardAttachments` publication) and stream
from the `cfs_gridfs.attachments` bucket, and legacy avatars
(`/cfs/files/avatars/<id>` URLs) are served from the `cfs_gridfs.avatars`
bucket instead of redirecting to a 404.
Thanks to Claude.
since after WeKan v7.93 and only generated docs for the `login`/`register`
endpoints (2 operations) instead of the full API. The Meteor 3 migration moved
the REST routes from `models/*.js` (`JsonRoutes.add(...)`) into
`server/models/*.js` (`WebApp.handlers.get/post/put/delete(...)`) and
introduced optional chaining (`?.`) that the `esprima` Python parser cannot
read, so `openapi/generate_openapi.py` silently skipped every route file.
The generator now understands both routing styles, scans both `models/` and
`server/models/`, downlevels modern JS syntax so files parse, handles the
`type: Array` SimpleSchema idiom, and `releases/rebuild-docs.sh` works directly
with Python 3.12.x (PEP 668). The generated `public/api/wekan.yml` /
`wekan.html` now cover the full API again (89 operations / 61 paths).
Thanks to Claude.
**Documented the attachment / file REST API in the OpenAPI docs** — file
upload, download, info, listing a board's files, copy, move and delete (the
`/api/attachment/*` endpoints registered via `WebApp.handlers.use()`, which the
generator cannot auto-discover) are now documented in `openapi/extra_paths.yml`
and injected into the generated docs, including their authentication and
permission requirements. Thanks to Claude.
**Security: attachment write operations in the REST/DDP API now require board
write access, not just membership (CWE-862, CWE-639).** Both attachment API
implementations (`server/routes/attachmentApi.js` HTTP routes and
`server/attachmentApi.js` Meteor methods) gated upload / copy / move / delete
on `board.hasMember()`, which is true for any active member regardless of role.
This let read-only, comment-only, no-comments, worker and assigned-only board
members add, copy, move and delete attachments through the API — actions the UI
forbids for those roles. They now require board write access (global site
admins still allowed); read operations (download / list / info) keep
membership-level access. Added security tests in
`server/lib/tests/attachmentApi.tests.js`. Thanks to Claude.
**The attachment copy API now honours the admin "Admin Panel / Attachments"
API transfer limits.** Copying an attachment creates a new attachment but
skipped the `apiUploadBlocked` / `apiUploadMaxBytes` checks that upload
enforces; copy now respects them in both API implementations. Thanks to Claude.
**SVG image uploads are now sanitized instead of rejected.** Uploaded SVGs
(attachments and avatars) are cleaned in place in `onAfterUpload` via the new
`models/lib/sanitizeSvg.js`, which removes JavaScript (`<script>`, inline
`on*=` event handlers, `javascript:`/`vbscript:` URIs, `<foreignObject>` /
`<iframe>` / `<object>` / `<embed>` and similar active content) and XML loops
(`<!DOCTYPE>` / `<!ENTITY>` entity-expansion / XXE constructs and
`<?xml-stylesheet?>`), so SVG images can be uploaded safely.
Thanks to Claude.
- Fixed one-finger drag-to-scroll not working in the All Boards view and the
Calendar view on Fairphone 4 Firefox and Ubuntu Touch Morph. These browsers
do not emit the synthetic mouse events the `@wekanteam/dragscroll` library
relies on, so scrolling only worked in the Swimlanes/Lists views. Added a new
native touch-scrolling helper `client/lib/dragscrollTouch.js` that scrolls the
nearest `.dragscroll` container with a one-finger drag (iOS is skipped, since
native momentum scrolling already works there), and added the `dragscroll`
class to the All Boards list and the Calendar view.
- Fixed all text, buttons, the All Boards tables and the top bars (header and
the second bar) rendering about 2x too large on Fairphone 4. `getMobileMode()`
defaulted to mobile mode only for iPhone, so every other phone fell back to
desktop mode, whose large desktop sizing looks oversized on a small phone
screen. The default is now mobile mode for any phone-sized touch device
(iPhone, Android/Mobile browsers, Ubuntu Touch, or a coarse-pointer touch
screen with viewport width ≤ 800px); desktop browsers stay in desktop mode and
the user's own Mobile/Desktop toggle still takes priority.
- Fixed not being able to reorder swimlanes and lists by dragging their drag
handle on touch devices. The new touch-scroll helper was hijacking touches
that started on a drag handle; `.handle` / `.ui-sortable-handle` elements are
now excluded from touch-scrolling so dragging the handle reorders as expected.
- Fixed a card duplicating into two cards when it was dragged by its title-row
drag handle while its Card Details panel was open. The card sortable in
`client/components/lists/list.js` was re-initialized on every list re-render
without first destroying the previous instance, so two `stop` handlers fired
per drag. It now destroys any existing sortable before re-initializing, the
same guard already used for swimlanes.
Attachment storage:
- Added configurable attachment storage backends in the admin panel: choose the
default save storage and store attachments on the local filesystem, GridFS, or
cloud object storage — S3-compatible (AWS S3, MinIO, Cloudflare R2, Backblaze
B2, Wasabi, DigitalOcean Spaces), Azure Blob Storage, or Google Cloud Storage
— via the `@tweedegolf/storage-abstraction` adapters (new
`models/lib/cloudStorage.js`). Includes a "Test connection" action and
English translations for the new settings.
- Added a server-side bulk attachment move (new `server/attachmentBulkMove.js`
and `models/attachmentBulkMoveStatus.js`) that moves all attachments from one
storage backend to another as a background job whose progress survives the
admin navigating away from or closing the page.
Thanks to xet7.
A new global "Roles" tab in Admin Panel / People lets a site admin choose which
board roles are allowed to invite users to a board ("Allow Invite to Board").
Each board role has its own toggle — Board Admin, Normal, Worker, Comment only,
No comments, Only Assigned Normal, Only Assigned Comment, Read Only and Only
Assigned Read — plus an "All Board Members" master toggle that selects them all.
The policy is enforced server-side in the `inviteUserToBoard` and `searchUsers`
methods, and the add-member button in the board sidebar is shown only to roles
the policy allows. Global Admin Panel users (site admins) always have all rights
and cannot be restricted here; this is kept clearly distinct in code from the
per-board "Board Admin" role. Secure default: only Board Admin and Normal may invite.
Thanks to xet7.
integration URLs (CWE-918). Wekan's outgoing-webhook integrations let a board
admin store an arbitrary URL that is later fetched server-side, so a
caller-controlled URL pointing at an internal address (cloud metadata at
`http://169.254.169.254/latest/meta-data/`, loopback, RFC 1918 ranges, etc.)
could be used to reach internal services or exfiltrate data.
**Originally fixed at the delivery layer in v8.35 and v8.36** (the
[IntegrationBleed](https://wekan.fi/hall-of-fame/integrationbleed/) fixes).
[Fix IntegrationBleed (v8.35)](https://github.com/wekan/wekan/commit/2cd702f48df2b8aef0e7381685f8e089986a18a4)
routed webhook delivery in `server/notifications/outgoing.js` through
`fetchSafe` (`server/lib/ssrfGuard.js`), which validates the URL and blocks
private/loopback IPs, and
[Fix RebindBleed of IntegrationBleed (v8.36)](https://github.com/wekan/wekan/commit/92beaa313706bc26fbea7e1cc8cfaf836609e038)
hardened it further to resolve DNS once, pin the connection to the validated
IP, and block redirects — closing the actual request-time SSRF including DNS
rebinding.
**New in this release:** the missing input-side validation the advisory
requested, added at the REST write paths in `server/models/integrations.js`.
Those endpoints accepted webhook URLs without a robust check:
`POST /api/boards/:boardId/integrations` relied only on the schema's regex
`custom()` validator (no DNS resolution, blind to a hostname that resolves to
a private IP and to decimal/octal/IPv4-mapped-IPv6 encodings), and
`PUT /api/boards/:boardId/integrations/:intId` wrote the URL via
`Integrations.direct.updateAsync`, which bypasses schema validation entirely,
so updated URLs were never validated at the data layer. Both endpoints now run
the DNS-aware `validateAttachmentUrl()`
(`models/lib/attachmentUrlValidation.js`, the same validator already used for
attachment imports) before storing the URL, rejecting
private/loopback/link-local/reserved targets with HTTP 400. Together with the
v8.35/v8.36 `fetchSafe` delivery guard and the schema validator on
client-side inserts/updates, webhook URLs are now validated on every write
path and again at delivery.
Thanks to Claude.
any user to copy private boards (CWE-862). The `copyBoard` Meteor method in
`server/publications/boards.js` had no authorization check: any logged-in user
could copy any board by ID — including private boards they are not a member of —
cloning all cards, checklists, custom fields, labels and rules, while the
equivalent REST endpoint `POST /api/boards/:boardId/copy` correctly required
board admin. The method also looped over caller-supplied `properties`
(`for (const key in properties) board[key] = properties[key]`) and copied them
onto the new board, letting an attacker inject arbitrary fields such as
`members` (to add themselves as admin of the copy) or `permission: 'public'`
(to expose the copy to everyone). The member-level fix shipped in v9.09 as part
of the AuthBleed fixes (caller must be authenticated, board must exist, caller
must be a board member, and `members`/`permission` are stripped from
`properties` before the copy). This release tightens it further to full parity
with the REST endpoint: `copyBoard` now requires the caller to be a board admin
(`board.hasAdmin(this.userId)`, the same `{isActive:true, isAdmin:true}`
condition the REST API enforces via `checkAdminOrCondition`) rather than merely
a member.
Thanks to Claude.
Six Meteor methods used internally by the OIDC login flow were registered as globally DDP-callable
with no authorization, while their non-OIDC counterparts require admin. `setCreateOrgFromOidc` and
`setOrgAllFieldsFromOidc` (`server/models/org.js`), `setCreateTeamFromOidc` and
`setTeamAllFieldsFromOidc` (`server/models/team.js`) let any authenticated user create/rename/
deactivate/modify arbitrary organizations and teams — including `orgAutoAddUsersWithDomainName` —
bypassing the admin-only restriction. Most critically, `groupRoutineOnLogin`
(`packages/wekan-oidc/oidc_server.js`) sets `isAdmin` from caller-supplied group data, so with
`PROPAGATE_OIDC_DATA` enabled any authenticated user could call it over DDP with
`{groups:[{isAdmin:true,forceCreate:true}]}` and promote themselves to global admin;
`boardRoutineOnLogin` could likewise add the caller to the default board. These six methods are only
ever invoked server-side (via `Meteor.callAsync`) during the OIDC handshake, where a fix that checks
`isAdmin`/`this.userId` would break legitimate group/admin propagation (the user is not yet logged in
or admin at that point). Fixed by rejecting any direct client/DDP invocation: a server-to-server
`Meteor.callAsync` runs with `this.connection === null`, whereas a client call has a non-null
connection, so each of the six methods now throws `not-authorized` when `this.connection !== null`.
The legitimate OIDC login flow is unaffected.
Thanks to alexwaira for the coordinated disclosure, and Claude.
`Accounts.onCreateUser` hook (CWE-287).
The `onCreateUser` hook in `server/models/users.js` unconditionally merged an incoming OIDC login
into any existing Wekan account whose email **or** username matched the (attacker-controlled) OIDC
claims — no ownership check, no email-verification check, no notification. An attacker who could
present a matching `email`/`username` claim (trivial on self-hosted Keycloak/Authentik, where the
`email`/`email_verified` claims are attacker-settable) inherited the victim's `_id`, boards, cards,
attachments, API tokens and admin status, all during the attacker's own first OIDC login with no
victim interaction. Fixed to fail closed, mirroring `LDAP_MERGE_EXISTING_USERS`: matching is now by
email only (never username); auto-linking is opt-in via the new `OAUTH2_MERGE_EXISTING_USERS`
setting (OFF by default, so the default deployment never merges); even when enabled the OIDC
provider must assert `email_verified=true`; otherwise the OIDC login is rejected with
`oidc-email-already-in-use` instead of merging. The provider's `email_verified` claim is now
captured into the OIDC service data in `packages/wekan-oidc/oidc_server.js`.
Thanks to alexwaira for the coordinated disclosure, and Claude.
(privilege escalation via read-level authz on write operations, CWE-862).
All six mutating REST handlers in `server/models/customFields.js` (POST/PUT custom-fields,
POST/PUT/DELETE dropdown-items, DELETE custom-fields) called the read-level
`Authentication.checkBoardAccess` instead of the write-level `checkBoardWriteAccess`, letting a
board member with the read-only role (`isReadOnly` / `isReadAssignedOnly`) write Custom Field
data via the REST API when `WITH_API=true`. Replaced the check with `checkBoardWriteAccess` in
all six mutating handlers (the two GET handlers correctly stay on `checkBoardAccess`), mirroring
`lists.js`/`swimlanes.js`/`cards.js`.
Thanks to Wernerina for the coordinated disclosure, and Claude.
The avatar RCE fix renamed `asyncExec` to `asyncExecFile` in `models/fileValidation.js`, but the
admin-configured external scanner command line still called the now-undefined `asyncExec`, throwing
`ReferenceError` (swallowed by the catch) and making every upload silently fail validation whenever
an external scanner was configured. Restored a shell-based `asyncExec` used only for that
admin-configured command line; MIME detection still uses the shell-free `asyncExecFile`.
Thanks to Claude !
from the interpolation regex in `compiler.js` and bundled `jade.js`. The greedy
`[\s\S]*` already matches to end of string, so dropping `$` keeps the same match
while eliminating the backtracking that CodeQL alert `js/polynomial-redos` flagged.
Thanks to CodeQL.
Thanks to mimZD and xet7 !
This is temporary fix, using click.
If sometime later browser protections prevents this
(like happened previously with click code),
fix this properly by extracting js-close-card-details() to function
and change to run that function directly, without using click.
Thanks to mimZD and xet7 !
Fixes#6161
The two changes made:
- **[cardDetails.js](client/components/cards/cardDetails.js)**:
Added `currentSwimlaneListsSorted` helper that queries lists
filtered by the card's `swimlaneId`
(mirroring the logic in `dialogWithBoardSwimlaneList.js`),
sorted by `sort` field (left-to-right column order → top-to-bottom in dropdown).
- **[cardDetails.jade](client/components/cards/cardDetails.jade:300)**:
Changed `each currentBoard.lists` to `each currentSwimlaneListsSorted`
so the dropdown only shows lists belonging to the card's current swimlane.
The default swimlane edge case is handled: if the card is in the default swimlane, lists with `swimlaneId` of `null`, `''`, or the default swimlane ID are all included.
Thanks to hmeunier95 and xet7 !
Fixes#6265
**Fix 1 — [`models/cards.js:797`](models/cards.js)**
`labels()` called `this.board().labels` without a null guard.
If `board()` ever returns `undefined` (board not yet in minimongo,
or a card with a bad `boardId`), this throws silently in Blaze
and all label chips disappear. Now returns `[]` safely.
**Fix 2 — [`client/components/swimlanes/swimlanes.js:660`](client/components/swimlanes/swimlanes.js)**
In swimlane view, `Template.swimlane`'s `visible()` helper
called `list.cards()` **without** the current swimlane ID.
This meant a list in swimlane S2 was considered "non-empty"
(and therefore kept visible) if swimlane S1 had
a card matching the label filter — even though S2 itself
had zero matching cards. The list would appear in S2
but be completely empty, explaining
"some lists are empty even when they contain cards with the label."
Now passes `this._id` (the current swimlane) so
only that swimlane's cards are counted.
Thanks to YosserDerbali and xet7 !
Fixes#6258
**Root cause:** When dragging a board in the Remaining view,
there were no visual cues indicating where to drop — users tried
dropping on other boards (which does nothing), not knowing
they need to drop on a workspace in the left sidebar.
**Changes made:**
1. **[boardsList.js](client/components/boards/boardsList.js)** —
`dragstart .js-board`: On drag start, adds `.board-drag-hint`
class to all workspace nodes and the Remaining menu item
in the sidebar, visually signalling valid drop targets.
New `dragend .js-board` cleans up the class.
2. **[boardsList.js](client/components/boards/boardsList.js)** —
`boardWorkspaceDragHint()`: Updated tooltip
from "Drag board: Remaining <-> Workspaces"
to "Drag board to assign to Workspaces
(drop on workspace in left sidebar)" —
removes the ambiguous bidirectional arrow that implied reordering.
3. **[boardsList.css](client/components/boards/boardsList.css)**:
Added green dashed border highlight for
`.workspace-node.board-drag-hint` and
`.js-select-menu.board-drag-hint` so valid drop targets
light up during a board drag.
4. **[en.i18n.json](imports/i18n/data/en.i18n.json)**:
Added `drag-board-to-workspace` key and
updated `board-open-and-move-between-remaining-and-workspaces`
to reflect the correct purpose (workspace assignment, not reordering).
This resolves the UX mismatch without implementing board reorder
(which carries regression risk).
The drag handle stays visible since it's genuinely functional
for workspace assignment — users just now get
a clear visual guide showing where to drop.
Thanks to aminsaidane and xet7 !
Fixes#6219
**Root cause:** [popup.css](client/components/main/popup.css)
had duplicate and conflicting rules at the bottom of the file.
Two rules were overriding the correct `cardDetailsActionsPopup`-specific fix:
1. `.pop-over:has(.pop-over-list) { max-height: 400px !important }` — had
the same CSS specificity as `.pop-over[data-popup="cardDetailsActionsPopup"]`
but appeared later, so it won, forcing the popup to 400px instead of 80vh.
2. `.pop-over .pop-over-list { max-height: 300px !important }` — forced each
individual list element to independently scroll at 300px, which is wrong
(the container should scroll, not each list).
3. There was an entire duplicate of the same block, adding the rules twice.
**What was removed:** The "STRONGER FIX" section and its duplicate (~55 lines),
which contained the conflicting `max-height: 400px` and `max-height: 300px` rules.
**What remains (and works correctly):**
- The `cardDetailsActionsPopup`-specific rules at lines 343–361
(max-height: 80vh, scrollable content-wrapper)
- The general flex-column layout at the end that constrains all popups to
`max-height: 90vh` with a fixed header and scrollable content area
- The `listActionsPopup`-specific rules
- The JS `_getOffset` in [popup.js](client/lib/popup.js:267) which
already correctly calculates available space above/below the opener
and passes `maxHeight` to the template
Thanks to robertjensen, harryadel and xet7 !
Fixes#6266,
fixes https://github.com/wekan/wekan/pull/6278
**Bug:** Cards disappear from lists that were previously minimized.
**Root cause** — [`client/components/lists/listBody.jade:2`](client/components/lists/listBody.jade):
```jade
template(name="listBody")
unless collapsed ← BUG: reads raw list.collapsed from MongoDB
```
`Template.listBody` has no `collapsed` helper defined in `listBody.js`.
In Blaze, when a helper isn't found, it falls through to
the raw data context — the list document from MongoDB.
So it reads `list.collapsed` directly from the database.
The parent template `list.jade` wraps `+listBody` with
its own `unless collapsed` that correctly uses
`Utils.getListCollapseState()`, which reads from the user's profile.
These two checks were reading from **different sources**.
**Failure sequence:**
1. Old WeKan versions (or a DB migration) stored `collapsed: true`
directly on the list document in MongoDB
2. User clicks the expand arrow → `Utils.setListCollapseState(list, false)
updates `profile.collapsedLists` only — it does **not** clear `list.collapsed`
in MongoDB
3. `list.jade`'s helper sees `false` (from profile) → renders `+listBody`
4. `listBody.jade`'s `unless collapsed` reads the raw
MongoDB field → still `true` → hides all cards, the add-card compose
and the `.js-minicards` container (breaking drag-and-drop too)
**Fix:** Remove the redundant `unless collapsed` from `listBody.jade`.
The parent template already guards it with the correct
helper — if `listBody` is being rendered at all, the list is expanded.
The inner check was both redundant and broken.
Thanks to hmeunier95 and xet7 !
Fixes#6267
In [boardHeader.js:429](client/components/boards/boardHeader.js),
the event handler uses `Template.currentData()` which returns
the popup template's data context — not the string from
the inner `with "watching"` / `with "tracking"` / `with "muted"` blocks.
It should use `this` instead, since `this` in a Blaze event handler
refers to the data context of the element that was clicked.
Thanks to apiccolim, Raawen-dev and xet7 !
Fixes#6280
**Fix 1 — [models/cards.js:2170](models/cards.js)**: Added `await` to `Cards.updateAsync()` in the `move()` method.
Without `await`, the card update was dispatched as a fire-and-forget Promise. In Meteor 3's AsyncLocalStorage-based DDP context, this can cause the collection hooks (`after.update`) to fire outside the original DDP method context, making `Meteor.userId()` return `null`. With `await`, the async chain is intact and the DDP userId context is preserved through to the hooks.
Also tightened the `UserPositionHistory` guard to `typeof Meteor.userId === 'function'` — the error log showed `TypeError: o.Meteor.userId is not a function` in certain bundle contexts, and this guard prevents that crash.
**Fix 2 — [server/rulesHelper.js](server/rulesHelper.js)**: Added `withUserId()` helper that sets `DDP._CurrentMethodInvocation` before calling `card.move()` from rule execution.
When automation rules trigger a card move, the code runs in a server-side callback with no active DDP method invocation. `CollectionHooks.getUserId()` → `Meteor.userId()` returns `null`, so the resulting activity gets `userId: null`, and the email shows `undefined`. Wrapping `card.move()` in `DDP._CurrentMethodInvocation.withValue({ userId: activity.userId }, ...)` restores the user context so hooks pick up the correct userId.
**Fix 3 — [server/notifications/email.js:37](server/notifications/email.js)**: Defensive fallback `const actorName = params.user || ''` instead of interpolating `params.user` directly.
If `params.user` is ever `undefined` (e.g., activity inserted without a userId from a migration or cron job), the template previously produced the literal string `"undefined moved card X to list Y"`. The fallback produces a blank actor name instead, which is less confusing and avoids the regression.
Thanks to titver968 and xet7 !
Fixes#6293
This fix matches the pattern already used in [server/publications/boards.js:329-330](server/publications/boards.js:329-330).
**Root cause:** `ReactiveCache.getAttachments(..., true)` can return either:
- A FilesCollection cursor (`FileCursor`) which has a `.cursor` property holding the real Mongo cursor, OR
- A plain Mongo cursor with no `.cursor` property
The old code always accessed `.cursor`, which would be `undefined` when the result is already a plain cursor. `undefined` in the cursors array causes Meteor DDP to throw "Publish function returned an array of non-Cursors".
**Fix:** Extract the result first, then use `attachmentsResult.cursor || attachmentsResult` — exactly like `boards.js` already does — so it works regardless of which type is returned.
Thanks to titver968 and xet7 !
Fixes#6294
**Bug:** When the jQuery sortable board-reordering code was replaced with HTML5 drag-and-drop (for workspace management), the board-to-board sorting was accidentally dropped. The `dragstart .js-board` handler only set `dataTransfer` data for workspace assignment — there were no `dragover`/`drop` handlers on `.js-board` elements to actually reorder boards.
**Fix in [boardsList.js](client/components/boards/boardsList.js):** Added four new event handlers after `dragstart .js-board`:
- `dragover .js-board` — allows drop (calls `preventDefault`) and shows a blue top/bottom border indicator based on cursor position relative to the target board's midpoint. Skips workspace drags by checking `dataTransfer.types`.
- `dragleave .js-board` — removes the visual indicator.
- `dragend .js-board` — cleans up all drag indicators on all boards.
- `drop .js-board` — determines drop position (before/after target), walks siblings to find the adjacent `.js-board` DOM elements, calls `Utils.calculateIndex()` to get a sort value, then saves it via `currentUser.setBoardSortIndex()` — matching exactly what the old jQuery sortable did.
**Fix in [boardsList.css](client/components/boards/boardsList.css):** Added `board-drag-over-top` and `board-drag-over-bottom` CSS classes with a 3px blue border to give visual feedback while dragging.
Thanks to snowsky and xet7 !
Fixes#6332
Summary of 3 fixes:
**`server/publications/cards.js`** — three publications were returning the raw `{ cursors, sessionData }` object from `findCards()` instead of publishing correctly:
- `brokenCards` (line 907–909): now destructures and pushes `sessionData` via `this.added()`, returns `cursors`
- `nextPage` (line 919–921): same fix
- `previousPage` (line 931–933): same fix
**`client/lib/cardSearch.js`** — `getResults()` had two bugs:
- A `directCards` undefined variable reference (would throw a `ReferenceError` at runtime)
- A fallback when `sessionData` is null that queried `Cards` filtered by `dueAt` — completely unrelated to search, and likely the direct cause of "1 card shown" (whichever card had a due date in MiniMongo)
The replacement uses `Cards.find({ _id: { $in: this.sessionData.cards } })` to fetch all matching cards in a single query, which is both correct and avoids the per-ID `ReactiveCache` staleness issues. When `sessionData` is null (subscription not ready yet), it now returns an empty array rather than random due-date cards.
Thanks to Meridiana and xet7 !
Fixes#6322
**[`client/00-startup.js`](client/00-startup.js)**
1. **Build chunks / language popup** — Sets `__webpack_public_path__` at module evaluation time (before any `import()` expressions run, which only happen inside `Meteor.startup` callbacks). Reads `window.__meteor_runtime_config__.ROOT_URL`, extracts the pathname (e.g. `/wekan`), and sets the prefix so rspack loads chunks from `/wekan/build-chunks/...` instead of `/build-chunks/...`.
2. **Service worker** — Replaces the hardcoded `/pwa-service-worker.js` path with `new URL('pwa-service-worker.js', Meteor.absoluteUrl()).pathname`, which automatically includes the context path.
**[`models/lib/universalUrlGenerator.js`](models/lib/universalUrlGenerator.js)**
3. **Attachment download links** — Added `getRootUrlPath()` which reads the pathname from ROOT_URL on the client (returns `''` on the server, where `WebApp.handlers` already strips the prefix before routing). `generateUniversalFileUrl` and `generateFallbackUrl` now prepend this prefix, so generated URLs are `/wekan/cdn/storage/attachments/...` instead of `/cdn/storage/attachments/...`.
**[`server/routes/universalFileServer.js`](server/routes/universalFileServer.js)**
4. **Redirect Location headers** — The `/attachments/` and `/avatars/` fallback routes issued relative-path redirects (`/cdn/storage/...`) that ignored the context path. They now use `Meteor.absoluteUrl(...)` which produces a correct absolute URL including the path prefix.
Thanks to JaumeFigueras and xet7 !
Fixes#6338
**Three bugs fixed:**
**1. `NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=0` was silently ignored** ([cards.js:2842](models/cards.js))
- Old: `!(iValue > 0 && iValue < 15)` — `0 > 0` is false, so `0` was always rejected
- Also: `.filter(Boolean)` would strip the number `0` even if the condition was fixed (`Boolean(0) === false`)
- Fix: changed condition to `isNaN(iValue) || iValue < -14 || iValue > 14`, and filter to `.filter(v => v !== false)`
**2. `day=1` produced an empty date range** ([cards.js:2825](models/cards.js))
- Old: `args = [then(1), then(day), ...]` → for `day=1`, both bounds were the same timestamp → `$gte: tomorrow, $lt: tomorrow` → zero results
- Fix: `args = [then(1), then(day + 1), ...]` so `day=1` correctly queries `[tomorrow, day_after_tomorrow)`
**3. Crash on cards with deleted/missing users** ([cards.js:2799](models/cards.js))
- Old: `user.username` was accessed without checking if `user` was null — a single bad card would crash the entire nightly scan silently
- Fix: null-guard with a `continue` and a warning log
**Practical impact for the reporter:**
- `NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=0` now works (today's due cards)
- `NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=1` now works (cards due tomorrow)
- `NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0` now works for both values
- Negative values (e.g. `-1`) now also work for past-due notifications
- Add !important to list header background/border overrides so they are
not overridden by list.css rules that use !important
- Add !important to list background and border-left overrides to prevent
the default #dedede background and 1px border from showing
- Add color overrides for pop-over-list links: white for CleanDark,
dark for CleanLight, to counter the global color:#000 !important in
popup.css (regression introduced with the popup overflow fixes)
Fixes#6168
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Append CSS fix to existing popup.css
- Add max-height: 300px and overflow-y: auto to .pop-over-list
- Prevents list actions menu from overflowing viewport
- Tested working on Mac (Chrome/Firefox)
Co-authored-by: Raawen-dev <rawenkhefifi19@gmail.com>
- Add user authentication check
- Add board membership verification
- Change update to updateAsync to fix server error
- List widths now persist after page refresh
- mongodb.url template now appends ?replicaSet=<name> for single-replica
setups when mongodb.replicaSet.name is set, eliminating the need for
a secretEnv workaround
- New mongodb.oplog.url helper generates MONGO_OPLOG_URL automatically
when a ReplicaSet is configured (single-node or multi-node)
- deployment.yaml injects MONGO_OPLOG_URL env var when the oplog URL
helper produces a non-empty value
- values.yaml documents the replicaSet.name option with example config
Disable jam:offline keepAll to prevent full-collection COLLSCAN queries.
WeKan uses modifiedAt (not updatedAt) and archived (not deleted),
so the default offline sync generates queries on non-existent fields
causing massive collection scans on every sync cycle.
Ensure standard GridFS index on attachments.chunks for efficient chunk lookups.
Without this, queries like find({files_id: ObjectId}) do full collection scans.
- Add migration script to convert dueDate to dueAt
- Convert string dates to Date objects
- Add missing userId and members fields
- Fix invalid board/list/swimlane references
This fixes cards not appearing in Due Cards view after migration from v7.95 to v8.20
Add a GitHub Actions workflow for releasing all platforms, including version extraction, building bundles for multiple architectures, and creating a GitHub release.
- Keep upstream's cardDetailsActionsPopup styling
- Combine date picker container styling from both versions
- Ensure both list actions and card details popups work correctly
- Added max-height constraint to card details actions popup
- Ensured popup fits within viewport on all screen sizes
- Implemented scrollable content when menu exceeds viewport
- Used min(380px, 85vw) for responsive width sizing
- Prevents menu items from being cut off or hidden
Resolves card pop-up menu content overflow issue
- Added top padding to popup content to prevent crowding
- Added max-height constraints for workspace actions popup
- Ensured modal fits within viewport on all screen sizes
- Implemented scrollable content when exceeding viewport
- Fixes issue with partially hidden controls in Workspace Settings modal
Closes#6200
ES module import statements are hoisted and evaluated in dependency-graph
order, not textual order. This means `import '/imports/collectionHelpers'`
being first in the file does NOT guarantee it runs first.
Fix: Split entry points into two stages:
- main.js: uses require() only (sequential, not hoisted)
1. require('/imports/collectionHelpers') — loads collection2, SimpleSchema
2. require('./imports') — loads all app code
- imports.js: contains all the import statements (safe because
collectionHelpers has already fully loaded via require())
This pattern guarantees attachSchema/SimpleSchema/helpers are available
before ANY model file evaluates, regardless of circular dependency chains.
The root cause: import statements are hoisted and trigger evaluation
chains that reach model files (via reactiveCache circular deps)
BEFORE collection2 has patched Mongo.Collection.prototype.attachSchema.
Fix: Convert collectionHelpers.js to use ONLY require() calls.
require() is NOT hoisted — each call completes synchronously before
the next, guaranteeing collection2 fully loads before any model
file can run.
settings.js is loaded early via circular dependency chain before
the entry point's hoisted imports finish. Add explicit require()
for aldeed:collection2 to ensure attachSchema is available.
Move all collection infrastructure into imports/collectionHelpers.js:
- Import aldeed:collection2 (provides attachSchema on Mongo.Collection)
- Import and set SimpleSchema as global
- Register denyUpdate/denyInsert as allowed schema extensions
- Provide Collection.helpers() shim
This ensures collection2 is loaded before any model file uses
attachSchema, regardless of circular dependency import ordering.
aldeed:simple-schema v2.0 no longer includes denyUpdate/denyInsert as
built-in properties. Register them via SimpleSchema.extendOptions() in:
- settings.js (first model loaded via circular dep chain)
- client/main.js and server/main.js (for all other models)
aldeed:simple-schema v2.0 uses `export default SimpleSchema`, not
a named export. Fix all import/require sites:
- settings.js: require().default
- attachmentStorageSettings.js: import default
- client/main.js, server/main.js: import default for global
ES module import statements are hoisted above all code, so placing
Settings = new Mongo.Collection() between imports has no effect.
Convert ALL imports to require() so the collection definition
truly executes first, breaking the circular dependency.
- settings.js: Import SimpleSchema explicitly from aldeed:simple-schema
before attachSchema call (breaks circular dep ordering)
- client/main.js, server/main.js: Set SimpleSchema as global for the
29 other model files that use it without importing
- users.js: Replace fibers/future pattern with async/await using
insertAsync/updateAsync for template container creation
- fileStoreStrategy.js: Remove duplicate sanitizeFilename declaration
- wekanCreator.js: Convert attachments.forEach to for...of loop
so await works inside the iteration body
Update .meteor/release from METEOR@2.16 to METEOR@3.4.
This activates Node 22, Express 5, async collection APIs,
MongoDB driver 6.x, and the Rspack bundler integration.
Add missing await to EmailLocalization.sendEmail() calls and replace
fallback Email.send with Email.sendAsync in the notification email
handler and rules helper. The notification handler runs inside an
async setTimeout callback where the missing await meant email errors
were unhandled promise rejections instead of being caught.
Add missing await to EmailLocalization.sendEmail() calls and replace
fallback Email.send with Email.sendAsync in board invitation and
registration invitation flows. Without await, promise rejections from
failed email sends were silently swallowed instead of being caught
by the surrounding try/catch blocks.
Email.sendAsync is available since Meteor 2.16 (email@2.2.5).
The synchronous Email.send is just a wrapper around it via
Promise.await. Switch to the async version directly since
sendEmail() is already an async function.
The only aggregation usage (metrics.js) already calls rawCollection().aggregate()
directly — the native MongoDB driver method — not the monkey-patched method from
this package. Convert to async/await (replacing Promise.await) for Meteor 3.0
Fibers removal compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The only aggregation usage (metrics.js) already calls rawCollection().aggregate()
directly — the native MongoDB driver method — not the monkey-patched method from
this package. Convert to async/await (replacing Promise.await) for Meteor 3.0
Fibers removal compatibility.
Replace the raix:handlebar-helpers package with inline Blaze helper
registrations for operator helpers ($eq, $neq, $gt, $lt, $gte, $lte,
$and, $or, $not, $in, $nin, $). This removes an external dependency
while preserving the same template helper API.
Also removes the unused ui@1.0.13 package pin.
All application code now uses native JavaScript equivalents.
underscore remains as a transitive dependency of
raix:handlebar-helpers in .meteor/versions.
Add findWhere, where, uniqBy, groupBy, indexBy, debounce, and once
helper functions in imports/lib/collectionHelpers.js. These provide
null-safe replacements for underscore methods that have no clean
native one-liner equivalent. Expose them on the Utils global for
client-side usage.
Convert all remaining BlazeComponent-based components to native Meteor
Template pattern: activities, comments, all rules actions/triggers,
swimlanes, users, gantt, import, and main utility components.
Convert sidebar, sidebarArchives, sidebarCustomFields, sidebarFilters,
sidebarSearches, and all settings panel components to use native Meteor
Template.onCreated/helpers/events pattern.
Convert popup, inlinedform, multiSelection, spinner, cardSearch,
datepicker, and dialog helper libraries to use native Meteor
Template.onCreated/helpers/events instead of BlazeComponent.
Update reactiveCache to remove BlazeComponent dependency.
Replace Spacebars .html templates with Jade equivalents for
originalPositionsView and originalPosition. Add new Jade template
for inlinedCardDescription. Update existing Jade templates to
reflect the BlazeComponent-to-Template migration changes.
Delete the old Mixins global and BlazeComponent-based InfiniteScrolling
mixin. Add standalone replacements:
- client/lib/infiniteScrolling.js: plain scroll-based pagination helper
- client/lib/currentCard.js: centralized current card resolution
- client/components/forms/datepicker.js: extracted date picker logic
Remove the blaze-components Meteor package and its peer dependencies
from .meteor/packages and .meteor/versions. Clean up related ESLint
globals (BlazeComponent, Mixins, InlinedForm, ReactiveTabs).
Drop the templates:tabs dependency as part of Meteor 3.0 migration prep.
The package is replaced by a lightweight inline basicTabs/tabContent
template pair with identical API, so no consumer template changes needed.
Remove the konecty:mongo-counter Meteor package dependency and replace
it with a minimal inline incrementCounter function using raw MongoDB
findOneAndUpdate. Also exports an async version for future Meteor 3.0
migration. No data migration needed — uses the same next_val field.
The createCronJob method's job callback was not async and did not await
this.runMigrationStep(step), causing the returned Promise to float.
When runMigrationStep threw, the unhandled rejection triggered
quave:synced-cron's global handler which called process.exit(0),
crashing the app on startup.
After a cross-board copy, ReactiveCache.getCard(newCardId) can return
null if the publication update hasn't reached the client yet. The card
is already created with a valid sort position server-side, so the
client-side repositioning is safely skippable.
Card.copy() mutated `this` directly (boardId, labelIds, customFields,
etc.), corrupting the cached source card object and causing intermittent
failures on repeated copy operations. Now works on a shallow copy.
Also normalizes customFields to [] when it's not an array (e.g. legacy
{} data in the database), preventing "Custom fields must be an array"
schema validation errors on both copy and move operations.
The click handler called setDone() without await then immediately
called Popup.back(2), destroying the popup template while the async
operation was still running. This caused unhandled promise rejections
and made errors invisible to the user.
- Convert all ReactiveCacheServer methods to async
- findOne() → findOneAsync()
- find().fetch() → find().fetchAsync()
- Meteor.user() → Meteor.userAsync()
- Convert ReactiveCache facade methods to async
- Server path uses await for async operations
- Client path returns sync Minimongo result (wrapped in Promise)
This prepares the codebase for Meteor 3.0 where MongoDB
operations are async.
- Clone @wekanteam/meteor-reactive-cache to npm-packages/
- Add .meteorignore to exclude npm-packages from Meteor
- Update package.json to use local file path
This allows direct modification of the package for Meteor 3.0
async migration while maintaining publishability to npm.
Since Card.copy() is now async, all callers in the copy chain need to
be updated to properly await the async operations:
- Make List.copy() async and await card.copy() in loop
- Make Swimlane.copy() async and await list.copy() in loop
- Fix mutateSelectedCards() to support async callbacks and method calls
- Make template copy event handler async in listBody.js
This also fixes the copySelection feature which was passing a callback
to mutateSelectedCards() but the function only supported method names.
The mapCustomFieldsToBoard() method is async but was being called without
await in copy() and move() methods. This caused a Promise to be assigned
to customFields instead of the actual array, failing MongoDB schema
validation on cross-board operations.
Changes:
- Make copy() method async and await mapCustomFieldsToBoard()
- Add await in move() for mapCustomFieldsToBoard()
- Make copyCard() server method async and await card.copy()
- Add null check in mapCustomFieldsToBoard() for cards without custom fields
- Update client to use Meteor.callAsync for server-only copyCard method
Fixes#6105
- Remove Fiber dependency (Npm.require('fibers'))
- Use Meteor.bindEnvironment for WebApp handler
- Convert login handler to async with findOneAsync
- Bump version 0.1.0 → 0.2.0
- Replace Meteor.wrapAsync with native Promises
- Convert all sync methods to async
- Use async DB operations (findOneAsync, updateAsync)
- Bump version 0.0.2 → 0.1.0
- Remove deprecated kadira:flow-router, kadira:blaze-layout, arillo:flow-router-helpers
- Add ostrio:flow-router-extra (modern, actively maintained)
- Add pwix:blaze-layout (maintained fork of kadira:blaze-layout)
- Convert all 22 BlazeLayout.render() calls to this.render() in route actions
- Maintain full backward compatibility with existing FlowRouter API
- All route definitions remain functional without syntax changes
- Build compilation succeeds without errors
This migration prepares Wekan for Meteor 3.0 compatibility by replacing
9-year-old deprecated routing packages with modern alternatives.
Next phase: Schema and async collection methods migration
This adds two actionable Windows notes to docs/Platforms/Propietary/Windows/Offline.md: - Explain that WRITABLE_PATH and ATTACHMENTS_STORE_PATH must be set before start to avoid the attachments error (“The 'path' argument must be of type string. Received undefined” from models/attachments.js). - Show how to expose the dev server on LAN on Windows (BIND_IP=0.0.0.0 + ROOT_URL=http://<LAN-IP>:4000). Tested on Windows 11 with Meteor dev server. Follow-up to #5871 (closed as wrong file).
Accessibility:
- Added product name to page titles, settings and global search.
- More accessible header and layout templates, modal dialogs, DOM structure, color contrast.
Added filtering of Admin Panel/People/People: All Users/Locked Users Only/Active/Not Active.
Added visual indicators: red lock icon for locked users, green check for active users, and red X for inactive users.
Added "Unlock All" button to quickly unlock all brute force locked users.
Added ability to toggle user active status directly from the People page.
Moved lockout settings from environment variables to database so admins can configure the lockout thresholds directly in the UI.
Thanks to xet7.
Previously, boards.js contains a `?.` operator, which is not supported by esprima, therefore boards.js is skipped.
This PR fixes the API generation for borads.js by removing `?.`.
See https://github.com/jquery/esprima/issues/2112
Fixes the visibility of card titles when card is opened for CleanDark theme as was inheriting black title from CleanLight theme.
Title now is white to match and heavily contrast theme.
- Fixed contrasting issue with tick check on custom fields, also fixed the ugly green tick coming up on the checklists in cards by utilisation of the !important tag to override it with the checkbox colour for custom fields also.
When using ReactiveCache.getCard, a buggy instance of Card is returned.
The bug experienced is that .assignees and .members are not always up-to-date.
This can be reproduced by trying to hover over cards and triggering a keyboard
shortcut that interacts with a card.
Add support for external email verification against OIDC login script. This will check local file for presence of email and log the user in if it is or deny them if it isn't.
conditions are:
- the checklist must have at least 1 checklist item
- all checklist items have to be finished
- the option "hide checked checklist items" is set at card details
- and added to show only the activities a card
- to display the card comments a connection to the server was needed to get the activities of the card comments, now, it's not necessary
- also performance relevant. until now there were a lot of activities loaded, now only of the current card
Added 10px margin to ExoDark lists also (following on from Clearblue fix) as with those rounded backgrounded lists I feel it looks nicer to give it some padding.
Open to other contributors perspective however.
- Added a wide range of updates/tweaks to various board themes
- Better defined theme start/endpoints in code
- Added modern scrollbars matching each theme
- Added styling to scrollbars in pop-over and horizontally across screen
- Tweaks to opacity/themes to work better with board backgrounds.
Have modified this in my test dev server, it appears this is the property that defines the spacing/containment of text for lists. When tested on iOS device this appears to work correctly.
Last commit removing vertical align, and iinitial commit unsetting line height for ModernDark fixes an issue where text was super spaced out on ModernDark theme only.
- Cleaned up theme descriptions as many themes weren't labelled/or had mismatched descriptions.
- Began adding scrollbars to default themes/board canvases.
- Test removal of lines/editing margins on nephritis theme
Removed drop shadow on exodark cards, as when background applied to board it looks a bit rough design wise.
Added background-color that will disappear when background is applied, and when background is not applied gives exodark a bit of a smoother cleaner look IMO
Fixed board backgrounds not showing correctly on the following themes. Various code modified as different themes have used different code to implement the background.
Have tried to stick with board-wrapper as a standard to apply the background colour to.
Board background image will override board-wrapper colour every time.
Dark
Relax
Natural
Modern
ModernDark
Exodark
CleanDark
CleanLight
- Updated pull due to original file I had stored on my pc 139 commits behind! So synced that and then redid code changes to bring it all up to date.
Fixed board backgrounds not showing correctly on the following themes.
Various code modified as different themes have used different code to implement the background.
Have tried to stick with board-wrapper as a standard to apply the background colour to.
Board background image will override this every time.
- Dark
- Relax
- Natural
- Modern
- ModernDark
- Exodark
- CleanDark
- CleanLight
Fix position from absolute to fixed, so it moves with the viewport when horizonally scrolling.
Fix card top border due to changing position, adjust top border px to 82px
Fix cards not showing up properly in ModernDark theme, when more lists are created than will fit on a single screen, inciting a horizontal scrollbar, and then scrolling across trying to open cards.
Due to position being absolute, this then opened the card off-screen
Bugfix, task card may overflow below the screen when maximized, making the bottom part not viewable.
Reason: Top position is set to 97px, Height of the element is set to calc(100% - 20px).
Fix: Set the height to be lower than 100% - Top position
1. It takes too long while server stops after 2 minutes (#5371).
2. It is useless, because when you copy a board, most of the time your users don't want to be notified of every copied cards.
Wekan is an open-source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
# sudo npm doesn't work right, so this is a workaround
function npm_call(){
TMPDIR="/tmp/tmp_npm_prefix"
if[[ -d "$TMPDIR"]];then
rm -rf $TMPDIR
fi
mkdir $TMPDIR
NPM_PREFIX="$(npm config get prefix)"
npm config set prefix $TMPDIR
npm "$@"
npm config set prefix "$NPM_PREFIX"
echo"Moving files to $NPM_PREFIX"
for i in $(ls -A $TMPDIR);do
cprec "$TMPDIR/$i""$NPM_PREFIX/$i"
done
rm -rf $TMPDIR
}
#function wekan_repo_check(){
## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN
# git_remotes="$(git remote show 2>/dev/null)"
# res=""
# for i in $git_remotes; do
# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')"
# if [[ "$res" == "wekan/wekan" ]]; then
# break
# fi
# done
#
# if [[ "$res" != "wekan/wekan" ]]; then
# echo "$PWD is not a wekan repository"
# exit;
# fi
#}
echo
PS3='Please enter your choice: '
options=("Install Wekan dependencies""Build Wekan""Run Meteor for dev on http://localhost:4000""Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000""Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT""Quit")
Wekan is an open-source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
WeKan ® is an Open Source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
Thanks for reporting a bug! Please ensure you are using the [latest version of WeKan](https://wekan.fi/install) and [Upgrade](https://wekan.fi/upgrade) before submitting.
- type:textarea
id:description
attributes:
label:Bug Description
description:A clear and concise description of what the bug is. Mention versions of WeKan, Node.js, database name and version, frontend webserver version like Caddy etc.
validations:
required:true
- type:textarea
id:reproduction
attributes:
label:Steps to Reproduce
description:How can we recreate this issue?
placeholder:|
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required:true
- type:textarea
id:logs
attributes:
label:Relevant Logs
description:|
- Please paste any relevant anonymized server logs or browser console errors here.
- Snap:sudo snap logs wekan.wekan
- Docker:sudo docker logs wekan-app
- If logs are very long, attach them in .zip file
render:shell
- type:textarea
id:context
attributes:
label:Additional Context
description:Add any other context, anonymized screenshots or GIF video about the bug, and screenshot mockups about how it should work.
## ⚠️ IMPORTANT: Please do not report security vulnerabilities via public issues.
To protect the WeKan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors.
### How to report:
Please read our **[Security Policy (SECURITY.md)](https://github.com/wekan/wekan/blob/main/SECURITY.md)** for the official reporting process and contact information.
description:Problems specifically related to the WeKan app on Univention Corporate Server
labels:["UCS"]
body:
- type:markdown
attributes:
value:|
## 🛑 Is this a UCS-specific issue?
If your issue is related to the **Univention Corporate Server (UCS) integration**, packaging, or installation via the Univention App Center, it should be reported in the dedicated Univention repository.
Reporting there ensures that the maintainers specifically focused on the UCS environment see your request.
If you are certain this is a **core WeKan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template.
- type:textarea
id:ucs-details
attributes:
label:Brief Description (Optional)
description:If you still wish to post here, please provide a brief summary of why this is a core Wekan issue and not a UCS-specific integration bug.
echo "::error::Could not find a v-prefixed WeKan version in the bundle's package.json files."
exit 1
fi
if [ "$found" != "v${VERSION}.0" ] && [ "$found" != "v${VERSION}" ]; then
echo "::error::Bundle reports app version $found but the release is v${VERSION}. The bundle baked into the image is stale/mismatched — the Admin Panel would show $found. Aborting before build."
exit 1
fi
echo "Bundle app version $found matches release v${VERSION}."
- name:Build and push multi-arch image
# Pass VERSION explicitly as a build-arg. The Dockerfile's `ARG VERSION`
# default is only a fallback for manual builds; the release version is the
# authoritative source here. Without this the image would build against the
# Dockerfile default (downloading that version's wekan-<v>-<arch>.zip and
# baking its package.json), so a v${VERSION}-tagged image could ship — and
# report in the Admin Panel — a stale, different version.
run:|
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSION=${VERSION} \
-t wekanteam/wekan:v${VERSION} \
-t wekanteam/wekan:latest \
-t quay.io/wekan/wekan:v${VERSION} \
-t quay.io/wekan/wekan:latest \
-t ghcr.io/wekan/wekan:v${VERSION} \
-t ghcr.io/wekan/wekan:latest \
--push \
.
# ─── 9. Build the snap on Launchpad, push to the store, attach to release ────
# The snap's `wekan` part downloads wekan-<version>-<arch>.zip from the GitHub
# Release, so this job runs only after `release` has published those assets.
# `snapcraft remote-build` builds every platform listed in snapcraft.yaml
- name:Verify snapcraft.yaml version matches the release
# The `bump` job (releases/version.sh) is the single source of truth for
# the snap version and updates snapcraft.yaml before the tag is created.
# snapcraft.yaml's `version:` names the produced files
# (wekan_<version>_<arch>.snap), so if a stale tag carried the wrong
# version the build would produce wekan_<old>_*.snap and the upload/attach
# globs (wekan_${VERSION}_*.snap) would match nothing — passing the literal
# glob to `snapcraft upload` ("is not a valid file", exit 64). Assert it
# here so that mismatch fails fast (before a ~20 min Launchpad build) with
# a clear pointer, instead of re-bumping the version inside this build job.
run:|
set -euo pipefail
if ! grep -qxF "version: '${VERSION}'" snapcraft.yaml; then
echo "::error::snapcraft.yaml is not at version '${VERSION}' ($(grep -m1 '^version:' snapcraft.yaml)). The bump job (releases/version.sh) did not update the tagged commit."
exit 1
fi
echo "snapcraft.yaml is at version '${VERSION}'."
- name:Build snaps on Launchpad (amd64 + arm64)
# `snapcraft remote-build` is experimental and talks to Launchpad over
# HTTPS after pushing the whole repo; it intermittently dies with a
# transient TLS drop (e.g. "SSLEOFError: EOF occurred in violation of
# protocol", exit 70) that has nothing to do with the build itself.
# Retry a few times so one network hiccup doesn't fail the release.
@@ -51,12 +47,13 @@ See bottom of https://github.com/wekan/wekan/issues/3874
## FAQ
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
- Please read the [FAQ](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md) first
- Please don't feed the [trolls](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
## About WeKan ®
WeKan ® is an completely [Open Source][open_source] and [Free software][free_software]
WeKan ® is a completely [Open Source][open_source] and [Free software][free_software]
collaborative kanban board application with MIT license.
Whether you’re maintaining a personal todo list, planning your holidays with some friends,
@@ -72,26 +69,23 @@ that by providing one-click installation on various platforms.
- WeKan ® largest user has 30k users using WeKan ® in their company.
- WeKan ® has been [translated](https://app.transifex.com/wekan/) to about 105 languages.
- [Features][features]: WeKan ® has real-time user interface.
- [Platforms][platforms]: WeKan ® supports many platforms.
- [Platforms](https://wekan.fi/install/): WeKan ® supports many platforms.
WeKan ® is critical part of new platforms Wekan is currently being integrated to.
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
We also welcome sponsors for features and bugfixes.
By working directly with WeKan ® you get the benefit of active maintenance and new features added by growing WeKan ® developer community.
## Getting Started with Development
The default branch uses Meteor 3.5-beta.7 with Node.js 24.x.
See [CHANGELOG.md](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) for the latest runtime updates.
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
Please refer to the [developer documentation](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Developer-Documentation.md) for more information.
## First-Time Setup for Development
### Prerequisites
Before building WeKan from source, ensure you have:
- **Git** - for cloning the repository
- **Node.js 24.x** - WeKan requires Node.js 24.x
- **Meteor** - the JavaScript framework WeKan is built with
WSL users can use Snap Candidate. See [install docs](https://wekan.fi/install/).
The development server will start at http://localhost:4000. Any changes you make to the source code will automatically trigger a rebuild and refresh your browser.
## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[More screenshots at Features page](https://github.com/wekan/wekan/tree/main/docs/Features)
[![Screenshot of WeKan ®][screenshot_wekan]][roadmap_wekan]
@@ -118,16 +183,12 @@ By working directly with WeKan ® you get the benefit of active maintenance and
WeKan ® is released under the very permissive [MIT license](LICENSE), and made
This document provides a detailed overview of WeKan’s **login and authentication system**, covering client-side UI, server-side logic, external authentication methods, and potential upgrade paths.
---
## 🖥️ Login Web UI
WeKan's login interface is implemented using a combination of:
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
## About Money: No bounty
Security is very important to us. If you discover any issue regarding security, please disclose
the information responsibly by sending an email to support (at) wekan.team using
[this PGP public key](support-at-wekan.team_pgp-publickey.asc) and not by
creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
There is no bounty, like described at [CONTRIBUTING.md](CONTRIBUTING.md),
because WeKan is NOT Big Tech. WeKan is FLOSS.
We thank you with a place at our hall of fame page, that is
at https://wekan.github.io/hall-of-fame
## Steps for Coordinated Vulnerability Disclosure
## How should reports be formatted?
### 1. Security Researcher:
- **Report**: Click "New draft security advisory" at [WeKan Security Advisories](https://github.com/wekan/wekan/security/advisories).
More info at [GitHub Docs about reporting privately](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-privately).
- **Proof of Concept (PoC)**: If possible, include a fix or a reproduction script/code.
- **If GitHub does not work**: Please send report to **security@wekan.fi**
- **Optional Email Encryption**: An optional PGP key is available at [security-at-wekan.fi.asc](security-at-wekan.fi.asc).
### 2. WeKan Security Team:
- **Remediation**: Please wait for a new WeKan release that addresses the issue.
Fixes are announced at the top of the [ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md).
- **Recognition**: We will acknowledge your contribution by adding you to our
[Hall of Fame](https://wekan.fi/hall-of-fame/).
- **CVE Policy**: WeKan Security requests CVE at GitHub when releasing Security Advisory.
### 3. Post-Release and Public Disclosure:
- Publicity: If you publish blog posts, give talks, or write articles about the
reported vulnerabilities, please send the links to us. We will link them
to your entry in the [Hall of Fame](https://wekan.fi/hall-of-fame/) for additional context.
- Advisories reported at GitHub are listed at [WeKan Security Advisories](https://github.com/wekan/wekan/security/advisories).
## How Should Reports Be Formatted?
```
Name: %name
@@ -26,35 +46,40 @@ CWSS (optional): %cwss
Anyone who reports a unique security issue in scope and does not disclose it to
a third party before we have patched and updated may be upon their approval
added to the Wekan Hall of Fame.
added to the WeKan Hall of Fame https://wekan.fi/hall-of-fame/
## Which domains are in scope?
No public domains, because all those are donated to Wekan Open Source project,
No public domains, because all those are donated to WeKan Open Source project,
and we don't have any permissions to do security scans on those donated servers.
Please don't perform research that could impact other users. Secondly, please keep
the reports short and succinct. If we fail to understand the logics of your bug, we will tell you.
Please don't perform research that could impact other users. Second, please keep
the reports short and succinct. If we fail to understand the logic of your bug, we will tell you.
You can [Install Wekan](https://github.com/wekan/wekan/releases) to your own computer
You can [Install WeKan](https://wekan.fi/install/) on your own computer
and scan it's vulnerabilities there.
## About Wekan versions
## About WeKan Versions
There are only 2 versions of Wekan: Standalone Wekan, and Sandstorm Wekan.
There are 2 versions of WeKan: Standalone WeKan, and Sandstorm WeKan.
### Standalone Wekan Security
## Meteor Security
Standalone Wekan includes all non-Sandstorm platforms. Some Standalone Wekan platforms
https://guide.meteor.com/security
### Standalone WeKan Security
Standalone WeKan includes all non-Sandstorm platforms. Some Standalone WeKan platforms
like Snap and Docker have their own specific sandboxing etc features.
Standalone Wekan by default does not load any files from Internet, like fonts, CSS, etc.
This also means all Standalone Wekan functionality works in offline local networks.
WeKan is used at most countries of the world https://snapcraft.io/wekan
and by by companies that have 30k users.
Standalone WeKan by default does not load any files from Internet, like fonts, CSS, etc.
This also means all Standalone WeKan functionality works in offline local networks.
WeKan is used in most countries of the world https://snapcraft.io/wekan
and by companies that have 30k users.
- Wekan private board attachments are not accessible without logging in.
- There is feature to set board public, so that board is visible without logging in in readonly mode, with realtime updates.
- WeKan private board attachments are not accessible without logging in.
- There is feature to set board public, so that board is visible without logging
in readonly mode, with realtime updates.
- Admin Panel has feature to disable all public boards, so all boards are private.
## SSL/TLS
@@ -63,32 +88,28 @@ and by by companies that have 30k users.
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530
I have just seen the Meteor DevTools Evolved extension and was wondering if anyone had asked themselves the question of security.
I have just seen the Meteor DevTools Evolved extension and was wondering if anyone
had asked themselves the question of security.
Insofar as all data is shown in the minimongo tab in plain text.
How can data be hidden from this extension?
@@ -96,32 +117,36 @@ A:
## PubSub
- It is not security issue to show some text or image, that user has permission to see. It is a security issue, if browserside is some text or image that user should not see.
- Meteor has browserside minimongo database, made with Javascript, updated with Publish/Subscribe, PubSub.
-Publish/Subscribe means, that realtime web framework reads database changes stream, and then immediately updates webpage,
like like dashboards, chat, kanban. That is the point in any realtime web framework in any programming language.
- Yes, you should check with Meteor DevTools Evolved Chromium/Firefox extension that at minimongo is only text that user has permission to see.
- Do checking as logged in user, and logged out user.
- It is not a security issue to show text or images that the user has permission to see.
It is a security issue, if browserside is some text or image that user should not see.
-Meteor has browserside minimongo database, made with JavaScript, updated with Publish/Subscribe, PubSub.
- Publish/Subscribe means, that realtime web framework reads database changes stream,
and then immediately updates webpage, like dashboards, chat, kanban.
That is the point of any realtime web framework in any programming language.
- Yes, you should check with Meteor DevTools Evolved Chromium/Firefox extension that
at minimongo is only text that user has permission to see.
- Check as a logged-in user and as a logged-out user.
- Check permissions and sanitize before allowing some change, because someone could modify content of input field,
PubSub/websocket data (for example with Burp Suite Community Edition), etc.
- If you have REST API, also check that only those that have login token, and have permission, can view or edit text
- If you have a REST API, also check that only those who have a login token and permission can view or edit text.
- You should not include any data user is not allowed to see. Not to webpage text, not to websockets/PubSub, etc.
- Minimongo should not have password hashes PubSub https://wekan.github.io/hall-of-fame/userbleed/
- PubSub uses Websockets, so you need those to be enabled at webserver like Caddy/Nginx/Apache etc, examples of settings
- Minimongo should not have password hashes PubSub https://wekan.fi/hall-of-fame/userbleed/
- PubSub uses WebSockets, so you need those to be enabled on the webserver like Caddy/Nginx/Apache etc, examples of settings
at right menu of https://github.com/wekan/wekan/wiki
- Clientside https://github.com/wekan/wekan/tree/main/client/components subscribes to
PubSub https://github.com/wekan/wekan/tree/main/server/publications or calls meteor methods at https://github.com/wekan/wekan/tree/main/models
- Clientside https://github.com/wekan/wekan/tree/main/client/components subscribes to
PubSub https://github.com/wekan/wekan/tree/main/server/publications or calls meteor methods
at https://github.com/wekan/wekan/tree/main/models
- For Admin:
- You can have input field for password https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- You can save password to database https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- Check that only current user or Admin can change password https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- Note that currentUser uses code like Meteor.user() in .js file
- Do not have password hashes in PubSub https://github.com/wekan/wekan/blob/main/server/publications/users.js
- Do not include password hashes in PubSub https://github.com/wekan/wekan/blob/main/server/publications/users.js
- Only show Admin Panel to Admin https://github.com/wekan/wekan/blob/main/client/components/settings/settingBody.jade#L3
- If there is a lot of data, use pagination https://github.com/wekan/wekan/blob/main/client/components/settings/peopleBody.js
- Only have limited amount of data published in PubSub. Limit in MongoDB query in publications how much is published. Too much could make browser too slow.
- Use Environment variables for any email etc passwords.
- But what if you would like to remove minimongo? And only use Meteor methods for saving? In that case, you don't have realtime updates,
- If there is a large amount of data, use pagination https://github.com/wekan/wekan/blob/main/client/components/settings/peopleBody.js
- Only publish a limited amount of data in PubSub. Limit in MongoDB query in publications how much is published. Too much could make browser too slow.
- Environment variables for email and other passwords.
- But what if you would like to remove Minimongo and only use Meteor methods for saving? In that case, you don't have realtime updates,
and you need to write much more code to load and save data yourself, handle any multi user data saving conflicts yourself,
and many Meteor Atmospherejs.com PubSub using packages would not work anymore https://github.com/wekan/we
@@ -129,14 +154,14 @@ A:
- With PubSub, there is checking, that someone modifying Websockets content, like permission isAdmin, can not change to Admin.
- Not implemented yet: Granular Roles https://github.com/wekan/wekan/issues/3022
- Check is user logged in, with `if (Meteor.user()) {`
@@ -145,8 +170,8 @@ A:
## Environment variables
- For any passwords, use environment variables, those are serverside
- Do not copy environment variable to public variable that is visible browserside https://github.com/wekan/wekan/blob/main/server/max-size.js
- For any passwords, use environment variables; those are serverside
- Do not copy environment variables to public variables that are visible on the browserside https://github.com/wekan/wekan/blob/main/server/max-size.js
```
Meteor.startup(() => {
@@ -157,42 +182,91 @@ Meteor.startup(() => {
```
- For serverside, you can set Meteor.settings.variablename, without text public
- For WeKan kanban, there is feature for setting board public, it can be viewed by anyone, there is realtime updates. But
- For WeKan kanban, there is a feature for setting a board to public; it can be viewed by anyone, with realtime updates. But
- Some of those permissions are checked at users.js models at https://github.com/wekan/wekan/tree/main/models
- Environment variables are used for email server passwords, etc, at all platforms https://github.com/wekan/wekan/commit/a781c0e7dcfdbe34c1483ee83cec12455b7026f7
## Escape HTML comment tags so that HTML comments are visible
- Someone reported, that it is problem that content of HTML comments in edit mode, are not visible at at view mode, so this makes HTML comments visible.
- Someone reported that it was a problem that the content of HTML comments in edit mode was not visible in view mode, so this change makes HTML comments visible.
On Sandstorm platform using environment variable Standalone Wekan features like Admin Panel etc are
turned off, because Sandstorm platform provides SSO for all apps running on Sandstorm.
On the Sandstorm platform, Standalone WeKan features like the Admin Panel are turned off using environment variables, because Sandstorm platform provides SSO for all apps running on Sandstorm.
[Sandstorm](https://sandstorm.io) is separate Open Source platform that has been
[Sandstorm](https://sandstorm.io) is a separate Open Source platform that has been
[security audited](https://sandstorm.io/news/2017-03-02-security-review) and found bugs fixed.
Sandstorm also has passwordless login, LDAP, SAML, Google etc auth options already.
At Sandstorm code is read-only and signed by app maintainers, only grain content can be modified.
Wekan at Sandstorm runs in sandboxed grain, it does not have access elsewhere without user-visible
PowerBox request or opening randomly-generated API key URL.
Sandstorm also has passwordless login, LDAP, SAML, Google, and other auth options already.
On Sandstorm, code is read-only and signed by app maintainers; only grain content can be modified.
WeKan on Sandstorm runs in a sandboxed grain; it does not have access elsewhere without a user-visible PowerBox request or opening a randomly-generated API key URL.
Also read [Sandstorm Security Practices](https://docs.sandstorm.io/en/latest/using/security-practices/) and
Loaded 100 of 2165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.