Repository navigation
Minor release from three batches of consumer requests: SSO operations (a check command, a declared list of session guards, a stricter signed token, the break-glass flag on the login page), table and detail presentation (full-width detail rows, column alignment, responsive columns, a sticky actions column, dropdown selects), and behaviour (the dashboard cache follows the cards, an emit message for actions, a field error helper for custom inputs, a throttle bucket for the metrics). One bug is fixed: SSO revocation on the Redis cache store. Read Upgrading to v2.23.0: the login page now opens the password form on ?password_login=1 only when a break-glass address is configured, and a field that already calls fullWidth() changes on the detail page.
Security
- A signed token whose payload or footer JSON repeats a key is refused. The check runs at any depth and compares keys after decoding, so escaped spellings of one key collide. The refusal reason is
malformed. Tokens Martis issues never repeat a key, so nothing legitimate is affected.
Added
martis.auth.session_guards. Declare which guards write Laravel'ssessions.user_id(defaultnull: everysessionguard counts, as before). With a list, only the Martis guard (always counted) and the listed guards decide whether a session row can be attributed to one person, so an admin-only app is no longer read as ambiguous because of thewebguard Laravel merges into every app: SSOrevokeFor(),authz.revoke_sessions_on_demoteand the profile's Browser sessions then work. A value that is not a list of guard names, a name that is not inauth.guards, or a guard whose driver is notsessionthrows, naming the key and the guard. The warnings name the guards behind the tables. The list must be complete: a guard that signs in people but is left out can make Martis delete, or list, the session of another person with the same id.php artisan martis:sso:check. Read-only validation of the issuer block, each enabled signed-token provider block and the SSO cache store, with the readers the runtime uses. One line per block, never prints a key or any part of one, exit code 1 on any failure and 0 when all is well or SSO is off. For a deploy script.passwordLogin.breakGlassin the published auth config. A boolean: password sign-in is off andauth.password_login.break_glass_emailsholds an entry. The list itself is never published, and the flag is computed without validating the list.Field::hideLabelOnDetail()hides a field's label on the detail page and the drawer (the label stays for screen readers). Detail rows carrydata-fieldanddata-type.Resource::detailKicker()sets, hides (false) or keeps (null) the small heading above the detail page.Field::textAlign('left'|'center'|'right')aligns an index column, header and cells; a right-aligned column uses tabular figures. Thecolumnblock of a field gainstextAlignandhideBelow(bothnullby default). It does not apply to relationship tables yet.Field::hideBelow('sm'|'md'|'lg'|'xl')hides an index column on viewports narrower than 640, 768, 1024 or 1280 px. It does not apply to relationship tables yet.Resource::stickyActions()pins the actions column to the right edge of the index while the table scrolls sideways, with the background of the row in every state (stripe, hover, selected) in light and dark.ActionResponse::emit()takes an optional message, andsilent()drops the toast.emit($event, $data, 'Saved')shows that text instead of the generic one;emit(...)->silent()shows none. The event is still emitted and the table still refreshes; an existing call produces the same payload as before.FieldErroranduseFieldError()are exported from@martis/runtime, so a custom input draws its error the way the built-in fields do and getsaria-invalidandaria-describedby.FieldWrapperuses the same component with the same markup.- A throttle bucket for the metrics.
martis.throttle.metrics_max_attemptsandmetrics_decay_minutes(MARTIS_THROTTLE_METRICS_MAX,MARTIS_THROTTLE_METRICS_DECAY, defaultnull: nothing changes). When set, the routes that read a dashboard or compute a metric count against a bucket of their own and never also against the API bucket. An empty variable reads as unset; a value that is set and is not a positive whole number throws, naming the key.martis.throttle.enabledfalse turns both off.
Changed
- The login page opens the password form on
?password_login=1only whenpasswordLogin.breakGlassis true. With password sign-in off and an emptybreak_glass_emails, the parameter is ignored (nobody could use the form). The flag is computed without validatingbreak_glass_emails, so a malformed list never fails a page render: it is reported by the break-glass sign-in that reads it, namingmartis.auth.password_login.break_glass_emails, as in v2.22.0. - Detail rows and the drawer honour
Field::fullWidth(): one column, the label above the value, the value aligned with the label (the browser's 40pxddindent is reset). It was ignored there before. Fields inside aSectionhonourfullWidth()(the whole row of the section grid, whatevercolSpan()says) andhideLabelOnDetail(), and carrydata-field/data-type, the same as plain rows. - The per-page and trashed selects on the index, lenses and relationship tables are PrimeReact dropdowns instead of native selects. The class
martis-perpage-selectstays on the root, and the focusable input carries the accessible name.
Fixed
- SSO revocation on the Redis cache store. Redis hands the revocation time back as a string and it was read as an unknown type, so a user who was revoked and later re-enabled was refused at every sign-in until the marker expired. The marker now reads as an integer or a string of digits; any other value still counts as revoked.
- The cached dashboard payload follows the cards. The
dashboardscache layer keyed the payload by user and locale only, so a card or filter added, removed or reordered in code was not shown until the TTL ran out. The key now carries an order-sensitive fingerprint of the cards and filters (class anduriKey). - A right- or center-aligned column with a plain (not sortable) header moves the header with the cells. The sticky actions cell matches the colour of its row in dark mode.