mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* [MM-68421] Frontend: PAT creation UI — expiry picker and status display Wires the webapp UI to the server-side support added in PR #36706 (and the MM-68419 model changes): - Extends UserAccessToken type with expires_at, and ClientConfig with EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays. - Threads expires_at through Client4.createUserAccessToken and the mattermost-redux createUserAccessToken action. - Adds a date/time picker to the PAT creation form (reuses DateTimePickerModal). Honors enforcement and clamps to the max-lifetime setting; maps server error ids (expires_at_required/in_past/too_far) to localized messages. - Displays expiry, derived status badge (active/expired/disabled), and an approaching-expiry warning (<7 days) in the account settings token list. Mirrors expiry + status in the admin Manage Tokens modal. - Adds the supporting i18n keys. * [MM-68421] Address review: i18n, ServerError type, scss, explicit guards - Inject intl so the "Expires in N days" tooltip and the picker ariaLabel are localized instead of hard-coded English. - Use the canonical ServerError type from @mattermost/types/errors instead of an ad-hoc inline cast. - Dedupe the new i18n ids — keep a single "Expires: " string per namespace and drop the duplicates introduced in the first pass. - Add scss for setting-box__token-expiry / __token-status / __token-expiry-warning so the new status pill and warning render with the expected styling. - Replace truthy checks on the numeric expiresAt with explicit > 0 comparisons for readability. * [MM-68421] Replace expiry datetime picker with preset chooser + custom date Per UX discussion: PATs are long-lived so sub-day precision is noise. Swap the DateTimePickerModal for a native <select> of presets (No expiry, 7d, 30d, 90d, 1 year, Custom date) with a date-only <input type="date"> revealed when Custom is selected. Effective time is end-of-local-day, which matches how users think about expiry. - Drops the DateTimePickerModal import and the picker open/close handlers; removes the moment-timezone import. - Adds isPresetAllowed() that hides presets exceeding MaximumPersonalAccessTokenLifetimeDays, and a defaultExpiryPreset() that picks 30d (then 7d, then Custom) when enforcement is on, No expiry otherwise. - The custom <input type="date"> uses min=today and max=now+maxDays for native bounds; submit-time validation still maps server error ids if the user bypasses the bounds. - i18n: adds preset labels; drops the now-unused picker/clear/change strings. * [MM-68421] Fix CI: playwright config and ESLint blank line - Add EnforcePersonalAccessTokenExpiry / MaximumPersonalAccessTokenLifetimeDays to e2e-tests/playwright/lib/src/server/default_config.ts so its tsc -b matches the updated ServiceSettings shape. - Drop a stray double blank line in user_access_token_section.tsx that tripped no-multiple-empty-lines. * [MM-68421] Sort i18n keys to match formatjs extract output ci/i18n-extract diffs en.json against the formatjs extractor's sorted output and fails on any difference. Re-run the extract so the new PAT-expiry keys land in alphabetical position. * [MM-68421] Address CodeRabbit findings - Clamp the default custom-expiry date to maxLifetimeDays when set so the form doesn't open in an invalid state when defaultExpiryPreset() falls back to 'custom'. - Reject a cleared/empty custom date with expires_at_required instead of silently submitting without an expiry; only forward expiresAt to the action when it's > 0. - Use an explicit undefined check in Client4.createUserAccessToken so an intentional 0 isn't dropped from the request body. * [MM-68421] Update manage_tokens_modal snapshot for expiry + status row The admin token list now renders an Expires row and a status badge per token; refresh the jest snapshot. * [MM-68421] Add unit tests for PAT expiry helpers Per the PR Test Analysis advisory: export the pure helpers from user_access_token_section.tsx and add unit tests covering them. Coverage: - deriveTokenStatus: active / expired / inactive branches. - mapServerErrorIdToMessage: all three server error ids (short and api.user.create_user_access_token.*.app_error variants) and the default null path. - endOfLocalDayPlusDays: end-of-day on the Nth future day, 0 days. - endOfLocalDayFromIsoDate: valid ISO date and malformed inputs. - PRESET_DAYS: snapshot of the preset durations. * [MM-68421] Freeze time in PAT helper tests to avoid midnight flake The date arithmetic helpers (Date.now, new Date()) could disagree across a midnight boundary, making the tests theoretically flaky. Pin system time to a stable mid-day in 2026 via jest.useFakeTimers. * [MM-68421] Add component tests for PAT expiry creation UI Covers the create-form validation branches (missing description, empty custom date, past date, beyond maxLifetimeDays), enforceExpiry rendering (no-expiry option hidden + enforced hint), maxLifetimeDays preset filtering, and token-list status display (active/expired/disabled, never, and the "expires soon" warning). * [MM-68421] Add behavioral status/expiry tests for manage_tokens_modal Covers the admin token modal's derived status display: Active + "Never" for an active token without expiry, Expired for an active token past its expiry, Disabled for an inactive token regardless of expiry, and Active with a rendered date (not "Never") for a token expiring in the future. * [MM-68421] Align PAT expiry UI with shipped server contract The server (#36706) did not ship a separate EnforcePersonalAccessTokenExpiry flag — expiry enforcement is implied by MaximumPersonalAccessTokenLifetimeDays > 0. Two webapp gaps surfaced once the server side merged: - enforceExpiry was read from the never-sent config.EnforcePersonalAccessToken Expiry, so the "hide No-expiry / require expiry" path was dead. Derive it from maxLifetimeDays > 0 instead and drop the dead config flag from the component, redux props, ClientConfig/ServiceSettings types, and the e2e default config. - The server returns app.user_access_token.expires_at_{required,in_past,too_far} .app_error, but mapServerErrorIdToMessage matched the api.user.create_user_ access_token.* namespace, so the localized errors never fired. Map the actual ids. Unit tests updated accordingly. * [MM-68421] Add Playwright e2e for PAT expiry UI Covers, against a real server, the personal access token expiry surfaces in Account Settings > Security: - the expiry picker renders all presets and reveals the custom-date input - a custom expiry with no date is blocked client-side - MaximumPersonalAccessTokenLifetimeDays > 0 hides "No expiry" and oversized presets, shows the enforced hint, and rejects an over-the-limit custom date - the token list shows Active/Never, an "expires in N days" warning, and the Disabled badge (seeded via the API) The expired-status badge is left to the component unit tests since the server rejects creating a token whose expiry is already in the past. * [MM-68421] Use locator('option') for native select assertions getByRole('option') does not reliably match the options of a closed native <select>, which would make the absence assertions (toHaveCount(0)) pass vacuously. Query option elements by DOM instead, matching the repo's house pattern for native selects. * [MM-68421] Fix expiry overshoot vs server cap and review nits Review found that presets/custom dates resolve to end-of-local-day, which can sit up to ~24h beyond the server cap of "now + MaximumPersonalAccessTokenLife timeDays" (an exact duration from creation time). With a max configured, the default preset equals the cap, so accepting the default and saving was rejected server-side with expires_at_too_far for most of the day. - Clamp the submitted expiry to the server cap when a maximum lifetime is set. Validation still runs on the raw end-of-day value so an explicitly out-of-range custom date is still rejected; only the in-range end-of-day overshoot is clamped. - Count "expires in N days" from the start of today and floor it, so an end-of-day expiry no longer over-reports by one (a 7-day token reads "7 days", not "8"). - Add an aria-label to the custom expiry date input. Tests: unit coverage for clampExpiresAtToMaxLifetime; a Playwright spec that creates a token with the default preset under a 30-day cap and asserts success. * [MM-68421] Apply prettier formatting to PAT e2e spec The ci/playwright/npm-check job (lint + prettier + tsc + lint:test-docs) failed on prettier formatting. eslint and tsc were clean; reformat the spec to satisfy prettier as well. * [MM-68421] Add PAT max-lifetime System Console setting and creation-form UX fixes - Add ServiceSettings.MaximumPersonalAccessTokenLifetimeDays number field to System Console > Integrations > Integration Management, after Enable Personal Access Tokens (disabled when tokens are disabled). - Disable the token creation Save button until a non-empty description is entered (description input is now controlled; whitespace-only rejected). - Render the token creation form as a distinct "Create New Token" card so it no longer blends into the existing token list. * Fix stylelint property order in new-token card * [MM-68421] Validate PAT expiry inline before submit Surface the expiry validation error in the create-token form and disable Save while the selection is invalid, instead of only failing inside the create-confirmation flow. Previously a system admin had to click Save then "Yes, Create" before seeing "An expiry date is required." for an empty custom date. Extracts the expiry checks into getExpiryValidationError(), reuses it as the handleCreateToken guard, and renders the result inline + in the Save button's disabled condition. * [MM-68421] Fix PAT expiry e2e specs for inline validation The "blocks submitting a custom expiry with no date chosen" and "enforces expiry when a maximum lifetime is configured" specs clicked the Save button and expected an inline error afterward. Since 7ccd65ea surfaces the expiry error inline and disables Save while the selection is invalid, the click timed out on a disabled button. Assert the inline error is visible and that Save is disabled, instead of clicking it. * MM-69075: server-side revoke non-compliant PATs Adds store queries (GetNonCompliantExpiry/CountNonCompliantExpiry), app methods to count and bulk-revoke (hard-delete) non-compliant PATs in batches with session-cache invalidation, sysadmin-gated api4 endpoints with audit logging, Client4 methods, and the OpenAPI spec. * MM-69075: System Console button to revoke non-compliant PATs Adds a 'Revoke non-compliant tokens' control under Integrations > Integration Management (next to Maximum Personal Access Token Lifetime). It shows the current non-compliant count (refreshed on load, save, and revoke), disables when there is nothing to revoke, and confirms the irreversible delete with the blast radius. Wires up the TS Client4 methods and i18n strings. * MM-69075: Address PR review feedback - Rename route from /tokens/revoke_non_compliant to /tokens/non_compliant/revoke for consistency with /tokens/non_compliant/count - Remove redundant c.LogAudit("") before permission check - Remove redundant c.LogAudit on success (structured audit record is sufficient) * MM-69075: Simplify non-compliant token revocation with single store call - Add DeleteNonCompliantExpiry store method: atomically deletes non-compliant tokens and their sessions in a single Postgres CTE, returning affected user IDs for session cache clearing - Replace the get->extract IDs->delete dance in RevokeNonCompliantUserAccessTokens with the new single store call per batch - Switch post-loop partial completion check to CountNonCompliantExpiry - Add partial completion error i18n string - Clear stale error banner in refreshCount on successful fetch * MM-69075: Remove GetNonCompliantExpiry, migrate tests to DeleteNonCompliantExpiry GetNonCompliantExpiry is now unused — DeleteNonCompliantExpiry supersedes it. Remove it from the store interface, sqlstore, retrylayer, timerlayer, and mock. Migrate the store test to exercise DeleteNonCompliantExpiry instead, adding session-deletion verification. * MM-69075: Fix DISTINCT undercounting bug and add multi-token-per-user test - Remove SELECT DISTINCT from DeleteNonCompliantExpiry CTE so each deleted token row is returned, not collapsed per user; totalRevoked now counts tokens, not users, and batch-continuation is correct - Deduplicate userIDs in app layer before ClearSessionCacheForUser calls - Add multi-token-per-user fixture to store test: two non-compliant tokens sharing a UserId verify len(userIDs)==4 and catch any future DISTINCT regression * MM-69075: Fix generated store layers and mock ordering Regenerate retrylayer/timerlayer via make store-layers and fix DeleteNonCompliantExpiry alphabetical position in mock file. * MM-69075: Inline nonCompliantExpiryWhere into its sole caller The helper was extracted to share the predicate between GetNonCompliantExpiry and CountNonCompliantExpiry. GetNonCompliantExpiry is gone; with a single call site the extracted function adds no value. * MM-69075: Fix flaky test — relax global count assertions Global count assertions (require.Equal against baseline) are fragile when other concurrent tests hold non-compliant tokens. Replace exact equality with GreaterOrEqual/LessOrEqual for global counts. The DISTINCT regression is still caught precisely: sharedUserID must appear exactly twice in the returned slice (once per token, not once per user). * MM-69075: Fix false partial-revoke error caused by read-replica lag After DeleteNonCompliantExpiry writes to master, the post-delete CountNonCompliantExpiry read targets the replica, which may not have caught up yet. This made the first revoke call return a spurious HTTP 500 even though all tokens were actually deleted, and a second click then showed 'Revoked 0' because nothing remained. Fix: track whether the batch loop exited via a natural break (all done) vs. exhausted revokeNonCompliantMaxBatches (genuinely incomplete), and signal the partial-revoke error only in the latter case. This removes the racy replica read entirely — the loop's own exit conditions prove completion on master. * MM-69075: Add Playwright e2e coverage for revoke non-compliant tokens UI Covers the gap flagged in PR review (#37030): button/disabled states, AlertBanner states, confirmation modal open/cancel/confirm, count refresh after policy save, and token auth invalidation (compliant and bot tokens survive a revoke). * MM-69075: Fix testid for number-type Maximum PAT Lifetime field Number-type TextSetting inputs use ${id}number as their test id, not ${id}input (only text-type inputs get the 'input' suffix). Found by actually running the spec locally against a server built from this branch - all 4 tests now pass. * MM-69075: Fix prettier formatting in e2e spec CI's prettier --check flagged this file; ran prettier --write to match project style. The other CI lint warnings (max-lines, no-warning-comments) are pre-existing, in files this branch never touched. * MM-69075: Fix TS2345 - UserAccessToken.token is optional The token secret field is only populated on the object returned from CreateUserAccessToken, so its type is string | undefined. Guard for that in tokenIsUsable instead of asserting non-null at every call site. --------- Co-authored-by: Hanzo Dev <[email protected]>