682 Commits
Author SHA1 Message Date
raucao 455bf93c81 Link dashboard Storage card to connected apps when present
CI / Test (push) Successful in 44s
Users with stored remoteStorage authorizations now jump straight to the connected apps dashboard from the services dashboard, instead of the storage info page.
2026-11-04 16:08:22 +04:00
raucao 4bca003983 Hash remoteStorage access tokens at rest
CI / Test (pull_request) Successful in 45s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
Store only SHA256(token) in a new token_digest column and use the digest for the Redis authorization keys instead of the plaintext token. The plaintext is now held in memory only for issuance.

This is the expand step: the plaintext token column stays in place so the old code can keep running during the deploy. A follow-up migration will drop it once rs:rekey_tokens has re-keyed Redis and confirmed no plaintext keys remain.

Refs #259
2026-11-04 09:33:57 +04:00
raucao 17bad40bf4 Show device timestamps in the reader's local time
CI / Test (pull_request) Successful in 48s
Release Drafter / Update release notes draft (pull_request) Successful in 3s
The exact issuance and expiry times in the device rows' hover titles were
rendered in UTC, which reads as wrong to anyone not living there.

- Mark the dates with a <time> element carrying the absolute (UTC) timestamp,
  rather than a custom data attribute whose name claimed to hold local time
- Rewrite those titles in the browser's own zone, keeping the server's UTC
  rendering as the fallback for when JavaScript is unavailable
- Only convert values that carry an explicit offset, so a date-only <time> is
  not read as UTC midnight and shifted onto the previous day
- Give the revoke button a little more room from the device name and dates

Part of #254
2026-11-03 18:28:14 +04:00
raucao faa7bf6146 Revoke device authorizations in place with Turbo Streams
Revoking a device redirected away from the storage dashboard, which closed the
details modal and reloaded the page. Refreshing the affected parts instead
keeps the modal open and shows the change where it happened.

- Extract the card and the modal's access list into their own components, so
  either can be re-rendered without touching the modal's open state, and share
  the group summaries between the components
- Derive the app's DOM ids from its client_id (the grouping key) and the device
  row ids from the authorization, through one helper used by both the templates
  and the controller
- Revoke one of several devices as a Turbo Stream: remove the row and refresh
  the card behind the modal and the access list
- Revoke an app's last device with a redirect, so the dashboard re-renders
  itself and grouping, sorting and the empty state stay in one place
- Fade the removed row out, deferring Turbo's own removal until the animation
  has ended

Part of #254
2026-11-03 17:45:37 +04:00
raucao 614ead90a9 Distinguish tokens without a recorded device
Tokens issued before user agents were captured cannot be attributed to a
single device, and the per-app tokens issued earlier could even be reused
from several devices at once.

- Add RemoteStorageAuthorization#device_label, falling back to
  "Unknown device(s)" so the card and the details modal share one source
- Show a lower-bound device count with a "+" whenever an app has such a
  token, so it reads as "could be more" instead of claiming a number we
  cannot support
- Drop the now-redundant device-name wrapper from the details modal

Part of #254
2026-11-03 16:55:05 +04:00
raucao d94fda6d26 Polish the storage dashboard and details modal
CI / Test (pull_request) Successful in 44s
Visual refinement pass on the connected-apps dashboard introduced for #254.

Card:
- Restructure into a header row (app icon, name, host, chevron) with the
  device/category summary as a full-width footer, so the icon aligns with
  the name and the metrics sit on a consistent baseline
- Always show the host alongside the app name, except when it is identical
- Render the icon fallback as a neutral rounded tile instead of a faint
  watermark, and stop distorting real icons (object-contain)
- Summarise the union of the app's scopes, with monitor/folder icons

Details modal:
- Replace the duplicated blue buttons with a secondary Close and a primary
  Launch app, split 50:50 on desktop like the consent dialog
- Tighten the header, section spacing and device rows; hide a redundant host
- Add exact date/time tooltips to the connected and expiry labels
- Align the scope-list folder icons with their labels

