Compare commits

...
133 Commits
Author SHA1 Message Date
github-actions[bot] 6b98272272 Bump versions for v12.78 2026-11-02 07:14:42 +00:00
Lauri Ojansivu ee22fb1a3b Prepare v12.78 release 2026-11-02 09:13:43 +02:00
Lauri Ojansivu 154c9a6e52 Updated Changelog. 2026-11-02 09:11:57 +02:00
Lauri Ojansivu 121f37be91 Tests: drawing refit, Due Cards board scope, dependency import counts, and SAML's one Issuer.
Changes since October whose commits lacked a unit, negative or browser
test:
- 2929b61c85: a new drawing refits when its window changes, and is left
  alone once drawn on, edited, or zoomed or resized by hand.
- c64b3a3b1d: Due Cards, in "me" and "all users", shows an assigned-only
  member only their assigned cards; the owner sees them all.
- 8a6aeba7d1: the All Boards dependency import says how many lines were
  added, already there and unmatched.
- 5c50f38e25: a signed SAML Response with two Issuers is refused, which
  node-saml 5.1.0 accepted; the same Response with one Issuer logs in.
The browser specs pass in Chromium.

Thanks to xet7 !
2026-11-02 00:54:59 +02:00
Lauri Ojansivu 79c40407d1 Tests: Admin Panel pane titles, audit log alignment, GCS help, Data health "Never", card reports.
October Admin Panel changes whose commits had no test of their own:
- 85af731c1b and 2017f2871f: the Password length and Audit log panes
  name themselves in the title bar, the audit log has its own URL, and
  its cells align to the start - right in Hebrew - with no left/right
  rule left in its CSS.
- e6f7d226ca: no help line of a storage field is shown twice.
- 77956c98ed: Data health's "Never" is its own key, also when an
  administrator translates the List Sync one differently.
- 69f3358154: the card reports stay instance-wide and admin-only.
tests/playwright/specs/admin-pane-titles-and-help.e2e.js passes in
Chromium.

Thanks to xet7 !
2026-11-02 00:31:24 +02:00
Lauri Ojansivu 148d18d456 Tests: OpenSSL 4 decrypt refusal, the Scrum rollover lease, and the backup catch-up scan.
October changes whose commits had no node test of their own:
- ffe49dd2fd: decryptFile() refuses both the OpenSSL 4 and the OpenSSL 3
  wording of a failed authentication as "changed or the key is wrong",
  on any OpenSSL; an unrelated error (ENOSPC) is passed through.
- 0218cd823f: the rollover and batch-job leases are claimed before every
  write, another server's live lease is refused, and each lease field is
  written in one place in the whole tree.
- 8cbe2605fe: on macOS, a stopped engine and one with watching off run no
  catch-up scan.

Thanks to xet7 !
2026-11-02 00:30:57 +02:00
Lauri Ojansivu 15877dd1bc Source server: a local package fork that a prebuilt package implies is loaded first.
ecmascript implies the local dynamic-import fork, so compiling ddp-server,
listed before it in .meteor/versions, stopped the source-loaded server
with "isopack dynamic-import not yet loaded". The loader now loads the
local package asked for and retries. The source server still stops later,
on "Cannot find module 'meteor/accounts-base'"; the bundle server is what
the browser tests ran against.

Thanks to xet7 !
2026-11-02 00:24:55 +02:00
Lauri Ojansivu 3ccb465c3f Tests: the #6755 browser test focuses the minicard link itself.
Chromium does not leave the focus on the minicard after its menu popup
is closed with Escape - the menu's opener is a link without href, which
cannot take the focus back - so the test asserted a state the browser
had not reached. It now focuses the minicard's link, the state of the
report, and checks that d still opens the Due date editor from there.
Passes in Chromium and WebKit.

Thanks to mimZD and xet7 !
2026-11-02 00:24:55 +02:00
Lauri Ojansivu 71d9776b61 CHANGELOG: the Rules page has a Back to the board.
Thanks to mimZD and xet7 !
2026-11-02 00:07:50 +02:00
Lauri Ojansivu 34d9817110 Rules page: Back to the board.
The Rules page lost its Back with its own sidebar; only the trigger, action
and rule details pages kept one, to the rules list, so the way back to the
board was the Board View menu. The rules list and its Workflow, Bots,
Blocks and History views now have a Back link to the board at the top. The
three sub-pages keep their own Back and show no second one.

Thanks to mimZD and xet7 !

Fixes #6852.
2026-11-02 00:07:42 +02:00
Lauri Ojansivu aa939d7bec Tests: CrossBoardExportBleed and UnassignedAttachBleed are in the security regression inventory.
Both are published on the Hall of Fame, so the inventory counts 143 and
names each one's unit and browser regression tests. CrossBoardExportBleed
was published without being added, which failed the whole node-suite run.

Thanks to xet7 !
2026-11-02 00:05:44 +02:00
Lauri Ojansivu 194e5b0a51 CHANGELOG: UnassignedAttachBleed, and shortcuts after a click on a minicard or a closed popup.
Thanks to Javier García Herrera, mimZD and xet7 !
2026-11-02 00:05:10 +02:00
Lauri Ojansivu 532a96da11 Security: UnassignedAttachBleed - attachment API writes obey assigned-only card scope.
An assigned-only member whose card assignment was removed kept the card and
attachment ids and, through POST /api/attachment/upload and
DELETE /api/attachment/delete/:id, still added a file to that card and
soft-deleted its existing one: both asked only for board write access. The
DDP methods api.attachment.upload/delete, and copy and move onto a target
card over REST and DDP, had the same shape.

mayWriteCardAttachment (server/lib/assignedOnlyAttachments.js) applies the
same card rule the ordinary card Delete already used to all eight handlers.
A refusal is recorded in Admin Panel / Problems as UnassignedAttachBleed
(authz.assigned-attachment-write, medium: never disables the account).
tests/unassignedAttachBleed.test.cjs reproduces the reported sequence and
fails if any attachment API write lacks the card check;
tests/playwright/specs/unassigned-attach-bleed.e2e.js replays it over REST.

GHSA-w26g-rc43-q6cm.

Thanks to Javier García Herrera and xet7 !
2026-11-02 00:04:03 +02:00
Lauri Ojansivu ba0de93f79 Keyboard shortcuts keep working after a click on a minicard or a closed popup.
The shortcut filter refused every key while a link or button had the focus.
The minicard is itself a link, so a click on its date badge or its menu left
that link focused, and closing a popup returns focus to the button that
opened it: hovering a card and pressing d then did nothing until a reload.

A focused control now keeps only the keys it uses itself - Enter for any of
them, Space for a button, summary or checkbox, End/Home/arrows for a tab -
so Space still activates a focused button (audit A01) and every other key
is a shortcut again.

Thanks to mimZD and xet7 !

Fixes #6755.
2026-11-02 00:03:54 +02:00
Lauri Ojansivu 9a15491985 CHANGELOG: sign-in errors kept through a redraw, and the Firefox full-run test fixes.
Thanks to xet7 !
2026-11-01 20:35:06 +02:00
Lauri Ojansivu c8ed3e0fd0 Tests: the bulk export popup is opened until it stays, in all five Haitian Creole tests.
Each test navigates to All Boards and at once opens Export all boards.
All Boards is still drawing just after the route changes, and in
EVERYTHING's full Firefox run the popup opened then was closed again: no
header title to read (the first test; it passed in the run before).

All five tests open the popup again until its title is there. The spec in
Firefox, three times beside the running suite: 15 of 15.

Thanks to xet7 !
2026-11-01 18:56:50 +02:00
Lauri Ojansivu 4a9c3f21d2 Tests: the drawing surface's width is waited for, not read once after its viewBox.
The test waited until the drawing's viewBox had grown past the old 800,
then read the surface's box once. The element's layout follows the
viewBox a frame later, and in EVERYTHING's full Firefox run it was still
800 px against more than 1088 expected (it passed in the run before).

It polls the surface's width as well. The spec in Firefox, three times
beside the running suite: 9 of 9.

Thanks to xet7 !
2026-11-01 18:23:13 +02:00
Lauri Ojansivu 3a2fef4dc7 Sign-in: login errors are rendered from template state, and the SAML error test clicks a finished form.
saml-popup-error's first test failed in EVERYTHING's full Firefox run and
2 of 3 times alone: #login-error-message stayed empty after the SAML
button was clicked with a stub that fails at once.

The test clicked #at-saml (force) as soon as Meteor connected, while the
sign-in form was still drawing: the click landed on a button being
replaced, or - in another attempt - below the window ("Element is
outside of the viewport"), and no handler ran. It now waits for the form,
scrolls the button into view and clicks until the error shows. With one
worker both tests pass 6 of 6 in Firefox against a rebuilt bundle.

Found on the way: #login-error-message sits inside `if isLoading`, and
showLoginError wrote into the element - when that block is drawn again
the element is new and empty, so an error shown just before was lost. The
template now renders the message from a ReactiveVar (loginErrorText) as
escaped text, so a redraw keeps it and it can still never be HTML.
oauthProviders.test.cjs pins the same region, the translated codes and
text-only output in that form.

The other sign-in specs (44 files) pass in Firefox with it, apart from
runs where two copies of a settings-changing spec ran at once.

Thanks to xet7 !
2026-11-01 17:49:21 +02:00
Lauri Ojansivu f241681e80 Tests: background notification recovery checks its own mail, not the first one the sink caught.
The SMTP sink captures every mail the shared test server sends while it
listens. The test waited until user2 had exactly one mail, then read
sink.messages[0] - in EVERYTHING's full Firefox run another worker's
comment notification ("Please look", to another user) had arrived first,
so "Frozen recovery body 0" was looked for in the wrong mail.

It reads the mail addressed to user2. The whole spec in Firefox, twice:
24 passed, 2 conditionally skipped.

Thanks to xet7 !
2026-11-01 17:32:09 +02:00
Lauri Ojansivu c71c875649 Tests: the custom field settings popup is opened until its Create button shows.
The String Template help test opened a card and at once its custom field
settings popup. The opened card was still drawing, and in Firefox under
the whole suite the popup sometimes closed again before Create could be
clicked: ve-CC and rup failed in EVERYTHING's full Firefox run, 42
others passed.

The test opens the popup again until its Create button is there. ve-CC,
rup and en, twice each in Firefox beside the running suite: 6 of 6.

Thanks to xet7 !
2026-11-01 17:06:02 +02:00
Lauri Ojansivu 0da86171e9 CHANGELOG: TODO Later - the whole EVERYTHING run has passed in the browsers, conformance and FerretDB.
Thanks to xet7 !
2026-11-01 14:15:06 +02:00
Lauri Ojansivu b80e21fd48 CHANGELOG: Android bundles start again.
Thanks to xet7 !
2026-11-01 14:11:20 +02:00
Lauri Ojansivu 24a7270b7f Android bundles start again: npm 12 runs argon2's and bcrypt's install scripts.
All four Android bundle jobs failed: WeKan crash-looped on

  No native build was found for platform=android arch=x64 runtime=node
  abi=147 ... loaded from: .../meteor/accounts-password/node_modules/argon2

until the 240 s smoke test gave up. Meteor's npm rebuild had printed
"install scripts blocked because they are not covered by allowScripts",
though prepare-bundle-npm.mjs writes argon2 and bcrypt into every
rebuild directory's allowScripts.

npm 12 applies allowScripts to a project's DECLARED dependencies only.
The package.json that prepare-bundle-npm writes there declared none, so
argon2 and bcrypt were "extraneous" and their scripts stayed blocked -
reproduced with npm 12.2.1 on this release's bundle, and with the two
declared the same rebuild runs both install scripts. Linux, macOS and
Windows never noticed: prebuilds load there, and the Windows jobs compile
the two themselves. Android has no prebuild, and check-addon-arch passed
the Linux ones it cannot load.

prepare-bundle-npm.mjs now declares each reviewed installer a rebuild
directory has installed, at its installed version, and leaves a declared
one as it is. The Android build runs it itself first - the Linux bundle
it rebuilds may predate this - and loads argon2 and bcrypt with Android's
Node.js right after the install, so a missing addon stops the build with
its name instead of a smoke-test timeout.

prepareBundleNpm.test.cjs and androidBundles.test.cjs pin both; they fail
on the code before. Not run on Android here: the build needs Termux on
an Android-CPU runner.

Thanks to xet7 !
2026-11-01 14:11:10 +02:00
Lauri Ojansivu 73ea323da1 CHANGELOG: CrossBoardExportBleed, GHSA-8885-xrq4-j47h.
Thanks to ccsimplyspolit and xet7 !
2026-11-01 14:01:07 +02:00
Lauri Ojansivu be6727607c CrossBoardExportBleed: a checklist scope stays on its board, and every export obeys Disable all export and Anonymize export users.
GHSA-8885-xrq4-j47h, reported by ccsimplyspolit, both findings confirmed
in the current code:

1. GET /api/boards/<board>/export?checklistId=<id> (and exportZip)
   selected the scoped checklist and its items by id alone. A checklist
   of another board - a private one too - was streamed with every item
   and its creator, whose profile the anonymization map did not cover.
   Both selectors are now bound to the exported board's cards, and a
   checklist of another board is recorded in Admin Panel / Problems
   (authz.export-checklist-scope).

2. The streaming CSV/TSV export asked neither Disable all export nor
   Anonymize export users. Neither did, it turned out, Excel (board,
   card, all boards), PDF (board and card), the chart exports, the
   calendar feed or the external formats: only the WeKan JSON, ZIP and
   Trello exports did, though the setting promises that "the server
   rejects any export request" and names CSV and Excel.
   - refuseDisabledExport() answers 403 before anything is written, and
     all 13 HTTP export routes call it first; assertExportEnabled() still
     guards the builders. Both record the attempt (authz.export-disabled,
     never blocking the account: a page opened before the switch still
     shows the menus).
   - anonymizeExportIdentities() anonymizes what one exporter writes -
     users, @mentions, requested-by / assigned-by, comments - with the map
     build() uses, board members included. The CSV/TSV stream, Excel,
     PDF, chart exports (forExport: the chart views keep names), the
     calendar feed and every external format call it.

tests/crossBoardExportBleed.test.cjs drives the gate and the anonymizer
and scans the tree: no scoped checklist selected by id alone, no export
route without the gate, no exporter naming people without anonymizing.
All 7 fail on the code before. The new spec
security-cross-board-export.e2e.js replays the reporter's requests and
checks every export route while export is disabled and the CSV and
Kanboard exports while anonymizing; it passes with export-access and
scrum-export-anonymization against a rebuilt bundle.

Thanks to ccsimplyspolit and xet7 !
2026-11-01 14:00:19 +02:00
Lauri Ojansivu 9c5fa760c3 CHANGELOG: glob 14.0.3, checked: no WeKan code loads it, no telemetry, tarballs match the lockfile.
Thanks to dependabot and xet7 !
2026-11-01 13:41:40 +02:00
Lauri Ojansivu 27040c6f2f Merge pull request #6851 from wekan/dependabot/npm_and_yarn/npm_and_yarn-0e36e05768
Bump glob from 13.0.6 to 14.0.3 in the npm_and_yarn group across 1 directory
2026-11-01 13:36:11 +02:00
Lauri Ojansivu c15e1ab4b2 Tests: the Card Settings splitter is dragged once the popup holds still.
The splitter test measured the splitter straight after opening Card
Settings, while the popup was still placing itself. In WebKit the press
landed where the splitter had been and dragged nothing: the pane stayed
526 px against more than 606 expected (the first attempt in EVERYTHING's
WebKit stage; the retry passed).

It now measures once two reads of the splitter and the panes 150 ms apart
agree. WebKit 5 of 5, Chromium 3 of 3.

Thanks to xet7 !
2026-11-01 11:58:06 +02:00
Lauri Ojansivu b676cdc169 CHANGELOG: the WebKit date-range refusal and the language and push fixes of the browser tests.
Thanks to xet7 !
2026-11-01 11:25:20 +02:00
Lauri Ojansivu 4ad13db5ef Filters: the date-range forms are checked by WeKan, not refused by WebKit after Clear.
After Clear emptied a refused range, WebKit kept a half-edited date
segment in the From field and fired `invalid` on it at the next submit -
though its validity read valid - so the form never submitted and the new
range never applied, with no message (card-date-range.e2e.js in WebKit,
also on retry: 0 submit events, `invalid` on .js-card-date-from). The old
Clear handler emptied the fields the same way, so this was there before.

Both range forms are novalidate now. The submit handler already decides
whether a range is valid (rangeRefused), marks the end date invalid with
the reason and says it in the form, so nothing is lost.

filterDateRangeRefusal.test.cjs pins novalidate on both forms. Against a
rebuilt bundle card-date-range and card-movement-filter pass twice each
in WebKit, Firefox and Chromium.

Thanks to xet7 !
2026-11-01 11:24:59 +02:00
Lauri Ojansivu efe0e9287c Tests: a language switch is waited for, and WebKit does not freeze on asking for a push subscription.
Two causes behind most of EVERYTHING's WebKit failures so far, both in the
test pages rather than in WeKan:

- 94 specs switch language with setLanguage and then click at once. The
  client loads that language afterwards and draws everything again, which
  closes a popup opened before that: the board view popup of the flow and
  report help specs (Latin monte-carlo in WebKit; Corsican and Scottish
  Gaelic in earlier Firefox runs) had no entry left to click. The
  fixture's context now wraps Meteor.callAsync in each page so that a
  setLanguage call resolves once <html lang> - which client/lib/i18n.js
  sets in the same flush that redraws - names the language. It waits at
  most 10 s and then resolves anyway, so it can only make a spec patient,
  never fail one. A page of a context a spec makes itself uses
  untilLanguageShown().
- Playwright's WebKit freezes the page's main thread on
  PushManager.getSubscription(): after it not even a timer runs. Member
  Notification Settings asks it on opening, to show whether this device
  gets push (client/lib/webPush.js), so every WebKit spec that opened it
  hung in its next click - notification-activity-mute and the member
  scope of notification-delivery-settings, in every language. In WebKit
  the page now answers as a device with no subscription does. Chromium,
  Firefox and Safari answer the real call; no spec subscribes.

WebKit: those notification specs 10 of 10, and the Latin, Corsican and
Product Backlog specs 13 of 13; Chromium and Firefox 11 of 11 each with
the same subset. playwrightPageInit.test.cjs runs both page scripts on a
fake page: setLanguage waits for the language and keeps its result,
other methods are not held, a page whose Meteor arrives later is
wrapped, the wait is capped; WebKit's getSubscription answers null and
Chromium's and Firefox's are left alone; the fixture installs both.

Thanks to xet7 !
2026-11-01 11:21:45 +02:00
Lauri Ojansivu cfcead6a1a CHANGELOG: the Firefox fixes - date range refusal, history Retry, whiteboard line width - and the browser test fixes.
Thanks to xet7 !
2026-11-01 09:23:34 +02:00
Lauri Ojansivu 40f60582b7 Whiteboard: a line is drawn at its own width, not the whiteboard's 800.
Inside `each strokes` the template drew stroke-width=width, and Spacebars
looks a name up among the template's helpers before the line's own
fields: the width helper is the drawing's width, so every line was drawn
800 wide - one black shape over the whole whiteboard, in every browser.
The lines are mapped with strokeWidth now.

whiteboard.test.cjs: every name the line's path uses is a field of the
line and none is a helper of the template, which would win - it fails on
the code before. whiteboard.e2e.js checks each drawn path is the pen's
width (4) in both browsers.

The same spec drew the second person in another context of the same
browser. In Firefox two contexts share the pointer capture the first pen
takes: one line zig-zagged between both paths and the other page drew
nothing, 4 of 4. The second person now has a browser of their own, as
two people do; both pens are still down at once. Firefox 6 of 6 and
Chromium 4 of 4 against a rebuilt bundle.

Thanks to xet7 !
2026-11-01 09:22:05 +02:00
Lauri Ojansivu 96845f6f4b History recovery: Retry is enabled only once the board lets this person write on it.
After a reload the notice for an unanswered keyboard undo or redo shows
as soon as its board is open - before the board and its members have
arrived. undoRedoLast does nothing while Utils.canModifyBoard() is false,
so a Retry clicked in that moment was dropped without a word and the
notice stayed (history-recovery-notice.e2e.js, Firefox).

Retry is disabled until the retry can act; Forget needs no right and
stays usable. For someone who may only read the board Retry stays
disabled, as it could never do anything for them.

historyRecoveryNotice.test.cjs pins the binding and the guard it mirrors,
and that Forget is not gated - it fails on the code before. The spec
waits for Retry to be enabled before it clicks; the spec passes in
Firefox against a rebuilt bundle.

Thanks to xet7 !
2026-11-01 09:22:05 +02:00
Lauri Ojansivu c5f5ad2338 Filters: a refused date range says why in the form, so Clear still works in Firefox.
The filter sidebar's two date-range forms (card dates, card movement)
reported a reversed range with reportValidity(). Firefox opens that
bubble just under the end date - over the Apply and Clear buttons - and
while the field has focus it takes every click on them: no mousedown and
no click reached the page, twice in a row, so a refused range could not
be cleared at all (card-date-range.e2e.js, 4 of 4 in Firefox; with the
bubble suppressed it passed).

The end date is still marked invalid with the reason (setCustomValidity,
for :invalid and screen readers), and the reason is shown in the form as
a role="alert" line under the buttons. Typing in the range or Clear takes
it back.

filterDateRangeRefusal.test.cjs: both forms report through rangeRefused,
show the line and take it back on input and Clear, and nothing in the
filter sidebar calls reportValidity() - it fails on the code before. The
card-date-range and card-movement-filter specs check the line appears
with the reason, is gone after Clear or an edit, and is absent for an
accepted range; both pass in Firefox against a rebuilt bundle.

Thanks to xet7 !
2026-11-01 09:22:05 +02:00
Lauri Ojansivu 0c8c254dc5 Tests: the viewport matrix cleans up only what it seeded.
viewport-matrix is Chromium-only (device emulation), so in Firefox and
WebKit every test of a viewport is skipped and its beforeAll never runs -
but its afterAll still does, and read board.boardId of a board that was
never seeded: each of the twelve viewports failed in EVERYTHING's Firefox
stage, on the last test of the group, in 0 ms.

afterAll returns when nothing was seeded. Firefox: 132 skipped, none
failed. Chromium: phone-portrait 11 of 11, and its board is cleaned up.

Thanks to xet7 !
2026-11-01 09:09:14 +02:00
Lauri Ojansivu 0a1f8584ee Tests: the board view popup is opened until it stays open for the Product Backlog.
The test switches language and then opens the board, and when the language
arrives the board header draws again and closes a popup opened before
that. In Firefox the click on the board view toggle often came first: no
popup at all, and no Product Backlog entry to open - 2 of 6 runs alone,
the st variant in EVERYTHING's Firefox stage.

The test opens the popup again until its Product Backlog entry is there.
9 of 9 in Firefox (st, en, haw, three times each).

Thanks to xet7 !
2026-11-01 09:01:09 +02:00
Lauri Ojansivu 2042fd5102 Tests: the Blocks drag test grabs the block's body, not its edge.
In Firefox a Khmer label overflows its Blocks block and widens the box the
test measures, so the first point of the block it found, scanning from the
left, was the block's outline - and a press there dragged nothing: km and
km-KH failed every time (4 of 4), every other language passed.

The grab point is now one with the block's own body on both sides of it.
The whole spec in Firefox: 104 of 104, Khmer, Georgian, Fijian,
Kinyarwanda (the long labels the search was written for) and Arabic
right-to-left among them.

Thanks to xet7 !
2026-11-01 08:23:41 +02:00
Lauri Ojansivu 0403e27c62 Tests: the notification delivery sections are opened until they stay open.
The member-scope test switches language just before it opens Notification
Settings, and when the language arrives the popup draws again with every
section closed. Under a whole-suite run in Firefox that came after the
click on the section's summary, so the text checks passed and the select
the test then used was hidden: vi and zh_SG failed, 101 others passed,
and vi passed 3 of 3 alone.

expand() opens the section when it is closed and waits until its content
shows, again if a redraw closes it. vi, zh_SG, en and fi twice each, and
the board-scope, webhook-form and tray tests: all pass.

Thanks to xet7 !
2026-11-01 08:04:55 +02:00
Lauri Ojansivu d9e48a909d Tests: the list-sync specs' mock servers are reachable from WeKan when the browsers run in Docker on macOS.
The list-sync specs answer as Jira or GitLab from a server in the test
process, and WeKan's server fetches from it. With the browsers in Docker
on macOS that server was on the container's loopback, which Docker
Desktop keeps from the host: "fetch failed", and every test of
list-sync-estimate, -planning, -source and -time-estimates failed in
EVERYTHING's Firefox stage.

build.sh already forwarded the test SMTP port from the host into the
container through docker exec; the same forwarder carries any TCP. On
macOS it now also forwards a small pool, 39101-39104 by default
(WEKAN_TEST_REVERSE_PORTS), and names it to the specs, which listen
through helpers/reachable-server.cjs: the first free port of the pool,
or any port when there is none, as before. Linux forwards nothing.

30 of 30 of those specs pass in Firefox in Docker now.
playwrightReachableServer.test.cjs: the pool is read strictly, a port in
use is skipped, no pool listens anywhere, and when every forwarded port
is taken it fails clearly instead of listening where WeKan cannot reach.
playwrightDockerHost.test.cjs: macOS forwards the SMTP port and the pool
(or the one configured), names it to the container and removes its ready
files; Linux forwards nothing.

Thanks to xet7 !
2026-11-01 07:50:40 +02:00
Lauri Ojansivu 0cea141702 Tests: the duplicate relation picker is opened once the card has opened.
The test clicked Add dependency straight after the minicard. Under a
whole-suite run in Firefox that click landed while the card was still
opening, and the picker it opened was closed again when the card's route
and details settled - no popup, and the wait for its type select ran out
(the ve-PP variant; 59 of 60 passed, and it passed 3 of 3 alone).

The test waits for the opened card, then opens the picker again until its
type select shows. 6 of 6 (en, ve-PP, tlh, twice each) beside the suite.

Thanks to xet7 !
2026-11-01 07:21:43 +02:00
Lauri Ojansivu 9188843369 Tests: the checklist drag tests drag once the opened card's rows hold still.
The opened card draws the sections above its checklist for a moment after
it opens, and they push the rows down: in Firefox they moved 50 px between
the test measuring them and releasing the mouse. With handles off the
release landed on item 1's own title and opened its editor, so the titles
read 2,3 (EVERYTHING's Firefox stage); with handles on the enabled drag
dropped item 1 between 2 and 3. 2 of 6 in a row failed that way.

drag() waits until two reads of the rows' positions 150 ms apart agree,
then measures and drags. 10 of 10, both variants five times, while the
whole suite ran beside it.

Thanks to xet7 !
2026-11-01 06:57:18 +02:00
Lauri Ojansivu dbf07aadf9 Tests: the activity recovery report is searched again until no worker lease covers it.
The report is a snapshot taken when the search runs, and the live
one-second recovery worker leases each pending intent while it looks at
it. A search made during that lease shows the seeded orphan as "Delivery
reservation active" - correct - and the page keeps showing it, so the
wait for "Original activity is missing" ran out in EVERYTHING's Firefox
stage (the en variant).

The test searches again until a snapshot falls between leases, up to 30 s,
and then asserts the same status and the disabled Retry. 10 of 10 in
Firefox, every test twice, while the whole suite ran beside it.

Thanks to xet7 !
2026-11-01 06:40:42 +02:00
Lauri Ojansivu e001c4f9c2 CHANGELOG: the Docker test proxy redials an unanswered connect.
Thanks to xet7 !
2026-11-01 06:23:55 +02:00
Lauri Ojansivu ffe207e191 Tests: the Docker test proxy dials the host again when a connect is never answered.
Even on the bridge network, EVERYTHING's Firefox stage lost a test now and
then to a page whose document never came (RuleBleed's cross-board test,
Red Strings, shared templates). Inside the container the cause was in
plain sight: a connect to host.docker.internal:3000 in SYN_SENT after nine
retransmits, while the next connection went straight through. Through the
container's proxy 9 of 1000 requests went unanswered; from the Mac itself
none did.

The proxy no longer waits on such a connect. It reads nothing from the
browser until a connection to the host is up (pauseOnConnect), drops a
dial that has not connected in 1.5 s - a normal one takes milliseconds -
and dials again, five times, so no byte is lost or sent twice. If none
connects the browser's connection is closed rather than left open.

playwrightDockerHostProxy.test.cjs: two unanswered dials, then one that
connects - the request arrives once and is answered, and the abandoned
dials are closed; when every dial is unanswered the client is closed after
exactly the attempts, in well under the old 30-60 s wait.

Thanks to xet7 !
2026-11-01 06:23:34 +02:00
Lauri Ojansivu b2198cb61b CHANGELOG: the Firefox stage's Docker network, host paths and phone invitation test.
Thanks to xet7 !
2026-11-01 06:10:30 +02:00
Lauri Ojansivu a6bec88266 Tests: the phone invitation tile is measured in one layout with its buttons.
43-mobile-allboards read the invitation tile's box, then each control's
box, one round trip at a time. Firefox draws the row above the board list
later than Chromium, and in between it moved the whole list down 29.4 px:
the Decline button (1102-1162) was compared with where the tile had been
(853-1139) and failed, 2 of 2, in EVERYTHING's Firefox stage. The tile was
never clipped - settled, it runs 882-1168 around all three controls.

The tile and its message and two buttons are measured in one layout now,
polled until they hold, and a missing or zero-height control fails too.
Firefox: 3 of 3. With the original #6488 bug put back (the tile forced to
4rem) it still fails, naming the message and both buttons.

Thanks to xet7 !
2026-11-01 06:10:03 +02:00
Lauri Ojansivu 8a1f7a14a9 Tests: the Playwright container sees the repo and the files root at their own host paths.
A spec and the server share files by path. admin-continuous-backup gives
the server a key file it wrote under .tools/tmp, and reads the stream.json
the server wrote to its target - and with the repository mounted at /repo
and the files root at /wekan-files, every such path named a file only one
side had: "The encryption key file cannot be read", and no stream.json, in
both of its tests in EVERYTHING's Firefox stage (the only browser there that
runs in Docker on this Mac, and on Linux arm64 every browser does).

Both are mounted at the path they have on the host now, the specs are told
that path in WEKAN_FILES_PATH, and the in-container script finds the proxy
and the Playwright binaries from its working directory. Both tests pass in
the container now.

playwrightDockerHost.test.cjs drives run_playwright_docker on macOS and
Linux: the repo and the files root are mounted at their own paths,
WEKAN_FILES_PATH and HOME follow, and the function names no container-only
path. It fails on the code before.

Thanks to xet7 !
2026-11-01 06:08:05 +02:00
Lauri Ojansivu 7f948bf438 Tests: the macOS Playwright container uses Docker's bridge network, not the host network.
Three Firefox tests of one EVERYTHING run waited the full 60 s in
waitForMeteor: their page's own document never arrived. Measured from
containers during that run, on Docker Desktop for macOS:

- host network, through the WeKan proxy:          3 of 600 requests lost
- host network, straight to host.docker.internal: 8 of 600 connects never
  completed
- bridge network, through the same proxy:          0 of 1000
- from the Mac itself:                             0 of 300

So it is Docker Desktop's host network, not WeKan and not the proxy. On
macOS nothing needs that network - WeKan and MongoDB are reached through
host.docker.internal and SMTP through docker exec - so the container runs
on the bridge network, where the proxy still serves localhost:3000 and the
browser keeps ROOT_URL's origin. Linux keeps the host network, which is
the real one there.

playwrightDockerHost.test.cjs drives run_playwright_docker with uname
stubbed: Darwin gets bridge and the proxy, Linux gets host and none, and
the function names no network of its own. It fails on the code before.

Thanks to xet7 !
2026-11-01 06:06:04 +02:00
Lauri Ojansivu 6d0ab6cf11 CHANGELOG: continuous backup, single-file builds and the EVERYTHING test fixes.
The seven commits since the Dependabot entries: two bug fixes (continuous
backup watching before its first scan; single-file Mach-O signing and
start/backup waiting for WeKan) and five test-run fixes, grouped by topic.
The short changelog guard is a group of its own now, so the developer-
tooling subsection is grouped throughout. CHANGELOG.md regenerated.

Thanks to xet7 !
2026-11-01 05:47:52 +02:00
Lauri Ojansivu 89b64966c6 Tests: the password minimum specs wait until the server has the policy they wrote.
password-min-length-signup-login.e2e.js writes the password policy straight
into the settings document. The server learns it from its settings observer,
which polls on the standalone test MongoDB EVERYTHING runs: for up to ten
seconds it still answered the old policy. A sign-up form loaded meanwhile kept
the old minimum (6 instead of 10), and an administrator's Set password was
refused under the minimum the test before had left enforced - two failures in
EVERYTHING's Chromium stage that never showed on a dev server, whose MongoDB
is a replica set.

untilServerHas() asks the server (getPasswordPolicy) until it has the policy
the test wrote; the sign-up form is loaded again after it. 6 of 6 with two
workers, each test twice.

Thanks to xet7 !
2026-11-01 05:01:18 +02:00
Lauri Ojansivu d5e3325e87 Tests: Card Settings section order waits for the published board before reading the card.
"with every field allowed, the card draws exactly the Card pane's
sections" writes the board's Scrum visibility straight to the database and
reloads. The board publication's cursor has limit: 1, which Meteor's
change-stream driver does not follow, so it polls: a reload could still be
served the board as it was for up to ten seconds, and the card drew no
Scrum section - failing every time in EVERYTHING's Chromium stage. Live in
the browser the update arrived after 9.8 s; in the app itself a write
through a method re-polls at once, so only this direct write waited. The
test waits for the published board (up to 15 s) before it reads the card.

Thanks to xet7 !
2026-11-01 03:27:46 +02:00
Lauri Ojansivu 897376ba54 Tests: the Board Settings button theme test checks the settings that still have action buttons.
button-theme-foreground.e2e.js opened Board Settings / Swimlane, List and
Card and asked each for its column-actions buttons. Card Settings has had
none since 3e5bc58105 ("Card Settings: a checkbox per section, ...":
card actions live in the card's own section hamburgers), so every theme
failed on Card, in EVERYTHING's Chromium stage. Swimlane and List are
checked, in all four themes, and the test says why Card is not.

Thanks to xet7 !
2026-11-01 03:17:38 +02:00
Lauri Ojansivu 16103f9ad2 Tests: the whole-suite run keeps its test database in .tools/, not beside the checkout.
build.sh and build.bat started the test mongod on :3001 with its dbpath
in the checkout's parent directory (../mongodb-test-3001). That is the
one thing in a test run that was written outside the repository, against
the rule that everything WeKan's scripts write stays in .tools/ - and a
sandboxed editor, which may write only the repository, could not create
it, so EVERYTHING's browser stages had no database to run against. It is
.tools/mongodb-test-<port> now, which git and Meteor already ignore.

toolsDirOnly.test.cjs pins the new path in both scripts and fails on a
test database beside the checkout.

Thanks to xet7 !
2026-11-01 02:51:41 +02:00
Lauri Ojansivu 8cbe2605fe Continuous backup: files are watched before the first scan, and a macOS start catches up once.
The file engine scanned its sources first and started watching them
after, so a change made between the two - after the scan had passed a
file, before the watcher began - waited for the next scan, five minutes
by default. And macOS reports file events through FSEvents, which
delivers nothing for a moment after a watcher is created: an EVERYTHING
run on a loaded Mac lost a log line appended right after the start
(tests/integration/continuousBackup.test.cjs, "the appended log line was
streamed by watching", 4 !== 5).

The watchers start first now; a change seen by the scan and a watcher
too is recorded once, as they share one queue. On macOS one catch-up
scan runs two seconds after the start. Not elsewhere: inotify is
watching when fs.watch returns, and a second full scan of a large store
would double the start-up cost.

A new test, on macOS and Linux, with a watcher that never reports:
watching starts before the first scan; on macOS the catch-up scan
records a change made right after the start; on Linux no second scan
runs. It fails on the code before this change.

Thanks to xet7 !
2026-11-01 02:42:03 +02:00
Lauri Ojansivu f5eb4ecf61 Mac smoke test: a process group's live members come from libproc, not ps.
When macOS refuses to signal a process group (EPERM, which it returns for
a group of zombies), the smoke test checked with `ps` whether any member
was still alive. A Mac that will not let the build run `ps` - "Operation
not permitted", as here, where EVERYTHING's node suites run - failed the
check itself, and with it a correct app: macAppPackaging.test.cjs, every
EVERYTHING run on this machine.

On macOS it asks the kernel through libproc now (proc_listpids for the
group, proc_pidinfo for each member: a zombie's info cannot be read, a
live one's status is not SZOMB), with no process started. Elsewhere it
uses ps exactly as before.

tests/mac-smoke-app.py: the ps cases run as the non-macOS path; on macOS
an EPERM asks libproc and never ps; and a real group - a live child, then
a zombie leader alone - is told apart. macAppPackaging.test.cjs passes,
three runs in a row.

Thanks to xet7 !
2026-11-01 02:32:31 +02:00
Lauri Ojansivu 8d4f4caa2f Single file: a macOS build is signed whatever its target is called, and start/backup return once WeKan runs.
EVERYTHING's node suites run under Meteor's Node.js, which has single
executable applications, so singleFile.test.cjs built and ran a real
single executable - and it failed twice:

- The builder ad-hoc signed only a target named mac*/darwin*. Built from
  this Mac's Node.js under another label (the test builds 'amd64'), the
  file is still a Mach-O binary, the injected archive had invalidated its
  signature, and macOS killed it (SIGKILL) before it printed anything:
  status null. It signs every Mach-O output now, read from the file's
  magic number.
- `start --background` and `backup` returned as soon as they had spawned
  WeKan, before it wrote its pid file - which it does only after reading
  its settings and extracting FerretDB. A `stop` right after them said
  "WeKan was not running" and left it running. Both wait now
  (status.waitUntilStarted) until the pid file names the process they
  started, give up at once if it ended, and say so if it did not start.

singleFile.test.cjs: 24 of 24 under Meteor's Node.js, the real single
executable included; a new test pins waitUntilStarted, and that both
commands use it.

Thanks to xet7 !
2026-11-01 02:23:36 +02:00
dependabot[bot] 9a74138a84 Bump glob
Bumps the npm_and_yarn group with 1 update in the / directory: [glob](https://github.com/isaacs/node-glob).


Updates `glob` from 13.0.6 to 14.0.3
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v13.0.6...v14.0.3)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 14.0.3
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 23:51:54 +00:00
Lauri Ojansivu fa7e447536 CHANGELOG: ten Dependabot updates, checked; node-saml 5.2.0 where logins run; @smithy/core 3.38.1.
Thanks to dependabot and xet7 !
2026-10-31 23:06:40 +02:00
Lauri Ojansivu 05cc2460ea Tests: the short changelog guard holds between releases too.
changelogShort.test.cjs asserted an Upcoming section always, and one
Upcoming in CHANGELOG.md - but right after a release there is none until
the next real entry (CLAUDE.md, Making a release), so it failed after
every release, v12.77 included. With no Upcoming it checks that the
newest release is first and that nothing links an Upcoming that does
not exist; with one, what it checked before.

Thanks to xet7 !
2026-10-31 23:06:08 +02:00
Lauri Ojansivu 5c50f38e25 SAML logins are verified by @node-saml/node-saml 5.2.0, as package.json says.
Dependabot's 5.1.0 -> 5.2.0 bump (#6850) changed the app's package.json,
but a SAML login is verified by the wekan-accounts-saml package's own
copy, pinned by its Npm.depends to 5.1.0 - so nothing that runs changed.
5.2.0 is a hardening release a login relies on: it rejects a message
with more than one Issuer, checks idpIssuer on the Response and its
assertion, rejects a parsed query whose RelayState is not the signed
one, and checks a POST LogoutResponse's status and issuer.

The package pins 5.2.0 now. samlLogin.test.cjs required '5.1.0' in one
place and '^5.1.0' in the other; it requires the package and package.json
to name the SAME version, and at least 5.2.0, so a bump of one alone
fails.

A login roundtrip with the package's own copy (node-saml 5.2.0,
xml-crypto 6.4.0) and WeKan's options: a signed Response and a signed
Assertion log in; a NameID changed after signing, a signature by another
key, an unsigned response, and an answer to a request WeKan never made
are refused. saml-popup-error.e2e.js completes a real signed login in
the browser.

Thanks to xet7 !
2026-10-31 23:06:08 +02:00
Lauri Ojansivu 338afc15f8 @smithy/core 3.38.1: the AWS SDK's logger redaction fix, and the telemetry gate passes again.
The @aws-sdk/client-s3 3.1159.0 bump (#6844) resolved @smithy/core to
3.37.0, which npm marks deprecated: "a bug in custom logger data
redaction" (aws/aws-sdk-js-v3#8393). With a custom logger passed to an
AWS client, sensitive fields nested in a list or map reached the logger
unredacted. @smithy/core 3.24.0 to 3.38.0 have it, so 3.36.2 before the
bump did too; 3.38.1 is the fix.

WeKan's only AWS client is the S3 backup (server/methods/backup.js), and
it passes no logger, so nothing was logged. Updated all the same, in the
lockfile only and within the ranges the SDK already allows - one package,
nothing else moved. Its tarball matches the lockfile's integrity hash,
and compared with 3.37.0 it adds no network host, telemetry, process
spawning or install script. A multipart upload to a local S3 stand-in
still arrives signed AWS4-HMAC-SHA256, whole.

releases/check-telemetry.py --source failed on it: the deprecation text
links github.com, a URL origin new to package-lock.json. It passes now.

Thanks to xet7 !
2026-10-31 23:06:08 +02:00
Lauri Ojansivu dd2082b0ea Merge pull request #6846 from wekan/dependabot/npm_and_yarn/isomorphic-git-1.44.8
Bump isomorphic-git from 1.44.3 to 1.44.8
2026-10-31 22:39:27 +02:00
Lauri Ojansivu 4a86e9c19e Merge pull request #6850 from wekan/dependabot/npm_and_yarn/node-saml/node-saml-5.2.0
Bump @node-saml/node-saml from 5.1.0 to 5.2.0
2026-10-31 22:38:24 +02:00
dependabot[bot] 568cd9b6ec Bump isomorphic-git from 1.44.3 to 1.44.8
Bumps [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) from 1.44.3 to 1.44.8.
- [Release notes](https://github.com/isomorphic-git/isomorphic-git/releases)
- [Commits](https://github.com/isomorphic-git/isomorphic-git/compare/v1.44.3...v1.44.8)

---
updated-dependencies:
- dependency-name: isomorphic-git
  dependency-version: 1.44.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:37:49 +00:00
Lauri Ojansivu fea468471d Merge pull request #6849 from wekan/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.72.1
Bump @typescript-eslint/eslint-plugin from 8.72.0 to 8.72.1
2026-10-31 22:37:36 +02:00
Lauri Ojansivu 83199b9af7 Merge pull request #6848 from wekan/dependabot/npm_and_yarn/rspack/dev-server-2.2.2
Bump @rspack/dev-server from 2.2.1 to 2.2.2
2026-10-31 22:37:10 +02:00
Lauri Ojansivu 3839ee698d Merge pull request #6845 from wekan/dependabot/npm_and_yarn/mailparser-3.12.4
Bump mailparser from 3.12.2 to 3.12.4
2026-10-31 22:36:48 +02:00
dependabot[bot] a60b996c92 Bump @node-saml/node-saml from 5.1.0 to 5.2.0
Bumps [@node-saml/node-saml](https://github.com/node-saml/node-saml) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/node-saml/node-saml/releases)
- [Changelog](https://github.com/node-saml/node-saml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/node-saml/node-saml/compare/v5.1.0...v5.2.0)

---
updated-dependencies:
- dependency-name: "@node-saml/node-saml"
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:36:39 +00:00
Lauri Ojansivu 1230164c16 Merge pull request #6847 from wekan/dependabot/npm_and_yarn/i18next-26.5.11
Bump i18next from 26.5.4 to 26.5.11
2026-10-31 22:36:29 +02:00
dependabot[bot] 8008f410fe Bump @typescript-eslint/eslint-plugin from 8.72.0 to 8.72.1
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.72.0 to 8.72.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.72.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.72.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:36:27 +00:00
Lauri Ojansivu bd90782da2 Merge pull request #6844 from wekan/dependabot/npm_and_yarn/aws-sdk/client-s3-3.1159.0
Bump @aws-sdk/client-s3 from 3.1155.0 to 3.1159.0
2026-10-31 22:35:45 +02:00
dependabot[bot] 5eadc45563 Bump @rspack/dev-server from 2.2.1 to 2.2.2
Bumps [@rspack/dev-server](https://github.com/rstackjs/rspack-dev-server) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/rstackjs/rspack-dev-server/releases)
- [Commits](https://github.com/rstackjs/rspack-dev-server/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: "@rspack/dev-server"
  dependency-version: 2.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:35:25 +00:00
Lauri Ojansivu 3785a13f6a Merge pull request #6842 from wekan/dependabot/npm_and_yarn/imapflow-2.9.1
Bump imapflow from 2.8.0 to 2.9.1
2026-10-31 22:35:18 +02:00
Lauri Ojansivu 97cabdeca4 Merge pull request #6843 from wekan/dependabot/github_actions/helm/chart-releaser-action-1.8.0
Bump helm/chart-releaser-action from 1.7.0 to 1.8.0
2026-10-31 22:34:58 +02:00
Lauri Ojansivu 2802326888 Merge pull request #6841 from wekan/dependabot/github_actions/docker/login-action-4.7.0
Bump docker/login-action from 4.6.0 to 4.7.0
2026-10-31 22:34:17 +02:00
dependabot[bot] 1f46af0639 Bump i18next from 26.5.4 to 26.5.11
Bumps [i18next](https://github.com/i18next/i18next) from 26.5.4 to 26.5.11.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.5.4...v26.5.11)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.5.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:33:51 +00:00
dependabot[bot] 1bcb1da92f Bump mailparser from 3.12.2 to 3.12.4
Bumps [mailparser](https://github.com/nodemailer/mailparser) from 3.12.2 to 3.12.4.
- [Release notes](https://github.com/nodemailer/mailparser/releases)
- [Changelog](https://github.com/nodemailer/mailparser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/mailparser/compare/v3.12.2...v3.12.4)

---
updated-dependencies:
- dependency-name: mailparser
  dependency-version: 3.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:33:31 +00:00
dependabot[bot] 067efac7f8 Bump @aws-sdk/client-s3 from 3.1155.0 to 3.1159.0
Bumps [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) from 3.1155.0 to 3.1159.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1159.0/clients/client-s3)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1159.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:33:21 +00:00
dependabot[bot] 2bdd16060e Bump imapflow from 2.8.0 to 2.9.1
Bumps [imapflow](https://github.com/postalsys/imapflow) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/postalsys/imapflow/releases)
- [Changelog](https://github.com/postalsys/imapflow/blob/master/CHANGELOG.md)
- [Commits](https://github.com/postalsys/imapflow/compare/v2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: imapflow
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:32:54 +00:00
dependabot[bot] 433bc62211 Bump helm/chart-releaser-action from 1.7.0 to 1.8.0
Bumps [helm/chart-releaser-action](https://github.com/helm/chart-releaser-action) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/helm/chart-releaser-action/releases)
- [Commits](https://github.com/helm/chart-releaser-action/compare/cae68fefc6b5f367a0275617c9f83181ba54714f...df287104677278f869ca5480bd260358397cc3f9)

---
updated-dependencies:
- dependency-name: helm/chart-releaser-action
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:32:54 +00:00
dependabot[bot] 29f9c62a64 Bump docker/login-action from 4.6.0 to 4.7.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/dbcb813823bdd20940b903addbd779551569679f...81c6f93d4bc991e510b342a00b419b3bb4efc5c6)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 20:32:36 +00:00
github-actions[bot] 21766cf38d Bump versions for v12.77 2026-10-31 19:47:43 +00:00
Lauri Ojansivu 101cdb7579 Prepare v12.77 release 2026-10-31 21:46:40 +02:00
Lauri Ojansivu 54cf951d9c Merge remote-tracking branch 'origin/main' 2026-10-31 21:46:38 +02:00
Lauri Ojansivu 57a43ee8e1 CHANGELOG: the date-popup guards follow movable popups and the date picker's empty cells.
Thanks to xet7 !
2026-10-31 21:44:13 +02:00
Lauri Ojansivu ff2c0ed7f6 Tests: the date-popup guards follow movable popups and the date picker's empty cells.
Two node guards still described v12.76's behaviour before it changed on
purpose. calendarPopupResize asserted that a popup without a date editor
does not drag; every popup drags by its title bar now (#6839), and with
drag handles on only by its handle - both pinned instead, its mock no
longer answering every selector as if a drag handle were there.
calendarTodayMarker picked a month's days with filter(Boolean), which an
empty cell passes now that it is an object with its own _id (#6838); it
skips the cells marked empty.

Thanks to xet7 !
2026-10-31 21:44:11 +02:00
github-actions[bot] 63d022fe5f Bump versions for v12.76 2026-10-31 19:44:06 +00:00
Lauri Ojansivu e9cf825b22 Prepare v12.76 release 2026-10-31 21:43:03 +02:00
Lauri Ojansivu 4ce8514e36 CHANGELOG: open issues #6834-#6840 - Today in the date picker, the card title and movable popups, Change Settings, the d shortcut, minicard dates.
#6834 is fixed in part and #6837 needs a decision: both in TODO Later.

Thanks to mimZD and xet7 !
2026-10-31 21:32:28 +02:00
Lauri Ojansivu 429fdc5319 Tests: a card-button rule is a button on the minicard (#6836 was already there).
"Show rule button on minicard" is Board Settings / Card, Card buttons,
Show on Minicard (allowsCardButtonsOnMinicard), in WeKan since v12.16:
the board's card-button rules are drawn under each minicard, and a click
runs the rule on that card without opening it. Off by default, which is
why it was not seen. It had no test.

minicard-rule-button-6836.e2e.js (Chromium and WebKit): with it on, a
card-button rule's button is on the minicard and a click archives the
card without opening it; with it off, no minicard has one.

Thanks to mimZD and xet7 !

Fixes #6836,
2026-10-31 21:31:36 +02:00
Lauri Ojansivu ffd5cbd2ad Change Settings: icons in a column on the first line, compact wrapped lines, rows divided.
Member Settings / Change Settings used the shared popup list as it is:
a flex row whose every line is 33px high with its icon centred. Its
settings are sentences, and most wrap - in Persian, in German - so a
setting became two tall lines with its icon floating between them and
the next setting right under it, with nothing to tell where one ended.
Each row is now an icon column, the words and the check mark at the
end; the icon sits on the first line, a wrapped line is an ordinary
line high, a rule divides the rows, and the Card settings checkbox has
the same space beside its words as an icon has.

Scoped to the popup's own list (change-settings-list): a popup keeps
the screens before it - the member menu - in the same .pop-over, so its
data-popup would have restyled those too.

change-settings-popup-6835.e2e.js, in Persian and English, Chromium and
WebKit: each row's icon is on its first line, a line is under 28px high
and a row only as tall as its lines, rows are divided, and the member
menu beside it is untouched.

Thanks to mimZD and xet7 !

Fixes #6835,
2026-10-31 21:29:31 +02:00
Lauri Ojansivu cf4b48df09 Minicard: a due date uses the card's width instead of three lines beside an empty strip.
The dates row of a minicard reserved 84px at its trailing edge - a third
of a 250px card - for nothing: the avatars float after it, and clear:
both starts it below the menu. A due date with its "1 day left" was
broken over three lines beside that empty strip, in a Persian board of
the report. It reserves the drag handle's room now, 24px, and the same
badge takes two lines at the default list width.

The board header bar wrapping to a second row, and the list header icons
under the card count, in the same report, did not reproduce on the
current code at the same window width, in Persian, with the card count
on; they are in CHANGELOG TODO Later until the reporter can say which
WeKan and which browser zoom showed them.

minicard-dates-width-6834.e2e.js (Chromium and WebKit, English and
Persian): the dates row reserves at most 24px, and the due date badge is
at most two lines high.

Thanks to mimZD and xet7 !
2026-10-31 21:26:15 +02:00
Lauri Ojansivu 73c21dfe3e Tests: typing d in the card title types at the end of the title in WebKit too.
WebKit puts the caret at the start of a field on focus and Chromium at
the end, so the d typed into the title landed at its start in WebKit and
the check for a title ending in dd failed there - while what the test is
for, that d typed in a field opens no Due date editor, held in both.
The test puts the caret at the end itself now.

Thanks to xet7 !
2026-10-31 21:23:04 +02:00
Lauri Ojansivu 22a12dc7f8 Opened card: the whole title edits; the window and every popup move by their bar or handle.
The opened card's title was split like a minicard's: the leading half
opened the editor and the trailing half dragged the window, so clicking
the right of the title, or beside a short last line, did not edit. Now
all of the title edits. With drag handles off, the window is moved from
the empty space of its header row above the title - between the collapse
caret and the menu; with drag handles on, only by the drag handle. A
reader who may not edit has no edit zone and still drags by the title.

Every popup now moves the same way - by its title bar with drag handles
off, by the handle before its title alone with them on - where only the
Drawing, Board View and Board Card settings popups and the date popups
moved before. A dragged popup is kept inside the window as it grows. The
drag pins a popup where it is before moving it: one placed from the
right edge (the member menu) jumped across the window, and one with a
margin by that margin, as the drag began.

card-title-edit-and-move-6839.e2e.js, in Chromium and WebKit: a two-line
title edits from its right, middle and left; the empty header row moves
the window by exactly the drag with handles off and not at all with them
on, when the handle does; and the card menu, Labels and the member menu
move by their title bar, or with handles on by the handle alone.
titleClickToEdit, popupMoveMaximize and popupBounds pin it.

Thanks to mimZD and xet7 !

Fixes #6839,
2026-10-31 21:23:04 +02:00
Lauri Ojansivu b1403ca7af The d shortcut opens the Due date editor of a card that is still being opened.
On a slower server a card's details render a few hundred milliseconds
after the click that opens it. `d` pressed in that moment found no Due
date control yet and did nothing - only the hovered-minicard path (#6755)
waited for it. Both paths now wait for THAT card's control, stop if
another card was opened meanwhile, click it once, and give up after 3
seconds.

The card details also closed any popup of their card when they were
destroyed (#6686), which a re-render of the card still open does too: a
popup could open and shut again at once. They close it now only when
the card is no longer the open one - closed, or another card opened.

The Playwright test emulates a 300 ms connection in Chromium, clicks
each card open and presses d at once: the editor opens and is still
open 1.5 s later, and closing the card still closes it. It fails on the
code before this change. dueDateKeyboardShortcut and
labelsPopupStaleCardContext pin both halves.

Thanks to mimZD and xet7 !

Fixes #6840,
2026-10-31 21:06:53 +02:00
Lauri Ojansivu bc21c901f4 Date picker: a Today button, and a month navigated back to is drawn whole.
The board Calendar view already has Today (and Calendar mode too), but
the date picker of every date popup - Due, Start, End, Received and the
rest - had only « ‹ › », so a user who paged to another month had to
page back. Today, between the month and ›, shows today's month and
focuses today; it changes nothing until a day is clicked and saved.

Testing it found a picker bug that was already there: going back to a
month drew it wrong - October came back as "29, empty, 30, empty", with
no 31st. Blaze matches list items by _id, and the weeks and days had
none; the empty cells, all alike, were moved around. Every week and
cell, empty ones too, now has an _id that names its month.

calendarPickerControls.test.cjs checks the ids, that Today shows today's
month and focuses today, and that it selects nothing; the Playwright test
pages two months ahead, presses Today, and checks every day of the month
in order, today focused, and the card's due date unchanged.

Thanks to mimZD and xet7 !

Fixes #6838,
2026-10-31 21:06:53 +02:00
Lauri Ojansivu 88fef5dc08 CHANGELOG: the Password length strings in every language, and its title bar.
Thanks to xet7 !
2026-10-31 15:18:23 +02:00
Lauri Ojansivu 3f9379ac86 Translations: the Password length page and Change your password, in 234 languages.
The 18 new strings of Admin Panel / Problems / Password length and of the
"Change your password" dialog, written in the language and script each
locale tag names, with the terms each file already uses for password,
account, save and sign in, and __min__ kept exactly. Only those 18 keys
changed in each file, written with fill-translations.mjs --apply, which
fills English placeholders only.

Low confidence, for a speaker to correct on Transifex: ace, ak, an, ary,
ast-ES, ay, ba, bho, bi, bm, bo, br, bua, chr, co, csb, cv, cy, dz, ee,
ff, fj, fo, fur, ga, gd, gn, gv, ha, haw, hsb, ig, iu, jv, kl, kok, ks,
kw, lg, lld, mg, mi, mt, nah, nap, nd, nso, ny, om, pap, qu, rm, rn,
rup, rw, sah, sc, scn, sd, se, si, sm, sn, so, ss, st, szl, te-IN, tg,
ti, tig, tk_TM, tlh, tn, to, tpi, ts, tt, uz-AR, ve, ve-CC, ve-PP, vo,
wa, wa-RR, wal, wo, wuu-Hans, xh, yi, zgh.

Thanks to xet7 !
2026-10-31 15:18:13 +02:00
Lauri Ojansivu 85af731c1b Admin Panel / Problems / Password length names itself in the title bar.
The title bar keeps its own copy of the menu labels, and the new pane
had none: tests/adminUrls.test.cjs failed on problems/password-length.

Thanks to xet7 !
2026-10-31 15:18:11 +02:00
Lauri Ojansivu bb32a50550 CHANGELOG: sign-up works again, Admin Panel / Problems / Password length, and a changed password signs every session out.
Thanks to xet7 !
2026-10-31 14:56:22 +02:00
Lauri Ojansivu c99aa7f845 Admin Panel / Problems / Password length, and a changed password signs every session out.
PASSWORD_MIN_LENGTH (default 8) is now the length below which a sign-in
is REPORTED, in the new Admin Panel / Problems / Password length, and
PASSWORD_MIN_LENGTH_ENFORCE (off by default) is what makes it a rule: a
shorter new password is refused, and an account that signs in with an
older shorter one sees only "Change your password" until it has a long
enough one. Signing in is never refused, enforced or not, so nobody is
locked out by a minimum set after their password. LDAP, OAuth2, OIDC,
SAML, CAS and Sandstorm accounts are neither reported nor asked: their
password is their provider's. An administrator's Set password no longer
applies WeKan's policy to such an account.

Meteor sends a password hashed, so only the browser knows its length: it
notes it on the sign-in form and, after a sign-in that worked, says "it
was short" - never the password or its length.

The page has both settings and a table, one row per account: username,
first and last short-password login, the latest IPv4 and IPv6 address
with the country's flag and city when a CDN says where it is, the count,
and when it was fixed, with the Too short (default) / Fixed / All filter.
Fixed is a changed password not seen short since; a short one seen
again reopens the row.

Changing one's own password - Member Settings, the forced change, a
reset link - now signs EVERY session of the account out, the one that
changed it too, and only the new password signs in again. The browser
goes to the sign-in page as soon as it is signed out, since the server
closes the connection before the answer can arrive.

Documented in docs/Features/Admin-Panel/Problems/Password-length.md, the
settings on every platform. The new strings are in every locale in
English for now. Tests: passwordPolicy, passwordChangeRequired and
shortPasswordLogins (node); password-min-length-signup-login,
password-length-report and password-change-other-sessions (Playwright,
Chromium and WebKit), which change each setting on the page and check
what it does, and that sign-up with no mail server registers and signs
the user in.

Thanks to xet7 !
2026-10-31 14:54:22 +02:00
Lauri Ojansivu 508484ef1e Sign-up, and every method and publication, pass audit-argument-checks again.
Registering answered "Internal server error" - and created the account
anyway, so the next try said it already existed - and the
starredPublicBoards and translation publications failed for everybody:
"Did not check() all arguments". audit-argument-checks asks,
synchronously, the moment a handler returns, whether check() saw every
argument. The REQUIRE_2FA wrapper of every method and publication, and
the Audit log's wrapper of the administrators' methods, were async and
awaited before they called the handler - even with REQUIRE_2FA off - so
the handler's own check() came after the audit had already failed the
call, and the handler went on running.

A wrapper now calls the handler at once when it needs no lookup
(REQUIRE_2FA off, nobody signed in, a cached answer). When it must await
first, it hands the audit over (server/lib/argumentChecks.js): the
arguments count as seen for the outer audit, and the handler runs under
an audit of its own that it must satisfy exactly as unwrapped.

tests/wrappedHandlerArgumentChecks.test.cjs runs both wrappers under a
model of the check package's audit: a checking handler passes, off and
on, a refusal stays a refusal, a handler that does not check still fails
on every path, and no wrapper in server/ awaits before it has checked.

Thanks to xet7 !
2026-10-31 14:13:40 +02:00
Lauri Ojansivu 548196c9a9 CHANGELOG: Meteor in .tools/.meteor no longer springboards forever.
Thanks to xet7 !
2026-10-31 13:23:36 +02:00
Lauri Ojansivu 9a118c83b2 Meteor in .tools/.meteor no longer springboards forever.
A build from the menu printed uname -p and sysctl hw.cpu64bit_capable
over and over and never reached its first package. With
METEOR_WAREHOUSE_DIR set and no SANDBOX, Meteor takes the running tool
to be wherever the warehouse's "meteor" link points - 3.5.4, the release
it was installed with - so with .meteor/release at 3.6-rc.1 every start
springboarded to 3.6.0-rc.1, found itself the wrong tool again and
springboarded again: 28 times in 20 seconds of meteor --version. The
earlier 1.1 GB build log was the same loop, its module trace printed
anew on every exec.

tools-env.sh and tools-env.bat now set SANDBOX=true beside the
warehouse, as Meteor's own self-test does; that one check is all it
changes. The Nextcloud test scripts and the dev-source runner, which set
the warehouse themselves, do the same. meteor --version now springboards
once, from the linked 3.5.4 to 3.6.0-rc.1.

tests/meteorSpringboardLoop.test.cjs checks both env scripts, that
nothing sets the warehouse without SANDBOX, and runs the installed
Meteor to count its springboards.

Thanks to xet7 !
2026-10-31 13:23:24 +02:00
Lauri Ojansivu d442bd8a7e CHANGELOG: builds trace module resolution only with WEKAN_BUILD_TRACE_MODULES=1.
Thanks to xet7 !
2026-10-31 13:02:19 +02:00
Lauri Ojansivu 5a13e927bc Builds trace module resolution only with WEKAN_BUILD_TRACE_MODULES=1.
Turning the NODE_DEBUG=module trace off for EVERYTHING alone was not
enough: a dev build from the build.sh menu wrote 1.1 GB, three million
lines, in its first three minutes, before it was interrupted. The trace
is now opt-in in build.sh and build.bat alike, and the diagnostics line
says whether it is on and how to turn it on. The other build diagnostics
(resolver commands, npm verbose, METEOR_PROFILE) are unchanged.

Thanks to xet7 !
2026-10-31 13:01:57 +02:00
Lauri Ojansivu c78af24a36 CHANGELOG: EVERYTHING builds without the per-require module trace.
Thanks to xet7 !
2026-10-31 12:46:14 +02:00
Lauri Ojansivu 6551062492 EVERYTHING builds without the per-require module trace.
The build's NODE_DEBUG=module trace wrote over 12 GB in the first half
hour of an EVERYTHING run on macOS - twice, through tee - and slowed the
build it traced, so the run would fill the disk before reaching the tests.
A build from the menu still traces by default; WEKAN_BUILD_TRACE_MODULES=0
turns it off, and EVERYTHING sets that unless the caller asked for 1. The
other build diagnostics (resolver commands, npm verbose, METEOR_PROFILE)
are unchanged.

Thanks to xet7 !
2026-10-31 04:15:01 +02:00
Lauri Ojansivu a0ed5de6f1 CHANGELOG: the breached-password check's lookup digest (CodeQL #599).
Thanks to GitHub CodeQL and xet7 !
2026-10-31 03:52:47 +02:00
Lauri Ojansivu f406d46fe7 Breached-password check: one Web Crypto lookup digest for the browser and the server.
CodeQL #599 (js/insufficient-password-hash, CWE-916) flagged the SHA-1
the server computed for PASSWORD_CHECK_PWNED. That digest is not password
storage - accounts-password keeps passwords with bcrypt - and it is never
stored or logged: Have I Been Pwned's range API is keyed by SHA-1, its
first 5 hex digits are the question and the rest is compared in memory.
A slow hash cannot be used, because the service could not match it.

The browser and the server now compute it with one implementation,
breachLookupDigest in models/lib/passwordPolicy.js, which says what it is
and is not; the server no longer hashes a password with a hash of its
own, and a digest that cannot be computed lets the password through.
Tests pin that the digest is SHA-1 for ASCII and UTF-8, that it reaches
only the lookup and the comparison, and that nothing in WeKan hashes a
password-named value with a fast hash. Unreleased hardening, so no Hall
of Fame entry: docs/Security/Password-Breach-Check-Digest-2026-10-31.md.

Thanks to GitHub CodeQL and xet7 !
2026-10-31 03:52:32 +02:00
Lauri Ojansivu 4b0aa715eb Merge remote-tracking branch 'origin/main' 2026-10-31 03:36:43 +02:00
Lauri Ojansivu f5af33ac38 CHANGELOG: Audit log documentation.
Thanks to xet7 !
2026-10-31 00:44:17 +02:00
Lauri Ojansivu f165a24192 docs: Admin Panel / People / Audit log.
Thanks to xet7 !
2026-10-31 00:44:14 +02:00
Lauri Ojansivu e6ea524541 CHANGELOG: password policy, compulsory 2FA, idle sign-out, audit log, HSTS, cross-site guard and S3 encryption.
Thanks to xet7 !
2026-10-31 00:33:31 +02:00
Lauri Ojansivu de23dece66 Translations: the password policy, two-factor, audit log and S3 encryption strings in 234 languages.
The 16 strings the security features added were English placeholders in
every locale; each is now in the language and script its locale tag
names, with the terms the file already uses for password, account,
attachment, backup and two-factor authentication. __min__ is kept
character for character. Where the right word is spelled like the
English ("Administrator"), the column header says "Admin" or "Who"
instead, because a value equal to English is not a translation.

fy and fy-NL: the existing two-factor strings were Dutch in a Frisian
file, and are Frisian now; tests/frisianTwoFactorLanguage.test.cjs.
tig: Tigre spells storage መክዘን, not Tigrinya's መኽዘን.

Low confidence, for a speaker to correct on Transifex: ace, ak, an, ary,
as, ast-ES, ay, ba, bho, bi, bm, bo, br, bua, chr, ckb, co, csb, cv, dz,
ee, ff, fj, fy, gn, gv, haw, hsb, ht, ig, iu, kl, kok, ks, kw, la, lg,
lld, mg, mi, nah, nd, nso, ny, om, pap, ps, qu, rm, rn, rup, rw, sah, sc,
scn, sd, se, si, sm, sn, so, ss, st, szl, tig, tlh, tn, to, tpi, ts,
uz-AR, ve, ve-CC, ve-PP, vo, wa, wa-RR, wal, wo, xh, yi, zgh.

Thanks to xet7 !
2026-10-31 00:32:43 +02:00
Lauri Ojansivu 2017f2871f Audit log: its own Admin Panel URL and title, and right-to-left table alignment.
/admin/people/audit-log opens the pane and names it in the title bar like
every other People pane, and the table aligns to the start of the line,
so it reads right to left in Arabic, Hebrew, Persian and the rest.

Thanks to xet7 !
2026-10-31 00:32:33 +02:00
Lauri Ojansivu 302b47db6f docs: Security settings, every hardening option in one page.
Thanks to xet7 !
2026-10-31 00:15:13 +02:00
Lauri Ojansivu 482c2c29f4 Attachments, avatars and backups in S3 can be encrypted at rest.
Admin Panel / Attachments / S3 / Server-side encryption: SSE-S3 (AES256)
or SSE-KMS (aws:kms, with a KMS key or the account's default). S3
encrypts each object as it is written and decrypts it for WeKan on read,
so reads, range requests and moves are unchanged. Every attachment and
avatar upload, every renamed object and every backup written to S3 now
carries the choice; a value that is not one of them, or a malformed key,
adds nothing.

On the filesystem, encryption at rest stays with the disk or volume
(LUKS, an encrypted cloud volume): exports, PDF, thumbnails, virus scans,
backups and WebDAV all read the stored files directly, and encrypting
them inside WeKan would change every one of those and convert every file
already stored.

The new interface strings are pending translation.

Thanks to xet7 !
2026-10-31 00:12:09 +02:00
Lauri Ojansivu 4d5b832499 Admin Panel / People / Audit log: what site administrators did.
Nothing recorded who set somebody's password, made somebody an admin,
changed a login setting or restored a backup. Every call of those
administrative methods by a site administrator is now kept, one document
each, for a year: when, who, which action, the outcome - a refused call
too - its arguments and the address. A password or a secret never
reaches the trail: values under a credential-named key, the arguments
that are a password by position, and a password inside a URL are
replaced by [redacted]. An administrator editing cards is not in it,
the trail is shown to site administrators only, and a trail that cannot
be written never changes what the method did. It and the REQUIRE_2FA
wrapper keep each other's marks, so neither wraps the other twice.

The new interface strings are pending translation.

Thanks to xet7 !
2026-10-31 00:09:20 +02:00
Lauri Ojansivu 41173f0f6b Sign a browser session out after it has been idle for LOGOUT_IDLE_MINUTES.
LOGOUT_WITH_TIMER ends a login at a deadline however busy its user is;
nothing ended a session somebody walked away from. LOGOUT_IDLE_MINUTES
(Admin Panel / People / Login, every platform; empty or 0 is off) does:
the browser reports a key press, click, touch or scroll at most once a
minute, the server keeps the time per login token in idleSessions, and
once a minute every token idle past the limit is removed, which signs its
connections out. A tab that reconnects and resumes its login by itself -
a laptop waking up - is not activity and does not extend the session.
REST API tokens have no session record and are never removed. The time
is kept beside the user rather than on the token, because updating one
element of the token array needs the positional $ operator, which not
every database WeKan runs on supports.

Thanks to xet7 !
2026-10-31 00:08:20 +02:00
Lauri Ojansivu d7abe9910a Password policy for new passwords, and compulsory two-factor authentication.
New passwords: PASSWORD_MIN_LENGTH (default 8) and PASSWORD_CHECK_PWNED
(off by default), in Admin Panel / People / Login and on every platform.
The browser sends a password to Meteor hashed, so the server checks every
password that reaches it as text - Admin Panel Set password and Add user,
POST /users/register - and the sign-up, Change Password, reset and
enrolment forms check in the browser. Signing in is never checked, so an
existing shorter password keeps working until it is changed. A breached
password is found with Have I Been Pwned's k-anonymity range API: only 5
hex digits of the SHA-1 leave WeKan, and an unreachable service never
refuses a password.

REQUIRE_2FA: off (default), admins or all. It covers accounts that sign in
with a WeKan password; LDAP, OAuth2, SAML, CAS and Sandstorm accounts sign
in through an identity provider and are not asked. Such an account can
still sign in, sees only the 2FA set-up until it has 2FA, and until then
the server refuses every method and publication but signing in and
enrolling, HTTP routes do not accept its login and the REST API gives it
no token. Once required, 2FA cannot be switched off.

The new interface strings are pending translation.

Thanks to xet7 !
2026-10-31 00:07:58 +02:00
Lauri Ojansivu 48a2e97ea3 A state-changing request that logs in only with the cookie must come from WeKan itself.
POST /import-trello and other HTTP routes accept the login cookie as the
login, so a page on another site could make a signed-in browser send one.
The cookie is SameSite=Strict, which keeps it off such a request in
today's browsers; this is the second line, for older browsers, same-site
sibling hosts and routes added later. A POST, PUT, PATCH or DELETE whose
only login is the cookie must carry an Origin, or Referer, naming
ROOT_URL's host or the Host / X-Forwarded-Host it was sent to, which on
Sandstorm is the grain's own host. Token-authenticated API calls, Meteor's
cookie routes and DDP are not affected. A refusal is recorded in Admin
Panel / Problems as csrf.cross-site-cookie and never blocks the account
whose cookie it was.

Thanks to xet7 !
2026-10-30 23:52:09 +02:00
Lauri Ojansivu 4993242c5f Strict-Transport-Security on every response of an HTTPS site.
WeKan sent no HSTS header, so a link typed as plain HTTP or a downgrade on
a hostile network could strip TLS from the next visit unless the reverse
proxy added it. It is now sent whenever ROOT_URL is https: 180 days, this
host only. HSTS_MAX_AGE, HSTS_INCLUDE_SUBDOMAINS and HSTS_PRELOAD (Admin
Panel / People / Login, every platform) change it; 0 stops it, and preload
is sent only with includeSubDomains and at least a year. Every response
also carries X-Content-Type-Options: nosniff and
Referrer-Policy: strict-origin-when-cross-origin.

Thanks to xet7 !
2026-10-30 23:50:02 +02:00
Lauri Ojansivu 12eef31162 CHANGELOG: snap backup and restore instructions work on AlmaLinux, Rocky Linux and Fedora.
Thanks to Group-Deed and xet7 !
2026-10-30 23:14:01 +02:00
Lauri Ojansivu 0b5c855820 Snap backup and restore instructions work on AlmaLinux, Rocky Linux and Fedora.
snapd keeps the snaps in /var/lib/snapd/snap on Fedora, RHEL, AlmaLinux,
Rocky Linux, CentOS Stream and Arch Linux, which have no /snap, so the
instructions that exported /snap/wekan/current/bin and its lib directory
did not find mongodump and mongorestore there. They now pick the directory
with one S= line and the CPU's library directory with $(uname -m), and the
scripts that can be run from a host shell find the snap in either place.

Thanks to Group-Deed and xet7 !
2026-10-30 23:13:50 +02:00
Lauri Ojansivu 0c53ef6960 CHANGELOG: boards from before the templates feature are shown on All Boards again.
Thanks to xet7 !
2026-10-30 22:56:38 +02:00
Lauri Ojansivu 2ab0afdc93 Old boards without a type are shown on All Boards again.
Boards, swimlanes, lists and cards from before the templates feature have
no `type`: the schemas' defaultValue fills it on insert only. All Boards
publishes `type: 'board'` and the card queries ask for 'cardType-card', so
a Sandstorm grain migrated from MongoDB 3 opened onto an empty All Boards
page although its board was intact in the database. The new
type-defaults-backfill startup step sets the schema default on every
document whose type is missing, null or empty, and never touches an
explicit type.

Thanks to xet7 !
2026-10-30 22:56:24 +02:00
github-actions[bot] f2a1d7b9ba Bump versions for v12.75 2026-10-30 20:26:45 +00:00
Lauri Ojansivu cd1f1b8f05 Prepare v12.75 release 2026-10-30 22:25:30 +02:00
Lauri Ojansivu b941f36e4c Merge remote-tracking branch 'origin/main' 2026-10-30 22:25:01 +02:00
Lauri Ojansivu 487a528806 CHANGELOG: a changed password signs the account out in every other browser.
Thanks to xet7 !
2026-10-30 21:39:35 +02:00
Lauri Ojansivu dc9a7bb1ac Tests: a changed password signs the account out in every other browser.
A report described "failure to invalidate session on password change": the
same account signed in on two browsers, the password changed in one, and
the other's session "updated" rather than ended. WeKan does not do that.
Member Settings / Change Password is Meteor's changePassword, which removes
every login token but the changing session's; the admin's Set password is
setPasswordAsync, which removes them all, as resetPassword does; and a
session whose token is gone is closed, while WeKan's HTTP routes accept only
a token still in the list.

password-change-other-sessions.e2e.js runs the report in two browser
contexts: both signed in with the password, the password changed through
the form in one, and the other is signed out within seconds, stays signed
out after a reload, and only the new password signs in. It passes three
times in a row in Chromium and in WebKit. passwordChangeSessions.test.cjs
pins those paths and fails on a password change with logout: false, the
LDAP sign-in's password sync being the one stated exception.

Thanks to xet7 !
2026-10-30 21:39:20 +02:00
505 changed files with 18700 additions and 687 deletions

No files matched your search

+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
+1 -1
View File
@@ -32,6 +32,6 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
uses: helm/chart-releaser-action@df287104677278f869ca5480bd260358397cc3f9 # v1.8.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+975 -7
View File
@@ -109,20 +109,988 @@ its reason.
<details>
<summary>Need the running app to reproduce/verify</summary>
- The browser test fixes of 2026-10-27 are not verified in a browser yet: the
second EVERYTHING run was stopped during its build, before any browser
stage. The first run's Chromium stage reached 3212 of 3703 tests, and every
failure it reported is fixed in the source; Firefox, the database
conformance runs and FerretDB's own suites did not run. Run `./build.sh`'s
EVERYTHING option again. The stopped build had started reinstalling
`node_modules`, so a clean install comes first.
- The WebKit stage cannot run in the Flatpak sandbox: MiniBrowser needs its
own sandbox, and turning that off is not allowed there. Run it on a host.
- [#6834](https://github.com/wekan/wekan/issues/6834) (the minicard due date is fixed; the board header bar wrapping to a second row and the list header icons under the card count did not reproduce on the current code at the same window width, in Persian, with the card count on - which WeKan release and browser zoom showed them is asked; wrapping the header rather than hiding buttons is by design)
</details>
<details>
<summary>Needs a maintainer decision on the intended contract</summary>
- [#6837](https://github.com/wekan/wekan/issues/6837) (a Simple and an Advanced mode, or profiles an administrator defines and assigns, to hide features from new users: which features each mode or profile shows, who chooses it, and how it relates to the existing per-board Board Settings / Card and Show on Minicard switches and the Admin Panel feature toggles)
</details>
</details>
# v12.78 2026-11-02 WeKan ® release
**In short:** **CrossBoardExportBleed** and **UnassignedAttachBleed** are
fixed: a checklist scope stays on its board, every export obeys Disable all
export and Anonymize export users, and an assigned-only member changes
attachments only on their own cards. **SAML** logins use **node-saml 5.2.0**,
and **continuous backup** no longer misses a change made right after it starts.
**Keyboard shortcuts** keep working after a popup closes, and the Rules page has
a way back to the board.
This release fixes the following CRITICAL SECURITY ISSUES:
**Exports** - what a board export may contain, and whether it runs at all.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/be6727607c">A checklist scope stays on its board, and every export obeys Disable all export and Anonymize export users</a>. Thanks to ccsimplyspolit and xet7.</summary>
[CrossBoardExportBleed](https://wekan.fi/hall-of-fame/crossboardexportbleed/),
[GHSA-8885-xrq4-j47h](https://github.com/wekan/wekan/security/advisories/GHSA-8885-xrq4-j47h).
A board export scoped with `?checklistId=` streamed a checklist of another,
even private, board with its items and creator; it is now bound to the
exported board's cards. The streaming CSV/TSV export ignored Disable all
export and Anonymize export users, and so did the Excel, PDF, chart, calendar
and external-format exports: every export route now refuses with 403 when
export is disabled, and every exporter that names people anonymizes. Refusals
are shown in Admin Panel / Problems.
</details>
**Attachments** - who may add or delete a card's files through the API.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/532a96da11">An assigned-only member adds and deletes attachments only on cards assigned to them</a>. Thanks to Javier García Herrera and xet7.</summary>
[UnassignedAttachBleed](https://wekan.fi/hall-of-fame/unassignedattachbleed/),
[GHSA-w26g-rc43-q6cm](https://github.com/wekan/wekan/security/advisories/GHSA-w26g-rc43-q6cm).
A member with the Normal, assigned only role whose card assignment was removed
kept the card and attachment ids, and `POST /api/attachment/upload` and
`DELETE /api/attachment/delete/:id` still added a file to that card and
soft-deleted its existing one: both asked only for board write access. The
DDP methods, and copy and move onto a target card, had the same gap. All
eight handlers now apply the card rule the ordinary card Delete used, and a
refusal is shown in Admin Panel / Problems.
`tests/unassignedAttachBleed.test.cjs`
fails if any attachment API write lacks it.
</details>
and updates the following dependencies:
- **isomorphic-git 1.44.3 → 1.44.8** — the GitHub wiki sync
([#6846](https://github.com/wekan/wekan/pull/6846)).
- **@node-saml/node-saml 5.1.0 → 5.2.0** — SAML; see the fix below, without
which it changed nothing that runs ([#6850](https://github.com/wekan/wekan/pull/6850)).
- **@typescript-eslint/eslint-plugin 8.72.0 → 8.72.1** — lint only
([#6849](https://github.com/wekan/wekan/pull/6849)).
- **@rspack/dev-server 2.2.1 → 2.2.2** — development server only
([#6848](https://github.com/wekan/wekan/pull/6848)).
- **mailparser 3.12.2 → 3.12.4** — inbound email and bounces
([#6845](https://github.com/wekan/wekan/pull/6845)).
- **i18next 26.5.4 → 26.5.11** — translations
([#6847](https://github.com/wekan/wekan/pull/6847)).
- **@aws-sdk/client-s3 3.1155.0 → 3.1159.0** — S3 backups; see the
@smithy/core fix below ([#6844](https://github.com/wekan/wekan/pull/6844)).
- **imapflow 2.8.0 → 2.9.1** — the IMAP mailbox
([#6842](https://github.com/wekan/wekan/pull/6842)).
- **glob 13.0.6 → 14.0.3** — no WeKan code loads it: it is only the target of
an `archiver-utils` override that archiver 8 no longer needs; @babel/cli and
rimraf keep their own copies ([#6851](https://github.com/wekan/wekan/pull/6851)).
- **helm/chart-releaser-action 1.7.0 → 1.8.0** and **docker/login-action 4.6.0
→ 4.7.0** — CI, each still pinned to the commit its tag names
([#6843](https://github.com/wekan/wekan/pull/6843),
[#6841](https://github.com/wekan/wekan/pull/6841)).
Thanks to dependabot.
and fixes the following bugs:
**Dependencies** - what the updates above did, and did not, change.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5c50f38e25">SAML logins are verified by node-saml 5.2.0, as package.json says</a>. Thanks to xet7.</summary>
The bump changed the app's `package.json`, but a login is verified by the
`wekan-accounts-saml` package's own copy, still pinned to 5.1.0 - so nothing
that runs changed. 5.2.0 rejects a message with more than one Issuer, checks
`idpIssuer`, rejects a `RelayState` that is not the signed one, and checks a
POST LogoutResponse's status and issuer. The package pins 5.2.0 now, and
`samlLogin.test.cjs` requires both to name the same version. A login
roundtrip with the package's copy logs in a signed Response and a signed
Assertion, and refuses a changed NameID, another key, an unsigned response and
an answer to a request WeKan never made.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/338afc15f8">@smithy/core 3.38.1, the AWS SDK's logger redaction fix</a>. Thanks to xet7.</summary>
The S3 SDK bump resolved `@smithy/core` 3.37.0, deprecated for a bug that
passed sensitive fields nested in lists or maps unredacted to a custom logger
([aws-sdk-js-v3#8393](https://github.com/aws/aws-sdk-js-v3/issues/8393));
3.36.2 before it had the bug too. WeKan's only AWS client, the S3 backup,
passes no logger, so nothing leaked. Updated in the lockfile only, one
package; the telemetry gate (`releases/check-telemetry.py`), which had failed
on the deprecation note's new github.com link, passes again. The ten updates
add no package, install script, network host or telemetry, and every tarball
matches its lockfile hash.
</details>
**Continuous backup** - what it sees right after it starts.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8cbe2605fe">Files are watched before the first scan, and a macOS start catches up once</a>. Thanks to xet7.</summary>
The file engine scanned its sources first and only then started watching, so
a change made between the two waited for the next scan, five minutes by
default. macOS FSEvents also delivers nothing for a moment after a watcher is
created. The watchers start first now, sharing one queue with the scan so a
change is recorded once, and on macOS one catch-up scan runs two seconds after
the start; Linux's inotify needs none. A new test with a silent watcher pins
the order, the macOS catch-up and that Linux runs no second scan.
</details>
**Single-file builds** - signing and the start/backup commands.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8d4f4caa2f">A macOS build is signed whatever its target is called, and start/backup return once WeKan runs</a>. Thanks to xet7.</summary>
Only a target named mac or darwin was ad-hoc signed, so a Mach-O built under
another label had the signature its injected archive invalidated, and macOS
killed it before it printed anything. Every Mach-O output is signed now, read
from the file's magic number. `start --background` and `backup` returned
before WeKan wrote its pid file, so a `stop` right after them said WeKan was
not running and left it running; both wait now until the pid file names the
process they started. `singleFile.test.cjs` passes 24 of 24, the real single
executable included.
</details>
**Release builds** - the bundles each platform downloads.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/24a7270b7f">Android bundles start again: npm 12 runs argon2's and bcrypt's install scripts</a>. Thanks to xet7.</summary>
npm 12 lets only declared dependencies run install scripts, and the rebuild
directories declared none, so argon2 and bcrypt were never compiled. Prebuilds
hid it everywhere except Android, whose bundles crash-looped on "No native build
was found for platform=android". Each rebuild directory now declares the
reviewed installers it holds, and the Android build loads both before starting
WeKan.
</details>
**Filters** - the filter sidebar's date ranges.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c5f5ad2338">A refused date range says why in the form, so Clear still works in Firefox</a>. Thanks to xet7.</summary>
A reversed card date or card movement range was reported with the browser's
validation bubble, which Firefox opens over the Apply and Clear buttons and
which took every click on them: the range could not be cleared. The end date
is still marked invalid, and the reason is a `role="alert"` line in the form;
editing the range or Clear takes it back.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4ad13db5ef">The date-range forms are checked by WeKan, not refused by WebKit after Clear</a>. Thanks to xet7.</summary>
After Clear, WebKit kept a half-edited date in the From field and silently
refused the next submit, so a new range never applied. Both forms are
`novalidate` now; WeKan's own check already marks a refused range and says
why.
</details>
**History recovery** - an undo or redo that is still waiting for the server.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/96845f6f4b">Retry is enabled only once the board lets this person write on it</a>. Thanks to xet7.</summary>
After a reload the notice showed before the board and its members arrived,
and a Retry clicked then did nothing and the notice stayed. Retry is disabled
until it can act; Forget stays usable.
</details>
**Keyboard shortcuts** - when a focused button or link lets them through.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ba0de93f79">Shortcuts keep working after a click on a minicard or a closed popup</a>. Thanks to mimZD and xet7.</summary>
The shortcut filter refused every key while a link or button had the focus.
The minicard is itself a link, so clicking its date badge or menu left it
focused, and closing a popup returns focus to its opener: hovering a card and
pressing d did nothing until a reload. A focused control now keeps only its
own keys - Enter, Space for a button, the arrows for a tab - and every other
key is a shortcut again ([#6755](https://github.com/wekan/wekan/issues/6755)).
</details>
**Rules** - the way back from the Rules page.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/34d9817110">The Rules page has a Back to the board</a>. Thanks to mimZD and xet7.</summary>
The page lost its Back with its old sidebar, and only the trigger, action and
rule details pages kept one, to the rules list. The list and its Workflow,
Bots, Blocks and History views now link back to the board; the three
sub-pages keep only their own Back ([#6852](https://github.com/wekan/wekan/issues/6852)).
</details>
**Sign-in** - the error shown on the sign-in page.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/3a2fef4dc7">Login errors are rendered from template state, so a redraw of the form keeps them</a>. Thanks to xet7.</summary>
The error region sits inside the form's loading switch, and an error written
into it was lost whenever that part of the form was drawn again. The template
now renders the message as escaped text from a reactive value. The SAML error
test also waits for the finished form before it clicks.
</details>
**Whiteboard** - how its lines are drawn.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/40f60582b7">A line is drawn at its own width, not the whiteboard's 800</a>. Thanks to xet7.</summary>
The line's `stroke-width=width` found the template's `width` helper - the
drawing's width - before the line's own field, so every line was drawn 800
wide, one black shape over the whole whiteboard. The lines use `strokeWidth`
now, and a test fails if any of their fields shares a helper's name.
</details>
and has the following developer-tooling fixes:
**The whole-suite run** - EVERYTHING, on a Mac and in a sandboxed editor.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/16103f9ad2">The test database is kept in .tools/, not beside the checkout</a>. Thanks to xet7.</summary>
`build.sh` and `build.bat` put the :3001 test database in the checkout's
parent directory, the one thing a test run wrote outside the repository, and a
sandboxed editor could not create it. It is `.tools/mongodb-test-<port>` now;
`toolsDirOnly.test.cjs` pins it in both scripts.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/7f948bf438">On macOS the Playwright container uses Docker's bridge network, not the host network</a>. Thanks to xet7.</summary>
Firefox tests waited the full 60 seconds for a page whose document never
came. Docker Desktop's host network lost about one connection in 75 to
`host.docker.internal` (8 of 600 connects never completed), while the bridge
network lost none in 1000. Nothing there needs the host network on macOS, so
the container uses the bridge network; Linux keeps the host network.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d9e48a909d">The list-sync specs' mock servers are reachable from WeKan when the browsers run in Docker on macOS</a>. Thanks to xet7.</summary>
WeKan fetched from mock Jira and GitLab servers on the container's loopback,
which Docker Desktop keeps from the host. build.sh already forwarded the test
SMTP port into the container; it now forwards a small pool of ports the same
way, and those specs take one of them.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ffe207e191">The Docker test proxy dials the host again when a connect is never answered</a>. Thanks to xet7.</summary>
On the bridge network too, Docker Desktop's gateway now and then left one
connect to the host unanswered through nine retransmits while the next went
straight through. The proxy reads nothing from the browser until a connection
is up, drops a dial that has not connected in 1.5 seconds and dials again, up
to five times, so no byte is lost or sent twice.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8a1f7a14a9">The Playwright container sees the repo and the files root at their own host paths</a>. Thanks to xet7.</summary>
Specs and the server share files by path. With the repository mounted at
`/repo`, the continuous backup tests gave the server a key file it could not
find and looked for a stream it wrote elsewhere. Both are mounted at their
host paths now, so a path names the same file on both sides.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/a6bec88266">The phone invitation tile is measured in one layout with its buttons</a>. Thanks to xet7.</summary>
Measured one at a time, the tile and its buttons were taken from two layouts:
Firefox drew the row above the board list in between and moved the tile down
29 pixels. They are measured together now, and the test still fails when the
tile clips its buttons, the original #6488 bug.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f5eb4ecf61">The Mac smoke test asks libproc, not ps, whether a process group still lives</a>. Thanks to xet7.</summary>
When macOS refuses to signal a group of zombies, the smoke test asked `ps`
whether a member was alive, and a Mac that does not let the build run `ps`
failed a correct app. On macOS it asks the kernel through `proc_listpids` and
`proc_pidinfo` now, starting no process; elsewhere it uses `ps` as before.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/897376ba54">The button theme test checks the Board Settings that still have action buttons</a>. Thanks to xet7.</summary>
Card Settings has had no column actions since card actions moved into each
section's own menu, so the test failed on Card in every theme. Swimlane and
List are checked in all four themes, and the test says why Card is not.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d5e3325e87">Card Settings section order waits for the published board before reading the card</a>. Thanks to xet7.</summary>
The test writes the board straight to the database and reloads. The board
publication's cursor has `limit: 1`, so on the standalone test MongoDB it
polls and a reload could get the old board for up to ten seconds. The test
waits for the published board, up to 15 seconds, before it reads the card.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/89b64966c6">The password minimum specs wait until the server has the policy they wrote</a>. Thanks to xet7.</summary>
The specs write the password policy straight into the settings document, and
on the standalone test MongoDB the server's settings observer polls, so for up
to ten seconds it still enforced the old policy. `untilServerHas()` asks
`getPasswordPolicy` until the server has it, then reloads the sign-up form; 6
of 6 with two workers.
</details>
**Browser tests** - specs that read the page before it had settled, in Firefox.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/dbf07aadf9">The activity recovery report is searched again until no worker lease covers it</a>. Thanks to xet7.</summary>
The report is a snapshot, and the live recovery worker's short lease could
make it show a seeded orphan as being delivered. The test searches again
until the snapshot falls between leases.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9188843369">The checklist drag tests drag once the opened card's rows hold still</a>. Thanks to xet7.</summary>
Sections drawn above the checklist moved its rows 50 pixels between measuring
and releasing, so the release opened an item's editor or dropped it in the
wrong place.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0cea141702">The duplicate relation picker is opened once the card has opened</a>. Thanks to xet7.</summary>
A picker opened while the card was still opening was closed again when the
card settled.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0403e27c62">The notification delivery sections are opened until they stay open</a>. Thanks to xet7.</summary>
The language arriving after the click drew the popup again with its sections
closed.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2042fd5102">The Blocks drag test grabs the block's body, not its edge</a>. Thanks to xet7.</summary>
A Khmer label overflows its block in Firefox, and the first point found was
the block's outline, where a press drags nothing.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0a1f8584ee">The board view popup is opened until it stays open for the Product Backlog</a>. Thanks to xet7.</summary>
The language arriving after the click drew the header again and closed the
popup.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/efe0e9287c">A language switch is waited for, and WebKit does not freeze on asking for a push subscription</a>. Thanks to xet7.</summary>
A spec that switched language and clicked at once clicked into the redraw
that followed, which closed its popup; every page of the test context now
waits until it shows the new language. Playwright's WebKit froze the page on
asking for a push subscription, which Member Notification Settings does on
opening; there the page answers that it has none.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0c8c254dc5">The viewport matrix cleans up only what it seeded</a>. Thanks to xet7.</summary>
The matrix is Chromium-only. In Firefox and WebKit its setup never ran, but
its cleanup did, and failed every viewport.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f241681e80">Background notification recovery checks its own mail</a>. Thanks to xet7.</summary>
The shared SMTP sink had caught another worker's mail first.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c71c875649">The custom field settings popup is opened until its Create button shows</a>. Thanks to xet7.</summary>
The opened card was still drawing and closed the popup again.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4a9c3f21d2">The drawing surface's width is waited for</a>. Thanks to xet7.</summary>
The element's layout follows its viewBox a frame later.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c8ed3e0fd0">The bulk export popup is opened until it stays</a>. Thanks to xet7.</summary>
All Boards was still drawing after the route changed, in all five Haitian
Creole tests.
</details>
**Test coverage** - October changes that had no test of their own.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/148d18d456">Server changes are tested: OpenSSL 4 decrypt refusal, the Scrum rollover lease, the backup catch-up scan</a>. Thanks to xet7.</summary>
Each with a negative test, and the lease with a tree-wide one: every lease
field is written only by its claim.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/79c40407d1">Admin Panel pane titles, audit log alignment, storage help, Data health and card reports are tested</a>. Thanks to xet7.</summary>
Unit and negative tests, and a Chromium browser test of the titles, the
right-to-left audit log, the storage help shown once and Data health's own
"Never".
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/121f37be91">The drawing refit, Due Cards scope, dependency import counts and SAML's one Issuer are tested</a>. Thanks to xet7.</summary>
A signed SAML Response with two Issuers is refused in the browser, which
node-saml 5.1.0 accepted; an assigned-only member's Due Cards show only their
own cards.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/3ccb465c3f">The keyboard shortcut browser test focuses the minicard link itself</a>. Thanks to mimZD and xet7.</summary>
Chromium does not leave the focus on the minicard after its menu is closed,
so the test makes the reported state directly; it passes in Chromium and
WebKit.
</details>
**The source-loaded server** - starting WeKan from source for development.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/15877dd1bc">A local package fork that a prebuilt package implies is loaded first</a>. Thanks to xet7.</summary>
The source server stopped at "isopack dynamic-import not yet loaded". It now
gets past that, and still stops later on "Cannot find module
'meteor/accounts-base'"; the browser tests ran against the bundle server.
</details>
**The changelog** - its guards between releases.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/05cc2460ea">The short changelog guard holds between releases too, when there is no Upcoming section yet</a>. Thanks to xet7.</summary>
`changelogShort.test.cjs` assumed an Upcoming section was always there, so it
failed right after every release, before anything new was written. With none
it checks that the newest release comes first and that nothing links an
Upcoming that does not exist.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.77 2026-10-31 WeKan ® release
**In short:** Two **node test guards** follow the movable popups and the date
picker of v12.76.
This release has the following developer-tooling fix:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ff2c0ed7f6">The date-popup guards follow movable popups and the date picker's empty cells</a>. Thanks to xet7.</summary>
`calendarPopupResize.test.cjs` asserted that a popup without a date editor does
not drag, and `calendarTodayMarker.test.cjs` picked a month's days with
`filter(Boolean)`. Both described v12.76 before it changed on purpose: every
popup drags by its title bar, or by its handle alone with drag handles on, and
an empty picker cell is an object with its own `_id`. Both are pinned instead.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.76 2026-10-31 WeKan ® release
**In short:** Security features: a **password policy** with an optional
breached-password check, **Admin Panel / Problems / Password length**,
**compulsory two-factor authentication**, sign-out of **idle sessions** and of
every session after a password change, an **audit log** of what administrators
did, **HSTS**, a cross-site request guard, and **S3 encryption at rest**.
**Sign-up** works again; the date picker has **Today**, the whole card title
edits and **every popup can be moved**; and an old **Sandstorm** grain migrated
from MongoDB 3 shows its boards again.
This release adds the following security features:
**Signing in** - which passwords are accepted and which are reported, who must
have a second factor, and when sessions end.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d7abe9910a">New passwords have a minimum length, and a breached one can be refused</a>. Thanks to xet7.</summary>
`PASSWORD_MIN_LENGTH` (default 8) and `PASSWORD_CHECK_PWNED` (off by default),
in Admin Panel / People / Login and on every platform. The browser sends a
password to Meteor hashed, so the server checks every password that reaches it
as text - Admin Panel Set password and Add user, `POST /users/register` - and
the sign-up, Change Password, reset and enrolment forms check in the browser.
Signing in is never checked, so an existing shorter password keeps working
until it is changed. A breached password is found with Have I Been Pwned's
k-anonymity range API: only 5 hex digits of the SHA-1 leave WeKan, and an
unreachable service never refuses a password. A refused password is ordinary
use, not an attack, so it is not recorded in Admin Panel / Problems.
`tests/passwordPolicy.test.cjs` checks every place a password reaches the
server as text, and that nothing else sets one without the check.
</details>
- [The breached-password check computes its lookup digest once, with Web Crypto, in browser and server; it is never stored (CodeQL \#599)](https://github.com/wekan/wekan/commit/f406d46fe7).
Thanks to GitHub CodeQL and xet7.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c99aa7f845">Admin Panel / Problems / Password length lists who signs in with a too short password</a>. Thanks to xet7.</summary>
The minimum above was enforced as soon as it existed. Now `PASSWORD_MIN_LENGTH`
(default 8) is the length below which a sign-in is REPORTED, and the new
`PASSWORD_MIN_LENGTH_ENFORCE` (off by default) makes it a rule: a shorter new
password is refused, and an account that signs in with an older shorter one
sees only "Change your password" until it has a long enough one. Signing in is
never refused, so nobody is locked out by a minimum set after their password.
LDAP, OAuth2, OIDC, SAML, CAS and Sandstorm passwords are their provider's, and
are neither reported nor asked. Meteor sends a password hashed, so the browser
that typed it says "it was short" after signing in - never the password or its
length. The page has both settings and one row per account: username, first and
last such login, the latest IPv4 and IPv6 address with flag and city when a CDN
says where it is, the count and when it was fixed, filtered Too short (default),
Fixed or All. Node suites `passwordPolicy`, `passwordChangeRequired` and
`shortPasswordLogins`; Playwright `password-length-report` changes each setting
on the page and checks what it does, in Chromium and WebKit.
[Password length](docs/Features/Admin-Panel/Problems/Password-length.md).
</details>
- [The Password length pane names itself in the title bar, like every other Admin Panel pane](https://github.com/wekan/wekan/commit/85af731c1b).
Thanks to xet7.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c99aa7f845">Changing one's own password signs every session of the account out, this one too</a>. Thanks to xet7.</summary>
Meteor's Change Password kept the session that changed it, and a reset link
signed the reset in. Now Member Settings, the forced change and a reset link
all end every session of the account, and only the new password signs in
again; the browser goes to the sign-in page as soon as it is signed out, since
the server closes the connection before the answer can arrive. An
administrator's Set password already signed everything out.
`password-change-other-sessions.e2e.js` changes it in one of two browsers:
both are signed out, the old password is refused and the new one signs in.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d7abe9910a">Two-factor authentication can be made compulsory, for administrators or for everybody</a>. Thanks to xet7.</summary>
`REQUIRE_2FA`: `off` (default), `admins` or `all`. It covers the accounts that
sign in with a WeKan password; LDAP, OAuth2, SAML, CAS and Sandstorm accounts
sign in through an identity provider and are never asked. Such an account can
still sign in - it has to, to enrol - and sees only the set-up until it has
2FA. Until then the server refuses every method and publication but signing in
and enrolling, HTTP routes do not accept its login and the REST API gives it no
token; once required, 2FA cannot be switched off. An account between signing
in and enrolling is ordinary use, so nothing is recorded in Problems.
`tests/requireTwoFactor.test.cjs` runs the real wrappers.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/41173f0f6b">A browser session is signed out after LOGOUT_IDLE_MINUTES without activity</a>. Thanks to xet7.</summary>
`LOGOUT_WITH_TIMER` ends a login at a deadline however busy its user is; nothing
ended a session somebody walked away from. The browser now reports a key press,
click, touch or scroll at most once a minute, the server keeps the time per
login token in `idleSessions`, and once a minute every token idle past the
limit is removed, which signs its connections out. A tab that reconnects by
itself - a laptop waking up - is not activity, and REST API tokens are never
removed. The time is kept beside the user rather than on the token, because
updating one element of the token array needs the positional `$` operator,
which not every database WeKan runs on supports. `tests/idleLogout.test.cjs`.
</details>
**HTTP** - what every response tells the browser, and which requests are
accepted only from WeKan's own pages.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4993242c5f">Strict-Transport-Security on every response of an HTTPS site</a>. Thanks to xet7.</summary>
Without HSTS, a link typed as plain HTTP or a downgrade on a hostile network
could strip TLS from the next visit unless the reverse proxy added the header.
It is now sent whenever `ROOT_URL` is https: 180 days, this host only.
`HSTS_MAX_AGE`, `HSTS_INCLUDE_SUBDOMAINS` and `HSTS_PRELOAD` change it; 0 stops
it, and preload is sent only with includeSubDomains and at least a year. Every
response also carries `X-Content-Type-Options: nosniff` and
`Referrer-Policy: strict-origin-when-cross-origin`. A header has no attempt to
record. `tests/securityHeaders.test.cjs`.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/48a2e97ea3">A state-changing request that logs in only with the cookie must come from WeKan itself</a>. Thanks to xet7.</summary>
`POST /import-trello` and other routes accept the login cookie as the login.
The cookie is SameSite=Strict, which keeps it off a cross-site request in
today's browsers; this is the second line, for older browsers, same-site
sibling hosts and routes added later. A POST, PUT, PATCH or DELETE whose only
login is the cookie must carry an Origin, or Referer, naming `ROOT_URL`'s host
or the Host / X-Forwarded-Host it was sent to - on Sandstorm, the grain's own
host. A refusal is recorded in Admin Panel / Problems as
`csrf.cross-site-cookie` and never blocks the account whose cookie it was.
`tests/crossSiteRequest.test.cjs` also fails when a route registers a handler
that runs before the guard.
</details>
**The Admin Panel** - a record of what administrators changed.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4d5b832499">People / Audit log shows what site administrators did</a>. Thanks to xet7.</summary>
Nothing recorded who set somebody's password, made somebody an admin, changed
a login setting or restored a backup. Every call of those administrative
methods by a site administrator is now kept for a year, one document each:
when, who, the action, its outcome - a refused call too - its arguments and
the address. A password or a secret never reaches the trail: credential-named
keys, password arguments by position and passwords inside URLs are replaced by
`[redacted]`. An administrator editing cards is not in it, and a trail that
cannot be written never changes what the method did.
`tests/adminAudit.test.cjs`.
</details>
- [The audit log has its own URL, /admin/people/audit-log, a title, and right-to-left alignment](https://github.com/wekan/wekan/commit/2017f2871f).
Thanks to xet7.
**Attachments** - where the files are kept, and how.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/482c2c29f4">Files and backups in S3 can be encrypted at rest</a>. Thanks to xet7.</summary>
Admin Panel / Attachments / S3 / Server-side encryption: SSE-S3 (AES256) or
SSE-KMS, with a KMS key or the account's default. S3 encrypts each object as it
is written and decrypts it for WeKan on read, so reads, range requests and
moves are unchanged; every upload, renamed object and backup written to S3
carries it. On the filesystem, encryption at rest stays with the disk or volume
(LUKS, an encrypted cloud volume): exports, PDF, thumbnails, virus scans,
backups and WebDAV all read the stored files directly.
`tests/s3Encryption.test.cjs`.
</details>
and adds the following new features:
**Dates** - choosing a date in a date popup.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/bc21c901f4">The date picker has a Today button, back to today's month from wherever the arrows went</a>. Thanks to mimZD and xet7.</summary>
The board Calendar view and Calendar mode already had Today; the date picker
of every date popup had only « ‹ › ». Today shows today's month and focuses
today, and changes nothing until a day is clicked and saved. Testing it found a
picker bug that was already there: a month navigated back to was drawn wrong -
October as "29, empty, 30, empty", without its 31st - because Blaze matched the
weeks and days, which had no `_id`, by position. Every week and cell has one
now. `calendarPickerControls.test.cjs` and `calendar-today-marker.e2e.js`,
which checks every day of the month in order after Today.
</details>
**The opened card and popups** - editing a card's title, and moving windows.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/22a12dc7f8">The whole card title edits; the window and every popup move by their bar or drag handle</a>. Thanks to mimZD and xet7.</summary>
The title was split like a minicard's: the leading half edited and the
trailing half dragged the window, so a click on the right of the title did not
edit. All of it edits now. With drag handles off the window is moved from the
empty space of its header row above the title, between the collapse caret and
the menu; with them on, by the drag handle alone. Every popup moves the same
way - by its title bar, or its handle alone - where only four kinds moved
before, and a drag no longer makes a popup placed from the right edge, or one
with a margin, jump as it begins. `card-title-edit-and-move-6839.e2e.js` in
Chromium and WebKit.
</details>
**Member Settings** - Change Settings.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/ffd5cbd2ad">Change Settings: icons in a column on the first line, compact wrapped lines, rows divided</a>. Thanks to mimZD and xet7.</summary>
Its settings are sentences, and most wrap; on the shared popup list each line
was 33px high with the icon centred between them and nothing between one
setting and the next. Each row is an icon column, the words and the check mark
now, the icon on the first line, a wrapped line an ordinary line high, and a
rule between rows. `change-settings-popup-6835.e2e.js` in Persian and English.
</details>
and fixes the following bugs:
**The d shortcut** - opening the Due date editor from the keyboard.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/b1403ca7af">d opens the Due date editor of a card that is still being opened, and it stays open</a>. Thanks to mimZD and xet7.</summary>
On a slower server a card's details render a few hundred milliseconds after
the click that opens it, and `d` pressed in that moment found no Due date
control and did nothing; only the hovered-minicard path waited. Both wait now
for that card's control. The card details also closed their card's popup when
merely rendered again, so a popup could open and shut at once; they close it
now only when the card is closed or another card opened. The Playwright test
emulates a 300 ms connection, and fails on the code before this change.
</details>
- [Typing d in the card title types at the end of the title in WebKit too](https://github.com/wekan/wekan/commit/73c21dfe3e).
Thanks to xet7.
**Minicards** - what a minicard shows, and where.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/cf4b48df09">A due date uses the minicard's width instead of three lines beside an empty strip</a>. Thanks to mimZD and xet7.</summary>
The dates row reserved 84px at its trailing edge - a third of a narrow card -
for nothing, and a due date with its "1 day left" was broken over three lines.
It reserves the drag handle's 24px now. The rest of
[#6834](https://github.com/wekan/wekan/issues/6834) is in TODO Later.
`minicard-dates-width-6834.e2e.js` in English and Persian.
</details>
- [A card-button rule is a button on the minicard: Board Settings / Card, Card buttons, Show on Minicard, since v12.16, now with a test](https://github.com/wekan/wekan/commit/429fdc5319).
Thanks to mimZD and xet7.
**Signing up** - registering a new account with the sign-up form.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/508484ef1e">Registering no longer answers Internal server error, and signs the new user in</a>. Thanks to xet7.</summary>
The REQUIRE_2FA wrapper of every method and publication, and the Audit log's
wrapper of the administrators' methods, awaited before calling the handler -
even with REQUIRE_2FA off. audit-argument-checks asks synchronously, the moment
a handler returns, whether check() saw every argument, so the handler's own
check() came too late: sign-up answered "Internal server error" while the
account was created anyway (the next try said it already existed), and the
starredPublicBoards and translation publications failed for everybody. A
wrapper now calls the handler at once when it needs no lookup, and hands the
audit over when it must await (`server/lib/argumentChecks.js`).
`tests/wrappedHandlerArgumentChecks.test.cjs` runs both wrappers under a model
of the audit, and the sign-up Playwright test registers - with no mail server
configured - and checks that the new user is signed in.
</details>
**Upgrading old data** - documents from old WeKan versions are brought to the
current structure at startup, so nothing that is in the database stays hidden.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/2ab0afdc93">Boards from before the templates feature are shown on All Boards again</a>. Thanks to xet7.</summary>
A Sandstorm grain migrated from MongoDB 3 to FerretDB copied every document -
1 board, 31 cards, 8 lists - and then showed an empty All Boards page. Boards,
swimlanes, lists and cards made before the templates feature have no `type`:
the schemas fill it on insert only. All Boards publishes `type: 'board'` and
the card queries ask for `cardType-card`, and a missing field matches neither.
The new `type-defaults-backfill` startup step sets the schema default where
`type` is missing, null or empty, and never changes an explicit type such as
a template or a linked card. `tests/schemaUpgradeSteps.test.cjs` upgrades the
grain's shape and checks that typed documents are left alone.
The same log showed `merge-per-swimlane-lists FAILED ... missing schema for key
"swimlaneId"`: FerretDB's SQLite `distinct` could not read documents without
the fields it asked for. That is fixed in the FerretDB fork (Upcoming FerretDB
release), which the next bundle includes.
</details>
**Snap** - backing up, restoring and repairing a snap from the host's shell.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0b5c855820">Backup and restore instructions find mongodump and mongorestore on AlmaLinux, Rocky Linux and Fedora</a>. Thanks to Group-Deed and xet7.</summary>
snapd keeps the snaps in `/var/lib/snapd/snap` on Fedora, RHEL, AlmaLinux,
Rocky Linux, CentOS Stream and Arch Linux, which have no `/snap`. The
instructions exported `/snap/wekan/current/bin` and its `lib/x86_64-linux-gnu`,
so restoring a backup on Rocky Linux failed until both were found by hand, and
only Arch had a note - for the library path alone. Every instruction now picks
the directory with one `S=` line and the CPU's library directory with
`$(uname -m)`, so arm64 works too. `database-role`, `database-compare` and
`database-merge`, which can be run from a host shell, find the snap in either
place. `tests/snapPathElLinux.test.cjs` fails on an export of a `/snap`-only
path in any document and on a script that falls back to `/snap` alone.
</details>
and has the following developer-tooling fixes:
**The build log** - what a build from build.sh and build.bat writes while it
runs, and how much of it.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/6551062492">EVERYTHING builds without the per-require module trace, so it no longer fills the disk</a>. Thanks to xet7.</summary>
The build's `NODE_DEBUG=module` trace wrote over 12 GB in the first half hour
of an EVERYTHING run on macOS - twice, through tee - and slowed the build it
traced, so the run would fill the disk before it reached the tests. A build
from the `build.sh` menu still traces by default; `WEKAN_BUILD_TRACE_MODULES=0`
turns it off, and EVERYTHING sets that unless the caller asked for `1`. The
other build diagnostics (resolver commands, npm verbose, `METEOR_PROFILE`) are
unchanged. `tests/buildCommandOutput.test.cjs` checks that a menu build still
traces and that nothing exports the module trace unconditionally.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/5a13e927bc">Every build traces module resolution only when WEKAN_BUILD_TRACE_MODULES=1 asks for it</a>. Thanks to xet7.</summary>
Turning the trace off for EVERYTHING alone was not enough: a dev build from the
menu wrote 1.1 GB, three million lines, in its first three minutes. So the
trace is now opt-in in `build.sh` and `build.bat` alike, and the diagnostics
line says whether it is on and how to turn it on. The entry above describes
the menu default as it was then. `tests/buildCommandOutput.test.cjs` pins the
opt-in in both scripts, and fails on a trace turned on by default or
unconditionally.
</details>
**Meteor in .tools** - starting the Meteor that build.sh and build.bat keep in
the repository's own tools directory.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/9a118c83b2">Meteor starts with one springboard instead of springboarding forever</a>. Thanks to xet7.</summary>
A build from the menu printed `uname -p` and `sysctl hw.cpu64bit_capable` over
and over and never reached its first package. With `METEOR_WAREHOUSE_DIR` set
and no `SANDBOX`, Meteor takes the running tool to be wherever the warehouse's
`meteor` link points - 3.5.4, the release it was installed with - so with
`.meteor/release` at 3.6-rc.1 every start springboarded to 3.6.0-rc.1, found
itself the wrong tool again and springboarded again: 28 times in 20 seconds of
`meteor --version`. The 1.1 GB build log above was the same loop, its module
trace printed again on every exec. `releases/tools-env.sh` and `tools-env.bat`
now set `SANDBOX=true` beside the warehouse, as Meteor's own self-test does,
and so do the Nextcloud test scripts and the dev-source runner.
`tests/meteorSpringboardLoop.test.cjs` checks both scripts, fails on any file
that sets the warehouse without `SANDBOX`, and runs the installed Meteor to
count its springboards.
</details>
and updates the following documentation:
- [Security settings: every hardening option of a WeKan site on one page](https://github.com/wekan/wekan/commit/302b47db6f).
Thanks to xet7.
- [Admin Panel / People / Audit log: what it records and what it never does](https://github.com/wekan/wekan/commit/f165a24192).
Thanks to xet7.
and updates the following translations:
**Languages updated:** 234 languages.
**Password length** - the strings of Admin Panel / Problems / Password length
and of the Change your password dialog.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/3f9379ac86">The 18 new password-length strings in every language</a>. Thanks to xet7.</summary>
Each string is in the language and script its locale tag names, with the terms
the file already uses for password, account, save and sign in, and `__min__`
kept exactly; only those 18 keys changed in each file. The commit lists the
languages written with low confidence, for a speaker to correct on Transifex.
</details>
**Security features** - the strings of the password policy, two-factor, the
audit log and S3 encryption.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/de23dece66">The 16 new security strings in every language, and Frisian two-factor text that was Dutch</a>. Thanks to xet7.</summary>
Each string is in the language and script its locale tag names, with the terms
the file already uses for password, account, attachment, backup and two-factor
authentication, and `__min__` kept exactly. fy and fy-NL had Dutch two-factor
strings, now Frisian (`tests/frisianTwoFactorLanguage.test.cjs`); Tigre spells
storage መክዘን, not Tigrinya's መኽዘን. The commit lists the languages written
with low confidence, for a speaker to correct on Transifex.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.75 2026-10-30 WeKan ® release
**In short:** Changing a password signs the account out in every other browser,
and now a test proves it in two real browsers.
This release adds the following security regression test:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/dc9a7bb1ac">A changed password signs the account out in every other browser</a>. Thanks to xet7.</summary>
A report described a session that stayed signed in on a second browser after
the password was changed on the first. WeKan does not do that: Member Settings
/ Change Password is Meteor's changePassword, which removes every login token
but the changing session's, the admin's Set password and a password reset
remove them all, and a session whose token is gone is closed.
`password-change-other-sessions.e2e.js` runs the report in two browsers - the
other one is signed out within seconds, stays signed out after a reload, and
only the new password works - three times in Chromium and in WebKit.
`tests/passwordChangeSessions.test.cjs` pins those paths and fails on a password
change that keeps the other sessions.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.74 2026-10-30 WeKan ® release
**In short:** **Caddy** and **FerretDB**, the Go servers WeKan ships, no longer
+39 -39
View File
@@ -5,6 +5,45 @@ each one changed and the project status are in
Written by `node releases/changelog-short.mjs` from CHANGELOG-big.md:
make changes there.
# v12.78 2026-11-02 WeKan ® release
**CrossBoardExportBleed** and **UnassignedAttachBleed** are
fixed: a checklist scope stays on its board, every export obeys Disable all
export and Anonymize export users, and an assigned-only member changes
attachments only on their own cards. **SAML** logins use **node-saml 5.2.0**,
and **continuous backup** no longer misses a change made right after it starts.
**Keyboard shortcuts** keep working after a popup closes, and the Rules page has
a way back to the board.
[All changes in v12.78](CHANGELOG-big.md#v1278-2026-11-02-wekan--release)
# v12.77 2026-10-31 WeKan ® release
Two **node test guards** follow the movable popups and the date
picker of v12.76.
[All changes in v12.77](CHANGELOG-big.md#v1277-2026-10-31-wekan--release)
# v12.76 2026-10-31 WeKan ® release
Security features: a **password policy** with an optional
breached-password check, **Admin Panel / Problems / Password length**,
**compulsory two-factor authentication**, sign-out of **idle sessions** and of
every session after a password change, an **audit log** of what administrators
did, **HSTS**, a cross-site request guard, and **S3 encryption at rest**.
**Sign-up** works again; the date picker has **Today**, the whole card title
edits and **every popup can be moved**; and an old **Sandstorm** grain migrated
from MongoDB 3 shows its boards again.
[All changes in v12.76](CHANGELOG-big.md#v1276-2026-10-31-wekan--release)
# v12.75 2026-10-30 WeKan ® release
Changing a password signs the account out in every other browser,
and now a test proves it in two real browsers.
[All changes in v12.75](CHANGELOG-big.md#v1275-2026-10-30-wekan--release)
# v12.74 2026-10-30 WeKan ® release
**Caddy** and **FerretDB**, the Go servers WeKan ships, no longer
@@ -12,42 +51,3 @@ open MPTCP listeners, which Linux kernel bug **CVE-2026-98432** is reachable
through. The real fix is a patched host kernel.
[All changes in v12.74](CHANGELOG-big.md#v1274-2026-10-30-wekan--release)
# v12.73 2026-10-30 WeKan ® release
**CHANGELOG.md** is now short: the In short of the upcoming
release and of the five newest releases, in plain Markdown. The status and every
change of each release are in **CHANGELOG-big.md**, which is the one that is
written. **CodeQL** alert #598 in the Sandstorm host test is fixed, and the
release script pulls before releasing, so a moved main no longer stops it.
[All changes in v12.73](CHANGELOG-big.md#v1273-2026-10-30-wekan--release)
# v12.72 2026-10-30 WeKan ® release
A card link can no longer launch an operating-system handler with
a code-execution history, such as **ms-msdt** (Follina) or **telnet**, even if
an administrator lists it, and an attempt to write one shows in **Admin Panel →
Problems**. The **Apache** guide follows the 2024 Confusion Attacks, and the
Sandstorm store build publishes to **both app markets**.
[All changes in v12.72](CHANGELOG-big.md#v1272-2026-10-30-wekan--release)
# v12.71 2026-10-30 WeKan ® release
The **Sandstorm** build installs Sandstorm from
**install.sandstorm.org**, as the Sandstorm workflows already did, and the
docs link sandstorm.org. The Hall of Fame guards read every vulnerability name
now that each one links to its own page.
[All changes in v12.71](CHANGELOG-big.md#v1271-2026-10-30-wekan--release)
# v12.70 2026-10-29 WeKan ® release
The **AppImages** and the **Launchpad** snaps build again: their
FerretDB and mongo-tools are readable by every user, and the snap source
carries what its downloads need. **Continuous backup** names a changed
encrypted file as changed with **OpenSSL 4**, and the release and test tooling
runs on macOS and reports a bad certificate as one.
[All changes in v12.70](CHANGELOG-big.md#v1270-2026-10-29-wekan--release)
+9 -1
View File
@@ -38,7 +38,7 @@ FROM --platform=$BUILDPLATFORM debian:trixie AS bundle
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=12.74
ARG VERSION=12.78
ARG DEBIAN_FRONTEND=noninteractive
COPY --chmod=755 releases/check-telemetry.py /tmp/check-telemetry.py
@@ -292,6 +292,14 @@ ENV \
LOGOUT_ON_HOURS="" \
LOGOUT_ON_MINUTES="" \
USERS_DISABLE_INACTIVE_AFTER_DAYS="" \
LOGOUT_IDLE_MINUTES="" \
REQUIRE_2FA="" \
PASSWORD_MIN_LENGTH="" \
PASSWORD_MIN_LENGTH_ENFORCE="" \
PASSWORD_CHECK_PWNED="" \
HSTS_MAX_AGE="" \
HSTS_INCLUDE_SUBDOMAINS="" \
HSTS_PRELOAD="" \
ATTACHMENTS_RETENTION_DAYS="" \
ATTACHMENTS_RETENTION_MODE="" \
ATTACHMENTS_PURGE_DELETED_AFTER_DAYS="" \
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v12.74.0"
appVersion: "v12.78.0"
files:
userUploads:
- README.md
+9 -6
View File
@@ -989,15 +989,18 @@ exit /b %errorlevel%
:buildcommon
REM The same diagnostics as build.sh build_wekan: npm verbose output and
REM foreground install scripts, METEOR_PROFILE, Node module resolution and the
REM resolver command/output tracer, so a failed Windows build leaves the same
REM evidence in its log. WEKAN_BUILD_HEAP_SNAPSHOT=1 caps the heap at
REM foreground install scripts, METEOR_PROFILE and the resolver command/output
REM tracer, so a failed Windows build leaves the same evidence in its log. Node
REM module resolution (one line per require, gigabytes a minute) only with
REM WEKAN_BUILD_TRACE_MODULES=1. WEKAN_BUILD_HEAP_SNAPSHOT=1 caps the heap at
REM WEKAN_BUILD_HEAP_SNAPSHOT_MB (4096) and writes a heap snapshot near the limit.
setlocal
set "npm_config_loglevel=verbose"
set "npm_config_foreground_scripts=true"
if not defined METEOR_PROFILE set "METEOR_PROFILE=100"
if defined NODE_DEBUG (set "NODE_DEBUG=%NODE_DEBUG%,module") else (set "NODE_DEBUG=module")
if "%WEKAN_BUILD_TRACE_MODULES%"=="1" (
if defined NODE_DEBUG (set "NODE_DEBUG=!NODE_DEBUG!,module") else (set "NODE_DEBUG=module")
)
if "%WEKAN_BUILD_HEAP_SNAPSHOT%"=="1" (
if not defined WEKAN_BUILD_HEAP_SNAPSHOT_MB set "WEKAN_BUILD_HEAP_SNAPSHOT_MB=4096"
set "TOOL_NODE_FLAGS=--max-old-space-size=!WEKAN_BUILD_HEAP_SNAPSHOT_MB! --heapsnapshot-near-heap-limit=1"
@@ -1005,7 +1008,7 @@ if "%WEKAN_BUILD_HEAP_SNAPSHOT%"=="1" (
echo Heap snapshot: ON, heap capped at !WEKAN_BUILD_HEAP_SNAPSHOT_MB! MB; the build will fail sooner and write Heap.*.heapsnapshot.
)
set "NODE_OPTIONS=%NODE_OPTIONS% --require="%REPO%\tools\build-command-output.cjs""
echo Build diagnostics: resolver command/output tracing, Node module resolution, npm verbose output, foreground install scripts, METEOR_PROFILE=%METEOR_PROFILE% ^(milliseconds^).
echo Build diagnostics: resolver command/output tracing, npm verbose output, foreground install scripts, METEOR_PROFILE=%METEOR_PROFILE% ^(milliseconds^).
call :buildcommon_steps
set "BUILD_RC=%errorlevel%"
if not "%BUILD_RC%"=="0" if defined WEKAN_BUILD_LOG (
@@ -1977,7 +1980,7 @@ netstat -ano | findstr /r /c:":3001 .*LISTENING" >nul 2>&1
if not errorlevel 1 (
echo ==^> Reusing the MongoDB already listening on :3001 ^(not started or stopped by this run^).
) else (
for %%i in ("%REPO%\..\mongodb-test-3001") do set "DBPATH=%%~fi"
for %%i in ("%REPO%\.tools\mongodb-test-3001") do set "DBPATH=%%~fi"
if not exist "!DBPATH!" md "!DBPATH!"
echo ==^> Starting MongoDB ^(Meteor's mongod^) on :3001, dbpath !DBPATH!.
start "WekanTestMongo" /MIN "%MONGOD_BIN%" --port 3001 --dbpath "!DBPATH!" --bind_ip 127.0.0.1 --logpath "%RUN_LOGDIR%\wekan-test-mongod.log"
+70 -30
View File
@@ -468,10 +468,17 @@ function build_wekan(){
echo "===== wekan build started $(date '+%F %T') ====="
export npm_config_loglevel=verbose npm_config_foreground_scripts=true
export METEOR_PROFILE="${METEOR_PROFILE:-100}"
export NODE_DEBUG="${NODE_DEBUG:+$NODE_DEBUG,}module"
# Node module resolution, one line per require. OFF unless
# WEKAN_BUILD_TRACE_MODULES=1: a Meteor build writes it at gigabytes a
# minute - 1.1 GB in the first three minutes of a menu dev build, over
# 12 GB in the first half hour of EVERYTHING on macOS, written twice
# through tee - and slows the build it is tracing.
if [ "${WEKAN_BUILD_TRACE_MODULES:-0}" = 1 ]; then
export NODE_DEBUG="${NODE_DEBUG:+$NODE_DEBUG,}module"
fi
# NODE_OPTIONS parses quoted paths, including checkouts with spaces.
export NODE_OPTIONS="${NODE_OPTIONS:-} --require=\"$(pwd)/tools/build-command-output.cjs\""
echo "Build diagnostics: resolver command/output tracing, Node module resolution, npm verbose output, foreground install scripts, METEOR_PROFILE=$METEOR_PROFILE (milliseconds)."
echo "Build diagnostics: resolver command/output tracing, npm verbose output, foreground install scripts, METEOR_PROFILE=$METEOR_PROFILE (milliseconds), Node module resolution $([ "${WEKAN_BUILD_TRACE_MODULES:-0}" = 1 ] && echo on || echo 'off (WEKAN_BUILD_TRACE_MODULES=1 turns it on)')."
build_stage "1/4 Remove dependencies and build caches" rm -rf node_modules node_modules/.cache .meteor/local .build _build || return $?
# Updating npm metadata has historically been best effort; installation is required.
build_stage "2/4 Compile app to resolve Meteor plugin npm dependencies" meteor update --npm || echo "WARNING: Meteor plugin npm dependency compilation failed; trying dependency installation."
@@ -645,8 +652,18 @@ function run_playwright_docker(){
local docker_base_url="${WEKAN_BASE_URL:-http://127.0.0.1:3000}"
local docker_mongo_url="${WEKAN_MONGO_URL:-mongodb://127.0.0.1:3001/meteor}"
local docker_host_proxy_port=""
local docker_network="host"
# Docker Desktop on macOS keeps the host outside the container's loopback.
if [ "$(uname -s)" = Darwin ]; then
# Its host network loses connections to host.docker.internal: from a
# container on that network about 1 connect in 75 never completed, so a
# test page waited 60 s for a document that never came (Firefox, 2026-11-01:
# 8 of 600 direct connects, against 0 of 1000 from the bridge network).
# Nothing here needs the host network on macOS - WeKan and Mongo are reached
# through host.docker.internal and SMTP through docker exec - so the
# container uses Docker's bridge network, where the proxy below still
# serves localhost.
docker_network="bridge"
# Meteor's dynamic imports use ROOT_URL (localhost in the test server).
# Keep the browser on that same origin and bridge its loopback to the host.
# Browsing host.docker.internal while ROOT_URL is localhost makes Firefox
@@ -670,35 +687,53 @@ function run_playwright_docker(){
mkdir -p "$filesroot"
echo "Running Playwright $browser in Docker ($image)."
echo "Expecting WeKan at $docker_base_url."
local smtp_proxy_pid="" smtp_proxy_ready="" container_name="wekan-playwright-${browser}-$$"
if [ "$(uname -s)" = Darwin ] && [ -n "${WEKAN_TEST_SMTP_PORT:-}" ]; then
smtp_proxy_ready="$(mktemp "$WEKAN_DIR/.tools/tmp/smtp-proxy.XXXXXX")"
node "$pwdir/helpers/docker-smtp-proxy.cjs" "$container_name" "$WEKAN_TEST_SMTP_PORT" "$smtp_proxy_ready" &
smtp_proxy_pid=$!
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -s "$smtp_proxy_ready" ] && break
kill -0 "$smtp_proxy_pid" 2>/dev/null || break
sleep 0.1
local container_name="wekan-playwright-${browser}-$$"
# Docker Desktop keeps the container's loopback from the host, so a server a
# spec starts in the container cannot be reached by WeKan outside it. Each
# port below is forwarded from the host into the container through docker
# exec (helpers/docker-smtp-proxy.cjs, which carries any TCP): the test SMTP
# server, and a small pool for the mock Jira/GitLab servers of the list-sync
# specs, which take a free port from WEKAN_TEST_REVERSE_PORTS
# (helpers/reachable-server.cjs). Elsewhere they listen on any port.
local forward_pids="" forward_ready="" reverse_ports=""
if [ "$(uname -s)" = Darwin ]; then
reverse_ports="${WEKAN_TEST_REVERSE_PORTS:-39101 39102 39103 39104}"
local fport fready fpid i
for fport in ${WEKAN_TEST_SMTP_PORT:-} $reverse_ports; do
fready="$(mktemp "$WEKAN_DIR/.tools/tmp/docker-port-proxy.XXXXXX")"
node "$pwdir/helpers/docker-smtp-proxy.cjs" "$container_name" "$fport" "$fready" &
fpid=$!
forward_pids="$forward_pids $fpid"; forward_ready="$forward_ready $fready"
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -s "$fready" ] && break
kill -0 "$fpid" 2>/dev/null || break
sleep 0.1
done
if [ ! -s "$fready" ]; then
kill $forward_pids 2>/dev/null; wait $forward_pids 2>/dev/null
rm -f $forward_ready
echo "ERROR: local test proxy for port $fport did not start."
return 1
fi
done
if [ ! -s "$smtp_proxy_ready" ]; then
kill "$smtp_proxy_pid" 2>/dev/null; wait "$smtp_proxy_pid" 2>/dev/null
rm -f "$smtp_proxy_ready"
echo "ERROR: local SMTP test proxy did not start."
return 1
fi
fi
# Mount the whole repo so specs that reach the repo-root node_modules
# (e.g. @wekanteam/exceljs) and .tools resolve; run from tests/playwright.
# The repo and the files root are mounted at their OWN host paths, so a path
# names the same file for the specs in here and for the server outside:
# admin-continuous-backup gives the server a key file it wrote and reads the
# stream the server wrote, and with the repo at /repo neither side found the
# other's file.
# Run as the host user (--user) with a writable HOME so the container does
# not leave root-owned files under test-results/ (which would later make
# native Chromium/Firefox runs fail with "EACCES: permission denied, mkdir
# .../test-results/.playwright-artifacts-N").
docker_exec run --rm --init --ipc=host --network host \
docker_exec run --rm --init --ipc=host --network "$docker_network" \
--name "$container_name" \
--label org.wekan.test-run=everything \
--user "$(id -u):$(id -g)" \
-e HOME=/repo/.tools/tmp \
-e TMPDIR=/repo/.tools/tmp \
-e HOME="$reporoot/.tools/tmp" \
-e TMPDIR="$reporoot/.tools/tmp" \
-e WEKAN_BASE_URL="$docker_base_url" \
-e WEKAN_DOCKER_HOST_PROXY_PORT="$docker_host_proxy_port" \
-e WEKAN_MONGO_URL="$docker_mongo_url" \
@@ -710,16 +745,17 @@ function run_playwright_docker(){
-e WEKAN_TEST_SERIAL="${WEKAN_TEST_SERIAL:-0}" \
-e WEKAN_TEST_SERVER_MODE="${WEKAN_TEST_SERVER_MODE:-bundle}" \
-e WEKAN_TEST_SMTP_PORT="${WEKAN_TEST_SMTP_PORT:-}" \
-e WEKAN_TEST_REVERSE_PORTS="$reverse_ports" \
-e PLAYWRIGHT_HTML_OPEN=never \
-e WEKAN_FILES_PATH=/wekan-files \
-e WEKAN_FILES_PATH="$filesroot" \
-e PLAYWRIGHT_JSON_OUTPUT_NAME="${PLAYWRIGHT_JSON_OUTPUT_NAME:-}" \
-v "$filesroot":/wekan-files \
-v "$reporoot":/repo -w /repo/tests/playwright \
-v "$filesroot":"$filesroot" \
-v "$reporoot":"$reporoot" -w "$pwdir" \
"$image" \
sh -c '
export PATH=/repo/tests/playwright/node_modules/.bin:$PATH
export PATH="$PWD/node_modules/.bin:$PATH"
if [ -n "$WEKAN_DOCKER_HOST_PROXY_PORT" ]; then
node /repo/tests/playwright/helpers/docker-host-proxy.cjs &
node "$PWD/helpers/docker-host-proxy.cjs" &
proxy_pid=$!
trap '\''kill "$proxy_pid" 2>/dev/null || true; wait "$proxy_pid" 2>/dev/null || true'\'' EXIT
ready=0
@@ -732,9 +768,9 @@ function run_playwright_docker(){
npx playwright test --project="$0" "$@"
' "$browser" "$@"
local playwright_rc=$?
if [ -n "$smtp_proxy_pid" ]; then
kill "$smtp_proxy_pid" 2>/dev/null; wait "$smtp_proxy_pid" 2>/dev/null
rm -f "$smtp_proxy_ready"
if [ -n "$forward_pids" ]; then
kill $forward_pids 2>/dev/null; wait $forward_pids 2>/dev/null
rm -f $forward_ready
fi
return "$playwright_rc"
}
@@ -1506,7 +1542,11 @@ function run_all_tests(){
echo "ERROR: nothing is listening on :$TEST_DB_PORT and no mongod was found (Meteor dev_bundle or PATH)."
echo " Meteor's bundled mongod is normally at <dev_bundle>/mongodb/bin/mongod."; rm -rf "$STATDIR"; return 1
fi
local DBPATH="../mongodb-test-$TEST_DB_PORT"
# In .tools/, where everything this script writes lives (CLAUDE.md;
# tests/toolsDirOnly.test.cjs). It was in the checkout's PARENT directory,
# which a sandboxed editor cannot write - the test database never
# started there.
local DBPATH="$WEKAN_DIR/.tools/mongodb-test-$TEST_DB_PORT"
mkdir -p "$DBPATH"
echo "==> Starting MongoDB (Meteor's mongod) on :$TEST_DB_PORT, dbpath $DBPATH."
{ echo "===== mongod :$TEST_DB_PORT - started $(date '+%Y-%m-%d %H:%M:%S %Z') ====="; exec "$MONGOD_BIN" --port "$TEST_DB_PORT" --dbpath "$DBPATH" --bind_ip 127.0.0.1 --nounixsocket; } > "$RUN_LOGDIR/wekan-test-mongod.log" 2>&1 &
@@ -1534,7 +1574,7 @@ function run_all_tests(){
export WEKAN_FILES_PATH_HOST="$WRITABLE_ABS/files"
# ...and the same path for a LOCAL playwright run, not only the Docker one.
# Only WEKAN_FILES_PATH_HOST was exported, which is read when the browsers run
# in a container (it is mounted there as /wekan-files). A local run fell back
# in a container (it is mounted there at the same path). A local run fell back
# to the spec's own default of .build/bundle/files, so a spec that writes a
# stored attachment wrote it OUTSIDE the root this server reads - and then
# tested what WeKan does with a file it does not own.
+22
View File
@@ -111,6 +111,28 @@ Meteor.startup(() => {
}
});
// LOGOUT_IDLE_MINUTES: report the user's activity while signed in, if the
// limit is set (models/lib/idleLogout.js).
const { createIdleActivityReporter } = require('/client/lib/idleActivity');
const idleReporter = createIdleActivityReporter({
target: document,
touch: () => Meteor.call('touchActivity', () => {}),
});
let idleCheckedFor = null;
Tracker.autorun(() => {
const userId = Meteor.userId();
if (!userId) {
idleCheckedFor = null;
idleReporter.stop();
return;
}
if (idleCheckedFor === userId) return;
idleCheckedFor = userId;
Meteor.call('getIdleLogout', (error, result) => {
if (!error && result && result.enabled && Meteor.userId() === userId) idleReporter.start();
});
});
// Initialize mobile mode on startup for iOS devices
// This ensures mobile mode is applied correctly on page load
Tracker.afterFlush(() => {
+23 -23
View File
@@ -366,43 +366,43 @@ body.desktop-mode .card-details.card-details-collapsed {
position: relative;
}
/* The title is two things at once, split like a minicard's: the LEADING half
opens the editor, the trailing half drags the window. With drag handles OFF
there is no handle, so the title bar is what the window is moved by - and a
heading that is entirely an edit target leaves nothing to take hold of.
/* #6839: the whole title edits - every word of it and the space beside a
short last line, which is what anybody clicks to change the title. The window
is moved from the empty space ABOVE the title, in the row of the collapse
caret and the menu (cardDetails.js), or by the drag handle when drag handles
are on. It was split like a minicard's - the trailing half dragged - and a
click on the right of the title did nothing anybody expected.
An overlay rather than a split of the text, because the text is a `.viewer`
and cutting it in two would cut the sentence in two. `inset-inline-start`, so
the edit half is the left in English and the right in Arabic from one rule.
The drag handler steps aside for this zone (cardDetails.js). */
An overlay rather than a link around the text, because the text is a
`.viewer` with its own links. */
.card-details .card-details-header .card-details-title .card-details-title-edit-zone {
position: absolute;
top: 0;
bottom: 0;
inset-inline-start: 0;
/* Half of the heading - and the heading is stretched to the row below, so
this is half of the BAR however long the title happens to be. */
width: 50%;
width: 100%;
cursor: text;
z-index: 1;
}
/* The heading fills the row rather than shrinking to its text, so "half the
title" and "half the bar" are the same half however long the title is. A
heading only as wide as its words leaves the empty space beside it belonging
to nothing - and that space is where anybody would grab to move the window.
It stays inline-block so the complete-toggle beside it keeps its place; the
buttons floating at the trailing edge take their room out of this width. */
/* The heading fills the row rather than shrinking to its text, so the edit
zone covers the space beside a short title too. It stays inline-block so
the complete-toggle beside it keeps its place. */
.card-details .card-details-header .card-details-title {
min-width: 60%;
}
/* With drag handles ON the handle is the only drag source, so the whole title
is the edit target - the same rule the minicard follows, from the same
question: `card-details-with-handle` is set by the template from
isTouchScreenOrShowDesktopDragHandles, which is Utils.showDragHandles(). */
.card-details.card-details-with-handle .card-details-header .card-details-title .card-details-title-edit-zone {
width: 100%;
/* The empty part of the header row is what moves the window when there is no
drag handle; with one, the handle alone does. */
.card-details:not(.card-details-with-handle) .card-details-header {
cursor: move;
}
.card-details .card-details-header .card-details-title,
.card-details .card-details-header a {
cursor: auto;
}
.card-details .card-details-header a {
cursor: pointer;
}
.card-details .card-details-header .linked-card-location {
font-style: italic;
+9 -10
View File
@@ -49,10 +49,9 @@ template(name="cardDetails")
a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
//- The handle appears only when drag handles are ON, like the
minicard's. With them off the window is dragged by its own title bar
- the trailing half of the title - so a handle would be a second way
to do what the bar already does; with them on it is the ONLY way, and
the title is all edit target.
minicard's. With them off the window is dragged by the empty space
of its header row, so a handle would be a second way to do what the
bar already does; with them on it is the ONLY way.
client/components/cards/minicard.jade
if canModifyCard
if isTouchScreenOrShowDesktopDragHandles
@@ -80,12 +79,12 @@ template(name="cardDetails")
if currentBoard.allowsDueComplete
a.card-details-complete-toggle.js-toggle-due-complete(title="{{#if getDueComplete}}{{_ 'card-mark-incomplete'}}{{else}}{{_ 'card-mark-complete'}}{{/if}}")
.materialCheckBox(class="{{#if getDueComplete}}is-checked{{/if}}")
//- The title is BOTH: the leading half opens the editor and the
trailing half drags the window, exactly as a minicard's title splits
between editing and dragging the card. `js-card-title-drag-handle`
stays on the whole heading - the drag handler is what steps aside for
the edit zone - so a reader who may not write can still drag the
window by any part of it, which is what that class was for.
//- #6839: the whole title opens the editor. The window is moved from
the empty space of the header row above it, or by the drag handle
when drag handles are on. `js-card-title-drag-handle` stays on the
whole header - the drag handler is what steps aside for the edit zone
- so a reader who may not write, who has no edit zone, can still drag
the window by the title.
if showLabelsAboveTitleOnCard
.card-details-labels-above-title
+minicardLabels(show=labelsShownAboveTitle labels=labels hideText=hiddenCardLabelText)
+13 -9
View File
@@ -701,9 +701,12 @@ Template.cardDetails.onDestroyed(function () {
// #6686: see the comment in onCreated above. Only close a popup that is
// still showing THIS card's data at the base of its stack - a popup opened
// for an unrelated card/context must not be touched.
// for an unrelated card/context must not be touched. #6840: and only when
// this card is no longer the open one - closed, or another card opened. A
// card whose details are merely rendered again is still open, and its popup
// (the Due date one `d` just opened) must not flash shut.
const openedCardId = this.openedCardId;
if (openedCardId && Popup.isOpen()) {
if (openedCardId && Popup.isOpen() && Utils.getCurrentCardId() !== openedCardId) {
const baseStackEntry = Popup._stack?.[0];
if (baseStackEntry?.dataContext?._id === openedCardId) {
Popup.close();
@@ -1287,17 +1290,18 @@ Template.cardDetails.events({
// selects text instead of moving the card window.
return;
}
// ...and don't drag from the half that EDITS. The title is split like a
// minicard's: the leading half opens the editor, the trailing half is what
// you take hold of. With drag handles ON the zone covers the whole title
// (cardDetails.css), so the window is dragged by its handle alone - which is
// what turning handles on means.
// #6839: ...and never from the title, which edits - all of it
// (cardDetails.css). The window is moved from the empty space of the
// header row above it, beside the collapse caret and the menu.
if ($(event.target).closest('.card-details-title-edit-zone').length > 0) {
return;
}
// The drag handle has its own handler; without this both would run and the
// window would be moved twice by one press.
if ($(event.target).closest('.js-card-drag-handle').length > 0) {
// window would be moved twice by one press. And when there IS a handle -
// drag handles on - it is the only thing that moves the window, which is
// what turning handles on means.
if ($(event.target).closest('.js-card-drag-handle').length > 0
|| $(event.target).closest('.card-details').find('.js-card-drag-handle').length > 0) {
return;
}
+6 -1
View File
@@ -374,7 +374,12 @@
flex-wrap: wrap;
position: relative;
z-index: 5;
margin-inline-end: 84px;
/* #6834: room for the drag handle beside it (20px, 1px from the edge), and
no more. It reserved 84px - a third of a 250px minicard - for nothing:
the avatars float AFTER this row and `clear: both` starts it below the
menu, so a due date "1405-08-10 12:00 (1 day left)" was broken over three
lines beside an empty strip. */
margin-inline-end: 24px;
clear: both;
}
.minicard .date {
+1 -1
View File
@@ -28,4 +28,4 @@ template(name="cardWhiteboard")
svg.card-whiteboard-surface.js-whiteboard-surface(viewBox="0 0 {{width}} {{height}}" preserveAspectRatio="xMidYMid meet" class="{{#if canDraw}}can-draw{{/if}}")
rect(width=width height=height fill="#ffffff")
each strokes
path(d=pathD stroke=color stroke-width=width fill="none" stroke-linecap="round" stroke-linejoin="round")
path(d=pathD stroke=color stroke-width=strokeWidth fill="none" stroke-linecap="round" stroke-linejoin="round")
+5 -1
View File
@@ -116,7 +116,11 @@ Template.cardWhiteboard.helpers({
const current = tpl.current.get();
return [...saved, ...pending, ...(current ? [current] : [])]
.filter(s => s && Array.isArray(s.points) && s.points.length)
.map(s => ({ color: s.color, width: s.width, pathD: pathD(s.points) }));
// strokeWidth, not width: inside `each strokes` Spacebars looks a name up
// among this template's helpers before the stroke's own fields, and the
// width helper is the drawing's (800) - every line was drawn 800 wide and
// covered the whiteboard.
.map(s => ({ color: s.color, strokeWidth: s.width, pathD: pathD(s.points) }));
},
saving() { return Template.instance().saving.get(); },
message() { return Template.instance().message.get(); },
@@ -102,6 +102,11 @@
padding-inline: 4px;
}
/* #6838: a word, not an arrow - it may be wider than the arrows beside it. */
.selected-calendar-heading .js-calendar-today {
white-space: nowrap;
padding-inline: 6px;
}
.selected-calendar-heading .selected-calendar-date {
margin-inline-start: 4px;
}
@@ -22,6 +22,9 @@ template(name="calendarDateInput")
| {{monthLabel}}
if inline
span.selected-calendar-date {{headingDateText}}
//- #6838: back to today's month from wherever the arrows went. It only
//- shows the month; the date stays as it is until a day is clicked.
button.primary.js-calendar-today(type="button" aria-label="{{_ 'today'}}") {{_ 'today'}}
button.primary.js-calendar-next(type="button" aria-label="{{_ 'next'}} {{_ 'month'}}") ›
button.primary.js-calendar-year-next(type="button" aria-label="{{_ 'next'}} {{_ 'predicate-year'}}") »
table
@@ -34,5 +37,5 @@ template(name="calendarDateInput")
tr
each days
td(class="{{#if this.today}}is-today-cell{{/if}}")
if this
unless this.empty
button.primary.js-calendar-day(type="button" class="{{#if today}}is-today{{/if}}" data-date=iso title=title aria-label=title aria-pressed=selected aria-current=ariaCurrent tabindex=tabIndex) {{label}}
+15 -2
View File
@@ -86,9 +86,17 @@ Template.calendarDateInput.helpers({
// the browser's local day, the same local calendar day formatDate() gives
// every other cell, so it is the cell the user means by "today".
const todayIso = formatDate(new Date());
// #6838: every row and every cell - an empty one too - has its own _id.
// Blaze matches list items by _id; without one, going from one month to
// another reused cells by position and the empty cells, all alike, were
// moved around: October came back as "29, empty, 30, empty", without the
// 31st. The month is part of each id, so a new month is new cells.
const month = days.find(Boolean);
const monthId = month ? formatDate(month) : 'empty';
const rows = [];
for (let index = 0; index < days.length; index += 7) {
rows.push({ days: days.slice(index, index + 7).map(date => date && ({
rows.push({ _id: `${monthId}-week-${index / 7}`, days: days.slice(index, index + 7).map((date, cell) => (date ? {
_id: formatDate(date),
iso: formatDate(date),
today: formatDate(date) === todayIso,
// null omits the attribute: only today's button carries aria-current.
@@ -97,7 +105,7 @@ Template.calendarDateInput.helpers({
title: formatDateForDisplay(date, false),
selected: tpl.selectedDate.get() && formatDate(tpl.selectedDate.get()) === formatDate(date),
tabIndex: focusDate && formatDate(focusDate) === formatDate(date) ? 0 : -1,
})) });
} : { _id: `${monthId}-empty-${index + cell}`, empty: true })) });
}
return rows;
},
@@ -121,6 +129,11 @@ Template.calendarDateInput.events({
event.preventDefault();
tpl.shownDate.set(calendarMonthRange(tpl.shownDate.get(), selectedSystem().intl).end);
},
'click .js-calendar-today'(event, tpl) {
event.preventDefault();
tpl.shownDate.set(new Date());
Tracker.afterFlush(() => tpl.find('.js-calendar-day.is-today')?.focus());
},
'click .js-calendar-year-previous'(event, tpl) { event.preventDefault(); tpl.shownDate.set(shiftCalendarYear(tpl.shownDate.get(), -1, selectedSystem().intl)); },
'click .js-calendar-year-next'(event, tpl) { event.preventDefault(); tpl.shownDate.set(shiftCalendarYear(tpl.shownDate.get(), 1, selectedSystem().intl)); },
'click .js-calendar-day'(event, tpl) {
@@ -7,7 +7,7 @@ template(name="historyRecoveryNotice")
.history-recovery-notice.js-history-recovery-notice(role="status" aria-live="polite")
p {{_ pending.messageKey}}
p.quiet {{_ 'history-request-hint'}}
button.primary.js-history-retry(type="button" disabled=busy) {{_ 'history-request-retry'}}
button.primary.js-history-retry(type="button" disabled=retryDisabled) {{_ 'history-request-retry'}}
button.js-history-forget(type="button" disabled=busy) {{_ 'history-request-forget'}}
if checkpoint
.history-recovery-notice.history-checkpoint-notice.js-history-checkpoint-notice(role="status" aria-live="polite" class=checkpointPlacement)
@@ -52,6 +52,13 @@ Template.historyRecoveryNotice.helpers({
busy() {
return Template.instance().busy.get();
},
// undoRedoLast does nothing for someone who may not write on the board, and
// right after a reload that is everyone until the board and its members
// arrive: the notice shows first, and a Retry clicked then was dropped
// without a word. It is enabled once the retry can act.
retryDisabled() {
return Template.instance().busy.get() || !Utils.canModifyBoard();
},
});
async function resolve(tpl, action) {
+1 -1
View File
@@ -55,7 +55,7 @@ template(name="userFormsLayout")
+loader
else
// ARIA live region for error messages
div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f; margin-bottom: 1em;")
div#login-error-message(role="alert" aria-live="assertive" style="color: #d32f2f; margin-bottom: 1em;")= loginErrorText
// Second sign-in step for accounts-2fa (issue #3058). Hidden unless a
// password login comes back with the "no-2fa-code" / "invalid-2fa-code"
// error - see decideTwoFactorLoginStep() in
+9 -5
View File
@@ -303,6 +303,7 @@ Template.userFormsLayout.onDestroyed(function () {
});
Template.userFormsLayout.helpers({
loginErrorText() { return loginErrorText.get(); },
passkeysSupported() { return require('/client/lib/passkeys').passkeysSupported(); },
passkeyMessage() { return Template.instance().passkeyMessage.get(); },
windowsSsoOffered() { return Meteor.settings?.public?.windowsSsoEnabled === true; },
@@ -557,20 +558,23 @@ Tracker.autorun(() => {
}
});
// The message in #login-error-message, rendered by the template as text. Not
// written into the element: it is inside `if isLoading`, and when that flips
// Blaze draws a new, empty element - a SAML popup error shown just before
// then was gone (saml-popup-error.e2e.js, Firefox). Text, never HTML.
const loginErrorText = new ReactiveVar('');
function showLoginError(err) {
const errorDiv = document.getElementById('login-error-message');
if (!errorDiv) return;
if (!err) {
errorDiv.textContent = '';
loginErrorText.set('');
return;
}
const reason = err.reason || err.message || '';
// #2460: an account its member locked to SQRL says so in their language.
const translated = ['oauth-account-conflict', 'sqrl-only', 'sqrl-hardlock']
.find(key => err.error === key || reason === key);
errorDiv.textContent = translated
loginErrorText.set(translated
? TAPi18n.__(translated)
: reason || String(err.error || err);
: reason || String(err.error || err));
}
Template.defaultLayout.events({
+10 -1
View File
@@ -1446,9 +1446,18 @@ body.mobile-mode .pop-over .content-wrapper {
cursor: move;
color: #888;
}
.pop-over .header:has(.popup-drag-handle) {
/* #6839: the title bar moves every popup while drag handles are off; with
them on, the handle does, and the bar is an ordinary title. */
.pop-over .header {
cursor: move;
}
.pop-over .header:has(.popup-drag-handle) {
cursor: auto;
}
.pop-over .header a,
.pop-over .header button {
cursor: pointer;
}
.pop-over .header .popup-maximize-btn {
position: absolute;
top: 0;
+34 -8
View File
@@ -29,8 +29,11 @@ function resizeDatePopup(element, width, height) {
Object.entries(styles).forEach(([key, value]) => element.style.setProperty(key, value, 'important'));
}
// Popups dragged by their title bar (or the drag handle beside it), as the
// opened card is. A date popup is movable too, found by its .edit-date.
// #6839: EVERY popup is moved like the opened card: by its title bar when drag
// handles are off, by the drag handle before its title when they are on - the
// handle alone, then, as the card's. These are the popups that may also GROW
// past the window while open (Drawing replacing a menu), so they are kept
// inside it as they grow; so is any popup the user has dragged.
const MOVABLE_POPUPS = new Set(['boardViewSettingsPopup', 'cardDrawingPopup', 'boardCardSettingsPopup']);
// Popups with the opened card's maximize / minimize button.
const MAXIMIZABLE_POPUPS = new Set(['cardDrawingPopup', 'boardCardSettingsPopup']);
@@ -66,7 +69,7 @@ Popup.template.onCreated(function () {
Popup.template.helpers({
popupMovable() {
return MOVABLE_POPUPS.has(this.popupName);
return true;
},
popupMaximizable() {
return MAXIMIZABLE_POPUPS.has(this.popupName);
@@ -87,21 +90,43 @@ Popup.template.events({
'pointerdown .header'(evt, tpl) {
const element = evt.currentTarget.closest('.pop-over');
// A maximized popup has no place to be dragged to.
// With drag handles on, the handle is the only thing that moves it.
const handle = element.querySelector('.js-popup-drag-handle');
if (evt.button !== 0 || element.classList.contains('pop-over--maximized') ||
(!element.querySelector('.edit-date') && !MOVABLE_POPUPS.has(element.dataset.popup)) ||
(handle && !evt.target.closest('.js-popup-drag-handle')) ||
evt.target.closest('a, button, input, select')) return;
evt.preventDefault(); evt.stopPropagation();
const bounds = element.getBoundingClientRect();
// Pin it where it is before it moves. A popup is placed by its margins, by
// inset-inline-end (the member menu, at the right edge) or by left/top, so
// `left: bounds.left` alone put some of them elsewhere - one jumped across
// the window as the drag began. Set it, see where it went, and correct.
const place = (left, top) => {
element.style.setProperty('inset-inline-start', 'auto', 'important');
element.style.setProperty('inset-inline-end', 'auto', 'important');
element.style.setProperty('left', `${left}px`, 'important');
element.style.setProperty('top', `${top}px`, 'important');
};
place(bounds.left, bounds.top);
const placed = element.getBoundingClientRect();
const left = bounds.left + (bounds.left - placed.left);
const top = bounds.top + (bounds.top - placed.top);
place(left, top);
element.dataset.userMoved = 'true';
tpl._dateMove = { element, pointerId: evt.pointerId, x: evt.clientX, y: evt.clientY,
left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height };
left, top, width: bounds.width, height: bounds.height,
// The clamp keeps the popup's VISIBLE box 12px inside the window.
dx: bounds.left - left, dy: bounds.top - top };
evt.currentTarget.setPointerCapture(evt.pointerId);
},
'pointermove .header'(evt, tpl) {
const drag = tpl._dateMove;
if (!drag || drag.pointerId !== evt.pointerId) return;
evt.preventDefault(); evt.stopPropagation();
const left = Math.max(12, Math.min(window.innerWidth - drag.width - 12, drag.left + evt.clientX - drag.x));
const top = Math.max(12, Math.min(window.innerHeight - drag.height - 12, drag.top + evt.clientY - drag.y));
const boxLeft = Math.max(12, Math.min(window.innerWidth - drag.width - 12, drag.left + drag.dx + evt.clientX - drag.x));
const boxTop = Math.max(12, Math.min(window.innerHeight - drag.height - 12, drag.top + drag.dy + evt.clientY - drag.y));
const left = boxLeft - drag.dx;
const top = boxTop - drag.dy;
// Dragging uses physical pointer coordinates in either text direction.
drag.element.style.setProperty('inset-inline-start', 'auto', 'important');
drag.element.style.setProperty('inset-inline-end', 'auto', 'important');
@@ -221,7 +246,8 @@ Popup.template.onRendered(function () {
if (typeof ResizeObserver !== 'undefined') {
const element = this._popupElement;
this._boundsObserver = new ResizeObserver(() => {
if (!MOVABLE_POPUPS.has(element.dataset.popup) || element.classList.contains('pop-over--maximized')) return;
if (!(MOVABLE_POPUPS.has(element.dataset.popup) || element.dataset.userMoved)
|| element.classList.contains('pop-over--maximized')) return;
const box = element.getBoundingClientRect();
const moved = keptInside(box, window.innerWidth, window.innerHeight);
if (!moved) return;
+4 -4
View File
@@ -14,10 +14,10 @@
.header
a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}" aria-label="{{_ 'go-back'}}")
i.fa.fa-caret-left(aria-hidden="true")
//- A movable popup (popup.js MOVABLE_POPUPS) is dragged by its title bar
or this handle, like the opened card - and, like the card's, the handle
is there only while drag handles are on; a maximizable one has the
card's maximize / minimize button beside its close button.
//- #6839: every popup is moved like the opened card - by its title bar
while drag handles are off, by this handle alone while they are on
(popup.js); a maximizable one has the card's maximize / minimize button
beside its close button.
if popupMovable
if isTouchScreenOrShowDesktopDragHandles
span.popup-drag-handle.js-popup-drag-handle(title="{{_ 'drag-to-move'}}")
+27
View File
@@ -247,6 +247,33 @@
.rules-back .js-goback .fa-arrow-left {
color: #fff !important;
}
/* #6852: Back to the board, at the top of the Rules page. */
.rules-back-to-board {
margin-block: 8px;
margin-inline: 14px;
}
/* A link, so it opens the board as a link does (also in a new tab), drawn as
the primary buttons around it: forms.css styles only button.primary. */
.rules-back-to-board .js-rules-back-to-board {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 20px;
border-radius: 3px;
background: var(--theme-accent, #005377);
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
font-weight: 700;
text-decoration: none;
}
.rules-back-to-board .js-rules-back-to-board:hover,
.rules-back-to-board .js-rules-back-to-board:focus {
background: var(--theme-accent, #004766);
color: #fff;
}
.rules-back-to-board .js-rules-back-to-board .fa-arrow-left {
color: #fff !important;
}
.flex,
.triggers-content .triggers-body .triggers-side-menu ul li a {
display: -webkit-box;
+9
View File
@@ -6,6 +6,15 @@
template(name="rulesMain")
.wrapper
.rules-page(class=currentBoard.colorClass)
//- #6852: the way back to the board. The trigger, action and rule
details pages have their own Back, to the rules list, so this one is
shown only where there is none - two Backs going to two places would
be one too many.
if showBackToBoard
.rules-back-to-board
a.js-rules-back-to-board(href="{{pathFor 'board' id=currentBoard._id slug=currentBoard.slug}}" title="{{currentBoard.title}}")
i.fa.fa-arrow-left
| {{_ 'back'}}
//- #2217: who may edit sees every view; a member a board shows its Rules
to reads the rules list and rule details, with no control that
changes a rule; anybody else sees nothing (client/lib/rulesAccess.js).
+4
View File
@@ -44,6 +44,10 @@ Template.rulesMain.helpers({
rulesCurrentTab() {
return Template.instance().rulesCurrentTab;
},
// #6852: every Rules page but the three that already go Back to the list.
showBackToBoard() {
return !['trigger', 'action', 'ruleDetails'].includes(Template.instance().rulesCurrentTab.get());
},
ruleName() {
return Template.instance().ruleName;
},
@@ -0,0 +1,11 @@
.admin-audit-log-search { margin: 0.5rem 0; max-width: 24rem; }
.admin-audit-log-table { width: 100%; border-collapse: collapse; }
.admin-audit-log-table th, .admin-audit-log-table td {
padding: 0.3rem 0.5rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.3);
text-align: start;
vertical-align: top;
}
.admin-audit-args { white-space: pre-wrap; word-break: break-all; font-size: 0.85em; }
.admin-audit-failed .admin-audit-error { color: #c0392b; }
.admin-audit-log-pager { margin-top: 0.5rem; display: flex; gap: 0.5rem; align-items: center; }
@@ -0,0 +1,39 @@
template(name="adminAuditLog")
//- Admin Panel / People / Audit log (models/lib/adminAudit.js): every
//- administrative action of a site administrator, newest first. The
//- arguments are shown as stored - passwords and secrets never are.
.admin-audit-log
p {{_ 'admin-audit-log-info'}}
.admin-audit-log-search
input.wekan-form-control.js-admin-audit-search(type="search" placeholder="{{_ 'search'}}" value=search)
if loading
+spinner
else
table.admin-audit-log-table
thead
tr
th {{_ 'admin-audit-log-when'}}
th {{_ 'admin-audit-log-who'}}
th {{_ 'admin-audit-log-action'}}
th {{_ 'admin-audit-log-details'}}
th IP
tbody
each rows
tr(class="{{#unless ok}}admin-audit-failed{{/unless}}")
td {{when at}}
td {{actorUsername}}
td
code {{method}}
unless ok
|
span.admin-audit-error {{error}}
td
code.admin-audit-args {{args}}
td {{ip}}
else
tr
td(colspan="5") {{_ 'admin-audit-log-empty'}}
.admin-audit-log-pager
button.js-admin-audit-prev(type="button" disabled=noPrev) ‹
span {{page}} / {{pages}} ({{total}})
button.js-admin-audit-next(type="button" disabled=noNext) ›
@@ -0,0 +1,47 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
// Admin Panel / People / Audit log: pages of server/adminAudit.js's trail.
Template.adminAuditLog.onCreated(function () {
this.page = new ReactiveVar(1);
this.search = new ReactiveVar('');
this.result = new ReactiveVar({ rows: [], total: 0, page: 1, perPage: 50 });
this.loading = new ReactiveVar(true);
this.autorun(() => {
const options = { page: this.page.get(), search: this.search.get() };
this.loading.set(true);
Meteor.call('adminAuditLogPage', options, (error, result) => {
this.loading.set(false);
if (!error && result) this.result.set(result);
});
});
});
Template.adminAuditLog.helpers({
loading() { return Template.instance().loading.get(); },
rows() { return Template.instance().result.get().rows; },
total() { return Template.instance().result.get().total; },
page() { return Template.instance().page.get(); },
search() { return Template.instance().search.get(); },
pages() {
const { total, perPage } = Template.instance().result.get();
return Math.max(1, Math.ceil(total / perPage));
},
noPrev() { return Template.instance().page.get() <= 1; },
noNext() {
const { total, perPage } = Template.instance().result.get();
return Template.instance().page.get() >= Math.ceil(total / perPage);
},
when(at) { return at ? new Date(at).toLocaleString() : ''; },
});
let searchTimer = null;
Template.adminAuditLog.events({
'input .js-admin-audit-search'(event, tpl) {
const value = event.currentTarget.value;
clearTimeout(searchTimer);
searchTimer = setTimeout(() => { tpl.page.set(1); tpl.search.set(value); }, 300);
},
'click .js-admin-audit-prev'(event, tpl) { tpl.page.set(Math.max(1, tpl.page.get() - 1)); },
'click .js-admin-audit-next'(event, tpl) { tpl.page.set(tpl.page.get() + 1); },
});
@@ -58,6 +58,8 @@ template(name="adminProblems")
+eventStreamReport(stream="api")
else if isPane 'report-office'
+officeReport
else if isPane 'report-password-length'
+passwordLengthReport
else if tablePageData
+tablePage(tablePageData)
@@ -321,6 +321,9 @@ const PROBLEMS_MENU = [
// Security, and the two sit in the same menu.
{ id: 'report-security', icon: 'fa-shield', labelKey: 'securityReportTitle' },
{ id: 'report-impersonation', icon: 'fa-user-secret', labelKey: 'impersonationReportTitle' },
// Who signs in with a password shorter than PASSWORD_MIN_LENGTH, and the two
// settings that decide it (passwordLengthReport.js).
{ id: 'report-password-length', icon: 'fa-key', labelKey: 'password-length-report-title' },
{ id: 'features-performance', icon: 'fa-tachometer', labelKey: 'features-performance', emoji: true },
{ id: 'report-speed', icon: 'fa-tachometer', labelKey: 'speedReportTitle' },
{ id: 'report-tests', icon: 'fa-flask', labelKey: 'testsReportTitle' },
@@ -538,6 +541,8 @@ const SELF_LOADING_PANES = [
'report-security', 'report-speed', 'report-tests', 'report-cpu', 'report-ram', 'report-disk',
'report-instrumentation',
'report-database', 'report-integrity', 'report-office', 'report-api', 'report-mail',
// passwordLengthReport.js loads its own rows and settings through methods.
'report-password-length',
];
function openReportPane(tmpl, targetID) {
@@ -281,6 +281,17 @@ template(name="attachments")
.materialCheckBox#s3-force-path-style(class="{{#if cloudValue.s3.forcePathStyle}}is-checked{{/if}}")
span {{_ 's3-force-path-style'}}
small.form-text.text-muted {{_ 's3-force-path-style-description'}}
//- Encryption at rest (models/lib/s3Encryption.js).
.form-group
label(for="s3-sse") {{_ 's3-sse'}}
select.wekan-form-control#s3-sse
option(value="" selected="{{s3SseSelected ''}}") {{_ 's3-sse-none'}}
option(value="AES256" selected="{{s3SseSelected 'AES256'}}") SSE-S3 (AES256)
option(value="aws:kms" selected="{{s3SseSelected 'aws:kms'}}") SSE-KMS (aws:kms)
small.form-text.text-muted {{_ 's3-sse-description'}}
.form-group
label(for="s3-sse-kms-key-id") {{_ 's3-sse-kms-key-id'}}
input.wekan-form-control#s3-sse-kms-key-id(type="text" value="{{cloudValue.s3.sseKmsKeyId}}" placeholder="arn:aws:kms:eu-north-1:111122223333:key/…")
.form-group
button.js-test-cloud-connection.btn.btn-secondary(data-provider="s3") {{_ 'test-cloud-connection'}}
@@ -32,6 +32,8 @@ const CLOUD_FIELD_IDS = {
accessKeyId: '#s3-access-key',
secretAccessKey: '#s3-secret-key',
forcePathStyle: '#s3-force-path-style',
serverSideEncryption: '#s3-sse',
sseKmsKeyId: '#s3-sse-kms-key-id',
},
azure: {
enabled: '#azure-enabled',
@@ -858,6 +860,11 @@ Template.attachments.helpers({
CLOUD_PROVIDERS.forEach(p => { out[p] = cloudConfigFromSettings(tpl, p).read !== false; });
return out;
},
// The S3 server-side encryption option that is stored (models/lib/s3Encryption.js).
s3SseSelected(mode) {
const stored = cloudConfigFromSettings(Template.instance(), 's3').serverSideEncryption || '';
return stored === mode ? 'selected' : null;
},
cloudValue() {
const tpl = Template.instance();
const s3 = cloudConfigFromSettings(tpl, 's3');
@@ -872,6 +879,8 @@ Template.attachments.helpers({
bucket: s3.bucket || '',
accessKeyId: s3.accessKeyId || '',
forcePathStyle: s3.forcePathStyle !== false,
serverSideEncryption: s3.serverSideEncryption || '',
sseKmsKeyId: s3.sseKmsKeyId || '',
},
azure: {
accountName: azure.accountName || '',
@@ -0,0 +1,23 @@
//- Admin Panel / Problems / Password length (docs/Features/Admin-Panel/Problems/Password-length.md):
//- the minimum, whether it is enforced, and who signs in with a shorter
//- password - through the shared table page (docs/Features/General/Table.md).
template(name="passwordLengthReport")
ul.setting-detail.password-length-settings
li
label(for="password-min-length-input") {{_ 'password-min-length'}}
input#password-min-length-input.js-password-min-length(type="number" min="1" max="256" value="{{minLength}}")
p.description {{_ 'password-min-length-description'}}
li
a.flex.js-toggle-password-min-length-enforce
.materialCheckBox(class="{{#if enforce}}is-checked{{/if}}")
span {{_ 'password-min-length-enforce'}}
p.description {{_ 'password-min-length-enforce-description'}}
li
button.primary.js-save-password-length(type="button") {{_ 'save'}}
if saved
span.password-length-saved {{_ 'password-length-saved'}}
if saveError
p.error.password-length-error= saveError
if fromEnvironment
p.description {{_ 'password-length-from-environment'}}
+tablePage(tablePageData)
@@ -0,0 +1,157 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { formatDateForDisplay } from '/client/lib/dateDisplay';
import { buildFilters, buildHeader, buildRows, pageInfo } from '/models/lib/tablePage';
import { errorMessage } from '/client/lib/errorMessage';
// Admin Panel / Problems / Password length: the two settings and the table of
// accounts that sign in with a password shorter than the minimum
// (server/lib/shortPasswordLogins.js; docs/Features/Admin-Panel/Problems/Password-length.md).
const { addressValue, locationText, locationFlag } = require('/models/lib/addressReportColumns');
const { DEFAULT_FILTER } = require('/models/lib/shortPasswordLogins');
const PER_PAGE = 25;
const formatDate = date => (date ? formatDateForDisplay(date) : '');
// One cell per family: the address, then the city when a CDN in front of
// WeKan said where it is, and the country's flag before both.
const addressColumn = (labelKey, family) => ({
labelKey,
nowrap: true,
value: row => [addressValue(row, family), locationText(row, family)].filter(Boolean).join(' '),
flag: row => locationFlag(row, family),
});
const COLUMNS = [
{ labelKey: 'username', value: row => row.username || row.userId },
{ labelKey: 'password-length-first-login', nowrap: true, value: row => formatDate(row.firstAt) },
addressColumn('event-ipv4', 'ipv4'),
addressColumn('event-ipv6', 'ipv6'),
{ labelKey: 'password-length-last-login', nowrap: true, value: row => formatDate(row.at) },
{ labelKey: 'password-length-login-count', align: 'end', value: row => row.count || 0 },
{ labelKey: 'password-length-fixed', nowrap: true, value: row => formatDate(row.fixedAt) },
];
const FILTER_OPTIONS = [
{ value: 'short', labelKey: 'password-length-filter-short' },
{ value: 'fixed', labelKey: 'password-length-filter-fixed' },
{ value: 'all', labelKey: 'password-length-filter-all' },
];
Template.passwordLengthReport.onCreated(function () {
this.rows = new ReactiveVar([]);
this.total = new ReactiveVar(0);
this.page = new ReactiveVar(1);
this.search = new ReactiveVar('');
this.filter = new ReactiveVar(DEFAULT_FILTER);
this.sources = new ReactiveVar(null);
// The working copy of the two settings, until Save.
this.minLength = new ReactiveVar(8);
this.enforce = new ReactiveVar(false);
this.saved = new ReactiveVar(false);
this.error = new ReactiveVar('');
this.load = () => {
Meteor.call('shortPasswordLoginsPage', {
filter: this.filter.get(),
search: this.search.get() || undefined,
limit: PER_PAGE,
skip: (this.page.get() - 1) * PER_PAGE,
}, (error, result) => {
if (error) return;
this.rows.set((result && result.rows) || []);
this.total.set((result && result.total) || 0);
});
};
this.loadSettings = () => {
Meteor.call('getAuthConfigSources', 'login', (error, result) => {
if (error || !result) return;
this.sources.set(result.sources || {});
this.minLength.set(result.sources?.passwordMinLength?.value || 8);
this.enforce.set(result.sources?.passwordMinLengthEnforce?.value === true);
});
};
this.load();
this.loadSettings();
});
Template.passwordLengthReport.helpers({
minLength() { return Template.instance().minLength.get(); },
enforce() { return Template.instance().enforce.get(); },
saved() { return Template.instance().saved.get(); },
saveError() { return Template.instance().error.get(); },
// An environment variable set on the host is overridden by a save here; say
// so, rather than let the admin wonder which one is in effect.
fromEnvironment() {
const sources = Template.instance().sources.get() || {};
return ['passwordMinLength', 'passwordMinLengthEnforce'].some(key => sources[key]?.source === 'env');
},
tablePageData() {
const t = Template.instance();
const rows = t.rows.get() || [];
const info = pageInfo(t.total.get(), t.page.get(), PER_PAGE);
return {
descKey: 'password-length-report-desc',
emptyKey: 'password-length-no-results',
searchTerm: t.search.get(),
filters: buildFilters([{
id: 'password-length-status', labelKey: 'status', current: t.filter.get(), options: FILTER_OPTIONS,
}]),
header: buildHeader(COLUMNS),
rows: buildRows(rows, COLUMNS),
rowCount: rows.length,
totalCount: t.total.get(),
page: info.page,
totalPages: info.totalPages,
hasPrev: info.hasPrev,
hasNext: info.hasNext,
};
},
});
Template.passwordLengthReport.events({
'input .js-password-min-length'(event, tmpl) {
tmpl.minLength.set(parseInt(event.currentTarget.value, 10) || '');
tmpl.saved.set(false);
},
'click .js-toggle-password-min-length-enforce'(event, tmpl) {
event.preventDefault();
tmpl.enforce.set(!tmpl.enforce.get());
tmpl.saved.set(false);
},
'click .js-save-password-length'(event, tmpl) {
event.preventDefault();
Meteor.call('saveAuthConfigSettings', 'login', {
passwordMinLength: tmpl.minLength.get() || null,
passwordMinLengthEnforce: tmpl.enforce.get(),
}, error => {
tmpl.error.set(error ? errorMessage(error) : '');
if (error) return;
tmpl.saved.set(true);
tmpl.loadSettings();
});
},
'change .js-table-page-filter'(event, tmpl) {
tmpl.filter.set(event.currentTarget.value || DEFAULT_FILTER);
tmpl.page.set(1);
tmpl.load();
},
'click .js-table-page-prev'(event, tmpl) {
event.preventDefault();
if (tmpl.page.get() > 1) { tmpl.page.set(tmpl.page.get() - 1); tmpl.load(); }
},
'click .js-table-page-next'(event, tmpl) {
event.preventDefault();
const info = pageInfo(tmpl.total.get(), tmpl.page.get(), PER_PAGE);
if (info.hasNext) { tmpl.page.set(tmpl.page.get() + 1); tmpl.load(); }
},
'keydown .js-table-page-search'(event, tmpl) {
if (event.keyCode === 13 && !event.shiftKey) {
event.preventDefault();
event.stopPropagation();
tmpl.search.set(event.currentTarget.value || '');
tmpl.page.set(1);
tmpl.load();
}
},
});
@@ -36,6 +36,8 @@ template(name="people")
+authProviderSettings(loginOptionsData)
//- Windows SSO, which works only on a Windows server.
+authProviderSettings(windowsSsoData)
//- Strict-Transport-Security, sent when ROOT_URL is https.
+authProviderSettings(httpSecurityData)
else if samlSetting
+authProviderSettings(samlSettingsData)
else if authConfigSection
@@ -56,6 +58,8 @@ template(name="people")
+peopleGeneral(peopleTablePageData)
else if lockedUsersSetting.get
+lockedUsersGeneral
else if auditLogSetting.get
+adminAuditLog
else if rolesSetting.get
+rolesGeneral
else if templatesSetting.get
+24 -1
View File
@@ -142,6 +142,7 @@ Template.people.onCreated(function () {
this.teamSetting = new ReactiveVar(false);
this.peopleSetting = new ReactiveVar(false);
this.lockedUsersSetting = new ReactiveVar(false);
this.auditLogSetting = new ReactiveVar(false);
this.rolesSetting = new ReactiveVar(false);
this.templatesSetting = new ReactiveVar(false);
// #5850: Admin Panel > People > Domains tab. The domains table itself
@@ -434,6 +435,7 @@ Template.people.onCreated(function () {
this.teamSetting.set('team-setting' === targetID);
this.peopleSetting.set('people-setting' === targetID);
this.lockedUsersSetting.set('locked-users-setting' === targetID);
this.auditLogSetting.set('audit-log-setting' === targetID);
this.rolesSetting.set('roles-setting' === targetID);
this.templatesSetting.set('templates-setting' === targetID);
this.domainSetting.set('domains-setting' === targetID);
@@ -551,6 +553,9 @@ function peopleMenu(user) {
{ id: 'team-setting', icon: 'fa-users', labelKey: 'teams' },
{ id: 'people-setting', icon: 'fa-user', labelKey: 'people' },
{ id: 'locked-users-setting', icon: 'fa-lock', labelKey: 'accounts-lockout-locked-users', iconWrapCls: 'text-red' },
// What site administrators did (models/lib/adminAudit.js). Site admins only:
// tenantAdminPeopleMenu keeps a per-tenant admin's menu to its own panes.
{ id: 'audit-log-setting', icon: 'fa-list-alt', labelKey: 'admin-audit-log' },
{ id: 'roles-setting', icon: 'fa-key', labelKey: 'roles' },
{ id: 'templates-setting', icon: 'fa-clone', labelKey: 'shared-templates' },
{ id: 'registration-setting', icon: 'fa-key', labelKey: 'login', emoji: true },
@@ -581,6 +586,15 @@ function peopleMenu(user) {
return items;
}
// A password the server refused (server/lib/passwordPolicy.js), in the
// reader's language; the popup that sent it has already closed.
function passwordErrorMessage(error) {
const key = error && error.error;
if (key === 'password-too-short') return TAPi18n.__('password-too-short', { min: error.details?.min });
if (['password-pwned', 'password-same-as-username'].includes(key)) return TAPi18n.__(key);
return error.reason || error.message;
}
// People panes drawn from models/lib/authConfigCatalog.js, and the data the
// catalog form (authProviderSettings) needs for one section.
const AUTH_CONFIG_PANES = {
@@ -899,6 +913,8 @@ Template.people.helpers({
// The Login pane's own login settings (PASSWORD_LOGIN_ENABLED, ...).
loginOptionsData() { return authConfigData('login'); },
windowsSsoData() { return authConfigData('windowsSso'); },
// HSTS (HSTS_MAX_AGE, ...), below the login settings.
httpSecurityData() { return authConfigData('httpSecurity'); },
ldapPane() { return Template.instance().activeMenuId.get() === 'ldap-setting'; },
authenticationSection() {
return {
@@ -927,6 +943,9 @@ Template.people.helpers({
lockedUsersSetting() {
return Template.instance().lockedUsersSetting;
},
auditLogSetting() {
return Template.instance().auditLogSetting;
},
rolesSetting() {
return Template.instance().rolesSetting;
},
@@ -2352,7 +2371,9 @@ Template.editUserPopup.events({
});
if (isChangePassword) {
Meteor.call('setPassword', password, this.userId);
Meteor.call('setPassword', password, this.userId, error => {
if (error) alert(passwordErrorMessage(error));
});
}
if (isChangeEmailVerified) {
@@ -2651,6 +2672,8 @@ Template.newUserPopup.events({
} else if (errorElement === 'email-already-taken') {
usernameMessageElement.hide();
emailMessageElement.show();
} else if (String(errorElement).startsWith('password-')) {
alert(passwordErrorMessage(error));
}
} else {
usernameMessageElement.hide();
+6
View File
@@ -750,6 +750,12 @@ body.sidebar-resizing-active {
margin-inline-end: 6px;
appearance: auto;
}
/* Why a date range was refused, below its buttons (sidebarFilters.js,
rangeRefused): the browser's own bubble covered those buttons in Firefox. */
.filter-date-range-error {
color: #c0392b;
margin: 6px 0 0;
}
.filter-native-group {
border: 0;
padding: 0;
@@ -140,7 +140,10 @@ template(name="filterSidebar")
| {{_ 'filter-dates-label' }}
each dateFilterProviderViews
+Template.dynamic(template=template data=data)
form.js-card-movement-range
//- novalidate: the range is checked in the submit handler (rangeRefused in
sidebarFilters.js). WebKit keeps a half-edited date segment after Clear
empties a field and then refused every submit of the form, with no message.
form.js-card-movement-range(novalidate)
h4 {{_ 'filter-movement-range'}}
label(for="filter-card-movement-from") {{_ 'filter-date-range-from'}}
input.js-card-movement-from(id="filter-card-movement-from" type="date" value=movementFrom)
@@ -148,7 +151,9 @@ template(name="filterSidebar")
input.js-card-movement-to(id="filter-card-movement-to" type="date" value=movementTo)
button.primary(type="submit") {{_ 'apply'}}
button.js-clear-movement-range(type="button") {{_ 'filter-clear'}}
form.js-card-date-range
if movementRangeError
p.js-card-movement-range-error.filter-date-range-error(role="alert") {{_ 'filter-date-range-invalid'}}
form.js-card-date-range(novalidate)
label(for="filter-card-date-field") {{_ 'filter-date-range-field'}}
select.js-card-date-field(id="filter-card-date-field")
each dateRangeFields
@@ -162,6 +167,8 @@ template(name="filterSidebar")
| {{_ 'filter-date-range-missing'}}
button.primary(type="submit") {{_ 'apply'}}
button.js-clear-date-range(type="button") {{_ 'filter-clear'}}
if dateRangeError
p.js-card-date-range-error.filter-date-range-error(role="alert") {{_ 'filter-date-range-invalid'}}
hr
fieldset.filter-native-group
legend {{_ 'due-date'}}
+26 -12
View File
@@ -13,9 +13,23 @@ import { getSidebarInstance } from '/client/features/sidebar/service';
import { CARD_DATE_RANGE_FIELDS, CARD_RECENCY_PRESETS } from '/models/lib/cardDateRange';
import { DEPENDENCY_TYPES } from '/models/metadata/dependencies';
// A refused date range marks its end field invalid - so it is :invalid and
// screen readers announce the reason - and says why in the form itself. Not
// with reportValidity(): Firefox's validation bubble opens over the Apply and
// Clear buttons below the field and takes every click on them while the field
// has focus, so the range could not be cleared at all.
function rangeRefused(end, error, refused) {
end.setCustomValidity(refused ? TAPi18n.__('filter-date-range-invalid') : '');
error.set(refused);
}
Template.filterSidebar.onCreated(function () {
this.presetMessage = new ReactiveVar('');
this.presetBusy = new ReactiveVar(false);
// Why a date range was refused, shown in the form rather than in the
// browser's own validation bubble (see rangeRefused below).
this.movementRangeError = new ReactiveVar(false);
this.dateRangeError = new ReactiveVar(false);
this.autorun(() => {
const boardId = Session.get('currentBoard');
if (Meteor.userId() && boardId) this.subscribe('savedCardFilters', boardId);
@@ -28,6 +42,8 @@ Template.filterSidebar.helpers({
},
presetMessage() { return Template.instance().presetMessage.get(); },
presetBusy() { return Template.instance().presetBusy.get(); },
movementRangeError() { return Template.instance().movementRangeError.get(); },
dateRangeError() { return Template.instance().dateRangeError.get(); },
filterProviderViews() { return Filter.providers.views(); },
dateFilterProviderViews() { return Filter.providers.views('dates'); },
dueUnrestricted() { return Filter.dueAt.state() === null; },
@@ -102,15 +118,15 @@ Template.filterSidebar.events({
const end = tpl.find('.js-card-movement-to');
const valid = Filter.movementDate.set({ field: 'createdAt',
from: tpl.find('.js-card-movement-from').value, to: end.value, includeMissing: false });
end.setCustomValidity(valid ? '' : TAPi18n.__('filter-date-range-invalid'));
if (!valid) end.reportValidity();
else Filter.resetExceptions();
rangeRefused(end, tpl.movementRangeError, !valid);
if (valid) Filter.resetExceptions();
},
'input .js-card-movement-range input'(event, tpl) {
tpl.find('.js-card-movement-to').setCustomValidity('');
rangeRefused(tpl.find('.js-card-movement-to'), tpl.movementRangeError, false);
},
'click .js-clear-movement-range'(event, tpl) {
event.preventDefault(); tpl.find('.js-card-movement-to').setCustomValidity('');
event.preventDefault();
rangeRefused(tpl.find('.js-card-movement-to'), tpl.movementRangeError, false);
Filter.movementDate.reset(); Filter.resetExceptions();
},
'submit .js-card-date-range'(event, tpl) {
@@ -119,22 +135,20 @@ Template.filterSidebar.events({
const valid = Filter.dateRange.set({ field: tpl.find('.js-card-date-field').value,
from: tpl.find('.js-card-date-from').value, to: end.value,
includeMissing: tpl.find('.js-card-date-missing').checked });
end.setCustomValidity(valid ? '' : TAPi18n.__('filter-date-range-invalid'));
if (!valid) end.reportValidity();
else Filter.resetExceptions();
rangeRefused(end, tpl.dateRangeError, !valid);
if (valid) Filter.resetExceptions();
},
'input .js-card-date-range input'(event, tpl) {
tpl.find('.js-card-date-to').setCustomValidity('');
rangeRefused(tpl.find('.js-card-date-to'), tpl.dateRangeError, false);
},
'click .js-clear-date-range'(event, tpl) {
event.preventDefault();
// Reset the native date controls as well as the reactive model. WebKit can
// retain an edited date segment after Blaze changes the value attribute.
for (const selector of ['.js-card-date-from', '.js-card-date-to']) {
const input = tpl.find(selector);
input.value = '';
input.setCustomValidity('');
tpl.find(selector).value = '';
}
rangeRefused(tpl.find('.js-card-date-to'), tpl.dateRangeError, false);
Filter.dateRange.reset(); Filter.resetExceptions();
},
'submit .js-column-age-filter'(event, tpl) {
@@ -0,0 +1,27 @@
/* PASSWORD_MIN_LENGTH: the Change Password form over everything, until done. */
.password-change-required {
position: fixed;
inset: 0;
z-index: 100000;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
background: rgba(0, 0, 0, 0.6);
}
.password-change-required-box {
max-width: 32rem;
width: calc(100% - 2rem);
margin: 1rem;
padding: 1.5rem;
border-radius: 6px;
background: #fff;
color: #222;
}
.password-change-required-box h2 {
margin-top: 0;
}
/* layouts.css hides the password form until a page shows it (#6380). */
.password-change-required .at-pwd-form {
display: block;
}
@@ -0,0 +1,12 @@
template(name="passwordChangeRequired")
//- PASSWORD_MIN_LENGTH (server/passwordChangeRequired.js): an account whose
//- password is shorter than the minimum signed in, and sees only this until
//- it has a longer one. The form is the same Change Password Member
//- Settings opens; changing it signs every session out, this one too.
.password-change-required(role="dialog" aria-modal="true" aria-labelledby="password-change-required-title")
.password-change-required-box
h2#password-change-required-title {{_ 'password-change-required-title'}}
p {{_ 'password-change-required-text' min=min}}
+atForm(state='changePwd')
.buttonsContainer
button.js-password-change-required-logout(type="button") {{_ 'log-out'}}
@@ -0,0 +1,77 @@
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// PASSWORD_MIN_LENGTH: after signing in, ask whether this account must change
// a password shorter than the minimum, and if so show nothing but the Change
// Password form (passwordChangeRequired.jade). The server asks the question
// (server/passwordChangeRequired.js); config/accounts.js tells it when the
// password just typed was too short. Changing a password signs every session
// out, so the account then signs in again, with the new password.
let overlay = null;
function closeOverlay() {
if (!overlay) return;
Blaze.remove(overlay.view);
overlay.node.remove();
overlay = null;
}
function openOverlay(min) {
if (overlay) return;
const node = document.createElement('div');
document.body.appendChild(node);
overlay = { node, view: Blaze.renderWithData(Template.passwordChangeRequired, { min }, node) };
}
Template.passwordChangeRequired.events({
'click .js-password-change-required-logout'() {
Meteor.logout(() => closeOverlay());
},
});
export function checkPasswordChangeRequirement() {
const userId = Meteor.userId();
if (!userId) return;
Meteor.call('getPasswordChangeRequirement', (error, result) => {
if (error || !result || Meteor.userId() !== userId) return;
if (result.required) openOverlay(result.minLength);
});
}
// A changed password signs this session out too (server/passwordChangeRequired.js).
// The server closes the connection as it removes the token, so the answer to
// the change - and useraccounts' onSubmitHook - may never arrive; being signed
// out soon after submitting a change is the answer, and the next stop is the
// sign-in page, from Member Settings and from this overlay alike.
const SIGN_OUT_WAIT_MS = 2 * 60 * 1000;
let changeSubmittedAt = 0;
document.addEventListener('submit', event => {
const form = event.target;
if (form && form.id === 'at-pwd-form' && form.querySelector('#at-field-current_password')) {
changeSubmittedAt = Date.now();
}
}, true);
Meteor.startup(() => {
let askedFor = null;
Tracker.autorun(() => {
const userId = Meteor.userId();
if (!userId) {
askedFor = null;
const changed = Date.now() - changeSubmittedAt < SIGN_OUT_WAIT_MS;
if (overlay || changed) {
// The change signed every session out: sign in with the new password.
changeSubmittedAt = 0;
closeOverlay();
Popup.close?.();
FlowRouter.go('atSignIn');
}
return;
}
if (askedFor === userId) return;
askedFor = userId;
Tracker.nonreactive(checkPasswordChangeRequirement);
});
});
@@ -0,0 +1,23 @@
/* REQUIRE_2FA: the set-up over everything, until it is done. */
.two-factor-required {
position: fixed;
inset: 0;
z-index: 100000;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
background: rgba(0, 0, 0, 0.6);
}
.two-factor-required-box {
max-width: 32rem;
width: calc(100% - 2rem);
margin: 1rem;
padding: 1.5rem;
border-radius: 6px;
background: #fff;
color: #222;
}
.two-factor-required-box h2 {
margin-top: 0;
}
@@ -0,0 +1,13 @@
template(name="twoFactorRequired")
//- REQUIRE_2FA (models/lib/twoFactorRequirement.js): an account that must
//- have two-factor authentication sees only this until it has set it up.
//- The server refuses everything else meanwhile (server/requireTwoFactor.js);
//- this is what the account can do about it. The set-up is the same
//- twoFactorAuthPopup Member Settings opens.
.two-factor-required(role="dialog" aria-modal="true" aria-labelledby="two-factor-required-title")
.two-factor-required-box
h2#two-factor-required-title {{_ 'two-factor-required-title'}}
p {{_ 'two-factor-required-text'}}
+twoFactorAuthPopup
.buttonsContainer
button.js-two-factor-required-logout(type="button") {{_ 'log-out'}}
@@ -0,0 +1,55 @@
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
// REQUIRE_2FA: after signing in, ask whether this account must still set up
// two-factor authentication, and if so show nothing but the set-up
// (twoFactorRequired.jade). accounts-2fa publishes the account's own
// services.twoFactorAuthentication.type, so finishing the set-up is seen at
// once; the page then reloads, because every subscription the server left
// empty meanwhile has to be made again.
let overlay = null;
function closeOverlay() {
if (!overlay) return;
Blaze.remove(overlay.view);
overlay.node.remove();
overlay = null;
}
function openOverlay() {
if (overlay) return;
const node = document.createElement('div');
document.body.appendChild(node);
overlay = { node, view: Blaze.render(Template.twoFactorRequired, node) };
}
Template.twoFactorRequired.events({
'click .js-two-factor-required-logout'() {
Meteor.logout(() => closeOverlay());
},
});
Meteor.startup(() => {
let askedFor = null;
Tracker.autorun(() => {
const userId = Meteor.userId();
const user = Meteor.user({ fields: { 'services.twoFactorAuthentication.type': 1 } });
if (!userId) {
askedFor = null;
closeOverlay();
return;
}
const enabled = user?.services?.twoFactorAuthentication?.type === 'otp';
if (overlay && enabled) {
window.location.reload();
return;
}
if (askedFor === userId) return;
askedFor = userId;
Meteor.call('getTwoFactorRequirement', (error, result) => {
if (error || !result || Meteor.userId() !== userId) return;
if (result.required && !result.enabled) openOverlay();
});
});
});
+54
View File
@@ -204,3 +204,57 @@ body.mobile-mode.userform-layout {
#at-meteor-developer, #at-weibo, #at-meetup) {
display: none;
}
/* #6835: Member Settings / Change Settings. Its rows are the shared popup list
(popup.css: a flex row, every line 33px high, the icon centred), which reads
well for one-line items and badly for these: a setting whose words wrap -
most of them, in Persian and German - became two 33px lines with its icon
floating between them and the next setting right below, with nothing to
tell one from the other. Here a row is an icon column, the words, and the
check mark at the end; the icon sits on the first line of the words, a
wrapped line is an ordinary line high, and a rule divides the rows. Scoped
to the list's own class: the member menu before it shares its .pop-over. */
.pop-over-list.change-settings-list li + li {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.pop-over-list.change-settings-list li > a {
align-items: flex-start;
gap: 0;
line-height: 1.35;
padding: 8px 10px;
}
.pop-over-list.change-settings-list li > a > i.fa:first-child {
flex: 0 0 1.4em;
margin-inline-end: 0.6em;
line-height: inherit;
text-align: center;
}
.pop-over-list.change-settings-list li > a > i.fa-check {
flex: 0 0 auto;
margin-inline-start: auto;
padding-inline-start: 0.6em;
line-height: inherit;
}
.pop-over-list.change-settings-list li > label.bold {
display: flex;
align-items: flex-start;
line-height: 1.35;
padding: 8px 10px 4px;
}
.pop-over-list.change-settings-list li > label.bold > i.fa {
flex: 0 0 1.4em;
margin-inline-end: 0.6em;
text-align: center;
line-height: inherit;
}
/* The Card settings checkbox row: the box on the first line of its words,
with the same space between them as an icon has. */
.pop-over-list.change-settings-list .js-rescue-card-description {
align-items: flex-start;
gap: 0.6em;
line-height: 1.35;
}
.pop-over-list.change-settings-list .js-rescue-card-description .materialCheckBox {
flex: 0 0 auto;
margin-top: 0.15em;
}
+4 -1
View File
@@ -410,7 +410,10 @@ template(name="changeFontPopup")
a.js-reset-text-color(class="{{#unless hasTextColor}}disabled{{/unless}}") {{_ 'remove-btn'}}
template(name="changeSettingsPopup")
ul.pop-over-list
//- #6835: its own class - a popup keeps the screens before it (the member
menu) side by side in the same .pop-over, so the popup's data-popup would
restyle those too. userForm.css
ul.pop-over-list.change-settings-list
li
a.js-toggle-desktop-drag-handles
i.fa.fa-arrows
+5
View File
@@ -1,4 +1,5 @@
import '/client/components/settings/adminProblems.jade';
import '/client/components/settings/adminAuditLog.jade';
import '/client/components/settings/attachments.jade';
// Backup status and history, shared by Attachments / Backup and Problems / Backup.
import '/client/components/settings/backupRunsStatus.jade';
@@ -33,6 +34,7 @@ import '/client/components/settings/invitationCode.js';
// the `collapsed` class either. docs/Features/All-Boards/Left-Menu.md
import '/client/components/settings/leftMenu.js';
import '/client/components/settings/lockedUsersBody.js';
import '/client/components/settings/adminAuditLog.js';
import '/client/components/settings/migrationProgress.js';
import '/client/components/settings/notificationSettingsPopup.js';
// #3695: notification delivery (content, grouping, schedule) per channel, in
@@ -48,6 +50,8 @@ import '/client/components/settings/sharedLabelsSettings.js';
import '/client/components/settings/settingHeader.js';
import '/client/components/settings/problemsSummary.jade';
import '/client/components/settings/problemsSummary.js';
import '/client/components/settings/passwordLengthReport.jade';
import '/client/components/settings/passwordLengthReport.js';
import '/client/components/settings/translationBody.js';
// adminProblems.css was missing here, so NONE of the Admin Panel report styling
@@ -59,6 +63,7 @@ import '/client/components/settings/adminProblems.css';
import '/client/components/settings/attachments.css';
import '/client/components/settings/informationBody.css';
import '/client/components/settings/lockedUsersBody.css';
import '/client/components/settings/adminAuditLog.css';
import '/client/components/settings/migrationProgress.css';
import '/client/components/settings/peopleBody.css';
import '/client/components/settings/settingBody.css';
+6
View File
@@ -2,13 +2,19 @@ import '/client/components/users/passwordInput.jade';
import '/client/components/users/userAvatar.jade';
import '/client/components/users/userHeader.jade';
import '/client/components/users/dependencyMenu.jade';
import '/client/components/users/twoFactorRequired.jade';
import '/client/components/users/passwordChangeRequired.jade';
import '/client/components/users/passwordInput.js';
import '/client/components/users/userAvatar.js';
import '/client/components/users/userHeader.js';
import '/client/components/users/dependencyMenu.js';
import '/client/components/users/twoFactorRequired.js';
import '/client/components/users/passwordChangeRequired.js';
import '/client/components/users/userAvatar.css';
import '/client/components/users/userForm.css';
import '/client/components/users/dependencyMenu.css';
import '/client/components/users/twoFactorRequired.css';
import '/client/components/users/passwordChangeRequired.css';
+36
View File
@@ -0,0 +1,36 @@
// LOGOUT_IDLE_MINUTES: tell the server, at most once a minute, that the user
// did something here - a key press, a click, a touch, a scroll - so a session
// in use is not signed out as idle (models/lib/idleLogout.js). Nothing is sent
// while the page only sits open, and nothing at all when the limit is off.
const { TOUCH_INTERVAL_MS } = require('../../models/lib/idleLogout');
const EVENTS = ['keydown', 'mousedown', 'touchstart', 'wheel', 'scroll'];
// target: where to listen (document); touch(): report; now(): clock.
function createIdleActivityReporter({ target, touch, now = () => Date.now(), interval = TOUCH_INTERVAL_MS }) {
let last = 0;
let listening = false;
function onActivity() {
const t = now();
if (t - last < interval) return;
last = t;
touch();
}
return {
start() {
if (listening) return;
listening = true;
last = now(); // signing in was the activity that started this
for (const name of EVENTS) target.addEventListener(name, onActivity, { capture: true, passive: true });
},
stop() {
if (!listening) return;
listening = false;
for (const name of EVENTS) target.removeEventListener(name, onActivity, { capture: true });
},
onActivity,
};
}
module.exports = { createIdleActivityReporter, EVENTS };
+36 -10
View File
@@ -53,7 +53,20 @@ hotkeys.filter = (event) => {
if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
return false;
if (currentElement.closest('button, a[href], summary, [role="button"], [role="tab"], [role="checkbox"]'))
// A focused control keeps the keys it uses itself, and only those: Enter
// activates any of them, Space a button, summary or checkbox (not a link),
// and End, Home and the arrows move between tabs (A01). Every other key is
// still a shortcut. Refusing ALL keys left shortcuts dead after a click on
// a minicard - the minicard is a link, and clicking its date badge or its
// menu leaves that link focused - and after any popup closed, because
// closing returns focus to the button that opened it (#6755).
const keysOwnedBy = {
'button, a[href], summary, [role="button"], [role="tab"], [role="checkbox"]': [13],
'button, summary, [role="button"], [role="checkbox"]': [32],
'[role="tab"]': [35, 36, 37, 38, 39, 40],
};
if (Object.keys(keysOwnedBy).some(controls =>
keysOwnedBy[controls].includes(event.keyCode) && currentElement.closest(controls)))
return false;
// We can trigger events!
@@ -404,10 +417,30 @@ function clickDueDateControl(cardId) {
control.click();
return true;
}
// #6840: the card's details render a moment AFTER the route names it - a few
// hundred milliseconds on a slow server. Pressing `d` right after opening a
// card found no control yet and did nothing, while the previous card's details
// were still on screen. So the click waits until THIS card's control is there,
// both for an opened card and for one `d` opens itself (#6755), and clicks it
// once; it gives up quietly after 3 seconds (no due dates on this board).
function clickDueDateControlWhenRendered(cardId) {
const started = Date.now();
const tryClick = () => {
if (Utils.getCurrentCardId() !== cardId) return;
if (clickDueDateControl(cardId)) return;
if (Date.now() - started < 3000) setTimeout(tryClick, 50);
};
tryClick();
}
export function openDueDateEditorOfOpenedCard() {
if (Meteor.userId() === null) return false;
const cardId = Utils.getCurrentCardId();
if (cardId) return clickDueDateControl(cardId);
if (cardId) {
const card = ReactiveCache.getCard(cardId);
if (!card || !Utils.canModifyCard(card)) return false;
clickDueDateControlWhenRendered(cardId);
return true;
}
// #6755: with no card opened, `d` on a hovered or selected minicard - the
// card every other card shortcut here acts on - opens that card and then
// its Due date editor, rather than doing nothing until the card is opened.
@@ -415,14 +448,7 @@ export function openDueDateEditorOfOpenedCard() {
const minicard = minicardId && ReactiveCache.getCard(minicardId);
if (!minicard || !Utils.canModifyCard(minicard)) return false;
Utils.goCardId(minicardId);
// The card details render after the route changes; wait for its control,
// and give up quietly if it never comes (no due dates on this board).
const started = Date.now();
const tryClick = () => {
if (Utils.getCurrentCardId() === minicardId && clickDueDateControl(minicardId)) return;
if (Date.now() - started < 3000) setTimeout(tryClick, 50);
};
setTimeout(tryClick, 0);
setTimeout(() => clickDueDateControlWhenRendered(minicardId), 0);
return true;
}
hotkeys('d, shift+d', (event) => {
+55
View File
@@ -0,0 +1,55 @@
// Breached-password check for the forms that set a NEW password - sign-up,
// Change Password, reset and enrolment (models/lib/passwordPolicy.js says why
// it is done in the browser). It captures the submit before useraccounts sees
// it, asks the server for the SHA-1 range of the password - five hex digits,
// never the password - and replays the submit when the password is not in it.
//
// A form that sets a new password is the one with a "password again" or a
// "current password" field; the sign-in form has neither and is left alone.
const { isPwned, breachLookupDigest } = require('../../models/lib/passwordPolicy');
function isNewPasswordForm(form) {
return !!(form && form.id === 'at-pwd-form' && form.querySelector('#at-field-password')
&& (form.querySelector('#at-field-password_again') || form.querySelector('#at-field-current_password')));
}
// policy() -> { checkPwned } (cached by the caller); range(prefix) -> Promise
// of suffixes or null; subtle is crypto.subtle (absent outside a secure
// context, where the check is skipped); showError(message); replay(form).
function createNewPasswordSubmitHandler({ policy, range, subtle, showError, replay, pwnedMessage }) {
let replaying = false;
let pending = false;
return async function submit(event) {
const form = event.target;
if (replaying || !isNewPasswordForm(form) || !policy().checkPwned || !subtle) return;
const password = form.querySelector('#at-field-password').value;
if (!password) return;
event.preventDefault();
event.stopImmediatePropagation();
if (pending) return;
pending = true;
try {
let breached = false;
try {
const hex = await breachLookupDigest(password, subtle);
const suffixes = await range(hex.slice(0, 5));
breached = Array.isArray(suffixes) && isPwned(hex, suffixes);
} catch (e) {
// An unreachable service must not stop anybody from setting a password.
breached = false;
}
if (breached) {
showError(pwnedMessage());
return;
}
await new Promise(resolve => setTimeout(resolve, 0));
replaying = true;
try { replay(form); } finally { replaying = false; }
} finally {
pending = false;
}
};
}
module.exports = { createNewPasswordSubmitHandler, isNewPasswordForm };
+63
View File
@@ -115,6 +115,63 @@ AccountsTemplates.addFields([
},
]);
// PASSWORD_MIN_LENGTH and PASSWORD_CHECK_PWNED (models/lib/passwordPolicy.js).
// useraccounts checks a NEW password's minLength in the browser - sign-up,
// Change Password, reset, enrolment; never sign-in - and reads it from the
// field at submit time, so the server's value applies once it has arrived.
// Unless the minimum is enforced (PASSWORD_MIN_LENGTH_ENFORCE, off by
// default) the fields keep their own 6.
// The breached-password check runs before useraccounts sees the submit.
if (Meteor.isClient) {
const passwordPolicy = { minLength: 8, enforce: false, checkPwned: false };
Meteor.call('getPasswordPolicy', (error, result) => {
if (error || !result) return;
Object.assign(passwordPolicy, result);
if (!passwordPolicy.enforce) return;
for (const id of ['password', 'password_again']) {
const field = AccountsTemplates.getField(id);
if (field) field.minLength = passwordPolicy.minLength;
}
});
// Signing in is never refused for a short password: it is reported to
// Admin Panel / Problems / Password length, and with the minimum enforced the
// account is asked to change it (server/passwordChangeRequired.js). Only this
// browser knows the length, so it is noted on the sign-in form - the length,
// never the password - and compared with the minimum once the login worked.
const { isNewPasswordForm } = require('/client/lib/newPasswordCheck');
let signInPasswordLength = null;
document.addEventListener('submit', event => {
const form = event.target;
const field = form && form.id === 'at-pwd-form' && form.querySelector('#at-field-password');
if (field && !isNewPasswordForm(form)) signInPasswordLength = [...field.value].length;
}, true);
Accounts.onLogin(() => {
const length = signInPasswordLength;
signInPasswordLength = null;
if (length === null) return;
// Asked again: the minimum may have changed since the page loaded.
Meteor.call('getPasswordPolicy', (error, result) => {
if (error || !result || !(result.minLength > 0) || length >= result.minLength) return;
Meteor.call('reportShortPasswordLogin', () => {
const { checkPasswordChangeRequirement } = require('/client/components/users/passwordChangeRequired');
checkPasswordChangeRequirement();
});
});
});
const { createNewPasswordSubmitHandler } = require('/client/lib/newPasswordCheck');
document.addEventListener('submit', createNewPasswordSubmitHandler({
policy: () => passwordPolicy,
range: prefix => Meteor.callAsync('passwordPwnedRange', prefix),
subtle: globalThis.crypto && globalThis.crypto.subtle,
pwnedMessage: () => tr('password-pwned'),
showError(message) {
AccountsTemplates.setDisabled(false);
AccountsTemplates.state.form.set('error', [message]);
},
replay: form => form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true })),
}), true);
}
// The sign-in / register form field labels and placeholders were rendered through
// accounts-t9n (`T9n.get`), whose per-language coverage is incomplete, and
// useraccounts' `getPlaceholder()` never invokes a function placeholder (unlike
@@ -333,6 +390,12 @@ AccountsTemplates.configure({
forbidClientAccountCreation: disableRegistration,
homeRoutePath: '/',
onSubmitHook(error, state) {
if (!error && state === 'changePwd') {
// The server has signed every session of the account out, this one too
// (server/passwordChangeRequired.js): sign in with the new password.
Meteor.logout(() => FlowRouter.go('atSignIn'));
return;
}
if (!error && (state === 'signUp' || state === 'signIn')) {
hideTwoFactorPrompt();
goHomeWhenSignedIn();
+29
View File
@@ -908,6 +908,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -910,6 +910,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -897,6 +897,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -914,6 +914,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -820,6 +820,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -1051,6 +1051,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29
View File
@@ -895,6 +895,35 @@ services:
# it too, and its value there wins.
#- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
#---------------------------------------------------------------
# LOGOUT_IDLE_MINUTES : Sign a browser session out after this many minutes without a key press, click,
# touch or scroll. Empty or 0 = off (default). REST API tokens are not affected.
#- LOGOUT_IDLE_MINUTES=30
#---------------------------------------------------------------
# REQUIRE_2FA : Compulsory two-factor authentication for accounts that sign in with a WeKan password:
# off (default), admins or all. They must set it up right after signing in.
#- REQUIRE_2FA=admins
#---------------------------------------------------------------
# PASSWORD_MIN_LENGTH : Minimum password length, in characters. Default 8. A sign-in with a shorter
# password is listed in Admin Panel / Problems / Password length; nothing is refused unless enforced:
#- PASSWORD_MIN_LENGTH=12
# PASSWORD_MIN_LENGTH_ENFORCE : true = refuse a shorter new password, and make an account that
# signs in with a shorter one change it; the change signs every session out. Off by default.
#- PASSWORD_MIN_LENGTH_ENFORCE=true
#---------------------------------------------------------------
# PASSWORD_CHECK_PWNED : true = refuse a new password found in a known data breach. Off by default.
# Only 5 hex digits of its SHA-1 are sent to api.pwnedpasswords.com.
#- PASSWORD_CHECK_PWNED=true
#---------------------------------------------------------------
# HSTS_MAX_AGE : Strict-Transport-Security max-age in seconds, sent only when ROOT_URL is https.
# Default 15552000 (180 days). 0 = do not send it.
#- HSTS_MAX_AGE=31536000
#---------------------------------------------------------------
# HSTS_INCLUDE_SUBDOMAINS : true = HSTS covers every subdomain too. Only when ALL of them have HTTPS.
#- HSTS_INCLUDE_SUBDOMAINS=false
#---------------------------------------------------------------
# HSTS_PRELOAD : true = add the preload token (needs includeSubDomains and max-age >= 31536000).
#- HSTS_PRELOAD=false
#---------------------------------------------------------------
# BACKUP_KEEP_DAYS : after each successful Admin Panel backup, delete
# local (filesystem) backups older than this many days; the newest is
# always kept. Unset or 0 = keep all (default). Admin Panel / Attachments
+29 -17
View File
@@ -329,15 +329,20 @@ docker compose up -d
# Backup Wekan Snap to directory dump
```bash
sudo snap stop wekan.wekan
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
export PATH="/snap/wekan/current/bin:$PATH"
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="$S/wekan/current/bin:$PATH"
mongodump --port 27019
sudo snap get wekan > snap-settings.sh
```
NOTE for Arch Linux: Use this path instead. [Source](https://github.com/wekan/wekan/issues/3941).
```bash
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH-}:/var/lib/snapd/snap/wekan/current/lib/x86_64-linux-gnu
```
The `S=` line picks where snapd keeps the snaps on this host. Ubuntu and Debian
have `/snap`; Fedora, RHEL, AlmaLinux, Rocky Linux, CentOS Stream and Arch Linux
keep them in `/var/lib/snapd/snap` and have no `/snap` unless somebody has made
the symlink (`sudo ln -s /var/lib/snapd/snap /snap`). The data is in
`/var/snap/wekan/common` on all of them. `$(uname -m)` picks the library
directory of the CPU: `x86_64-linux-gnu` on amd64, `aarch64-linux-gnu` on arm64.
[Arch Linux report](https://github.com/wekan/wekan/issues/3941).
username is your /home/username
```bash
sudo chown username:username snap-settings.sh
@@ -355,8 +360,9 @@ chmod +x snap-settings.sh
# Restore Wekan Snap
```bash
sudo snap stop wekan.wekan
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan/current/bin"
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan/current/bin"
mongorestore --drop --port 27019
sudo snap start wekan.wekan
./snap-settings.sh
@@ -454,8 +460,9 @@ sudo snap refresh
# Backup Wekan Gantt GPLv2 Snap to directory dump
```bash
sudo snap stop wekan-gantt-gpl.wekan
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan-gantt-gpl/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan-gantt-gpl/current/bin"
S=/snap; [ -d /snap/wekan-gantt-gpl ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan-gantt-gpl/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan-gantt-gpl/current/bin"
mongodump --port 27019
sudo snap get wekan-gantt-gpl > snap-settings.sh
```
@@ -476,8 +483,9 @@ chmod +x snap-settings.sh
# Restore Wekan Gantt GPLv2 Snap
```bash
sudo snap stop wekan-gantt-gpl.wekan
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan-gantt-gpl/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan-gantt-gpl/current/bin"
S=/snap; [ -d /snap/wekan-gantt-gpl ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan-gantt-gpl/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan-gantt-gpl/current/bin"
mongorestore --drop --port 27019
sudo snap start wekan-gantt-gpl.wekan
./snap-settings.sh
@@ -820,8 +828,9 @@ You could use [daff](https://github.com/paulfitz/daff) to compare tables.
Add to your `~/.bashrc`
```bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan/current/bin"
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan/current/bin"
```
Then you can backup:
```bash
@@ -838,7 +847,8 @@ mongoshell.sh
```bash
#/bin/bash
export LC_ALL=C
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
mongo=$"/snap/wekan/$version/bin/mongo"
$mongo --port 27019
@@ -856,7 +866,8 @@ wekan-backup.sh
```bash
#!/bin/bash
export LC_ALL=C
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
now=$(date +"%Y%m%d-%H%M%S")
@@ -907,7 +918,8 @@ wekan-restore.sh
```bash
#!/bin/bash
export LC_ALL=C
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
makesRestore()
{
+3 -2
View File
@@ -5,9 +5,10 @@ sudo su
snap stop wekan
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="/snap/wekan/current/bin:$PATH"
export PATH="$S/wekan/current/bin:$PATH"
mongod --dbpath "/var/snap/wekan/common" --repair >> /root/repairlog.txt
+2 -1
View File
@@ -51,7 +51,8 @@ export PATH="/snap/wekan/current/migratemongo/bin:$PATH"
export LD_LIBRARY_PATH="/snap/wekan/current/migratemongo/lib:$LD_LIBRARY_PATH"
# MongoDB 7.x (default)
export PATH="/snap/wekan/current/bin:$PATH"
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export PATH="$S/wekan/current/bin:$PATH"
export LD_LIBRARY_PATH="/snap/wekan/current/usr/lib:$LD_LIBRARY_PATH"
```
+3 -1
View File
@@ -257,7 +257,9 @@ plugin npm dependencies. `METEOR_PROFILE` defaults to 100 milliseconds, exposing
Meteor tool timing diagnostics when available. Profiling output may appear only
when a measured operation finishes; elapsed status still appears while it is quiet.
During builds, Node module debugging prints module resolution as it happens. The
Node module debugging, which prints every module resolution as it happens, is
off unless `WEKAN_BUILD_TRACE_MODULES=1` is set: a Meteor build writes that trace
at gigabytes a minute (1.1 GB in the first three minutes of a dev build). The
local `tools/build-command-output.cjs` preload prints resolver subprocess commands
and echoes their captured stdout and stderr while preserving exit codes and callback
results. Both terminal and build log receive this output. Command arguments redact
@@ -58,6 +58,7 @@ OAuth providers and Passwordless login each have their own menu entry.
| `/admin/people/organizations` | Organizations | `org-setting` |
| `/admin/people/teams` | Teams | `team-setting` |
| `/admin/people/locked-users` | Locked Users | `locked-users-setting` |
| `/admin/people/audit-log` | Audit log | `audit-log-setting` |
| `/admin/people/roles` | Roles | `roles-setting` |
| `/admin/people/shared-templates` | Shared templates | `templates-setting` |
| `/admin/people/login` | Login | `registration-setting` |
@@ -80,6 +81,7 @@ OAuth providers and Passwordless login each have their own menu entry.
| `/admin/problems/backup` | Backup (status and history; settings stay in Attachments / Backup) | `features-backup` |
| `/admin/problems/security-report` | Security Report | `report-security` |
| `/admin/problems/impersonation` | Impersonation Report | `report-impersonation` |
| `/admin/problems/password-length` | Password length | `report-password-length` |
| `/admin/problems/performance` | Performance | `features-performance` |
| `/admin/problems/speed` | Speed | `report-speed` |
| `/admin/problems/tests` | Tests | `report-tests` |
@@ -0,0 +1,22 @@
# People / Audit log
`/admin/people/audit-log`, site administrators only.
Every administrative action of a site administrator, newest first, one row
each: when, who, which action, whether it succeeded (a refused call is shown
too, with its error), its arguments and the address it came from. The search
box matches the administrator, the action and the arguments.
What is recorded: changes to people (Add user, Set password, username, email,
admin and active flags, removing a user, impersonation, unlocking), to
organizations and teams, to login and site settings, to attachment storage,
and backups, restores and data repairs. An administrator editing cards is a
member editing cards and is not recorded. The list of actions is
`AUDITED_METHODS` in `models/lib/adminAudit.js`.
A password or a secret is never recorded: values under a credential-named key,
the arguments that are a password by position and a password inside a URL are
shown as `[redacted]`.
Entries are kept for a year and then removed. Attacks WeKan refused are in
Admin Panel / Problems instead; see [Security settings](../../../Security/Security-Settings.md).
@@ -19,6 +19,7 @@ Who may sign in, who they belong to, and what they may do — in menu order.
| People / Teams | `teams` | [Teams.md](Teams.md) | Teams, their per-team switches, and the same-Team board restriction. |
| People / People | `people` | [People.md](People.md) | Every user: e-mail, admin flag, active state, lockout, created date. |
| People / Locked Users | `locked-users` | [Locked-Users.md](Locked-Users.md) | Brute-force protection settings and who is locked out. |
| People / Audit log | `audit-log` | [Audit-Log.md](Audit-Log.md) | What site administrators did, newest first; kept for a year. |
| People / Roles | `roles` | [Roles.md](Roles.md) | Which board roles may invite people to a board. |
| People / Shared templates | `shared-templates` | [Shared-Templates.md](Shared-Templates.md) | Which scopes may share template boards. |
@@ -0,0 +1,83 @@
# Admin Panel / Problems / Password length
Who signs in with a password shorter than the minimum length, from where, and
whether they have changed it since. The URL is `/admin/problems/password-length`.
## Settings
Above the table, with one **Save** button:
| Setting | Environment variable | Default | What it does |
| --- | --- | --- | --- |
| Minimum password length | `PASSWORD_MIN_LENGTH` | `8` | A sign-in with a shorter password is listed in the table below. Nothing is refused for it. |
| Enforce the minimum length | `PASSWORD_MIN_LENGTH_ENFORCE` | off | A shorter **new** password is refused (sign-up, Change Password, reset, enrolment, an administrator's Set password and Add user, `POST /users/register`), and an account that signs in with an older shorter password sees only **Change your password** until it has a long enough one. |
Both are also in **Admin Panel / People / Login**, and saving either page saves
the same setting. A value saved here overrides the environment variable; the
page says so when an environment variable is set.
**Signing in is never refused** for a short password, enforced or not: nobody
is locked out by a minimum set after their password was. Changing a password -
forced or not, in Member Settings or with a reset link - signs **every** session
of the account out, the one that changed it too, and the account then signs in
with the new password.
LDAP, OAuth2, OIDC, SAML, CAS and Sandstorm accounts are neither listed nor
asked: their password is their provider's, and so is its policy.
## The table
One row per account, never one per sign-in, like every Problems report: an
account that signs in every day is one row whose count grows.
| Column | What it shows |
| --- | --- |
| Username | The account. |
| First login | The first time it signed in with a password shorter than the minimum. |
| IPv4 address | The latest IPv4 address it did so from, with the country's flag and the city when a CDN or proxy in front of WeKan said where the address is. |
| IPv6 address | The same for IPv6. |
| Last login | The latest time it signed in with a short password. |
| Times logged in | How many times it has. |
| Fixed | When its password was changed. Empty while it is still too short. |
The filter above the table, as on every [table page](../../General/Table.md):
| Filter | Shows |
| --- | --- |
| **Too short password** (default) | Accounts that still have a short password. |
| **Fixed to long enough password** | Accounts that changed theirs. |
| **All** | Both. |
The search box matches the username and the addresses.
## How WeKan knows a password is short
Meteor sends a password to the server hashed, so the server never sees how long
it is. The browser that typed it does: after a sign-in that worked, it compares
the length with the minimum and, when it is shorter, tells the server "this one
was short" - never the password and never its length.
The same is true of a password changed through a form, so **Fixed** means the
password was changed and the account has not signed in with a short one since.
An account that changes its password to another short one is listed as too short
again the next time it signs in. An administrator's Set password is the one
change the server can measure, so a short one set there does not count as fixed.
Where an address is comes only from headers a CDN or proxy in front of WeKan sets
(`models/lib/geoHeaders.js`). WeKan sends no address to a geolocation service,
and the location is for display only - nothing is decided by it.
## Related files
| File | What it is |
| --- | --- |
| `models/lib/passwordPolicy.js` | The minimum, whether it is enforced, and which new passwords are refused. |
| `models/lib/shortPasswordLogins.js` | What a row is, the filter, and how one more short sign-in updates a row. |
| `models/shortPasswordLogins.js` | The collection. |
| `server/lib/shortPasswordLogins.js` | Recording a short sign-in, marking it fixed, and one page of the table. |
| `server/passwordChangeRequired.js` | The browser's report, the forced change, and signing every session out after a change. |
| `client/components/settings/passwordLengthReport.*` | This page. |
| `client/components/users/passwordChangeRequired.*` | **Change your password**, shown over everything while the change is required. |
| `config/accounts.js` | Where the browser notes the length on the sign-in form and reports a short one. |
| `tests/passwordPolicy.test.cjs`, `tests/passwordChangeRequired.test.cjs`, `tests/shortPasswordLogins.test.cjs` | Node tests. |
| `tests/playwright/specs/password-min-length-signup-login.e2e.js`, `tests/playwright/specs/password-length-report.e2e.js` | Browser tests. |
+3 -2
View File
@@ -64,7 +64,7 @@ unsafe or noisy:
failed or overdue backup is also listed on Summary
([Backup.md](../../../Backup/Backup.md)).
**Reports** — Security Report, Impersonation Report, Performance, Speed, Tests, CPU
**Reports** — Security Report, Impersonation Report, Password length, Performance, Speed, Tests, CPU
usage, Instrumentation, Broken Cards, Files Report, Rules Report, Boards Report,
Cards Report, Recovery, Offices, API, Database problems, Filesystem integrity and
Email.
@@ -127,6 +127,7 @@ the address uses: `/admin/problems/<slug>`.
| Problems / Backup | `backup` | [Backup.md](../../../Backup/Backup.md#did-the-backup-work-history-health-and-monitoring) |
| Problems / Security Report | `security-report` | — |
| Problems / Impersonation Report | `impersonation` | — |
| Problems / Password length | `password-length` | [Password-length.md](Password-length.md) |
| Problems / Performance | `performance` | — |
| Problems / Speed | `speed` | — |
| Problems / Tests | `tests` | — |
@@ -146,7 +147,7 @@ the address uses: `/admin/problems/<slug>`.
| Problems / Filesystem integrity | `integrity` | — |
| Problems / Email | `mail` | [Bounces.md](../../General/Email/Bounces.md#when-something-goes-wrong) |
15 of these 25 panes has no page of its own yet. A dash is a
15 of these 26 panes has no page of its own yet. A dash is a
gap to fill, not a pane that does nothing - what the pane shows is described in
this README until somebody writes it up.
+2
View File
@@ -79,6 +79,7 @@ are not reachable from the current Admin Panel menu. Current storage panes use
| Teams (`teams`) | [team.js](../../../models/team.js) | Team records and membership operations; scoped server permissions. |
| People (`people`) | [users.js](../../../server/models/users.js) | Create/edit, roles, password/email operations, impersonation and removal. |
| Locked Users (`locked-users`) | [lockedUsers.js](../../../server/methods/lockedUsers.js) | Locked-account listing and administrator unlock methods. |
| Audit log (`audit-log`) | [adminAudit.js](../../../server/adminAudit.js) | New: every administrative method a site administrator calls, one document each for a year, secrets redacted; site administrators only. Real wrapper in [adminAudit.test.cjs](../../../tests/adminAudit.test.cjs). |
| Roles (`roles`) | [inviteToBoardRolesSettings.js](../../../models/inviteToBoardRolesSettings.js) | Invitation-role defaults and per-role settings. |
| Shared Templates (`shared-templates`) | [users.js](../../../server/models/users.js) | Shared-template administration with tenant boundaries. |
@@ -111,6 +112,7 @@ are not reachable from the current Admin Panel menu. Current storage panes use
| Backup (`backup`) | [backup.js](../../../server/methods/backup.js) | Recorded backup runs, health of the instance backup and its history (backupHistory method); settings stay in Attachments / Backup. |
| Security Report (`security-report`) | [eventLog.js](../../../models/eventLog.js) | Recorded security events, search, detail and acknowledgement. |
| Impersonation Report (`impersonation`) | [impersonationReport.js](../../../server/publications/impersonationReport.js) | Scoped event publication and counts. |
| Password length (`password-length`) | [shortPasswordLogins.js](../../../server/lib/shortPasswordLogins.js) | The minimum password length and whether it is enforced, saved as Login settings; one row per account that signed in with a shorter password, reported by its browser, with the Too short / Fixed / All filter. |
| Performance (`performance`) | [adminProblems.js](../../../client/components/settings/adminProblems.js) | Persisted performance options; card loading uses the active loading policy. |
| Speed (`speed`) | [eventLog.js](../../../models/eventLog.js) | Recorded speed events; no events is a valid empty report. |
| Tests (`tests`) | [eventLog.js](../../../models/eventLog.js) | Recorded test results; opening the pane does not run every repository test. |
+1
View File
@@ -63,6 +63,7 @@ Menu path is what you click to reach the page.
| Domains | Admin Panel / People / Domains | Every e-mail domain in use with the number of users on it. The first People pane converted to this design. |
| Roles Status | Admin Panel / People / Roles | What each board role may do — which cards it sees, and whether it may comment, create and edit, or change the board's settings. **Read-only**: no `rowTemplate`, no actions, nothing editable, because a role's capabilities are a property of the code and not a setting. It sits BELOW the Save button of the checkbox list above it and follows that list's working copy, so an admin sees what a change means before saving it. Rendered from `models/lib/boardRoleCapabilities.js`, the same table the allow rules decide with — see [Board roles](../Board-Sidebar/Members/Roles.md). |
| Security | Admin Panel / Problems / Security | Security events from the event log: blocked uploads, rejected URL schemes, auth failures. One row per event, newest first. |
| Password length | Admin Panel / Problems / Password length | Accounts that signed in with a password shorter than the minimum: username, first and last such login, the latest IPv4 and IPv6 address each with its flag and city when known, the count, and when it was fixed. Its filter is the status - Too short password (the default), Fixed to long enough password, All - and above the table are the two settings, the minimum length and whether it is enforced. See [Password length](../Admin-Panel/Problems/Password-length.md). |
| Speed | Admin Panel / Problems / Speed | Slow-operation events — what took too long, where, and for how long. |
| Tests | Admin Panel / Problems / Tests | Test-run events recorded on the server. |
| CPU usage | Admin Panel / Problems / CPU usage | Past high-CPU periods. Adds a **status row**: the live CPU percent, core count and load average, because the table itself is history, not the current state. |
+6 -4
View File
@@ -2,8 +2,9 @@
**a) Wekan Snap**
```
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan/current/bin"
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan/current/bin"
mongo --port 27019
```
If you have disabled new user registration at Admin Panel, you can enable it, and create new user your https://wekan.example.com/sign-up :
@@ -34,8 +35,9 @@ Then login to Wekan and change any users passwords at `Admin Panel / People / Pe
**b) Wekan Gantt GPL Snap**
```
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan-gantt-gpl/current/lib/x86_64-linux-gnu
export PATH="$PATH:/snap/wekan-gantt-gpl/current/bin"
S=/snap; [ -d /snap/wekan-gantt-gpl ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan-gantt-gpl/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:$S/wekan-gantt-gpl/current/bin"
mongo --port 27019
```
If you have disabled new user registration at Admin Panel, you can enable it, and create new user your https://wekan.example.com/sign-up :
@@ -131,9 +131,10 @@ sudo snap stop wekan.wekan
```
#### 10. Use WeKan Stable Snap mongodump version for dumping database
```
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
S=/snap; [ -d /snap/wekan ] || S=/var/lib/snapd/snap
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:$S/wekan/current/lib/$(uname -m)-linux-gnu"
export PATH="$PATH:/snap/wekan/current/bin"
export PATH="$PATH:$S/wekan/current/bin"
mongodump --port 27019
```
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-12.74-win64.zip](https://github.com/wekan/wekan/releases/download/v12.74/wekan-12.74-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-12.78-win64.zip](https://github.com/wekan/wekan/releases/download/v12.78/wekan-12.78-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
2. [node.exe](https://nodejs.org/dist/latest-v26.x/win-x64/node.exe)
@@ -22,7 +22,7 @@ Right click and download files 1-4:
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
7. Unzip `wekan-12.74-win64.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-12.78-win64.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
@@ -0,0 +1,19 @@
# Password breach check digest (CodeQL #599)
Reviewed on **2026-10-31** from the saved alert in `.tools/log/wekansec2`.
| Alert | Source review | Changes |
| --- | --- | --- |
| 599: use of password hash with insufficient computational effort (`js/insufficient-password-hash`, CWE-916) | `server/lib/passwordPolicy.js` computed the SHA-1 of a new password for `PASSWORD_CHECK_PWNED` (off by default, unreleased). That digest is not password storage: accounts-password stores passwords with bcrypt, and this one is never stored or logged. Have I Been Pwned's range API is keyed by SHA-1, so its first 5 hex digits are sent and the remaining 35 are compared with the answer in memory and dropped. A slow hash cannot be used, because the service could not match it. | The browser and the server now compute the lookup digest with ONE implementation, `breachLookupDigest` in `models/lib/passwordPolicy.js` (Web Crypto in both), whose comment says what it is and is not. The server no longer hashes a password with a hash of its own, and a failure to compute the digest lets the password through, like an unreachable service. Tests: the digest equals SHA-1 for ASCII and UTF-8 input, it reaches only the range lookup and the comparison, and nothing anywhere in WeKan hashes a password-named value with a fast hash (`tests/passwordPolicy.test.cjs`). |
This is hardening of a check that had not been released, not a confirmed
vulnerability: no password or password hash was stored or sent beyond five
hex digits. No Hall of Fame entry is created, and nothing is recorded in Admin
Panel / Problems - a password being checked is ordinary use, not an attack.
If the alert is raised again for the Web Crypto digest, it is the same false
positive and can be dismissed as "Won't fix - the API requires SHA-1; not
stored", with a link to this page.
Reference: [Have I Been Pwned range API](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange),
[OWASP Password Storage](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
+57
View File
@@ -0,0 +1,57 @@
# Security settings
Settings an administrator can turn on to harden a WeKan site. Every one of them
is an environment variable on every platform (Snap: `sudo snap set wekan
<key>=<value>`, Docker: `docker-compose.yml`, `start-wekan.sh`,
`start-wekan.bat`, the Helm chart's `values.yaml`), and the ones marked
**Admin Panel** can also be set there, where the value wins over the variable.
## Signing in
| Setting | Snap key | Default | What it does |
| --- | --- | --- | --- |
| `PASSWORD_MIN_LENGTH` | `password-min-length` | `8` | Minimum password length, in characters. A sign-in with a shorter password is listed in **Admin Panel / Problems / Password length**; nothing is refused unless `PASSWORD_MIN_LENGTH_ENFORCE` is on. Also set on that page and in **Admin Panel / People / Login**. |
| `PASSWORD_MIN_LENGTH_ENFORCE` | `password-min-length-enforce` | `false` | Refuse a shorter NEW password, and make an account that signs in with an older shorter one change it before anything else; the change signs every session of the account out. Only WeKan's own password accounts: LDAP, OAuth2, OIDC, SAML, CAS and Sandstorm passwords are their provider's. [Password length](../Features/Admin-Panel/Problems/Password-length.md) |
| `PASSWORD_CHECK_PWNED` | `password-check-pwned` | `false` | Refuse a new password that appears in a known data breach. Only the first 5 hex digits of its SHA-1 are sent to `api.pwnedpasswords.com`; if the service cannot be reached, the password is accepted. **Admin Panel / People / Login** |
| `REQUIRE_2FA` | `require-2fa` | `off` | `admins` or `all`: accounts that sign in with a WeKan password must set up two-factor authentication right after signing in, and can do nothing else until they have. LDAP, OAuth2, SAML, CAS and Sandstorm accounts are not covered: their identity provider is where a second factor belongs. **Admin Panel / People / Login** |
| `LOGOUT_IDLE_MINUTES` | `logout-idle-minutes` | off | Sign a browser session out after this many minutes without a key press, click, touch or scroll. REST API tokens are not affected. **Admin Panel / People / Login** |
| `LOGOUT_WITH_TIMER`, `LOGOUT_IN`, `LOGOUT_ON_HOURS`, `LOGOUT_ON_MINUTES` | | off | Sign everybody out at a deadline after signing in, however busy. **Admin Panel / People / Login** |
| `ACCOUNTS_LOCKOUT_*` | | on | Lock an account or an address after failed logins; [Brute force protection](brute-force-protection.md). **Admin Panel / People / Locked Users** |
| `USERS_DISABLE_INACTIVE_AFTER_DAYS` | | off | Disable accounts unused for this many days. **Admin Panel / People / Login** |
Each member can turn on two-factor authentication for themselves in Member
Settings, whatever `REQUIRE_2FA` says, and can add passkeys.
## HTTP
| Setting | Snap key | Default | What it does |
| --- | --- | --- | --- |
| `HSTS_MAX_AGE` | `hsts-max-age` | `15552000` (180 days) | Strict-Transport-Security, sent only when `ROOT_URL` is https. `0` stops it. **Admin Panel / People / Login** |
| `HSTS_INCLUDE_SUBDOMAINS` | `hsts-include-subdomains` | `false` | HSTS for every subdomain too. Only when all of them have HTTPS. |
| `HSTS_PRELOAD` | `hsts-preload` | `false` | The preload token; sent only with includeSubDomains and a max-age of at least a year. |
| `BROWSER_POLICY_ENABLED`, `TRUSTED_URL` | | | Which site may show WeKan in a frame. |
Always on: `X-Content-Type-Options: nosniff`, `Referrer-Policy:
strict-origin-when-cross-origin`, and a cross-site request guard - a POST, PUT,
PATCH or DELETE that logs in only with the cookie must come from WeKan's own
pages. A refused request appears in Admin Panel / Problems as
`csrf.cross-site-cookie`.
## Attachments
- **Admin Panel / Attachments / S3 / Server-side encryption**: SSE-S3 or SSE-KMS
for every attachment, avatar and backup written to S3.
- On the filesystem, encrypt the disk or volume that holds `WRITABLE_PATH`
(LUKS, an encrypted cloud volume).
- `ATTACHMENTS_UPLOAD_MAX_SIZE`, `ATTACHMENTS_UPLOAD_MIME_TYPES` and
`ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM` (a virus scanner, `{file}` is the
upload) limit and check what can be uploaded; the same exist for avatars.
## Watching
- **Admin Panel / Problems** - attacks WeKan refused, summarised: what, how
often, since when, from whom.
- **Admin Panel / People / Audit log** - every administrative action of a site
administrator: who set a password, made somebody an admin, changed a login
setting, restored a backup. Passwords and secrets are never recorded; kept
for a year.
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Bagaih",
"progress": "Keumajuan",
"password-again": "Sandi (sigo treuk)",
"password-pwned": "Kata sandi nyoe ka meutamöng lam bocoran data. Pileh nyang laen.",
"password-too-short": "Kata sandi beu paleng kureung __min__ karakter.",
"password-length-report-title": "Panyang kata sandi",
"password-length-report-desc": "Akun nyang tamong ngön kata sandi nyang leubeh paneuk nibak panyang paleng kureung. Cit browser mantong nyang tuphö padum panyang kata sandi, maka jih jilapor kata sandi nyang paneuk lheuh tamong; kata sandi ngön panyang jih hana tom teubiet nibak browser. Ka geupeugot meunyo kata sandi akun ka geu-ubah - akun nyang tamong lom ngön kata sandi paneuk geupeuleumah lom sibagoe paneuk that.",
"password-length-no-results": "Hana akun disinoe.",
"password-length-first-login": "Tamong phon",
"password-length-last-login": "Tamong keuneulheuëh",
"password-length-login-count": "Padum go tamong",
"password-length-fixed": "Ka geupeugot",
"password-length-filter-short": "Kata sandi paneuk that",
"password-length-filter-fixed": "Ka geupeugot keu kata sandi nyang cukôp panyang",
"password-length-filter-all": "Mandum",
"password-min-length": "Panyang paleng kureung kata sandi",
"password-min-length-description": "Tamong ngön kata sandi nyang leubeh paneuk geupeuleumah di yup nyoe. Tamong hana tom geutolak keu nyan.",
"password-min-length-enforce": "Paksa panyang paleng kureung",
"password-min-length-enforce-description": "Kata sandi barô nyang leubeh paneuk geutolak, ngön akun nyang tamong ngön kata sandi leubeh paneuk wajeb jiubah dilee sigohlom nyang laen. Meng-ubah jih jiteubiet mandum sesi akun. Kata sandi LDAP, OAuth2, OIDC, SAML, CAS ngön Sandstorm nyan milek penyedia jih, ngön hana geuperiksa.",
"password-length-saved": "Ka geukeubah.",
"password-length-from-environment": "Nilai nyang geupasang lam lingkungan server teungöh beurlaku; keubah disinoe jigantoe nyan.",
"password-change-required-title": "Ubah kata sandi droeneuh",
"password-change-required-text": "Situs nyoe jinoe jilakee kata sandi paleng kureung __min__ karakter, ngön kata sandi droeneuh leubeh paneuk. Pileh nyang leubeh panyang mangat jeuet lanjut. Mandum sesi akun droeneuh laju geuteubiet, ngön droeneuh tamong lom ngön kata sandi barô.",
"password-same-as-username": "Kata sandi bek sama ngon nan ureueng ngui atawa alamat emel.",
"if-you-already-have-an-account": "Meunyoe droeneueh ka na akun",
"register": "Daftar",
"forgot-password": "Tuwo sandi",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Tingkap gagai (detik)",
"accounts-lockout-settings-updated": "Peuatôran peulindông brute force ka geuubah",
"accounts-lockout-locked-users": "Ureueng Ngui Geugunci",
"admin-audit-log": "Log Audit",
"admin-audit-log-info": "Peue nyang geupeubuet lé peungatô situs: meurubah ureueng ngui, tamöng, keubah data ngon saleunan, nyang barô phon. Kata sandi ngon rahsia hana tom geucatat. Geukeubah sithon.",
"admin-audit-log-when": "Watèe",
"admin-audit-log-who": "Peungatô",
"admin-audit-log-action": "Buet",
"admin-audit-log-details": "Haba Leungkap",
"admin-audit-log-empty": "Goh na sapeue nyang geucatat.",
"accounts-lockout-locked-users-info": "Ureueng ngui nyang jinoe geugunci sabab le that usaha tamong nyang gagai",
"accounts-lockout-no-locked-users": "Jinoe hana ureueng ngui nyang geugunci",
"accounts-lockout-failed-attempts": "Usaha Gagai",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "Storan objek kompatibel S3: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces ngön nyang laén.",
"s3-force-path-style": "Peupaksa URL gaya-rot",
"s3-force-path-style-description": "Geupeureulee lé MinIO ngön le that server kompatibel S3 nyang kon AWS.",
"s3-sse": "Enkripsi bak peuladèn",
"s3-sse-none": "Hana geulakèe (standar bucket)",
"s3-sse-description": "S3 geu-enkripsi bandum lampiran, avatar ngon saleunan nyang geutuléh lé WeKan, ngon geubuka enkripsi keu WeKan watèe geubaca.",
"s3-sse-kms-key-id": "ID kunci KMS keu SSE-KMS (soh = kunci standar akun)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Teumpat keubah Microsoft Azure Blob.",
"azure-account-name": "Nan akun",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Scan kode QR nyoe ngön aplikasi otentikator (umpama Google Authenticator, Authy), laju neutuléh kode 6 angka nyang jipeuleumah keu peukong.",
"twoFactorAuth-manual-entry": "Atawa tuléh kode nyoe ngon manual: ",
"twoFactorAuth-confirm": "Peukong ngön Peuhudép",
"two-factor-required-title": "Atô Otentikasi Dua Faktor",
"two-factor-required-text": "Situs nyoe meuphôm otentikasi dua faktor keu akun droeneuh. Atô dilèe mangat jeut neulanjut.",
"twoFactorCode-prompt": "Tuléh kode otentikasi 6 angka droe neuh",
"twoFactorCode-submit": "Peukong",
"twoFactorCode-cancel": "Pubateuë",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Spoed",
"progress": "Vordering",
"password-again": "Wagwoord (weer)",
"password-pwned": "Hierdie wagwoord het in 'n datalek verskyn. Kies 'n ander een.",
"password-too-short": "Die wagwoord moet ten minste __min__ karakters lank wees.",
"password-length-report-title": "Wagwoordlengte",
"password-length-report-desc": "Rekeninge wat aangemeld het met 'n wagwoord korter as die minimum lengte. Slegs die blaaier weet hoe lank 'n wagwoord is, dus rapporteer dit 'n kort een ná aanmelding; die wagwoord en sy lengte verlaat dit nooit. Reggestel is wanneer die rekening se wagwoord verander is - 'n rekening wat weer met 'n kort wagwoord aanmeld, word weer as te kort getoon.",
"password-length-no-results": "Geen rekeninge hier nie.",
"password-length-first-login": "Eerste aanmelding",
"password-length-last-login": "Laaste aanmelding",
"password-length-login-count": "Aantal kere aangemeld",
"password-length-fixed": "Reggestel",
"password-length-filter-short": "Te kort wagwoord",
"password-length-filter-fixed": "Reggestel na lank genoeg wagwoord",
"password-length-filter-all": "Alles",
"password-min-length": "Minimum wagwoordlengte",
"password-min-length-description": "Aanmelding met 'n korter wagwoord word hieronder gelys. Aanmelding word nooit daarom geweier nie.",
"password-min-length-enforce": "Dwing die minimum lengte af",
"password-min-length-enforce-description": "'n Korter nuwe wagwoord word geweier, en 'n rekening wat met 'n korter wagwoord aanmeld, moet dit eers verander voor enigiets anders. Om dit te verander, teken elke sessie van die rekening uit. LDAP-, OAuth2-, OIDC-, SAML-, CAS- en Sandstorm-wagwoorde behoort aan hul verskaffer en word nie nagegaan nie.",
"password-length-saved": "Gestoor.",
"password-length-from-environment": "'n Waarde wat in die bediener se omgewing gestel is, is van krag; om hier te stoor, oorskryf dit.",
"password-change-required-title": "Verander jou wagwoord",
"password-change-required-text": "Hierdie werf vereis nou wagwoorde van ten minste __min__ karakters, en joune is korter. Kies 'n langer een om voort te gaan. Elke sessie van jou rekening word dan uitgeteken, en jy meld weer aan met die nuwe wagwoord.",
"password-same-as-username": "Die wagwoord mag nie die gebruikersnaam of die e-posadres wees nie.",
"if-you-already-have-an-account": "As jy reeds 'n rekening het",
"register": "Registreer",
"forgot-password": "Wagwoord vergeet",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Mislukkingsvenster (sekondes)",
"accounts-lockout-settings-updated": "Brute force beskerming instellings is opgedateer",
"accounts-lockout-locked-users": "Geslote gebruikers",
"admin-audit-log": "Ouditlog",
"admin-audit-log-info": "Wat werfadministrateurs gedoen het: gebruiker-, aanmeld-, berging- en rugsteunveranderinge, nuutste eerste. Wagwoorde en geheime word nooit aangeteken nie. Word vir 'n jaar gehou.",
"admin-audit-log-when": "Wanneer",
"admin-audit-log-who": "Administrateur",
"admin-audit-log-action": "Aksie",
"admin-audit-log-details": "Besonderhede",
"admin-audit-log-empty": "Nog niks aangeteken nie.",
"accounts-lockout-locked-users-info": "Gebruikers word tans uitgesluit weens te veel mislukte aanmeldpogings",
"accounts-lockout-no-locked-users": "Daar is tans geen geslote gebruikers nie",
"accounts-lockout-failed-attempts": "Mislukte pogings",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-versoenbare objekberging: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces en ander.",
"s3-force-path-style": "Dwing pad-styl URL's af",
"s3-force-path-style-description": "Vereis deur MinIO en die meeste nie-AWS S3-versoenbare bedieners.",
"s3-sse": "Bedienerkant-enkripsie",
"s3-sse-none": "Niks versoek nie (die emmer se verstek)",
"s3-sse-description": "S3 enkripteer elke aanhegsel, avatar en rugsteun wat WeKan skryf, en dekripteer dit vir WeKan wanneer dit gelees word.",
"s3-sse-kms-key-id": "KMS-sleutel-ID vir SSE-KMS (leeg = die rekening se verstek sleutel)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob-berging.",
"azure-account-name": "Rekeningnaam",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Skandeer hierdie QR-kode met 'n verifikasie-toepassing (bv. Google Authenticator, Authy), voer dan die 6-syfer-kode wat gewys word in om te bevestig.",
"twoFactorAuth-manual-entry": "Of voer hierdie kode self in: ",
"twoFactorAuth-confirm": "Bevestig en aktiveer",
"two-factor-required-title": "Stel twee-faktor-verifikasie op",
"two-factor-required-text": "Hierdie werf vereis twee-faktor-verifikasie vir jou rekening. Stel dit op om voort te gaan.",
"twoFactorCode-prompt": "Voer jou 6-syfer-verifikasiekode in",
"twoFactorCode-submit": "Verifieer",
"twoFactorCode-cancel": "Kanselleer",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Spoed",
"progress": "Vordering",
"password-again": "Wagwoord (weer)",
"password-pwned": "Hierdie wagwoord het in 'n datalek verskyn. Kies 'n ander een.",
"password-too-short": "Die wagwoord moet ten minste __min__ karakters lank wees.",
"password-length-report-title": "Wagwoordlengte",
"password-length-report-desc": "Rekeninge wat aangemeld het met 'n wagwoord korter as die minimum lengte. Slegs die blaaier weet hoe lank 'n wagwoord is, dus rapporteer dit 'n kort een ná aanmelding; die wagwoord en sy lengte verlaat dit nooit. Reggestel is wanneer die rekening se wagwoord verander is - 'n rekening wat weer met 'n kort wagwoord aanmeld, word weer as te kort getoon.",
"password-length-no-results": "Geen rekeninge hier nie.",
"password-length-first-login": "Eerste aanmelding",
"password-length-last-login": "Laaste aanmelding",
"password-length-login-count": "Aantal kere aangemeld",
"password-length-fixed": "Reggestel",
"password-length-filter-short": "Te kort wagwoord",
"password-length-filter-fixed": "Reggestel na lank genoeg wagwoord",
"password-length-filter-all": "Alles",
"password-min-length": "Minimum wagwoordlengte",
"password-min-length-description": "Aanmelding met 'n korter wagwoord word hieronder gelys. Aanmelding word nooit daarom geweier nie.",
"password-min-length-enforce": "Dwing die minimum lengte af",
"password-min-length-enforce-description": "'n Korter nuwe wagwoord word geweier, en 'n rekening wat met 'n korter wagwoord aanmeld, moet dit eers verander voor enigiets anders. Om dit te verander, teken elke sessie van die rekening uit. LDAP-, OAuth2-, OIDC-, SAML-, CAS- en Sandstorm-wagwoorde behoort aan hul verskaffer en word nie nagegaan nie.",
"password-length-saved": "Gestoor.",
"password-length-from-environment": "'n Waarde wat in die bediener se omgewing gestel is, is van krag; om hier te stoor, oorskryf dit.",
"password-change-required-title": "Verander jou wagwoord",
"password-change-required-text": "Hierdie werf vereis nou wagwoorde van ten minste __min__ karakters, en joune is korter. Kies 'n langer een om voort te gaan. Elke sessie van jou rekening word dan uitgeteken, en jy meld weer aan met die nuwe wagwoord.",
"password-same-as-username": "Die wagwoord mag nie die gebruikersnaam of die e-posadres wees nie.",
"if-you-already-have-an-account": "As jy reeds 'n rekening het",
"register": "Registreer",
"forgot-password": "Wagwoord vergeet",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Mislukkingsvenster (sekondes)",
"accounts-lockout-settings-updated": "Brute force beskerming instellings is opgedateer",
"accounts-lockout-locked-users": "Geslote gebruikers",
"admin-audit-log": "Ouditlog",
"admin-audit-log-info": "Wat werfadministrateurs gedoen het: gebruiker-, aanmeld-, berging- en rugsteunveranderinge, nuutste eerste. Wagwoorde en geheime word nooit aangeteken nie. Word vir 'n jaar gehou.",
"admin-audit-log-when": "Wanneer",
"admin-audit-log-who": "Administrateur",
"admin-audit-log-action": "Aksie",
"admin-audit-log-details": "Besonderhede",
"admin-audit-log-empty": "Nog niks aangeteken nie.",
"accounts-lockout-locked-users-info": "Gebruikers word tans uitgesluit weens te veel mislukte aanmeldpogings",
"accounts-lockout-no-locked-users": "Daar is tans geen geslote gebruikers nie",
"accounts-lockout-failed-attempts": "Mislukte pogings",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-versoenbare objekberging: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces en ander.",
"s3-force-path-style": "Dwing pad-styl URL's af",
"s3-force-path-style-description": "Vereis deur MinIO en die meeste nie-AWS S3-versoenbare bedieners.",
"s3-sse": "Bedienerkant-enkripsie",
"s3-sse-none": "Niks versoek nie (die emmer se verstek)",
"s3-sse-description": "S3 enkripteer elke aanhegsel, avatar en rugsteun wat WeKan skryf, en dekripteer dit vir WeKan wanneer dit gelees word.",
"s3-sse-kms-key-id": "KMS-sleutel-ID vir SSE-KMS (leeg = die rekening se verstek sleutel)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob-berging.",
"azure-account-name": "Rekeningnaam",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Skandeer hierdie QR-kode met 'n verifikasie-toepassing (bv. Google Authenticator, Authy), voer dan die 6-syfer-kode wat gewys word in om te bevestig.",
"twoFactorAuth-manual-entry": "Of voer hierdie kode self in: ",
"twoFactorAuth-confirm": "Bevestig en aktiveer",
"two-factor-required-title": "Stel twee-faktor-verifikasie op",
"two-factor-required-text": "Hierdie werf vereis twee-faktor-verifikasie vir jou rekening. Stel dit op om voort te gaan.",
"twoFactorCode-prompt": "Voer jou 6-syfer-verifikasiekode in",
"twoFactorCode-submit": "Verifieer",
"twoFactorCode-cancel": "Kanselleer",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Ahoɔhare",
"progress": "Nkɔso",
"password-again": "Asɛmfua a wode kɔ mu (san hyɛ bio)",
"password-pwned": "Saa ahintasɛm yi apue wɔ data a ayi adi bi mu. Paw foforo.",
"password-too-short": "Ɛsɛ sɛ ahintasɛm no nkyerɛwde dodow yɛ __min__ anaa ɛboro saa.",
"password-length-report-title": "Ahintasɛm tenten",
"password-length-report-desc": "Akontaa a wɔde ahintasɛm a ɛyɛ tiaa sen tenten a ɛsɛ no na ɛkɔɔ mu. Brawsa no nko ara na enim ahintasɛm tenten, enti ɛbɔ amanneɛ fa tiaa bi ho wɔ wɔkɔ mu akyi; ahintasɛm no ne ne tenten mfi brawsa no mu da. Wɔasiesie kyerɛ bere a wɔsesaa akontaa no ahintasɛm - akontaa a ɛsan de ahintasɛm tiaa kɔ mu no, wɔsan kyerɛ sɛ ɛyɛ tiaa dodo.",
"password-length-no-results": "Akontaa biara nni ha.",
"password-length-first-login": "Kɔ mu a edi kan",
"password-length-last-login": "Kɔ mu a etwa to",
"password-length-login-count": "Mpɛn dodow a wɔkɔɔ mu",
"password-length-fixed": "Wɔasiesie",
"password-length-filter-short": "Ahintasɛm a ɛyɛ tiaa dodo",
"password-length-filter-fixed": "Wɔasiesie akɔ ahintasɛm a ɛware sɛnea ɛsɛ",
"password-length-filter-all": "Ne nyinaa",
"password-min-length": "Ahintasɛm tenten a ɛsɛ sɛ ɛyɛ kakraa koraa",
"password-min-length-description": "Wɔkyerɛw nkɔ-mu a wɔde ahintasɛm tiaa yɛe no wɔ ase ha. Wɔmpo nkɔ-mu da esiane eyi nti.",
"password-min-length-enforce": "Hyɛ tenten kakraa koraa no so",
"password-min-length-enforce-description": "Wɔpo ahintasɛm foforo a ɛyɛ tiaa, na akontaa a ɛde ahintasɛm tiaa kɔ mu no, ɛsɛ sɛ ɛsesa no ansa na ayɛ biribi foforo. Sɛ wosesa a, ɛyi akontaa no nhyiamu biara fi mu. LDAP, OAuth2, OIDC, SAML, CAS ne Sandstorm ahintasɛm yɛ wɔn mamfo dea, na wɔnhwɛ mu.",
"password-length-saved": "Wɔasie.",
"password-length-from-environment": "Botae bi a wɔde ahyɛ server no tebea mu na ɛreyɛ adwuma; sɛ wosie wɔ ha a, ɛsesa no.",
"password-change-required-title": "Sesa wo ahintasɛm",
"password-change-required-text": "Saa site yi hwehwɛ seesei sɛ ahintasɛm nkyerɛwde dodow yɛ __min__ anaa ɛboro saa, na wo dea yɛ tiaa. Paw nea ɛware kakra na toa so. Afei wobeyi wo akontaa nhyiamu biara afi mu, na wode ahintasɛm foforo no bɛsan akɔ mu.",
"password-same-as-username": "Ɛnsɛ sɛ ahintasɛm no yɛ dwumadini din anaa ɛmɛl address no.",
"if-you-already-have-an-account": "Sɛ wowɔ akontaabu dedaw a",
"register": "Kyerɛw wo din",
"forgot-password": "Wo werɛ afi ahintasɛm",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Bere a wɔkan nkogu wɔ mu (sɛkɛnd)",
"accounts-lockout-settings-updated": "Wɔayɛ nsɛmfua ahintaw pii sɔhwɛ ntua ho banbɔ nhyehyɛe no foforo",
"accounts-lockout-locked-users": "Locked Ɔdefos",
"admin-audit-log": "Nhwehwɛmu nkrataa",
"admin-audit-log-info": "Nea sohwɛfoɔ a wɔwɔ site no so ayɛ: dwumadini, ahyɛnmu, akorae ne nsɛm akora nsakrae, foforo di kan. Wɔnkyerɛw ahintasɛm ne kokoam nsɛm da. Wɔkora so afe baako.",
"admin-audit-log-when": "Bere",
"admin-audit-log-who": "Sohwɛfoɔ",
"admin-audit-log-action": "Adeyɛ",
"admin-audit-log-details": "Nkyerɛkyerɛmu",
"admin-audit-log-empty": "Biribiara nni hɔ a wɔakyerɛw ase.",
"accounts-lockout-locked-users-info": "Wɔn a wɔde dwumadi yi di dwuma a wɔasiw wɔn kwan seesei esiane sɛ wɔabɔ mmɔden mpɛn pii sɛ wɔbɛkɔ mu na wɔantumi nti",
"accounts-lockout-no-locked-users": "Seesei obiara nni hɔ a wɔasiw ne kwan",
"accounts-lockout-failed-attempts": "Mfomso bi sii wɔ dwumadi yi mu",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "Nneɛma akorabea a ɛne S3 yɛ adwuma: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces ne afoforo.",
"s3-force-path-style": "Hyɛ sɛ wɔmfa path-style URL nni dwuma",
"s3-force-path-style-description": "MinIO ne sɛɛva dodow no ara a ɛne S3 yɛ adwuma na ɛnyɛ AWS no hia eyi.",
"s3-sse": "Som afiri fam ahintakyerɛw",
"s3-sse-none": "Wɔammisa biribiara (bucket no deɛ wɔahyɛ dedaw)",
"s3-sse-description": "S3 de ahintakyerɛw kata deɛ wɔde aka ho, avatar ne nsɛm akora biara a WeKan kyerɛw, na ɔyi ahintakyerɛw no ma WeKan bere a wɔkenkan.",
"s3-sse-kms-key-id": "KMS safe ID ma SSE-KMS (hwee nni mu = akawnt no safe a wɔahyɛ dedaw)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Akontaabu din",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Fa nokwaredi app san QR koodu yi (sɛnea, Google Authenticator, Authy), afei hyɛ nɔma 6 a ɛda adi no ma wɔnhu mu.",
"twoFactorAuth-manual-entry": "Anaasɛ fa wo nsa hyɛ koodu yi: ",
"twoFactorAuth-confirm": "Si so dua na ma so",
"two-factor-required-title": "Hyehyɛ nneɛma abien nokwaredi",
"two-factor-required-text": "Saa site yi hwehwɛ nneɛma abien nokwaredi ma wo akawnt. Hyehyɛ no na woakɔ so.",
"twoFactorCode-prompt": "Hyɛ wo nokwaredi nɔma 6 no",
"twoFactorCode-submit": "Si so dua",
"twoFactorCode-cancel": "Twa mu",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "ፍጥነት",
"progress": "ሂደት",
"password-again": "የይለፍ ቃል (እንደገና)",
"password-pwned": "ይህ የይለፍ ቃል በመረጃ ፍሰት ውስጥ ታይቷል። ሌላ ይምረጡ።",
"password-too-short": "የይለፍ ቃሉ ቢያንስ __min__ ቁምፊዎች ሊኖሩት ይገባል።",
"password-length-report-title": "የይለፍ ቃል ርዝመት",
"password-length-report-desc": "ከዝቅተኛው ርዝመት ባጠረ የይለፍ ቃል የገቡ መለያዎች። የይለፍ ቃል ምን ያህል እንደሚረዝም የሚያውቀው አሳሹ ብቻ ነው፣ ስለዚህ ከገቡ በኋላ አጭር የይለፍ ቃልን ሪፖርት ያደርጋል፤ የይለፍ ቃሉና ርዝመቱ ከአሳሹ በጭራሽ አይወጡም። ተስተካክሏል ማለት የመለያው የይለፍ ቃል የተቀየረበት ጊዜ ነው - እንደገና በአጭር የይለፍ ቃል የሚገባ መለያ እንደገና በጣም አጭር ሆኖ ይታያል።",
"password-length-no-results": "እዚህ ምንም መለያዎች የሉም።",
"password-length-first-login": "የመጀመሪያ መግቢያ",
"password-length-last-login": "የመጨረሻ መግቢያ",
"password-length-login-count": "የገቡበት ብዛት",
"password-length-fixed": "ተስተካክሏል",
"password-length-filter-short": "በጣም አጭር የይለፍ ቃል",
"password-length-filter-fixed": "በበቂ ረጅም የይለፍ ቃል ተስተካክሏል",
"password-length-filter-all": "ሁሉም",
"password-min-length": "ዝቅተኛ የይለፍ ቃል ርዝመት",
"password-min-length-description": "በአጭር የይለፍ ቃል መግባት ከዚህ በታች ይዘረዘራል። በዚህ ምክንያት መግባት በጭራሽ አይከለከልም።",
"password-min-length-enforce": "ዝቅተኛውን ርዝመት አስገድድ",
"password-min-length-enforce-description": "አጭር አዲስ የይለፍ ቃል ውድቅ ይደረጋል፣ እና በአጭር የይለፍ ቃል የሚገባ መለያ ከማንኛውም ነገር በፊት መቀየር አለበት። መቀየሩ የመለያውን ሁሉንም ክፍለ ጊዜዎች ያስወጣል። የLDAP፣ OAuth2፣ OIDC፣ SAML፣ CAS እና Sandstorm የይለፍ ቃሎች የአቅራቢያቸው ናቸው፣ አይፈተሹም።",
"password-length-saved": "ተቀምጧል።",
"password-length-from-environment": "በአገልጋዩ አካባቢ የተቀመጠ እሴት በሥራ ላይ ነው፤ እዚህ ማስቀመጥ ይሽረዋል።",
"password-change-required-title": "የይለፍ ቃልዎን ይቀይሩ",
"password-change-required-text": "ይህ ጣቢያ አሁን ቢያንስ __min__ ቁምፊዎች ያላቸውን የይለፍ ቃሎች ይፈልጋል፣ የእርስዎም አጭር ነው። ለመቀጠል ረዘም ያለ ይምረጡ። ከዚያ የመለያዎ ሁሉም ክፍለ ጊዜዎች ይወጣሉ፣ እና በአዲሱ የይለፍ ቃል እንደገና ይገባሉ።",
"password-same-as-username": "የይለፍ ቃሉ የተጠቃሚ ስሙ ወይም የኢሜይል አድራሻው መሆን የለበትም።",
"if-you-already-have-an-account": "አስቀድመው መለያ ካለዎት",
"register": "ተመዝገብ",
"forgot-password": "የይለፍ ቃል ረሱ",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "የውድቀት የጊዜ መስኮት (ሰከንዶች)",
"accounts-lockout-settings-updated": "የጉልበት ጥቃት መከላከያ ቅንብሮች ተዘምነዋል",
"accounts-lockout-locked-users": "የተቆለፉ ተጠቃሚዎች",
"admin-audit-log": "የኦዲት መዝገብ",
"admin-audit-log-info": "የጣቢያው አስተዳዳሪዎች ያደረጉት፦ የተጠቃሚ፣ የመግቢያ፣ የማከማቻ እና የምትኬ ለውጦች፣ አዲሶቹ መጀመሪያ። የይለፍ ቃላት እና ሚስጥሮች በፍጹም አይመዘገቡም። ለአንድ ዓመት ይቀመጣል።",
"admin-audit-log-when": "መቼ",
"admin-audit-log-who": "አስተዳዳሪ",
"admin-audit-log-action": "ድርጊት",
"admin-audit-log-details": "ዝርዝሮች",
"admin-audit-log-empty": "እስካሁን ምንም አልተመዘገበም።",
"accounts-lockout-locked-users-info": "በብዙ ያልተሳኩ የመግቢያ ሙከራዎች ምክንያት አሁን የተቆለፉ ተጠቃሚዎች",
"accounts-lockout-no-locked-users": "አሁን ምንም የተቆለፈ ተጠቃሚ የለም",
"accounts-lockout-failed-attempts": "ያልተሳኩ ሙከራዎች",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "ከS3 ጋር ተኳሃኝ የነገር ማከማቻ፦ AWS S3፣ MinIO፣ Cloudflare R2፣ Backblaze B2፣ Wasabi፣ DigitalOcean Spaces እና ሌሎች።",
"s3-force-path-style": "የዱካ ቅጥ URLዎችን አስገድድ",
"s3-force-path-style-description": "ለMinIO እና ለአብዛኞቹ AWS ያልሆኑ S3-ተኳኋኝ አገልጋዮች ያስፈልጋል።",
"s3-sse": "በአገልጋይ በኩል ምስጠራ",
"s3-sse-none": "ምንም አልተጠየቀም (የባልዲው ነባሪ)",
"s3-sse-description": "S3 WeKan የሚጽፈውን እያንዳንዱን አባሪ፣ አምሳያ እና ምትኬ ያመሰጥራል፣ ሲነበብም ለWeKan ይፈታዋል።",
"s3-sse-kms-key-id": "ለSSE-KMS የKMS ቁልፍ መታወቂያ (ባዶ = የመለያው ነባሪ ቁልፍ)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "የመለያ ስም",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "ይህን የQR ኮድ በማረጋገጫ መተግበሪያ ይቃኙ (ለምሳሌ Google Authenticator, Authy)፣ ከዚያ ለማረጋገጥ የሚታየውን የ6- አሃዝ ኮድ ያስገቡ።",
"twoFactorAuth-manual-entry": "ወይም ይህን ኮድ በእጅ ያስገቡ፦ ",
"twoFactorAuth-confirm": "አረጋግጥና አንቃ",
"two-factor-required-title": "ባለ ሁለት-ደረጃ ማረጋገጫን ያዋቅሩ",
"two-factor-required-text": "ይህ ጣቢያ ለመለያዎ ባለ ሁለት-ደረጃ ማረጋገጫ ይፈልጋል። ለመቀጠል ያዋቅሩት።",
"twoFactorCode-prompt": "የ6-አሃዝ ማረጋገጫ ኮድዎን ያስገቡ",
"twoFactorCode-submit": "አረጋግጥ",
"twoFactorCode-cancel": "ሰርዝ",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Velocidad",
"progress": "Progreso",
"password-again": "Clau (repetir)",
"password-pwned": "Ista clau de paso ha amaneciu en una filtración de datos. Tría-ne unatra.",
"password-too-short": "La clau de paso ha de tener a lo menos __min__ caracters.",
"password-length-report-title": "Longaria d'a clau de paso",
"password-length-report-desc": "Cuentas que dentroron con una clau de paso más curta que a longaria minima. Nomás o navegador sabe quán larga ye una clau de paso, asinas que informa d'una curta dimpués de dentrar; a clau de paso y a suya longaria nunca no salen d'ell. Apanyau ye quan se cambió a clau de paso d'a cuenta - una cuenta que torna a dentrar con una clau de paso curta se torna a amostrar como masiau curta.",
"password-length-no-results": "No i hai cuentas aquí.",
"password-length-first-login": "Primer acceso",
"password-length-last-login": "Zaguer acceso",
"password-length-login-count": "Vegadas que ha dentrau",
"password-length-fixed": "Apanyau",
"password-length-filter-short": "Clau de paso masiau curta",
"password-length-filter-fixed": "Apanyau con una clau de paso prou larga",
"password-length-filter-all": "Totz",
"password-min-length": "Longaria minima d'a clau de paso",
"password-min-length-description": "Dentrar con una clau de paso más curta se lista aquí baixo. Nunca no se refusa l'acceso por ixo.",
"password-min-length-enforce": "Fer cumplir a longaria minima",
"password-min-length-enforce-description": "Se refusa una nueva clau de paso más curta, y una cuenta que dentra con una clau de paso más curta ha de cambiar-la antes que cosa más. Cambiar-la zarra totas as sesions d'a cuenta. As claus de paso de LDAP, OAuth2, OIDC, SAML, CAS y Sandstorm son d'o suyo proveyedor, y no se comprebán.",
"password-length-saved": "Alzau.",
"password-length-from-environment": "Ye en vigor una valor definida en l'entorno d'o servidor; alzar aquí la sobrescribe.",
"password-change-required-title": "Cambia a tuya clau de paso",
"password-change-required-text": "Iste puesto requiere agora claus de paso de a lo menos __min__ caracters, y a tuya ye más curta. Tría-ne una más larga pa continar. Dimpués se zarran totas as sesions d'a tuya cuenta, y tornas a dentrar con a nueva clau de paso.",
"password-same-as-username": "La clau de paso no puede estar o nombre d'usuario ni l'adreza de correu electronico.",
"if-you-already-have-an-account": "Si ya tiens una cuenta",
"register": "Rechistrar-se",
"forgot-password": "He xublidau a clau de paso",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Ventana de fallos (segundos)",
"accounts-lockout-settings-updated": "La configuración de protección de fuerza bruta s'ha actualizado",
"accounts-lockout-locked-users": "Usuarios bloqueados",
"admin-audit-log": "Rechistro d'auditoría",
"admin-audit-log-info": "Lo que han feito os administradors d'o sitio: cambeos d'usuarios, d'inicio de sesión, d'almagazenamiento y de copias de seguridat, os mas recients primero. As claus de paso y os secretos no se rechistran nunca. Se conserva un anyo.",
"admin-audit-log-when": "Quan",
"admin-audit-log-who": "Administrador",
"admin-audit-log-action": "Acción",
"admin-audit-log-details": "Detalles",
"admin-audit-log-empty": "Encara no s'ha rechistrau cosa.",
"accounts-lockout-locked-users-info": "Usuarios blocaus actualment por masiaus intentos fallius d'iniciar sesión",
"accounts-lockout-no-locked-users": "Actualment no i hai usuarios blocaus",
"accounts-lockout-failed-attempts": "Intentos fallidos",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "Almagazenamiento d’obchectos compatible con S3: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces y atros.",
"s3-force-path-style": "Forzar URLs de estilo de ruta",
"s3-force-path-style-description": "Requerido por MinIO y la mayoría de os servidores compatibles con S3 que no son de AWS.",
"s3-sse": "Cifrau en o servidor",
"s3-sse-none": "Garra demandau (o valor por defecto d'o bucket)",
"s3-sse-description": "S3 cifra cada adchunto, avatar y copia de seguridat que escribe WeKan, y lo descifra pa WeKan quan se leye.",
"s3-sse-kms-key-id": "ID d'a clau KMS pa SSE-KMS (vuedo = la clau por defecto d'a cuenta)",
"azure-blob-storage": "Almagazenamiento Azure Blob",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Nombre de cuenta",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Escaneye iste codigo QR con una app d'autenticación (per eixemplo, Google Authenticator, Authy), dimpués introduzca lo codigo de 6 digitos que s'amuestra ta confirmar.",
"twoFactorAuth-manual-entry": "U introduzca iste codigo manualment: ",
"twoFactorAuth-confirm": "Confirmar y Activar",
"two-factor-required-title": "Configura l'autenticación de dos factors",
"two-factor-required-text": "Iste sitio requiere l'autenticación de dos factors pa la tuya cuenta. Configura-la pa continar.",
"twoFactorCode-prompt": "Introduzca lo suyo codigo d'autenticación de 6 digitos",
"twoFactorCode-submit": "Verificar",
"twoFactorCode-cancel": "Cancelar",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "السرعة",
"progress": "التقدّم",
"password-again": "كلمة المرور (مرة أخرى)",
"password-pwned": "ظهرت كلمة المرور هذه في تسريب بيانات. اختر كلمة مرور أخرى.",
"password-too-short": "يجب ألا يقل طول كلمة المرور عن __min__ حرفًا.",
"password-length-report-title": "طول كلمة المرور",
"password-length-report-desc": "الحسابات التي سجّلت الدخول بكلمة مرور أقصر من الحد الأدنى للطول. المتصفح وحده يعرف طول كلمة المرور، لذا فهو يُبلغ عن الكلمة القصيرة بعد تسجيل الدخول؛ ولا تغادره كلمة المرور ولا طولها أبدًا. «تم الإصلاح» هو وقت تغيير كلمة مرور الحساب - والحساب الذي يسجّل الدخول بكلمة مرور قصيرة مرة أخرى يُعرض مجددًا على أنه قصير جدًا.",
"password-length-no-results": "لا توجد حسابات هنا.",
"password-length-first-login": "أول تسجيل دخول",
"password-length-last-login": "آخر تسجيل دخول",
"password-length-login-count": "عدد مرات تسجيل الدخول",
"password-length-fixed": "تم الإصلاح",
"password-length-filter-short": "كلمة مرور قصيرة جدًا",
"password-length-filter-fixed": "تم الإصلاح إلى كلمة مرور بطول كافٍ",
"password-length-filter-all": "الكل",
"password-min-length": "الحد الأدنى لطول كلمة المرور",
"password-min-length-description": "يُدرَج أدناه تسجيل الدخول بكلمة مرور أقصر. ولا يُرفض تسجيل الدخول أبدًا بسبب ذلك.",
"password-min-length-enforce": "فرض الحد الأدنى للطول",
"password-min-length-enforce-description": "تُرفض كلمة المرور الجديدة الأقصر، ويجب على الحساب الذي يسجّل الدخول بكلمة مرور أقصر تغييرها قبل أي شيء آخر. يؤدي تغييرها إلى تسجيل خروج جميع جلسات الحساب. كلمات مرور LDAP وOAuth2 وOIDC وSAML وCAS وSandstorm تخص مزوّديها، ولا يتم فحصها.",
"password-length-saved": "تم الحفظ.",
"password-length-from-environment": "هناك قيمة مضبوطة في بيئة الخادم سارية المفعول؛ الحفظ هنا يتجاوزها.",
"password-change-required-title": "غيّر كلمة المرور",
"password-change-required-text": "يتطلب هذا الموقع الآن كلمات مرور لا تقل عن __min__ حرفًا، وكلمة مرورك أقصر. اختر كلمة أطول للمتابعة. بعد ذلك يتم تسجيل خروج جميع جلسات حسابك، وتسجّل الدخول مرة أخرى بكلمة المرور الجديدة.",
"password-same-as-username": "يجب ألا تكون كلمة المرور هي اسم المستخدم أو عنوان البريد الإلكتروني.",
"if-you-already-have-an-account": "إذا كان لديك حساب بالفعل",
"register": "تسجيل",
"forgot-password": "نسيت كلمة المرور",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "نافذة الإخفاق (بالثواني)",
"accounts-lockout-settings-updated": "تم تحديث إعدادات الحماية من القوة الغاشمة",
"accounts-lockout-locked-users": "المستخدمون المقفلون",
"admin-audit-log": "سجل التدقيق",
"admin-audit-log-info": "ما قام به مديرو الموقع: تغييرات المستخدمين وتسجيل الدخول والتخزين والنسخ الاحتياطي، الأحدث أولاً. لا تُسجَّل كلمات المرور والأسرار أبدًا. يُحتفَظ به لمدة سنة.",
"admin-audit-log-when": "الوقت",
"admin-audit-log-who": "المدير",
"admin-audit-log-action": "الإجراء",
"admin-audit-log-details": "التفاصيل",
"admin-audit-log-empty": "لم يُسجَّل شيء بعد.",
"accounts-lockout-locked-users-info": "المستخدمون المقفلون حالياً بسبب عدد كبير جداً من محاولات تسجيل الدخول الفاشلة",
"accounts-lockout-no-locked-users": "لا يوجد حالياً مستخدمون مقفلون",
"accounts-lockout-failed-attempts": "المحاولات الفاشلة",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "تخزين كائنات متوافق مع S3: AWS S3، MinIO، Cloudflare R2، Backblaze B2، Wasabi، DigitalOcean Spaces وغيرها.",
"s3-force-path-style": "فرض روابط بنمط المسار (path-style)",
"s3-force-path-style-description": "مطلوب لـ MinIO ومعظم الخوادم المتوافقة مع S3 غير التابعة لـ AWS.",
"s3-sse": "التشفير من جانب الخادم",
"s3-sse-none": "لم يُطلب أي تشفير (الإعداد الافتراضي للحاوية)",
"s3-sse-description": "يشفّر S3 كل مرفق وصورة رمزية ونسخة احتياطية يكتبها WeKan، ويفك تشفيرها لـ WeKan عند قراءتها.",
"s3-sse-kms-key-id": "معرّف مفتاح KMS لـ SSE-KMS (فارغ = المفتاح الافتراضي للحساب)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "تخزين Microsoft Azure Blob.",
"azure-account-name": "اسم الحساب",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "امسح رمز QR هذا باستخدام تطبيق مصادقة (مثل Google Authenticator أو Authy)، ثم أدخل الرمز المكوّن من 6 أرقام الظاهر للتأكيد.",
"twoFactorAuth-manual-entry": "أو أدخل هذا الرمز يدويًا: ",
"twoFactorAuth-confirm": "تأكيد وتفعيل",
"two-factor-required-title": "إعداد المصادقة الثنائية",
"two-factor-required-text": "يتطلب هذا الموقع المصادقة الثنائية لحسابك. قم بإعدادها للمتابعة.",
"twoFactorCode-prompt": "أدخل رمز المصادقة المكوّن من 6 أرقام",
"twoFactorCode-submit": "تحقق",
"twoFactorCode-cancel": "إلغاء",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "السرعة",
"progress": "التقدّم",
"password-again": "كلمة المرور (مرة أخرى)",
"password-pwned": "ظهرت كلمة المرور هذه في تسريب بيانات. اختر كلمة مرور أخرى.",
"password-too-short": "يجب ألا يقل طول كلمة المرور عن __min__ حرفًا.",
"password-length-report-title": "طول كلمة المرور",
"password-length-report-desc": "الحسابات التي سجّلت الدخول بكلمة مرور أقصر من الحد الأدنى للطول. المتصفح وحده يعرف طول كلمة المرور، لذا فهو يُبلغ عن الكلمة القصيرة بعد تسجيل الدخول؛ ولا تغادره كلمة المرور ولا طولها أبدًا. «تم الإصلاح» هو وقت تغيير كلمة مرور الحساب - والحساب الذي يسجّل الدخول بكلمة مرور قصيرة مرة أخرى يُعرض مجددًا على أنه قصير جدًا.",
"password-length-no-results": "لا توجد حسابات هنا.",
"password-length-first-login": "أول تسجيل دخول",
"password-length-last-login": "آخر تسجيل دخول",
"password-length-login-count": "عدد مرات تسجيل الدخول",
"password-length-fixed": "تم الإصلاح",
"password-length-filter-short": "كلمة مرور قصيرة جدًا",
"password-length-filter-fixed": "تم الإصلاح إلى كلمة مرور بطول كافٍ",
"password-length-filter-all": "الكل",
"password-min-length": "الحد الأدنى لطول كلمة المرور",
"password-min-length-description": "يُدرَج أدناه تسجيل الدخول بكلمة مرور أقصر. ولا يُرفض تسجيل الدخول أبدًا بسبب ذلك.",
"password-min-length-enforce": "فرض الحد الأدنى للطول",
"password-min-length-enforce-description": "تُرفض كلمة المرور الجديدة الأقصر، ويجب على الحساب الذي يسجّل الدخول بكلمة مرور أقصر تغييرها قبل أي شيء آخر. يؤدي تغييرها إلى تسجيل خروج جميع جلسات الحساب. كلمات مرور LDAP وOAuth2 وOIDC وSAML وCAS وSandstorm تخص مزوّديها، ولا يتم فحصها.",
"password-length-saved": "تم الحفظ.",
"password-length-from-environment": "هناك قيمة مضبوطة في بيئة الخادم سارية المفعول؛ الحفظ هنا يتجاوزها.",
"password-change-required-title": "غيّر كلمة المرور",
"password-change-required-text": "يتطلب هذا الموقع الآن كلمات مرور لا تقل عن __min__ حرفًا، وكلمة مرورك أقصر. اختر كلمة أطول للمتابعة. بعد ذلك يتم تسجيل خروج جميع جلسات حسابك، وتسجّل الدخول مرة أخرى بكلمة المرور الجديدة.",
"password-same-as-username": "يجب ألا تكون كلمة المرور هي اسم المستخدم أو عنوان البريد الإلكتروني.",
"if-you-already-have-an-account": "إذا كان لديك حساب بالفعل",
"register": "تسجيل",
"forgot-password": "نسيت كلمة المرور",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "نافذة الإخفاق (بالثواني)",
"accounts-lockout-settings-updated": "تم تحديث إعدادات الحماية من القوة الغاشمة",
"accounts-lockout-locked-users": "المستخدمون المقفلون",
"admin-audit-log": "سجل التدقيق",
"admin-audit-log-info": "ما قام به مديرو الموقع: تغييرات المستخدمين وتسجيل الدخول والتخزين والنسخ الاحتياطي، الأحدث أولاً. لا تُسجَّل كلمات المرور والأسرار أبدًا. يُحتفَظ به لمدة سنة.",
"admin-audit-log-when": "الوقت",
"admin-audit-log-who": "المدير",
"admin-audit-log-action": "الإجراء",
"admin-audit-log-details": "التفاصيل",
"admin-audit-log-empty": "لم يُسجَّل شيء بعد.",
"accounts-lockout-locked-users-info": "المستخدمون المقفلون حالياً بسبب عدد كبير جداً من محاولات تسجيل الدخول الفاشلة",
"accounts-lockout-no-locked-users": "لا يوجد حالياً مستخدمون مقفلون",
"accounts-lockout-failed-attempts": "المحاولات الفاشلة",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "تخزين كائنات متوافق مع S3: AWS S3، MinIO، Cloudflare R2، Backblaze B2، Wasabi، DigitalOcean Spaces وغيرها.",
"s3-force-path-style": "فرض روابط بنمط المسار (path-style)",
"s3-force-path-style-description": "مطلوب لـ MinIO ومعظم الخوادم المتوافقة مع S3 غير التابعة لـ AWS.",
"s3-sse": "التشفير من جانب الخادم",
"s3-sse-none": "لم يُطلب أي تشفير (الإعداد الافتراضي للحاوية)",
"s3-sse-description": "يشفّر S3 كل مرفق وصورة رمزية ونسخة احتياطية يكتبها WeKan، ويفك تشفيرها لـ WeKan عند قراءتها.",
"s3-sse-kms-key-id": "معرّف مفتاح KMS لـ SSE-KMS (فارغ = المفتاح الافتراضي للحساب)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "تخزين Microsoft Azure Blob.",
"azure-account-name": "اسم الحساب",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "امسح رمز QR هذا باستخدام تطبيق مصادقة (مثل Google Authenticator أو Authy)، ثم أدخل الرمز المكوّن من 6 أرقام الظاهر للتأكيد.",
"twoFactorAuth-manual-entry": "أو أدخل هذا الرمز يدويًا: ",
"twoFactorAuth-confirm": "تأكيد وتفعيل",
"two-factor-required-title": "إعداد المصادقة الثنائية",
"two-factor-required-text": "يتطلب هذا الموقع المصادقة الثنائية لحسابك. قم بإعدادها للمتابعة.",
"twoFactorCode-prompt": "أدخل رمز المصادقة المكوّن من 6 أرقام",
"twoFactorCode-submit": "تحقق",
"twoFactorCode-cancel": "إلغاء",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "السرعة",
"progress": "التقدّم",
"password-again": "كلمة المرور (مرة أخرى)",
"password-pwned": "ظهرت كلمة المرور هذه في تسريب بيانات. اختر كلمة مرور أخرى.",
"password-too-short": "يجب ألا يقل طول كلمة المرور عن __min__ حرفًا.",
"password-length-report-title": "طول كلمة المرور",
"password-length-report-desc": "الحسابات التي سجّلت الدخول بكلمة مرور أقصر من الحد الأدنى للطول. المتصفح وحده يعرف طول كلمة المرور، لذا فهو يُبلغ عن الكلمة القصيرة بعد تسجيل الدخول؛ ولا تغادره كلمة المرور ولا طولها أبدًا. «تم الإصلاح» هو وقت تغيير كلمة مرور الحساب - والحساب الذي يسجّل الدخول بكلمة مرور قصيرة مرة أخرى يُعرض مجددًا على أنه قصير جدًا.",
"password-length-no-results": "لا توجد حسابات هنا.",
"password-length-first-login": "أول تسجيل دخول",
"password-length-last-login": "آخر تسجيل دخول",
"password-length-login-count": "عدد مرات تسجيل الدخول",
"password-length-fixed": "تم الإصلاح",
"password-length-filter-short": "كلمة مرور قصيرة جدًا",
"password-length-filter-fixed": "تم الإصلاح إلى كلمة مرور بطول كافٍ",
"password-length-filter-all": "الكل",
"password-min-length": "الحد الأدنى لطول كلمة المرور",
"password-min-length-description": "يُدرَج أدناه تسجيل الدخول بكلمة مرور أقصر. ولا يُرفض تسجيل الدخول أبدًا بسبب ذلك.",
"password-min-length-enforce": "فرض الحد الأدنى للطول",
"password-min-length-enforce-description": "تُرفض كلمة المرور الجديدة الأقصر، ويجب على الحساب الذي يسجّل الدخول بكلمة مرور أقصر تغييرها قبل أي شيء آخر. يؤدي تغييرها إلى تسجيل خروج جميع جلسات الحساب. كلمات مرور LDAP وOAuth2 وOIDC وSAML وCAS وSandstorm تخص مزوّديها، ولا يتم فحصها.",
"password-length-saved": "تم الحفظ.",
"password-length-from-environment": "هناك قيمة مضبوطة في بيئة الخادم سارية المفعول؛ الحفظ هنا يتجاوزها.",
"password-change-required-title": "غيّر كلمة المرور",
"password-change-required-text": "يتطلب هذا الموقع الآن كلمات مرور لا تقل عن __min__ حرفًا، وكلمة مرورك أقصر. اختر كلمة أطول للمتابعة. بعد ذلك يتم تسجيل خروج جميع جلسات حسابك، وتسجّل الدخول مرة أخرى بكلمة المرور الجديدة.",
"password-same-as-username": "يجب ألا تكون كلمة المرور هي اسم المستخدم أو عنوان البريد الإلكتروني.",
"if-you-already-have-an-account": "إذا كان لديك حساب بالفعل",
"register": "تسجيل",
"forgot-password": "نسيت كلمة المرور",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "نافذة الإخفاق (بالثواني)",
"accounts-lockout-settings-updated": "تم تحديث إعدادات الحماية من القوة الغاشمة",
"accounts-lockout-locked-users": "المستخدمون المقفلون",
"admin-audit-log": "سجل التدقيق",
"admin-audit-log-info": "ما قام به مديرو الموقع: تغييرات المستخدمين وتسجيل الدخول والتخزين والنسخ الاحتياطي، الأحدث أولاً. لا تُسجَّل كلمات المرور والأسرار أبدًا. يُحتفَظ به لمدة سنة.",
"admin-audit-log-when": "الوقت",
"admin-audit-log-who": "المدير",
"admin-audit-log-action": "الإجراء",
"admin-audit-log-details": "التفاصيل",
"admin-audit-log-empty": "لم يُسجَّل شيء بعد.",
"accounts-lockout-locked-users-info": "المستخدمون المقفلون حالياً بسبب عدد كبير جداً من محاولات تسجيل الدخول الفاشلة",
"accounts-lockout-no-locked-users": "لا يوجد حالياً مستخدمون مقفلون",
"accounts-lockout-failed-attempts": "المحاولات الفاشلة",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "تخزين كائنات متوافق مع S3: AWS S3، MinIO، Cloudflare R2، Backblaze B2، Wasabi، DigitalOcean Spaces وغيرها.",
"s3-force-path-style": "فرض روابط بنمط المسار (path-style)",
"s3-force-path-style-description": "مطلوب لـ MinIO ومعظم الخوادم المتوافقة مع S3 غير التابعة لـ AWS.",
"s3-sse": "التشفير من جانب الخادم",
"s3-sse-none": "لم يُطلب أي تشفير (الإعداد الافتراضي للحاوية)",
"s3-sse-description": "يشفّر S3 كل مرفق وصورة رمزية ونسخة احتياطية يكتبها WeKan، ويفك تشفيرها لـ WeKan عند قراءتها.",
"s3-sse-kms-key-id": "معرّف مفتاح KMS لـ SSE-KMS (فارغ = المفتاح الافتراضي للحساب)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "تخزين Microsoft Azure Blob.",
"azure-account-name": "اسم الحساب",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "امسح رمز QR هذا باستخدام تطبيق مصادقة (مثل Google Authenticator أو Authy)، ثم أدخل الرمز المكوّن من 6 أرقام الظاهر للتأكيد.",
"twoFactorAuth-manual-entry": "أو أدخل هذا الرمز يدويًا: ",
"twoFactorAuth-confirm": "تأكيد وتفعيل",
"two-factor-required-title": "إعداد المصادقة الثنائية",
"two-factor-required-text": "يتطلب هذا الموقع المصادقة الثنائية لحسابك. قم بإعدادها للمتابعة.",
"twoFactorCode-prompt": "أدخل رمز المصادقة المكوّن من 6 أرقام",
"twoFactorCode-submit": "تحقق",
"twoFactorCode-cancel": "إلغاء",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "السرعة",
"progress": "التقدّم",
"password-again": "كلمة المرور (مرة أخرى)",
"password-pwned": "هاد كلمة المرور بانت فشي تسريب ديال البيانات. ختار شي وحدة أخرى.",
"password-too-short": "خاص كلمة المرور يكون فيها على الأقل __min__ حروف.",
"password-length-report-title": "الطول ديال كلمة المرور",
"password-length-report-desc": "الحسابات اللي دخلات بكلمة مرور قصيرة على الطول الأدنى. غير المتصفح اللي كيعرف شحال طويلة كلمة المرور، داكشي علاش كيبلّغ على القصيرة من بعد الدخول؛ كلمة المرور والطول ديالها عمرهم ما كيخرجو منو. «تصلّح» هو الوقت اللي تبدّلات فيه كلمة المرور ديال الحساب - والحساب اللي كيدخل عاوتاني بكلمة مرور قصيرة كيبان عاوتاني قصير بزاف.",
"password-length-no-results": "ما كاين حتى حساب هنا.",
"password-length-first-login": "أول دخول",
"password-length-last-login": "آخر دخول",
"password-length-login-count": "شحال من مرة دخل",
"password-length-fixed": "تصلّح",
"password-length-filter-short": "كلمة مرور قصيرة بزاف",
"password-length-filter-fixed": "تصلّح لكلمة مرور طويلة بما فيه الكفاية",
"password-length-filter-all": "كلشي",
"password-min-length": "الطول الأدنى ديال كلمة المرور",
"password-min-length-description": "الدخول بكلمة مرور قصيرة كيتسجّل لتحت. والدخول عمرو ما كيترفض على قبلها.",
"password-min-length-enforce": "فرض الطول الأدنى",
"password-min-length-enforce-description": "كلمة المرور الجديدة القصيرة كاتترفض، والحساب اللي كيدخل بكلمة مرور قصيرة خاصو يبدّلها قبل أي حاجة أخرى. التبديل ديالها كيخرّج كاع الجلسات ديال الحساب. كلمات المرور ديال LDAP وOAuth2 وOIDC وSAML وCAS وSandstorm ديال المزوّد ديالهم، وما كاتّفحصش.",
"password-length-saved": "تسجّل.",
"password-length-from-environment": "كاينة قيمة محطوطة فالبيئة ديال السيرفر خدّامة دابا؛ التسجيل هنا كيعوّضها.",
"password-change-required-title": "بدّل كلمة المرور ديالك",
"password-change-required-text": "هاد الموقع دابا كيطلب كلمات مرور فيها على الأقل __min__ حروف، وديالك قصيرة. ختار وحدة أطول باش تكمّل. من بعد كاع الجلسات ديال الحساب ديالك كيتخرّجو، وكتدخل عاوتاني بكلمة المرور الجديدة.",
"password-same-as-username": "كلمة المرور ما خاصهاش تكون هي اسم المستخدم ولا عنوان البريد الإلكتروني.",
"if-you-already-have-an-account": "إذا كان لديك حساب بالفعل",
"register": "تسجيل",
"forgot-password": "نسيت كلمة المرور",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "نافذة الإخفاق (بالثواني)",
"accounts-lockout-settings-updated": "تم تحديث إعدادات الحماية من القوة الغاشمة",
"accounts-lockout-locked-users": "المستخدمون المقفلون",
"admin-audit-log": "سجل التدقيق",
"admin-audit-log-info": "شنو دارو المسيرين ديال الموقع: التبديلات ديال المستخدمين، الدخول، التخزين والنسخ الاحتياطي، الجديد هو اللول. كلمات المرور والأسرار عمرهم ما كيتسجلو. كيتحفظ لمدة عام.",
"admin-audit-log-when": "إمتى",
"admin-audit-log-who": "المدير",
"admin-audit-log-action": "الإجراء",
"admin-audit-log-details": "التفاصيل",
"admin-audit-log-empty": "مازال ما تسجل والو.",
"accounts-lockout-locked-users-info": "المستخدمون المقفلون حالياً بسبب عدد كبير جداً من محاولات تسجيل الدخول الفاشلة",
"accounts-lockout-no-locked-users": "لا يوجد حالياً مستخدمون مقفلون",
"accounts-lockout-failed-attempts": "المحاولات الفاشلة",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "تخزين كائنات متوافق مع S3: AWS S3، MinIO، Cloudflare R2، Backblaze B2، Wasabi، DigitalOcean Spaces وغيرها.",
"s3-force-path-style": "فرض روابط بنمط المسار (path-style)",
"s3-force-path-style-description": "مطلوب لـ MinIO ومعظم الخوادم المتوافقة مع S3 غير التابعة لـ AWS.",
"s3-sse": "التشفير من جهة الخادم",
"s3-sse-none": "ما مطلوب والو (الإعداد الافتراضي ديال الحاوية)",
"s3-sse-description": "S3 كيشفر كل مرفق، صورة رمزية ونسخة احتياطية كيكتبها WeKan، وكيفك التشفير ديالها لـ WeKan ملي كتتقرا.",
"s3-sse-kms-key-id": "معرف مفتاح KMS لـ SSE-KMS (خاوي = المفتاح الافتراضي ديال الحساب)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "تخزين Microsoft Azure Blob.",
"azure-account-name": "اسم الحساب",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "امسح رمز QR هذا باستخدام تطبيق مصادقة (مثل Google Authenticator أو Authy)، ثم أدخل الرمز المكوّن من 6 أرقام الظاهر للتأكيد.",
"twoFactorAuth-manual-entry": "أو أدخل هذا الرمز يدويًا: ",
"twoFactorAuth-confirm": "تأكيد وتفعيل",
"two-factor-required-title": "وجّد المصادقة الثنائية",
"two-factor-required-text": "هاد الموقع كيطلب المصادقة الثنائية للحساب ديالك. وجّدها باش تكمل.",
"twoFactorCode-prompt": "أدخل رمز المصادقة المكوّن من 6 أرقام",
"twoFactorCode-submit": "تحقق",
"twoFactorCode-cancel": "إلغاء",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "গতি",
"progress": "অগ্ৰগতি",
"password-again": "পাছৱৰ্ড (পুনৰ)",
"password-pwned": "এই পাছৱৰ্ডটো এটা তথ্য ফাদিলত দেখা গৈছে। আন এটা বাছনি কৰক।",
"password-too-short": "পাছৱৰ্ডটো অতি কমেও __min__ টা আখৰৰ হ'ব লাগিব।",
"password-length-report-title": "পাছৱৰ্ডৰ দৈৰ্ঘ্য",
"password-length-report-desc": "নিম্নতম দৈৰ্ঘ্যতকৈ চুটি পাছৱৰ্ডেৰে লগ ইন কৰা একাউণ্টসমূহ। পাছৱৰ্ড কিমান দীঘল সেয়া কেৱল ব্ৰাউজাৰেহে জানে, সেয়েহে ই লগ ইনৰ পিছত চুটি পাছৱৰ্ডৰ বিষয়ে জনায়; পাছৱৰ্ড আৰু ইয়াৰ দৈৰ্ঘ্য কেতিয়াও ব্ৰাউজাৰৰ পৰা ওলাই নাযায়। সমাধান হৈছে মানে একাউণ্টৰ পাছৱৰ্ড সলনি কৰাৰ সময় - পুনৰ চুটি পাছৱৰ্ডেৰে লগ ইন কৰা একাউণ্ট পুনৰ অতি চুটি বুলি দেখুওৱা হয়।",
"password-length-no-results": "ইয়াত কোনো একাউণ্ট নাই।",
"password-length-first-login": "প্ৰথম লগ ইন",
"password-length-last-login": "শেষ লগ ইন",
"password-length-login-count": "লগ ইন কৰাৰ সংখ্যা",
"password-length-fixed": "সমাধান হৈছে",
"password-length-filter-short": "অতি চুটি পাছৱৰ্ড",
"password-length-filter-fixed": "যথেষ্ট দীঘল পাছৱৰ্ডলৈ সমাধান হৈছে",
"password-length-filter-all": "সকলো",
"password-min-length": "নিম্নতম পাছৱৰ্ড দৈৰ্ঘ্য",
"password-min-length-description": "চুটি পাছৱৰ্ডেৰে লগ ইন কৰাটো তলত তালিকাভুক্ত কৰা হয়। ইয়াৰ বাবে লগ ইন কেতিয়াও নাকচ কৰা নহয়।",
"password-min-length-enforce": "নিম্নতম দৈৰ্ঘ্য বাধ্যতামূলক কৰক",
"password-min-length-enforce-description": "চুটি নতুন পাছৱৰ্ড নাকচ কৰা হয়, আৰু চুটি পাছৱৰ্ডেৰে লগ ইন কৰা একাউণ্টে আন যিকোনো কামৰ আগতে ইয়াক সলনি কৰিব লাগিব। ইয়াক সলনি কৰিলে একাউণ্টৰ সকলো ছেছন লগ আউট হয়। LDAP, OAuth2, OIDC, SAML, CAS আৰু Sandstorm পাছৱৰ্ডসমূহ সিহঁতৰ প্ৰদানকাৰীৰ, আৰু পৰীক্ষা কৰা নহয়।",
"password-length-saved": "সংৰক্ষিত।",
"password-length-from-environment": "ছাৰ্ভাৰৰ পৰিৱেশত ছেট কৰা এটা মান কাৰ্যকৰী হৈ আছে; ইয়াত সংৰক্ষণ কৰিলে সেয়া অগ্ৰাহ্য হয়।",
"password-change-required-title": "আপোনাৰ পাছৱৰ্ড সলনি কৰক",
"password-change-required-text": "এই চাইটে এতিয়া অতি কমেও __min__ টা আখৰৰ পাছৱৰ্ড বিচাৰে, আৰু আপোনাৰটো চুটি। আগবাঢ়িবলৈ এটা দীঘল পাছৱৰ্ড বাছক। তাৰ পিছত আপোনাৰ একাউণ্টৰ সকলো ছেছন লগ আউট হয়, আৰু আপুনি নতুন পাছৱৰ্ডেৰে পুনৰ লগ ইন কৰে।",
"password-same-as-username": "পাছৱৰ্ডটো ব্যৱহাৰকাৰীৰ নাম বা ইমেইল ঠিকনা হ'ব নালাগে।",
"if-you-already-have-an-account": "যদি আপোনাৰ ইতিমধ্যে একাউণ্ট আছে",
"register": "পঞ্জীয়ন কৰক",
"forgot-password": "পাছৱৰ্ড পাহৰিছে",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "বিফলতাৰ সময়-খিৰিকী (ছেকেণ্ড)",
"accounts-lockout-settings-updated": "ব্ৰুট ফ'ৰ্চ সুৰক্ষাৰ ছেটিংছ আপডেট কৰা হৈছে",
"accounts-lockout-locked-users": "লক কৰা ব্যৱহাৰকাৰীসকল",
"admin-audit-log": "নিৰীক্ষণ লগ",
"admin-audit-log-info": "ছাইটৰ প্ৰশাসকসকলে কি কৰিছিল: ব্যৱহাৰকাৰী, লগইন, সংৰক্ষণ আৰু বেকআপৰ সলনি, শেহতীয়াখিনি প্ৰথমে। পাছৱৰ্ড আৰু গোপন তথ্য কেতিয়াও ৰেকৰ্ড কৰা নহয়। এবছৰৰ বাবে ৰখা হয়।",
"admin-audit-log-when": "কেতিয়া",
"admin-audit-log-who": "প্ৰশাসক",
"admin-audit-log-action": "কাৰ্য",
"admin-audit-log-details": "সবিশেষ",
"admin-audit-log-empty": "এতিয়ালৈকে একো ৰেকৰ্ড কৰা হোৱা নাই।",
"accounts-lockout-locked-users-info": "অত্যধিক বিফল লগইন প্ৰচেষ্টাৰ বাবে বৰ্তমান লক কৰা ব্যৱহাৰকাৰীসকল",
"accounts-lockout-no-locked-users": "বৰ্তমান কোনো লক কৰা ব্যৱহাৰকাৰী নাই",
"accounts-lockout-failed-attempts": "বিফল প্ৰচেষ্টাসমূহ",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-সামঞ্জস্যপূৰ্ণ অবজেক্ট সংৰক্ষণ: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces আৰু অন্যান্য।",
"s3-force-path-style": "পথ-শৈলীৰ URL বাধ্যতামূলক কৰক",
"s3-force-path-style-description": "MinIO আৰু অধিকাংশ AWS-বহিৰ্ভূত S3-সুসংগত ছাৰ্ভাৰৰ বাবে প্ৰয়োজনীয়।",
"s3-sse": "ছাৰ্ভাৰ-পক্ষৰ এনক্ৰিপচন",
"s3-sse-none": "একো অনুৰোধ কৰা হোৱা নাই (বাকেটৰ অবিকল্পিত)",
"s3-sse-description": "WeKan-এ লিখা প্ৰতিটো সংলগ্ন, অৱতাৰ আৰু বেকআপ S3-এ এনক্ৰিপ্ট কৰে, আৰু পঢ়োঁতে WeKan-ৰ বাবে ডিক্ৰিপ্ট কৰে।",
"s3-sse-kms-key-id": "SSE-KMS-ৰ বাবে KMS চাবিৰ ID (খালী = একাউণ্টৰ অবিকল্পিত চাবি)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "একাউণ্টৰ নাম",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "এটা প্ৰমাণীকৰণ এপৰ সৈতে এই QR ক'ডটো স্কেন কৰক (যেনে Google Authenticator, Authy), তাৰ পিছত নিশ্চিত কৰিবলৈ দেখুওৱা 6-অংকীয় ক'ডটো দিয়ক।",
"twoFactorAuth-manual-entry": "নাইবা এই ক'ডটো হাতেৰে দিয়ক: ",
"twoFactorAuth-confirm": "নিশ্চিত কৰক আৰু সক্ৰিয় কৰক",
"two-factor-required-title": "দুই-উপাদান প্ৰমাণীকৰণ ছেট আপ কৰক",
"two-factor-required-text": "এই ছাইটে আপোনাৰ একাউণ্টৰ বাবে দুই-উপাদান প্ৰমাণীকৰণ বিচাৰে। আগবাঢ়িবলৈ ইয়াক ছেট আপ কৰক।",
"twoFactorCode-prompt": "আপোনাৰ 6-অংকীয় প্ৰমাণীকৰণ ক'ড দিয়ক",
"twoFactorCode-submit": "নিশ্চিত কৰক",
"twoFactorCode-cancel": "বাতিল কৰক",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Velocidad",
"progress": "Progreso",
"password-again": "Contraseña (repetir)",
"password-pwned": "Esta contraseña apaeció nuna filtración de datos. Escueyi otra.",
"password-too-short": "La contraseña tien de tener polo menos __min__ caráuteres.",
"password-length-report-title": "Llonxitú de la contraseña",
"password-length-report-desc": "Cuentes qu'aniciaron sesión con una contraseña más curtia que la llonxitú mínima. Namás el restolador sabe cuánto mide una contraseña, polo qu'informa d'una curtia dempués d'aniciar sesión; la contraseña y la so llonxitú enxamás nun salen d'él. Iguada ye cuando se camudó la contraseña de la cuenta - una cuenta qu'anicia sesión otra vegada con una contraseña curtia amuésase otra vegada como enforma curtia.",
"password-length-no-results": "Equí nun hai cuentes.",
"password-length-first-login": "Primer aniciu de sesión",
"password-length-last-login": "Últimu aniciu de sesión",
"password-length-login-count": "Vegaes qu'anició sesión",
"password-length-fixed": "Iguada",
"password-length-filter-short": "Contraseña enforma curtia",
"password-length-filter-fixed": "Iguada a una contraseña abondo llarga",
"password-length-filter-all": "Too",
"password-min-length": "Llonxitú mínima de la contraseña",
"password-min-length-description": "Aniciar sesión con una contraseña más curtia apaez llistao abaxo. Enxamás nun se refuga l'aniciu de sesión por ello.",
"password-min-length-enforce": "Imponer la llonxitú mínima",
"password-min-length-enforce-description": "Refúgase una contraseña nueva más curtia, y una cuenta qu'anicia sesión con una contraseña más curtia tien de camudala enantes de cualquier otra cosa. Camudala zarra toles sesiones de la cuenta. Les contraseñes de LDAP, OAuth2, OIDC, SAML, CAS y Sandstorm son del so fornidor y nun se comprueben.",
"password-length-saved": "Guardóse.",
"password-length-from-environment": "Ta n'efeutu un valor definíu nel entornu del sirvidor; guardar equí sobrescríbelu.",
"password-change-required-title": "Camuda la to contraseña",
"password-change-required-text": "Esti sitiu agora rique contraseñes de polo menos __min__ caráuteres, y la to ye más curtia. Escueyi una más llarga pa siguir. Dempués zárrense toles sesiones de la to cuenta, y anicies sesión otra vegada cola contraseña nueva.",
"password-same-as-username": "La contraseña nun pue ser el nome d'usuariu nin la direición de corréu electrónicu.",
"if-you-already-have-an-account": "Si ja tens un compte",
"register": "Registra't",
"forgot-password": "Has oblidat la contrasenya",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Ventana de fallos (segundos)",
"accounts-lockout-settings-updated": "Actualizáronse les preferencies de proteición contra ataques de fuercia bruto",
"accounts-lockout-locked-users": "Usuarios bloquiaos",
"admin-audit-log": "Rexistru d'auditoría",
"admin-audit-log-info": "Lo que ficieron los alministradores del sitiu: cambeos d'usuarios, d'aniciu de sesión, d'almacenamientu y de copies de seguridá, primero lo más recién. Les contraseñes y los secretos enxamás se rexistren. Caltiénse un añu.",
"admin-audit-log-when": "Cuándo",
"admin-audit-log-who": "Administrador",
"admin-audit-log-action": "Aición",
"admin-audit-log-details": "Detalles",
"admin-audit-log-empty": "Entá nun se rexistró nada.",
"accounts-lockout-locked-users-info": "Usuarios bloquiaos anguaño por demasiaos intentos fallíos d'aniciu de sesión",
"accounts-lockout-no-locked-users": "Anguaño nun hai usuarios bloquiaos",
"accounts-lockout-failed-attempts": "Intentos fallidos",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "Almacenamientu d'oxetos compatible con S3: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces y otros.",
"s3-force-path-style": "Forzar URLs con estilu de ruta",
"s3-force-path-style-description": "Necesariu pa MinIO y la mayoría de los sirvidores compatibles con S3 que nun son d'AWS.",
"s3-sse": "Cifráu nel sirvidor",
"s3-sse-none": "Nun se pidió nengún (el predetermináu del bucket)",
"s3-sse-description": "S3 cifra cada axuntu, avatar y copia de seguridá qu'escribe WeKan, y descífralo pa WeKan al lleelo.",
"s3-sse-kms-key-id": "ID de clave KMS pa SSE-KMS (baleru = la clave predeterminada de la cuenta)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Almacenamientu Microsoft Azure Blob.",
"azure-account-name": "Nome de la cuenta",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Escanea esti códigu QR con una aplicación autenticadora (p. ex. Google Authenticator, Authy), y dempués introduz el códigu de 6 díxitos qu'amuesa pa confirmar.",
"twoFactorAuth-manual-entry": "O introduz esti códigu manualmente: ",
"twoFactorAuth-confirm": "Confirmar y Activar",
"two-factor-required-title": "Configura l'autenticación de dos factores",
"two-factor-required-text": "Esti sitiu rique l'autenticación de dos factores pa la to cuenta. Configúrala pa siguir.",
"twoFactorCode-prompt": "Introduz el to códigu d'autenticación de 6 díxitos",
"twoFactorCode-submit": "Verificar",
"twoFactorCode-cancel": "Encaboxar",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Aymar aruna: Speed",
"progress": "Nayrar sartawi",
"password-again": "jamasaru (again)",
"password-pwned": "Aka imantata aruxa mä datos lluqhsuwinwa uñstawayi. Yaqha ajlliñamawa.",
"password-too-short": "Imantata aruxa __min__ qillqanakat juk'ampiniwa utjañapa.",
"password-length-report-title": "Imantata aru jach'a",
"password-length-report-desc": "Jakhuwinaka, imantata aru jisk'ampimpi mantapki, juk'a jach'atxa jisk'akiwa. Navegadorakiw imantata aru qhawqha jach'achixa uk yati, ukatwa mantatat qhipat jisk'a imantata aru yatiyi; imantata aru, jach'apasa janipuniw navegadorat mistki. Askichatawa, jakhuwin imantata arupa mayjt'ayatäki uka pachawa - mayamp jisk'a imantata arumpi mantki uka jakhuwixa mayampiw sinti jisk'jam uñacht'ayasi.",
"password-length-no-results": "Akan janiw jakhuwinakax utjkiti.",
"password-length-first-login": "Nayrïr mantawi",
"password-length-last-login": "Qhipïr mantawi",
"password-length-login-count": "Qhawqha kuti mantawi",
"password-length-fixed": "Askichata",
"password-length-filter-short": "Sinti jisk'a imantata aru",
"password-length-filter-fixed": "Jach'a imantata aruru askichata",
"password-length-filter-all": "Taqpacha",
"password-min-length": "Imantata aru juk'a jach'a",
"password-min-length-description": "Jisk'ampi imantata arumpi mantawixa aka manqhan qillqatawa. Mantawixa janipuniw ukatxa jark'atäkiti.",
"password-min-length-enforce": "Juk'a jach'a phuqhayaña",
"password-min-length-enforce-description": "Machaq jisk'a imantata aruxa janiw katuqatäkiti, ukat jisk'a imantata arumpi mantki uka jakhuwixa yaqha luräwinak nayraqatxa mayjt'ayañapawa. Mayjt'ayañaxa jakhuwin taqpach sesionanakap mistuyi. LDAP, OAuth2, OIDC, SAML, CAS, Sandstorm imantata arunakaxa churirinakapankiwa, janiw uñakipatäkiti.",
"password-length-saved": "Imatawa.",
"password-length-from-environment": "Servidoran pachapan uskuta chimpuw jichhax irnaqaski; akan imañaxa ukar mayjt'ayi.",
"password-change-required-title": "Imantata arum mayjt'ayam",
"password-change-required-text": "Aka sitioxa jichhax __min__ qillqanakat juk'ampi imantata arunak mayi, jumanxa jisk'awa. Sarantañatakix juk'amp jach'a ajllim. Ukatxa jakhuwiman taqpach sesionanakapax mistuyatawa, ukat machaq imantata arumpiw mayamp mantäta.",
"password-same-as-username": "Imantata aruxa apnaqirin sutipa jan ukax aruskipäwi chiqapa janiw utjañapakiti.",
"if-you-already-have-an-account": "Aymar aruna: If you already have an account",
"register": "Qillqantasiña",
"forgot-password": "Imantata aru armt'ata",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Failure window (segundunaka)",
"accounts-lockout-settings-updated": "Brute force protection wakichäwinaka have been updated",
"accounts-lockout-locked-users": "Locked apnaqirinaka",
"admin-audit-log": "Uñakipäwi qillqata",
"admin-audit-log-info": "Kuntix sitio apnaqirinakax lurapki: apnaqirinaka, mantañanaka, imañanaka ukat jark'aqawi copia mayjt'ayawinaka, machaqanakax nayraqata. Imantata arunaka ukat imantatanakax janipuniw qillqatäkiti. Mä mara imatawa.",
"admin-audit-log-when": "Kunawsa",
"admin-audit-log-who": "Apnaqañ irpiri",
"admin-audit-log-action": "Lurawi",
"admin-audit-log-details": "Qhanañchäwinaka",
"admin-audit-log-empty": "Janiw kunas qillqatäkiti.",
"accounts-lockout-locked-users-info": "apnaqirinaka currently locked out tukuyañ ukaru too many jani phuqhata login yantʼawinaka",
"accounts-lockout-no-locked-users": "There apxi currently janiwa locked apnaqirinaka",
"accounts-lockout-failed-attempts": "jani phuqhata yantʼawinaka",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3 kikip yänak imaña: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces ukat yaqhanaka.",
"s3-force-path-style": "Aymar aruna: Force path-style URLs",
"s3-force-path-style-description": "wakisiri by MinIO ukat most non-AWS S3-compatible servers.",
"s3-sse": "Servidor tuqin imantaña",
"s3-sse-none": "Janiw mayitäkiti (bucket nayrïri)",
"s3-sse-description": "S3 ukax sapa chinuta, avatar ukat jark'aqawi copia WeKan qillqki ukanak imantaraki, ullañanx WeKan layku jist'arakiwa.",
"s3-sse-kms-key-id": "SSE-KMS layku KMS llave ID (ch'usa = cuenta nayrïri llave)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Account suti",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Aka código QR ukar sarayam mä app chiqapachañataki (Google Authenticator, Authy ukhamanaka), ukat 6 jakhu código uñacht'ayatapa yatiyañataki qillqam.",
"twoFactorAuth-manual-entry": "Jan ukax aka código amparampi qillqam: ",
"twoFactorAuth-confirm": "Chiqapachaña ukat Apnaqaña",
"two-factor-required-title": "Pä Kunayman Chaqañ Chiqapachäwi wakicht'aña",
"two-factor-required-text": "Aka sitiox cuentamatakix Pä Kunayman Chaqañ Chiqapachäwi mayi. Sarantañatakix wakicht'am.",
"twoFactorCode-prompt": "Jumana 6 jakhu chiqapachäwi código qillqam",
"twoFactorCode-submit": "Chiqapachaña",
"twoFactorCode-cancel": "Jark'aña",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Sürət",
"progress": "Tərəqqi",
"password-again": "Parol (yenidən)",
"password-pwned": "Bu parol məlumat sızmasında aşkar edilib. Başqa parol seçin.",
"password-too-short": "Parol ən azı __min__ simvol uzunluğunda olmalıdır.",
"password-length-report-title": "Parol uzunluğu",
"password-length-report-desc": "Minimum uzunluqdan qısa parolla daxil olan hesablar. Parolun nə qədər uzun olduğunu yalnız brauzer bilir, buna görə daxil olduqdan sonra qısa parol barədə məlumat verir; parol və onun uzunluğu heç vaxt brauzeri tərk etmir. Düzəldildi hesabın parolunun dəyişdirildiyi vaxtdır - yenidən qısa parolla daxil olan hesab yenidən çox qısa kimi göstərilir.",
"password-length-no-results": "Burada hesab yoxdur.",
"password-length-first-login": "İlk giriş",
"password-length-last-login": "Son giriş",
"password-length-login-count": "Giriş sayı",
"password-length-fixed": "Düzəldildi",
"password-length-filter-short": "Çox qısa parol",
"password-length-filter-fixed": "Kifayət qədər uzun parola düzəldildi",
"password-length-filter-all": "Hamısı",
"password-min-length": "Minimum parol uzunluğu",
"password-min-length-description": "Daha qısa parolla daxil olmaq aşağıda siyahıya alınır. Buna görə giriş heç vaxt rədd edilmir.",
"password-min-length-enforce": "Minimum uzunluğu tətbiq et",
"password-min-length-enforce-description": "Daha qısa yeni parol rədd edilir və daha qısa parolla daxil olan hesab hər şeydən əvvəl onu dəyişməlidir. Onu dəyişmək hesabın bütün sessiyalarından çıxış edir. LDAP, OAuth2, OIDC, SAML, CAS və Sandstorm parolları öz provayderlərinə aiddir və yoxlanılmır.",
"password-length-saved": "Saxlanıldı.",
"password-length-from-environment": "Serverin mühitində təyin edilmiş dəyər qüvvədədir; burada saxlamaq onu əvəz edir.",
"password-change-required-title": "Parolunuzu dəyişin",
"password-change-required-text": "Bu sayt indi ən azı __min__ simvoldan ibarət parollar tələb edir, sizinki isə daha qısadır. Davam etmək üçün daha uzun birini seçin. Sonra hesabınızın bütün sessiyalarından çıxış edilir və yeni parolla yenidən daxil olursunuz.",
"password-same-as-username": "Parol istifadəçi adı və ya e-poçt ünvanı olmamalıdır.",
"if-you-already-have-an-account": "Əgər artıq hesabınız varsa",
"register": "Qeydiyyatdan keçin",
"forgot-password": "parol unutmusunuz",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Xəta pəncərəsi (saniyə)",
"accounts-lockout-settings-updated": "Qəddar qüvvədən qorunma parametrləri yeniləndi",
"accounts-lockout-locked-users": "Bağlı İstifadəçilər",
"admin-audit-log": "Audit jurnalı",
"admin-audit-log-info": "Sayt inzibatçılarının etdikləri: istifadəçi, giriş, yaddaş və ehtiyat nüsxə dəyişiklikləri, ən yenilər əvvəldə. Parollar və gizli məlumatlar heç vaxt qeydə alınmır. Bir il saxlanılır.",
"admin-audit-log-when": "Nə vaxt",
"admin-audit-log-who": "İnzibatçı",
"admin-audit-log-action": "Əməliyyat",
"admin-audit-log-details": "Təfərrüatlar",
"admin-audit-log-empty": "Hələ heç nə qeydə alınmayıb.",
"accounts-lockout-locked-users-info": "İstifadəçilər çoxlu uğursuz giriş cəhdləri səbəbindən hazırda bloklanıb",
"accounts-lockout-no-locked-users": "Hazırda kilidlənmiş istifadəçi yoxdur",
"accounts-lockout-failed-attempts": "Uğursuz cəhdlər",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3 uyğun obyekt saxlaması: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces və digərləri.",
"s3-force-path-style": "Yol tərzli URL-ləri məcbur et",
"s3-force-path-style-description": "MinIO və əksər qeyri-AWS S3 uyğun serverləri üçün tələb olunur.",
"s3-sse": "Server tərəfində şifrələmə",
"s3-sse-none": "Tələb olunmayıb (bucket-in defolt ayarı)",
"s3-sse-description": "S3 WeKan-ın yazdığı hər qoşmanı, avatarı və ehtiyat nüsxəni şifrələyir və oxunduqda WeKan üçün deşifrə edir.",
"s3-sse-kms-key-id": "SSE-KMS üçün KMS açar ID-si (boş = hesabın defolt açarı)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob saxlancı.",
"azure-account-name": "Hesab adı",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Bu QR kodunu doğrulama tətbiqi ilə (məsələn, Google Authenticator, Authy) skan edin, sonra təsdiqləmək üçün göstərilən 6 rəqəmli kodu daxil edin.",
"twoFactorAuth-manual-entry": "Və ya bu kodu əl ilə daxil edin: ",
"twoFactorAuth-confirm": "Təsdiqlə və Aktiv Et",
"two-factor-required-title": "İki faktorlu doğrulamanı qurun",
"two-factor-required-text": "Bu sayt hesabınız üçün iki faktorlu doğrulama tələb edir. Davam etmək üçün onu qurun.",
"twoFactorCode-prompt": "6 rəqəmli doğrulama kodunuzu daxil edin",
"twoFactorCode-submit": "Doğrula",
"twoFactorCode-cancel": "Ləğv et",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Sürət",
"progress": "Tərəqqi",
"password-again": "Parol (yenidən)",
"password-pwned": "Bu parol məlumat sızmasında aşkar edilib. Başqa parol seçin.",
"password-too-short": "Parol ən azı __min__ simvol uzunluğunda olmalıdır.",
"password-length-report-title": "Parol uzunluğu",
"password-length-report-desc": "Minimum uzunluqdan qısa parolla daxil olan hesablar. Parolun nə qədər uzun olduğunu yalnız brauzer bilir, buna görə daxil olduqdan sonra qısa parol barədə məlumat verir; parol və onun uzunluğu heç vaxt brauzeri tərk etmir. Düzəldildi hesabın parolunun dəyişdirildiyi vaxtdır - yenidən qısa parolla daxil olan hesab yenidən çox qısa kimi göstərilir.",
"password-length-no-results": "Burada hesab yoxdur.",
"password-length-first-login": "İlk giriş",
"password-length-last-login": "Son giriş",
"password-length-login-count": "Giriş sayı",
"password-length-fixed": "Düzəldildi",
"password-length-filter-short": "Çox qısa parol",
"password-length-filter-fixed": "Kifayət qədər uzun parola düzəldildi",
"password-length-filter-all": "Hamısı",
"password-min-length": "Minimum parol uzunluğu",
"password-min-length-description": "Daha qısa parolla daxil olmaq aşağıda siyahıya alınır. Buna görə giriş heç vaxt rədd edilmir.",
"password-min-length-enforce": "Minimum uzunluğu tətbiq et",
"password-min-length-enforce-description": "Daha qısa yeni parol rədd edilir və daha qısa parolla daxil olan hesab hər şeydən əvvəl onu dəyişməlidir. Onu dəyişmək hesabın bütün sessiyalarından çıxış edir. LDAP, OAuth2, OIDC, SAML, CAS və Sandstorm parolları öz provayderlərinə aiddir və yoxlanılmır.",
"password-length-saved": "Saxlanıldı.",
"password-length-from-environment": "Serverin mühitində təyin edilmiş dəyər qüvvədədir; burada saxlamaq onu əvəz edir.",
"password-change-required-title": "Parolunuzu dəyişin",
"password-change-required-text": "Bu sayt indi ən azı __min__ simvoldan ibarət parollar tələb edir, sizinki isə daha qısadır. Davam etmək üçün daha uzun birini seçin. Sonra hesabınızın bütün sessiyalarından çıxış edilir və yeni parolla yenidən daxil olursunuz.",
"password-same-as-username": "Parol istifadəçi adı və ya e-poçt ünvanı olmamalıdır.",
"if-you-already-have-an-account": "Əgər artıq hesabınız varsa",
"register": "Qeydiyyatdan keçin",
"forgot-password": "parol unutmusunuz",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Xəta pəncərəsi (saniyə)",
"accounts-lockout-settings-updated": "Qəddar qüvvədən qorunma parametrləri yeniləndi",
"accounts-lockout-locked-users": "Bağlı İstifadəçilər",
"admin-audit-log": "Audit jurnalı",
"admin-audit-log-info": "Sayt inzibatçılarının etdikləri: istifadəçi, giriş, yaddaş və ehtiyat nüsxə dəyişiklikləri, ən yenilər əvvəldə. Parollar və gizli məlumatlar heç vaxt qeydə alınmır. Bir il saxlanılır.",
"admin-audit-log-when": "Nə vaxt",
"admin-audit-log-who": "İnzibatçı",
"admin-audit-log-action": "Əməliyyat",
"admin-audit-log-details": "Təfərrüatlar",
"admin-audit-log-empty": "Hələ heç nə qeydə alınmayıb.",
"accounts-lockout-locked-users-info": "İstifadəçilər çoxlu uğursuz giriş cəhdləri səbəbindən hazırda bloklanıb",
"accounts-lockout-no-locked-users": "Hazırda kilidlənmiş istifadəçi yoxdur",
"accounts-lockout-failed-attempts": "Uğursuz cəhdlər",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3 uyğun obyekt saxlaması: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces və digərləri.",
"s3-force-path-style": "Yol tərzli URL-ləri məcbur et",
"s3-force-path-style-description": "MinIO və əksər qeyri-AWS S3 uyğun serverləri üçün tələb olunur.",
"s3-sse": "Server tərəfində şifrələmə",
"s3-sse-none": "Tələb olunmayıb (bucket-in defolt ayarı)",
"s3-sse-description": "S3 WeKan-ın yazdığı hər qoşmanı, avatarı və ehtiyat nüsxəni şifrələyir və oxunduqda WeKan üçün deşifrə edir.",
"s3-sse-kms-key-id": "SSE-KMS üçün KMS açar ID-si (boş = hesabın defolt açarı)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob saxlancı.",
"azure-account-name": "Hesab adı",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Bu QR kodu autentifikasiya tətbiqi ilə skan edin (məs. Google Authenticator, Authy), sonra təsdiq üçün göstərilən 6 rəqəmli kodu daxil edin.",
"twoFactorAuth-manual-entry": "Və ya bu kodu əl ilə daxil edin: ",
"twoFactorAuth-confirm": "Təsdiq Et və Aktivləşdir",
"two-factor-required-title": "İki mərhələli doğrulamanı qurun",
"two-factor-required-text": "Bu sayt hesabınız üçün iki mərhələli doğrulama tələb edir. Davam etmək üçün onu qurun.",
"twoFactorCode-prompt": "6 rəqəmli doğrulama kodunuzu daxil edin",
"twoFactorCode-submit": "Doğrula",
"twoFactorCode-cancel": "Ləğv et",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Sürət",
"progress": "Tərəqqi",
"password-again": "Parol (yenidən)",
"password-pwned": "Bu parol məlumat sızmasında aşkar edilib. Başqa parol seçin.",
"password-too-short": "Parol ən azı __min__ simvol uzunluğunda olmalıdır.",
"password-length-report-title": "Parol uzunluğu",
"password-length-report-desc": "Minimum uzunluqdan qısa parolla daxil olan hesablar. Parolun nə qədər uzun olduğunu yalnız brauzer bilir, buna görə daxil olduqdan sonra qısa parol barədə məlumat verir; parol və onun uzunluğu heç vaxt brauzeri tərk etmir. Düzəldildi hesabın parolunun dəyişdirildiyi vaxtdır - yenidən qısa parolla daxil olan hesab yenidən çox qısa kimi göstərilir.",
"password-length-no-results": "Burada hesab yoxdur.",
"password-length-first-login": "İlk giriş",
"password-length-last-login": "Son giriş",
"password-length-login-count": "Giriş sayı",
"password-length-fixed": "Düzəldildi",
"password-length-filter-short": "Çox qısa parol",
"password-length-filter-fixed": "Kifayət qədər uzun parola düzəldildi",
"password-length-filter-all": "Hamısı",
"password-min-length": "Minimum parol uzunluğu",
"password-min-length-description": "Daha qısa parolla daxil olmaq aşağıda siyahıya alınır. Buna görə giriş heç vaxt rədd edilmir.",
"password-min-length-enforce": "Minimum uzunluğu tətbiq et",
"password-min-length-enforce-description": "Daha qısa yeni parol rədd edilir və daha qısa parolla daxil olan hesab hər şeydən əvvəl onu dəyişməlidir. Onu dəyişmək hesabın bütün sessiyalarından çıxış edir. LDAP, OAuth2, OIDC, SAML, CAS və Sandstorm parolları öz provayderlərinə aiddir və yoxlanılmır.",
"password-length-saved": "Saxlanıldı.",
"password-length-from-environment": "Serverin mühitində təyin edilmiş dəyər qüvvədədir; burada saxlamaq onu əvəz edir.",
"password-change-required-title": "Parolunuzu dəyişin",
"password-change-required-text": "Bu sayt indi ən azı __min__ simvoldan ibarət parollar tələb edir, sizinki isə daha qısadır. Davam etmək üçün daha uzun birini seçin. Sonra hesabınızın bütün sessiyalarından çıxış edilir və yeni parolla yenidən daxil olursunuz.",
"password-same-as-username": "Parol istifadəçi adı və ya e-poçt ünvanı olmamalıdır.",
"if-you-already-have-an-account": "Əgər artıq hesabınız varsa",
"register": "Qeydiyyatdan keçin",
"forgot-password": "parol unutmusunuz",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Xəta pəncərəsi (saniyə)",
"accounts-lockout-settings-updated": "Qəddar qüvvədən qorunma parametrləri yeniləndi",
"accounts-lockout-locked-users": "Bağlı İstifadəçilər",
"admin-audit-log": "Audit jurnalı",
"admin-audit-log-info": "Sayt inzibatçılarının etdikləri: istifadəçi, giriş, yaddaş və ehtiyat nüsxə dəyişiklikləri, ən yenilər əvvəldə. Parollar və gizli məlumatlar heç vaxt qeydə alınmır. Bir il saxlanılır.",
"admin-audit-log-when": "Nə vaxt",
"admin-audit-log-who": "İnzibatçı",
"admin-audit-log-action": "Əməliyyat",
"admin-audit-log-details": "Təfərrüatlar",
"admin-audit-log-empty": "Hələ heç nə qeydə alınmayıb.",
"accounts-lockout-locked-users-info": "İstifadəçilər çoxlu uğursuz giriş cəhdləri səbəbindən hazırda bloklanıb",
"accounts-lockout-no-locked-users": "Hazırda kilidlənmiş istifadəçi yoxdur",
"accounts-lockout-failed-attempts": "Uğursuz cəhdlər",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3 uyğun obyekt saxlaması: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces və digərləri.",
"s3-force-path-style": "Yol tərzli URL-ləri məcbur et",
"s3-force-path-style-description": "MinIO və əksər qeyri-AWS S3 uyğun serverləri üçün tələb olunur.",
"s3-sse": "Server tərəfində şifrələmə",
"s3-sse-none": "Tələb olunmayıb (bucket-in defolt ayarı)",
"s3-sse-description": "S3 WeKan-ın yazdığı hər qoşmanı, avatarı və ehtiyat nüsxəni şifrələyir və oxunduqda WeKan üçün deşifrə edir.",
"s3-sse-kms-key-id": "SSE-KMS üçün KMS açar ID-si (boş = hesabın defolt açarı)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob saxlancı.",
"azure-account-name": "Hesab adı",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Bu QR kodu autentifikasiya tətbiqi ilə skan edin (məs. Google Authenticator, Authy), sonra təsdiq üçün göstərilən 6 rəqəmli kodu daxil edin.",
"twoFactorAuth-manual-entry": "Və ya bu kodu əl ilə daxil edin: ",
"twoFactorAuth-confirm": "Təsdiq Et və Aktivləşdir",
"two-factor-required-title": "İki mərhələli doğrulamanı qurun",
"two-factor-required-text": "Bu sayt hesabınız üçün iki mərhələli doğrulama tələb edir. Davam etmək üçün onu qurun.",
"twoFactorCode-prompt": "6 rəqəmli doğrulama kodunuzu daxil edin",
"twoFactorCode-submit": "Doğrula",
"twoFactorCode-cancel": "Ləğv et",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Тиҙлек",
"progress": "Алға китеш",
"password-again": "Серһүҙ (ҡабат)",
"password-pwned": "Был серһүҙ мәғлүмәттәр ағыуында күренгән. Башҡаһын һайлағыҙ.",
"password-too-short": "Серһүҙ кәмендә __min__ символдан торорға тейеш.",
"password-length-report-title": "Серһүҙ оҙонлоғо",
"password-length-report-desc": "Минималь оҙонлоҡтан ҡыҫҡараҡ серһүҙ менән ингән иҫәп яҙмалары. Серһүҙҙең оҙонлоғон браузер ғына белә, шуға ла ул ингәндән һуң ҡыҫҡа серһүҙ тураһында хәбәр итә; серһүҙ һәм уның оҙонлоғо бер ҡасан да браузерҙан сыҡмай. Төҙәтелгән — иҫәп яҙмаһының серһүҙе үҙгәртелгән ваҡыт - яңынан ҡыҫҡа серһүҙ менән ингән иҫәп яҙмаһы яңынан бик ҡыҫҡа булып күрһәтелә.",
"password-length-no-results": "Бында иҫәп яҙмалары юҡ.",
"password-length-first-login": "Беренсе инеү",
"password-length-last-login": "Һуңғы инеү",
"password-length-login-count": "Инеүҙәр һаны",
"password-length-fixed": "Төҙәтелгән",
"password-length-filter-short": "Бик ҡыҫҡа серһүҙ",
"password-length-filter-fixed": "Етерлек оҙон серһүҙгә төҙәтелгән",
"password-length-filter-all": "Барыһы ла",
"password-min-length": "Серһүҙҙең минималь оҙонлоғо",
"password-min-length-description": "Ҡыҫҡараҡ серһүҙ менән инеү түбәндә күрһәтелә. Был сәбәпле инеү бер ҡасан да кире ҡағылмай.",
"password-min-length-enforce": "Минималь оҙонлоҡто мотлаҡ итеү",
"password-min-length-enforce-description": "Ҡыҫҡараҡ яңы серһүҙ кире ҡағыла, ә ҡыҫҡараҡ серһүҙ менән ингән иҫәп яҙмаһы уны барыһынан да элек үҙгәртергә тейеш. Уны үҙгәртеү иҫәп яҙмаһының бөтә сеанстарын ябып сығара. LDAP, OAuth2, OIDC, SAML, CAS һәм Sandstorm серһүҙҙәре уларҙың провайдерыныҡы, һәм тикшерелмәй.",
"password-length-saved": "Һаҡланды.",
"password-length-from-environment": "Серверҙың мөхитендә ҡуйылған ҡиммәт ғәмәлдә; бында һаҡлау уны алмаштыра.",
"password-change-required-title": "Серһүҙегеҙҙе үҙгәртегеҙ",
"password-change-required-text": "Был сайт хәҙер кәмендә __min__ символлы серһүҙҙәр талап итә, ә һеҙҙеке ҡыҫҡараҡ. Дауам итеү өсөн оҙонораҡ серһүҙ һайлағыҙ. Шунан һеҙҙең иҫәп яҙмағыҙҙың бөтә сеанстары ябыла, һәм һеҙ яңы серһүҙ менән яңынан инәһегеҙ.",
"password-same-as-username": "Серһүҙ ҡулланыусы исеме йәки электрон почта адресы булырға тейеш түгел.",
"if-you-already-have-an-account": "Иҫәбегеҙ булһа",
"register": "Теркәлеү",
"forgot-password": "Серһүҙҙе онотҡанһығыҙмы",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Уңышһыҙлыҡ арауығы (секунд)",
"accounts-lockout-settings-updated": "Тупаҫ көс һөжүменән һаҡлау көйләүҙәре яңыртылды",
"accounts-lockout-locked-users": "Бикләнгән ҡулланыусылар",
"admin-audit-log": "Аудит журналы",
"admin-audit-log-info": "Сайт администраторҙары ни эшләгән: ҡулланыусы, инеү, һаҡлағыс һәм резерв күсермә үҙгәрештәре, иң яңылары башта. Серһүҙҙәр һәм серҙәр бер ҡасан да яҙылмай. Бер йыл һаҡлана.",
"admin-audit-log-when": "Ҡасан",
"admin-audit-log-who": "Администратор",
"admin-audit-log-action": "Ғәмәл",
"admin-audit-log-details": "Ентеклектәр",
"admin-audit-log-empty": "Әлегә бер нәмә лә яҙылмаған.",
"accounts-lockout-locked-users-info": "Артыҡ күп уңышһыҙ инеү ынтылышы арҡаһында әлеге ваҡытта бикләнгән ҡулланыусылар",
"accounts-lockout-no-locked-users": "Әлеге ваҡытта бикләнгән ҡулланыусылар юҡ",
"accounts-lockout-failed-attempts": "Уңышһыҙ ынтылыштар",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3 менән тап килгән объект һаҡлағысы: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces һәм башҡалар.",
"s3-force-path-style": "Юл стилендәге URL-дарҙы мәжбүри ҡулланырға",
"s3-force-path-style-description": "MinIO һәм AWS булмаған күпселек S3-ярашлы серверҙар өсөн кәрәк.",
"s3-sse": "Сервер яғында шифрлау",
"s3-sse-none": "Һоралмаған (bucket-тың килешелгән көйләүе)",
"s3-sse-description": "S3 WeKan яҙған һәр ҡушымтаны, аватарҙы һәм резерв күсермәне шифрлай, ә уҡығанда WeKan өсөн шифрҙы сисә.",
"s3-sse-kms-key-id": "SSE-KMS өсөн KMS асҡыс ID-һы (буш = иҫәп яҙмаһының килешелгән асҡысы)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Иҫәп исеме",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Был QR-кодты раҫлау ҡушымтаһы менән (мәҫәлән, Google Authenticator, Authy) сканерлағыҙ, унан раҫлау өсөн күрһәтелгән 6 цифрлы кодты индерегеҙ.",
"twoFactorAuth-manual-entry": "Йәки был кодты ҡулдан индерегеҙ: ",
"twoFactorAuth-confirm": "Раҫларға һәм асырға",
"two-factor-required-title": "Ике факторлы раҫлауҙы көйләгеҙ",
"two-factor-required-text": "Был сайт һеҙҙең иҫәп яҙмағыҙ өсөн ике факторлы раҫлау талап итә. Дауам итер өсөн уны көйләгеҙ.",
"twoFactorCode-prompt": "6 цифрлы раҫлау кодыгыҙҙы индерегеҙ",
"twoFactorCode-submit": "Тикшерергә",
"twoFactorCode-cancel": "Кире ҡағырға",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Хуткасць",
"progress": "Прагрэс",
"password-again": "Пароль (яшчэ раз)",
"password-pwned": "Гэты пароль трапіў ва ўцечку даных. Выберыце іншы.",
"password-too-short": "Пароль павінен мець не менш за __min__ сімвалаў.",
"password-length-report-title": "Даўжыня пароля",
"password-length-report-desc": "Уліковыя запісы, якія ўвайшлі з паролем, карацейшым за мінімальную даўжыню. Толькі браўзер ведае, якой даўжыні пароль, таму ён паведамляе пра кароткі пасля ўваходу; пароль і яго даўжыня ніколі яго не пакідаюць. Выпраўлена — гэта калі пароль уліковага запісу быў зменены - уліковы запіс, які зноў уваходзіць з кароткім паролем, зноў паказваецца як занадта кароткі.",
"password-length-no-results": "Тут няма ўліковых запісаў.",
"password-length-first-login": "Першы ўваход",
"password-length-last-login": "Апошні ўваход",
"password-length-login-count": "Колькасць уваходаў",
"password-length-fixed": "Выпраўлена",
"password-length-filter-short": "Занадта кароткі пароль",
"password-length-filter-fixed": "Выпраўлена на дастаткова доўгі пароль",
"password-length-filter-all": "Усе",
"password-min-length": "Мінімальная даўжыня пароля",
"password-min-length-description": "Уваход з карацейшым паролем пералічваецца ніжэй. Уваход праз гэта ніколі не адхіляецца.",
"password-min-length-enforce": "Патрабаваць мінімальную даўжыню",
"password-min-length-enforce-description": "Карацейшы новы пароль адхіляецца, а ўліковы запіс, які ўваходзіць з карацейшым паролем, павінен змяніць яго перш за ўсё. Змена пароля завяршае ўсе сеансы ўліковага запісу. Паролі LDAP, OAuth2, OIDC, SAML, CAS і Sandstorm належаць іх пастаўшчыку і не правяраюцца.",
"password-length-saved": "Захавана.",
"password-length-from-environment": "Дзейнічае значэнне, зададзенае ў асяроддзі сервера; захаванне тут перавызначае яго.",
"password-change-required-title": "Змяніце свой пароль",
"password-change-required-text": "Гэты сайт цяпер патрабуе паролі даўжынёй не менш за __min__ сімвалаў, а ваш карацейшы. Выберыце даўжэйшы, каб працягнуць. Пасля гэтага ўсе сеансы вашага ўліковага запісу будуць завершаны, і вы ўвойдзеце зноў з новым паролем.",
"password-same-as-username": "Пароль не павінен супадаць з імем карыстальніка або адрасам электроннай пошты.",
"if-you-already-have-an-account": "Калі ў вас ужо ёсць акаўнт",
"register": "Зарэгістравацца",
"forgot-password": "Забыў пароль",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Акно няўдач (секунды)",
"accounts-lockout-settings-updated": "Налады абароны ад грубай сілы абноўлены",
"accounts-lockout-locked-users": "Заблакіраваныя карыстальнікі",
"admin-audit-log": "Журнал аўдыту",
"admin-audit-log-info": "Што рабілі адміністратары сайта: змены карыстальнікаў, уваходу, сховішча і рэзервовых копій, спачатку найноўшыя. Паролі і сакрэты ніколі не запісваюцца. Захоўваецца адзін год.",
"admin-audit-log-when": "Калі",
"admin-audit-log-who": "Адміністратар",
"admin-audit-log-action": "Дзеянне",
"admin-audit-log-details": "Дэталі",
"admin-audit-log-empty": "Пакуль нічога не запісана.",
"accounts-lockout-locked-users-info": "Карыстальнікі, у цяперашні час заблакіраваныя праз занадта шмат няўдалых спроб уваходу",
"accounts-lockout-no-locked-users": "У цяперашні час няма заблакіраваных карыстальнікаў",
"accounts-lockout-failed-attempts": "Няўдалыя спробы",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-сумяшчальнае аб'ектнае сховішча: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces і іншыя.",
"s3-force-path-style": "Прымусова выкарыстоўваць URL у стылі шляху",
"s3-force-path-style-description": "Патрабуецца MinIO і большасцю не-AWS S3-сумяшчальных сервераў.",
"s3-sse": "Шыфраванне на баку сервера",
"s3-sse-none": "Не запытана (прадвызначана для бакета)",
"s3-sse-description": "S3 шыфруе кожнае ўкладанне, аватар і рэзервовую копію, якія запісвае WeKan, і расшыфроўвае іх для WeKan пры чытанні.",
"s3-sse-kms-key-id": "Ідэнтыфікатар ключа KMS для SSE-KMS (пуста = прадвызначаны ключ уліковага запісу)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Сховішча Microsoft Azure Blob.",
"azure-account-name": "Назва акаўнта",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Скануйце гэты QR-код з дапамогай прыкладання-аўтэнтыфікатара (напрыклад, Google Authenticator, Authy), затым увядзіце паказаны 6-значны код для пацвярджэння.",
"twoFactorAuth-manual-entry": "Або ўвядзіце гэты код уручную: ",
"twoFactorAuth-confirm": "Пацвердзіць і ўключыць",
"two-factor-required-title": "Наладзьце двухфактарную аўтэнтыфікацыю",
"two-factor-required-text": "Гэты сайт патрабуе двухфактарную аўтэнтыфікацыю для вашага ўліковага запісу. Наладзьце яе, каб працягнуць.",
"twoFactorCode-prompt": "Увядзіце ваш 6-значны код аўтэнтыфікацыі",
"twoFactorCode-submit": "Праверыць",
"twoFactorCode-cancel": "Скасаваць",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Скорост",
"progress": "Напредък",
"password-again": "Парола (повторно)",
"password-pwned": "Тази парола е открита при изтичане на данни. Изберете друга.",
"password-too-short": "Паролата трябва да е дълга поне __min__ знака.",
"password-length-report-title": "Дължина на паролата",
"password-length-report-desc": "Профили, които са влезли с парола, по-къса от минималната дължина. Само браузърът знае колко дълга е паролата, затова съобщава за къса парола след влизане; паролата и дължината ѝ никога не го напускат. Поправено е моментът, в който паролата на профила е била сменена - профил, който отново влезе с къса парола, отново се показва като твърде къса.",
"password-length-no-results": "Няма профили тук.",
"password-length-first-login": "Първо влизане",
"password-length-last-login": "Последно влизане",
"password-length-login-count": "Брой влизания",
"password-length-fixed": "Поправено",
"password-length-filter-short": "Твърде къса парола",
"password-length-filter-fixed": "Поправено с достатъчно дълга парола",
"password-length-filter-all": "Всички",
"password-min-length": "Минимална дължина на паролата",
"password-min-length-description": "Влизането с по-къса парола се показва по-долу. Влизането никога не се отказва заради това.",
"password-min-length-enforce": "Налагане на минималната дължина",
"password-min-length-enforce-description": "По-къса нова парола се отхвърля, а профил, който влиза с по-къса парола, трябва да я смени преди всичко друго. Смяната ѝ излиза от всички сесии на профила. Паролите от LDAP, OAuth2, OIDC, SAML, CAS и Sandstorm са на техния доставчик и не се проверяват.",
"password-length-saved": "Запазено.",
"password-length-from-environment": "В сила е стойност, зададена в средата на сървъра; запазването тук я замества.",
"password-change-required-title": "Сменете паролата си",
"password-change-required-text": "Този сайт вече изисква пароли от поне __min__ знака, а вашата е по-къса. Изберете по-дълга, за да продължите. След това всички сесии на профила ви се прекратяват и влизате отново с новата парола.",
"password-same-as-username": "Паролата не трябва да бъде потребителското име или имейл адресът.",
"if-you-already-have-an-account": "Ако вече имате акаунт",
"register": "Регистрация",
"forgot-password": "Забравена парола",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Период за отчитане на неуспешните опити (секунди)",
"accounts-lockout-settings-updated": "Настройките за защита от атаки с отгатване на пароли са обновени",
"accounts-lockout-locked-users": "Блокирани потребители",
"admin-audit-log": "Одитен журнал",
"admin-audit-log-info": "Какво са направили администраторите на сайта: промени в потребители, вход, хранилище и резервни копия, най-новите първо. Пароли и тайни никога не се записват. Пази се една година.",
"admin-audit-log-when": "Кога",
"admin-audit-log-who": "Администратор",
"admin-audit-log-action": "Действие",
"admin-audit-log-details": "Подробности",
"admin-audit-log-empty": "Все още няма записи.",
"accounts-lockout-locked-users-info": "Потребители, блокирани в момента поради твърде много неуспешни опити за вход",
"accounts-lockout-no-locked-users": "В момента няма блокирани потребители",
"accounts-lockout-failed-attempts": "Неуспешни опити",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-съвместимо обектно хранилище: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces и други.",
"s3-force-path-style": "Наложи URL адреси в стил път (path-style)",
"s3-force-path-style-description": "Изисква се от MinIO и повечето не-AWS S3-съвместими сървъри.",
"s3-sse": "Криптиране от страна на сървъра",
"s3-sse-none": "Не е заявено (по подразбиране за bucket-а)",
"s3-sse-description": "S3 криптира всеки прикачен файл, аватар и резервно копие, които WeKan записва, и ги декриптира за WeKan при четене.",
"s3-sse-kms-key-id": "ID на KMS ключ за SSE-KMS (празно = ключът по подразбиране на акаунта)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Име на акаунта",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Сканирайте този QR код с приложение за удостоверяване (напр. Google Authenticator, Authy), след което въведете показания 6-цифрен код за потвърждение.",
"twoFactorAuth-manual-entry": "Или въведете този код ръчно: ",
"twoFactorAuth-confirm": "Потвърждаване и активиране",
"two-factor-required-title": "Настройте двуфакторно удостоверяване",
"two-factor-required-text": "Този сайт изисква двуфакторно удостоверяване за вашия акаунт. Настройте го, за да продължите.",
"twoFactorCode-prompt": "Въведете вашия 6-цифрен код за удостоверяване",
"twoFactorCode-submit": "Проверка",
"twoFactorCode-cancel": "Отмени",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "गति",
"progress": "प्रगति",
"password-again": "पासवर्ड (फेर से)",
"password-pwned": "ई पासवर्ड कवनो डेटा लीक में देखाई देले बा। कवनो दोसर चुनीं।",
"password-too-short": "पासवर्ड कम से कम __min__ अक्षर के होखे के चाहीं।",
"password-length-report-title": "पासवर्ड के लंबाई",
"password-length-report-desc": "ऊ खाता जवन न्यूनतम लंबाई से छोट पासवर्ड से साइन इन कइलें। पासवर्ड केतना लंबा बा ई खाली ब्राउजर जानेला, एही से साइन इन के बाद ऊ छोट पासवर्ड के जानकारी देला; पासवर्ड आ ओकर लंबाई कबो ओकरा से बाहर ना जाला। ठीक भइल माने जब खाता के पासवर्ड बदलल गइल - जवन खाता फेर से छोट पासवर्ड से साइन इन करी, ऊ फेर से बहुत छोट देखावल जाई।",
"password-length-no-results": "इहाँ कवनो खाता नइखे।",
"password-length-first-login": "पहिला लॉगिन",
"password-length-last-login": "आखिरी लॉगिन",
"password-length-login-count": "केतना बेर लॉगिन भइल",
"password-length-fixed": "ठीक भइल",
"password-length-filter-short": "बहुत छोट पासवर्ड",
"password-length-filter-fixed": "काफी लंबा पासवर्ड से ठीक भइल",
"password-length-filter-all": "सब",
"password-min-length": "न्यूनतम पासवर्ड लंबाई",
"password-min-length-description": "छोट पासवर्ड से साइन इन कइल नीचे सूची में देखावल जाला। एकरा चलते साइन इन कबो मना ना कइल जाला।",
"password-min-length-enforce": "न्यूनतम लंबाई लागू करीं",
"password-min-length-enforce-description": "छोट नया पासवर्ड मना कर दिहल जाला, आ जवन खाता छोट पासवर्ड से साइन इन करेला ओकरा कवनो दोसर काम से पहिले एकरा के बदले के पड़ी। एकरा के बदलला से खाता के हर सत्र से साइन आउट हो जाला। LDAP, OAuth2, OIDC, SAML, CAS आ Sandstorm के पासवर्ड ओह लोग के प्रदाता के होला, आ जाँचल ना जाला।",
"password-length-saved": "सहेजल गइल।",
"password-length-from-environment": "सर्वर के वातावरण में सेट कइल एगो मान लागू बा; इहाँ सहेजला से ऊ बदल जाला।",
"password-change-required-title": "आपन पासवर्ड बदलीं",
"password-change-required-text": "ई साइट अब कम से कम __min__ अक्षर के पासवर्ड माँगेले, आ रउआ के छोट बा। आगे बढ़े खातिर एगो लंबा पासवर्ड चुनीं। ओकरा बाद रउआ खाता के हर सत्र से साइन आउट हो जाई, आ रउआ नया पासवर्ड से फेर से साइन इन करब।",
"password-same-as-username": "पासवर्ड उपयोगकर्ता नाम भा ईमेल पता ना होखे के चाहीं।",
"if-you-already-have-an-account": "अगर रउआ लगे पहिले से खाता बा",
"register": "पंजीकरण करीं",
"forgot-password": "पासवर्ड भुला गइनी",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "असफलता समय-सीमा (सेकंड)",
"accounts-lockout-settings-updated": "जबरदस्ती हमला सुरक्षा सेटिंग नया कर दिहल गइल",
"accounts-lockout-locked-users": "तालाबंद उपयोगकर्ता",
"admin-audit-log": "ऑडिट लॉग",
"admin-audit-log-info": "साइट के प्रशासक लोग का कइल: उपयोगकर्ता, लॉगिन, स्टोरेज आ बैकअप में बदलाव, सबसे नया पहिले। पासवर्ड आ गुप्त बात कबो दर्ज ना कइल जाला। एक साल ले रखल जाला।",
"admin-audit-log-when": "कब",
"admin-audit-log-who": "प्रशासक",
"admin-audit-log-action": "कार्रवाई",
"admin-audit-log-details": "विवरण",
"admin-audit-log-empty": "अबहीं ले कुछुओ दर्ज नइखे भइल।",
"accounts-lockout-locked-users-info": "बहुत अधिक असफल लॉग इन कोसिस के कारण अबहीं तालाबंद उपयोगकर्ता",
"accounts-lockout-no-locked-users": "अबहीं कवनो तालाबंद उपयोगकर्ता नइखे",
"accounts-lockout-failed-attempts": "असफल कोसिस",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-संगत ऑब्जेक्ट भंडारण: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces आ दोसर।",
"s3-force-path-style": "पथ-जइसन URL जरूरी करीं",
"s3-force-path-style-description": "MinIO आ अधिकतर गैर-AWS S3-संगत सर्वर खातिर जरूरी।",
"s3-sse": "सर्वर ओर एन्क्रिप्शन",
"s3-sse-none": "कुछ ना माँगल गइल (बकेट के पहिले से तय)",
"s3-sse-description": "WeKan जवन हर संलग्नक, अवतार आ बैकअप लिखेला, ओकरा के S3 एन्क्रिप्ट करेला, आ पढ़े घरी WeKan खातिर डिक्रिप्ट करेला।",
"s3-sse-kms-key-id": "SSE-KMS खातिर KMS कुंजी ID (खाली = खाता के पहिले से तय कुंजी)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "खाता नाँव",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "ई QR कोड के प्रमाणीकरण ऐप से स्कैन करीं (जइसे Google Authenticator, Authy), फेर पुष्टि करे खातिर देखावल 6-अंक के कोड डालीं।",
"twoFactorAuth-manual-entry": "या ई कोड मैन्युअल रूप से डालीं: ",
"twoFactorAuth-confirm": "पुष्टि करीं आ चालू करीं",
"two-factor-required-title": "दू-कारक प्रमाणीकरण सेट करीं",
"two-factor-required-text": "ई साइट रउरा खाता खातिर दू-कारक प्रमाणीकरण माँगेले। आगे बढ़े खातिर एकरा के सेट करीं।",
"twoFactorCode-prompt": "अपना 6-अंक के प्रमाणीकरण कोड डालीं",
"twoFactorCode-submit": "सत्यापित करीं",
"twoFactorCode-cancel": "रद्द करीं",
+34
View File
@@ -2586,6 +2586,27 @@
"speed": "Spid",
"progress": "Wok we i gohed",
"password-again": "Sikret wod (bakegen)",
"password-pwned": "Paswod ia i bin kamaot long wan data we i lik. Jusum narawan.",
"password-too-short": "Paswod i mas gat __min__ leta o moa.",
"password-length-report-title": "Longfala blong paswod",
"password-length-report-desc": "Ol akaon we oli saen-in wetem wan paswod we i sot moa long smol longfala. Bravsa nomo i save hamas paswod i long, ale hem i ripotem wan sot paswod afta long saen-in; paswod mo longfala blong hem oli neva lego bravsa. Stretem i taem we paswod blong akaon i jenis - wan akaon we i saen-in bakegen wetem sot paswod bambae i soem bakegen se i sot tumas.",
"password-length-no-results": "I no gat akaon long ples ia.",
"password-length-first-login": "Fes login",
"password-length-last-login": "Las login",
"password-length-login-count": "Hamas taem i login",
"password-length-fixed": "Stretem finis",
"password-length-filter-short": "Paswod i sot tumas",
"password-length-filter-fixed": "Stretem finis long paswod we i long naf",
"password-length-filter-all": "Evriwan",
"password-min-length": "Smol longfala blong paswod",
"password-min-length-description": "Saen-in wetem wan sot paswod i stap long lis daon. Saen-in i neva blokem from samting ia.",
"password-min-length-enforce": "Mekem man i folem smol longfala",
"password-min-length-enforce-description": "Wan niu paswod we i sot moa bambae oli no akseptem, mo wan akaon we i saen-in wetem sot paswod i mas jenisim fastaem bifo long eni narafala samting. Taem yu jenisim, evri sesen blong akaon i aot. Ol paswod blong LDAP, OAuth2, OIDC, SAML, CAS mo Sandstorm oli blong provaeda blong olgeta, mo oli no jekem.",
"password-length-saved": "I sevem finis.",
"password-length-from-environment": "Wan valiu we oli putum long envaeronmen blong seva i wok naoia; sevem long ples ia i jenisim hem.",
"password-change-required-title": "Jenisim paswod blong yu",
"password-change-required-text": "Saet ia naoia i nidim ol paswod we i gat __min__ leta o moa, mo paswod blong yu i sot moa. Jusum wan we i long moa blong gohed. Afta, evri sesen blong akaon blong yu i aot, mo yu saen-in bakegen wetem niu paswod.",
"password-same-as-username": "Paswod i no save sem mak nem blong yusa o adres blong imel.",
"if-you-already-have-an-account": "Sapos yu gat wan akaon finis",
"register": "Rejista",
"forgot-password": "Yu fogetem paswod",
@@ -2661,6 +2682,13 @@
"accounts-lockout-failure-window": "Taem blong kaontem ol fel (seken)",
"accounts-lockout-settings-updated": "Oli jenisim ol seting blong protektem long atak blong traem plante paswod",
"accounts-lockout-locked-users": "Ol yusa we oli Lokem",
"admin-audit-log": "Odit lok",
"admin-audit-log-info": "Wanem ol administreta blong saet oli mekem: ol jenis long yusa, login, storej mo bekap, niuwan fastaem. Oli neva raetemdaon ol paswod mo ol sikret. Oli kipim blong wan yia.",
"admin-audit-log-when": "Wetaem",
"admin-audit-log-who": "Administreta",
"admin-audit-log-action": "Aksen",
"admin-audit-log-details": "Ol ditel",
"admin-audit-log-empty": "I no gat samting we oli raetemdaon yet.",
"accounts-lockout-locked-users-info": "Ol yusa we oli lokem olgeta naoia from oli traem go insaed plante taem tumas we i no wok",
"accounts-lockout-no-locked-users": "Naoia i no gat yusa we oli lokem",
"accounts-lockout-failed-attempts": "Ol traem we oli fel",
@@ -2738,6 +2766,10 @@
"s3-minio-storage-description": "S3-compatible object storage: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces mo others.",
"s3-force-path-style": "Fosem ol URL blong yusum fomat blong rod",
"s3-force-path-style-description": "I mas gat blong MinIO mo plante seva we oli wok olsem S3 be oli no blong AWS.",
"s3-sse": "Enkripsen long saed blong seva",
"s3-sse-none": "I no askem wan (stamba blong baket)",
"s3-sse-description": "S3 i enkriptem evri samting we i joen, avata mo bekap we WeKan i raetem, mo i dekriptem bakegen blong WeKan taem i ridim.",
"s3-sse-kms-key-id": "KMS ki ID blong SSE-KMS (i emti = stamba ki blong akaon)",
"azure-blob-storage": "Azure Blob Storage",
"azure-blob-storage-description": "Microsoft Azure Blob Storage.",
"azure-account-name": "Nem blong akaon",
@@ -3255,6 +3287,8 @@
"twoFactorAuth-scan-instructions": "Skanem QR kod ia wetem wan aplikesen blong pruvum (olsem Google Authenticator, Authy), biaen putum kod we i gat 6 namba we hem i soem blong pruvum.",
"twoFactorAuth-manual-entry": "O putum kod ia wetem han blong yu: ",
"twoFactorAuth-confirm": "Pruvum mo Statem",
"two-factor-required-title": "Setemap Tu-Fakta Pruvum",
"two-factor-required-text": "Saet ia i nidim Tu-Fakta Pruvum blong akaon blong yu. Setemap blong go hed.",
"twoFactorCode-prompt": "Putum kod blong yu we i gat 6 namba blong pruvum",
"twoFactorCode-submit": "Pruvum",
"twoFactorCode-cancel": "Kanselem",
Loaded 100 of 505 files, more files were not shown because too many files have changed in this diff. Show more