Misc:
- Use fewer near-identical grays across the cards and modal
- Drop the redundant top margin on the app grid

Part of #254
2026-11-03 14:36:39 +04:00
raucao 30e45d3d34 Address second-round review nits in the storage dashboard
CI / Test (pull_request) Successful in 43s
Show the union of the app's authorizations' scopes in the modal's Access
section too, so it matches the card's summary, and cover the union
summary with a feature spec.

Part of #254
2026-11-03 12:01:56 +04:00
raucao 27168dde06 Address review nits in the storage dashboard
- Move the app display name and a root-access helper onto
  RemoteStorageAuthorization, used by both components and the controller,
  removing the duplicated fallback chain and the unused OauthHelper includes
- Summarise the card's categories from the union of the app's authorizations'
  scopes, so an older token with broader access is not understated

Part of #254
2026-11-03 12:00:40 +04:00
raucao f2248ea744 Rework storage dashboard and fix authorization revocation
- One card per app (grouped by client_id) showing distinct device and
  category counts; the whole card opens the details modal (with hover and
  focus highlight) instead of a separate Details button
- Details modal: access list styled like the OAuth consent dialog via a
  shared RsScopeListComponent, plus per-token device, connected date and
  expiry, each with its own Revoke
- "folder" -> "category" in the card, modal, consent dialog and the
  auth-created notification
- Make revoke a real DELETE: it was a GET link that Turbo 8's
  prefetch-on-hover fired on its own, silently deleting authorizations.
  Missing or foreign authorizations now return 404 instead of 500.
- Move "Launch app" to the app level
  (GET /services/storage/apps/:client_id/launch); no token id in the URL

Part of #254
2026-11-03 12:00:39 +04:00
raucao c4df1772e0 Determine device names with device_detector
Replace the hand-rolled user-agent regexes with the device_detector gem.
Its UA database recognises many more clients (and, e.g., GNOME Web when
its UA carries the Epiphany token). WebKit "Safari" user agents on Linux
(WebKitGTK, which GNOME Web sends) are labelled "WebKit on Linux", since
Safari never runs on Linux.

Part of #254
2026-11-03 12:00:39 +04:00
raucao c1548035ec Add storage dashboard card and per-app details modal
- Rework RsAuthComponent into a card that shows the app, its device, an
  access summary and the permission status
- Group connected apps by app and device, so repeated authorizations for
  the same app and device (e.g. after a browser update) are shown together,
  each with its own revoke action
- Add RsAuthDetailsModalComponent with the full scope list, device,
  connected tokens and permission expiry, plus Launch and Revoke actions
  (built on the existing ModalComponent)
- Add RemoteStorageAuthorization#device_name (parsed from the user agent)
  and #expired?

Part of #254
2026-11-03 12:00:38 +04:00
raucao eeb399ff83 Document the storage-first launch flow
Add an explicit spec for the storage-first handoff (remoteStorage spec
section 11): launching an app passes only `#remotestorage=<address>`,
never an access token. The app then runs the application-first
(now PKCE-capable) flow itself.

Part of #254
2026-11-03 11:59:51 +04:00
raucao 7c4bad860a Store per-device user agent for remoteStorage authorizations
- Add user_agent to remote_storage_authorizations and rs_authorization_codes
- Capture the browser's user agent at consent, carry it on the authorization
  code, and store it on the authorization when the code is exchanged
- Store it directly for the implicit flow too

With the reuse shortcut gone, each authorization is already a distinct
per-device token; this adds the metadata needed to label and display them.

Part of #254
2026-11-03 11:59:51 +04:00
raucao cd2b4c9ba5 Address second-round review findings in the PKCE core
CI / Test (pull_request) Successful in 42s
- Enqueue the expiry job and the "app connected" notification on
  after_commit instead of after_create, so they only fire once the (now
  transactional) code exchange commits
- Enforce the PKCE code_challenge format (43-128 unreserved chars, S256)
  with model validations, not only at the controller
- Drop the now-unused RsAuthorizationCode#expired?
- Cover the client_id-vs-stored-client_id check

Part of #254
2026-11-03 11:59:12 +04:00
raucao 295261b332 Address review findings in the PKCE flow
CI / Test (pull_request) Failing after 46s
- Make authorization-code redemption atomic: claim the code with a guarded
  delete inside the transaction that creates the authorization, so concurrent
  exchanges can't both issue a token (RFC 6749 4.1.3)
- Compare the token request's client_id host to the code's stored client_id
  (defense-in-depth alongside the exact redirect_uri match)
- Reject unknown response_type values with unsupported_response_type
- Validate the PKCE code_challenge (43-128 unreserved chars, S256) so a client
  can't persist an over-long or malformed challenge
- Return invalid_request for missing token-endpoint parameters, and set
  Cache-Control: no-store on error responses too
- Use the right separator when the redirect_uri already has a query string
- Look codes up through the active scope; drop the unused consume!; serialize
  permissions as JSON
- Purge abandoned authorization codes via a recurring Solid Queue task

Part of #254
2026-11-03 11:40:18 +04:00
raucao 1e69957777 Shorten the authorization code lifetime to 60 seconds
CI / Test (pull_request) Failing after 45s
The code is redeemed by the client immediately in the OAuth callback, so
a 60-second window is plenty (and matches common practice; RFC 6749 caps
authorization codes at 10 minutes).

Part of #254
2026-11-03 09:17:44 +04:00
raucao 8dd0bf10ce Add PKCE support to remoteStorage OAuth
Implements the optional OAuth 2.0 Authorization Code grant with PKCE
(RFC 7636) for remoteStorage, as described in the remoteStorage spec
(section 10.1).

- Advertise the authorization and token endpoints plus the supported
  code_challenge method (S256) in WebFinger, and bump the advertised
  spec version to draft-dejong-remotestorage-27
- Add RsAuthorizationCode: a short-lived (5 min), single-use authorization
  code stored only as a SHA256 digest
- Add RsOauthManager services for issuing and exchanging codes, including
  S256 verification
- Add a cross-origin POST /rs/oauth/token endpoint that exchanges a code
  (and code_verifier) for an access token, with CORS and no CSRF token
- Consent dialog now carries response_type / code_challenge through the
  form and redirects with the code in the query string for the code flow
- Remove the existing-token reuse shortcut so every authorization results
  in a distinct authorization (and token)

Part of #254
2026-11-03 09:17:43 +04:00
raucao bc37df52a0 Allow remote access to dev services
CI / Test (pull_request) Successful in 46s
Release Drafter / Update release notes draft (pull_request) Successful in 19s
2026-10-06 19:34:24 +02:00
raucao 8fae099c12 Gate remoteStorage by serviceEnabled instead of Flipper
CI / Test (pull_request) Successful in 2m4s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
The admin "Default services" setting writes the LDAP serviceEnabled
attribute, but remoteStorage access was also gated behind an unused
per-user Flipper flag that nothing ever enabled, so remoteStorage was
inaccessible for everyone.

Gate remoteStorage on service_enabled? like the other services, and make
the admin toggle reflect the LDAP attribute (this also fixes it reading
current_user instead of the user being viewed). E-Mail keeps its Flipper
gate for now.
2026-10-06 15:10:00 +02:00
raucao 1957df3bbd Fix avatars failing to be set on XMPP accounts
CI / Test (pull_request) Successful in 32s
Release Drafter / Update release notes draft (pull_request) Successful in 3s
There's a stray `end` that prematurely closes the class
2026-08-10 21:35:28 -06:00
raucao 7a63c87b03 Fix deprecation warning 2026-08-10 17:02:20 -06:00
raucao c79c421b89 Fix Devise Turbo Stream responses for flash messages
CI / Test (pull_request) Successful in 33s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Wire Users::DeviseController as Devise parent controller with the
Turbo-aware responder, so failed form submissions (login, password
reset) render correctly as HTML with 422 status instead of
unprocessable turbo-stream content that Turbo silently drops.

Also fixes a NameError in the responder (rendering_options ->
error_rendering_options) and removes the dead TurboController.
2026-08-10 16:37:01 -06:00
raucao 8a8d407bcc Add missing whitespace between Devise (sign-in) errors and forms
CI / Test (pull_request) Successful in 32s
2026-08-10 15:17:43 -06:00
raucao 89184cd851 Redirect to root after password reset
CI / Test (pull_request) Successful in 32s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2026-08-10 11:22:03 -06:00
raucao 29a9dee1cb Consume user_return_to on sign-in 2026-08-10 11:21:59 -06:00
raucao a62d84e20a Validate rs/oauth redirect_uri before storing return URL 2026-08-10 11:21:33 -06:00
raucao d58c995230 Admin users page: filter by username
CI / Test (pull_request) Successful in 33s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Allow admins to filter the user list/index by username (partial matches
allowed)
2026-08-10 10:36:05 -06:00
raucao 9f25b23e2e Show ToS and Privacy Policy links when logged out
CI / Test (pull_request) Successful in 41s
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2026-08-09 14:10:25 -06:00
raucao 6a32dd5ee9 Show log-in link in header when logged out
Except on the login page itself
2026-08-09 14:10:00 -06:00
raucao b44b689caf Additional styles for rich text
CI / Test (pull_request) Failing after 40s
Especially for editable content
2026-08-09 11:42:41 -06:00
raucao 3bd07472b2 Fix pages views when signed out
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2025-06-12 16:09:42 +04:00
raucao 32b1c2748a Fix wrong variable
continuous-integration/drone/push Build is passing
2025-05-30 20:22:04 +04:00
raucao fc6cac8368 Remove superfluous link
continuous-integration/drone/push Build is passing
Already linked in the same paragraph
2025-05-30 16:53:05 +04:00
raucao f2e8ca790c Add Privacy and ToS pages, footer menu
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2025-05-30 13:27:15 +04:00
raucao 32cd4d896d Fix link color for Devise links
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-29 17:26:18 +04:00
raucao 67c450860a Fix tab links
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-29 16:24:33 +04:00
raucao f1d9cf1e3d Remove special link class
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
This cleans up the code quite a bit, but also allows links in editable
content to be rendered with the default style.
2025-05-29 16:10:34 +04:00
raucao ab1490f472 Remove Kosmos name from wording
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
refs #222
2025-05-29 14:24:43 +04:00
raucao 6014134396 Finish MVP for content editing 2025-05-29 14:18:14 +04:00
raucao 6713665a61 WIP Rename "projects" page, make content editable
continuous-integration/drone/push Build is failing
2025-05-28 18:42:10 +04:00
raucao 315cf4dd9f Add editable content helpers 2025-05-28 18:41:53 +04:00
raucao 2f86b3c16f Add admin/editable_contents controller 2025-05-28 18:40:54 +04:00
raucao 55c63be9e2 Memoize instance variable 2025-05-28 18:39:48 +04:00
raucao 5c8ffc2630 Add editable contents table 2025-05-28 18:39:25 +04:00
raucao c7a21c7a69 Add top margin to h3 within content 2025-05-28 18:37:59 +04:00
raucao 252b0f1792 Revert "Add ActionText configs, update spec helpers/configs"
This reverts commit c9d23f829d.
2025-05-28 16:53:31 +04:00
raucao 57246ea76d Fix navbar current link 2025-05-28 15:35:57 +04:00
raucao c9d23f829d Add ActionText configs, update spec helpers/configs 2025-05-28 14:52:31 +04:00
raucao 55111f1b8b Allow using icons without custom class
continuous-integration/drone/push Build is passing
2025-05-28 14:50:59 +04:00
raucao 4c6e64095f Fix unused invitations count
continuous-integration/drone/push Build is failing
2025-05-28 14:28:59 +04:00